已封装包组件 react-antd-search-select - npm
基于React Antd远程 SearchSelect 组件,参数灵活可配置,持续迭代中~开源_五虎战画戟-CSDN博客
// index.jsx 受控组件
import React from 'react'
import AntdSelect from './AntdSelect'
/**
* Select组件
* @param value Antd Select value
* @param onChange Antd Select onChange
* @param handleList 外部处理并返回list数据
* @param url String 请求接口
* @param ajaxData Object 接口入参
* @param debounceMs Number 防抖延时时间
* @param max Number 多选时可选最大数量
* @param remote Boolean 是否为远程搜索 默认true
* @param mode Antd Select mode, 设置 Select 的模式为多选或标签 multiple | tags
* @param echoOptions 回显的list数据
* @param pageSize 每页搜索数量 默认15
* @param curPageKey 分页请求页码键值 'curPage'
* @param queryKey String 搜索键值 默认 'queryKeyword'
* @param disabledKey String 指定要使用的 disabled 字段 默认 'disabled'
* @param labelKey String 指定要显示的 label 字段 默认 'label'
* @param valueKey String 指定要显示的 value 字段 默认 'value'
* @param rich Boolean Option是否显示更多信息 默认 false
* @param rightLabelKey String 指定要显示的 地区 字段 默认 ''
* @param bottomLabelKey String 指定要显示的 公司/组 字段 默认 ''
* @param rest Antd Select API
* @returns {JSX.Element}
*/
export default ({
value,
onChange,
handleList,
url,
ajaxData,
debounceMs,
remote,
mode,
max,
echoOptions,
pageSize,
curPageKey,
queryKey,
labelKey,
disabledKey,
valueKey,
rich,
rightLabelKey,
bottomLabelKey,
...rest
}) => {
return (
<AntdSelect
url={url}
ajaxData={ajaxData}
debounceMs={debounceMs}
remote={remote}
mode={mode}
echoOptions={echoOptions}
propsPageSize={pageSize}
curPageKey={curPageKey}
queryKey={queryKey}
labelKey={labelKey}
disabledKey={disabledKey}
valueKey={valueKey}
rich={rich}
rightLabelKey={rightLabelKey}
bottomLabelKey={bottomLabelKey}
handleList={handleList}
{...rest}
value={value}
onChange={(val) => {
// 下面是处理 max 逻辑
if (mode !== 'multiple' && mode !== 'tags') {
// 单选
onChange(val)
} else {
// 多选
if (max && val.length > max) {
onChange(val.slice(0, max))
} else {
onChange(val)
}
}
}}
/>
)
}
// AntdSelect.jsx
import React, { useEffect, useState } from 'react'
import { useDebounce } from 'react-use'
import { Select, message } from 'antd'
import { axios } from '@common'
import './index.less'
import qs from 'qs'
const { Option } = Select
/**
* Select组件
* @param value Antd Select value
* @param onChange Antd Select onChange
* @param handleList 外部处理并返回list数据
* @param url String 请求接口
* @param ajaxData Object 接口入参
* @param debounceMs Number 防抖延时时间
* @param remote Boolean 是否为远程搜索 默认true
* @param mode Antd Select mode, 设置 Select 的模式为多选或标签 multiple | tags
* @param echoOptions 回显的list数据
* @param propsPageSize 每页搜索数量 默认15
* @param curPageKey String 分页请求页码键值 'curPage'
* @param queryKey String 搜索键值 默认 'queryKeyword'
* @param disabledKey String 指定要使用的 disabled 字段 默认 'disabled'
* @param labelKey String 指定要显示的 label 字段 默认 'label'
* @param valueKey String 指定要显示的 value 字段 默认 'value'
* @param rich Boolean Option是否显示更多信息 默认 false
* @param rightLabelKey String 指定要显示的 地区 字段 默认 ''
* @param bottomLabelKey String 指定要显示的 公司/组 字段 默认 ''
* @param rest Antd Select API
* @returns {JSX.Element}
*/
export default ({
value,
onChange,
handleList,
url,
ajaxData,
debounceMs,
remote = true,
mode,
echoOptions,
propsPageSize,
curPageKey,
queryKey,
labelKey,
disabledKey,
valueKey,
rich,
rightLabelKey,
bottomLabelKey,
...rest
}) => {
const [loading, setLoading] = useState(false) // loding
const [list, setList] = useState([]) // 下列选项
const [searchText, setSearchText] = useState('') // 搜索文本,防抖用
const [pageSize, setPageSize] = useState(propsPageSize || 15) // 每页搜索数量
const [curPage, setCurPage] = useState(0) // 搜索页码
const [pageSt, setPageSt] = useState(0) // 滚动的距离
const [lastPage, setLastPage] = useState(false) // 是否是最后一页
const [_curPageKey, setCurPageKey] = useState(curPageKey ? curPageKey : 'curPage') // 每页搜索数量
const [_queryKeyword, setQueryKeyword] = useState(queryKey ? queryKey : 'queryKeyword') // 搜索键值
const [_disabledKey, setDisabledKey] = useState(disabledKey ? disabledKey : 'disabled') // 指定要使用的 disabled 字段
const [_labelKey, setLabelKey] = useState(labelKey ? labelKey : 'label') // 指定要显示的 label 字段
const [_valueKey, setValueKey] = useState(valueKey ? valueKey : 'value') // 指定要显示的 value 字段
const [_rightLabelKey, setCity] = useState(rightLabelKey ? rightLabelKey : '') // 指定要显示的 右侧 字段
const [_bottomLabelKey, setCompanyName] = useState(bottomLabelKey ? bottomLabelKey : '') // 指定要显示的 底部 字段
useEffect(() => {
if (echoOptions?.length) {
setList(echoOptions)
}
}, [])
useEffect(() => {
getList()
}, [curPage])
// 防抖
useDebounce(
() => {
getList(searchText)
},
debounceMs || 300,
[searchText]
)
// 获取列表
const getList = (val) => {
setLoading(true)
const params = {
pageSize,
[_curPageKey]: curPage,
[_queryKeyword]: val || '',
...ajaxData
}
axios.post(url, qs.stringify(params))
.then(({ flag, data, msg }) => {
setLoading(false)
if (flag) {
setLastPage(() => {
if (data.totalRows) {
// 如果有分页
return Math.ceil(data.totalRows / pageSize) <= curPage + 1
} else {
// 没有分页则不再滚动加载
return true
}
})
if (curPage === 0) {
if (Array.isArray(data)) {
handleList ? handleList2(data) : setList(data)
} else {
handleList ? handleList2(data.dataList) : setList(data.dataList)
}
} else {
if (Array.isArray(data)) {
handleList ? handleList2([...list, ...data]) : setList((value) => [...value, ...data])
} else {
handleList ? handleList2([...list, ...data.dataList]) : setList((value) => [...value, ...data.dataList])
}
}
} else {
message.error(msg || '请求失败')
}
})
.catch((error) => {
setLoading(false)
message.error(error.toString().includes('timeout') ? '服务超时' : error.toString())
})
}
// 外部函数处理数据
const handleList2 = (val) => {
const list = handleList(val)
setList(list)
}
// 当清空时
const onClear = (val) => {
setSearchText(val)
}
// 搜索
const handleSearch = (val) => {
if (remote) {
setSearchText(val)
}
}
// 滚动,分页搜索
const handlePopupScroll = (e) => {
// scrollTop // 滚动条距元素(不一定是屏幕最顶端)顶部滚动的距离
// offsetHeight // 元素CSS高度
// scrollHeight // 文档内容实际高度,包括超出视窗的溢出部分
e.persist()
const { target } = e
const st = target.scrollTop
if (st === 0 && pageSt) {
target.scrollTop = pageSt
}
if (st + target.offsetHeight + 3 >= target.scrollHeight && !lastPage) {
setPageSt(st)
setCurPage(curPage + 1)
} else {
setPageSt(0)
}
}
return (
<Select
className="custom-select"
optionLabelProp="label"
getPopupContainer={(triggerNode) => triggerNode.parentNode}
filterOption={false}
allowClear
mode={mode}
showSearch
onSearch={handleSearch}
onPopupScroll={handlePopupScroll}
loading={loading}
value={value}
dropdownMatchSelectWidth={false}
onChange={onChange}
onClear={onClear}
{...rest}
>
{list?.map((item) => {
return (
<Option disabled={item[_disabledKey]} value={item[_valueKey]} key={item[_valueKey]} label={item[_labelKey]}>
{rich ? (
<>
<div className="select-top-title">
<span className="top-label-key" title={item[_labelKey]}>{item[_labelKey]}</span>
{item?.ext[_rightLabelKey] ? (
<span className="right-label-key" title={item?.ext[_rightLabelKey]}>{item?.ext[_rightLabelKey]}</span>
) : null}
</div>
{item?.ext[_bottomLabelKey] ? <div className="bottom-label-key" title={item?.ext[_bottomLabelKey]}>{item?.ext[_bottomLabelKey]}</div> : null}
</>
) : (
<p className="label-key" title={item[_labelKey]}>{item[_labelKey]}</p>
)}
</Option>
)
})}
</Select>
)
}
// index.less
.custom-select {
.ant-select-selection-item {
height: auto;
}
.ant-select-item-option-selected:not(.ant-select-item-option-disabled) .ant-select-item-option-content {
padding-right: 0;
}
.ant-select-dropdown {
width: auto !important;
.label-key {
max-width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.select-top-title {
display: flex;
justify-content: space-between;
.top-label-key {
float: left;
max-width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.right-label-key {
float: right;
color: #8593a5;
font-size: 12px;
margin-left: 10px;
max-width: 100px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.bottom-label-key {
color: #8593a5;
font-size: 12px;
max-width: 240px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}