import React, { Component } from 'react'; import { Select, Spin, Empty } from 'antd' import { SelectProps } from 'antd/lib/select/index' import * as Tools from 'jad-tool' import { throttle, loading, funToPromise_callbackType } from 'jad-tool' type resultProps = { code:number, data:any[], totalSize:number, pageSize:number, currentPage:number } type params = { page:number, size:number, search?:string } interface ScrollSelectProps { loading?:boolean, fetchParams?:any, paramsPage?:string, paramsPageSize?:string, paramsSearch?:string, disabled?:boolean, onFetchOptions?: Function, onPopupScroll?: Function, onGetSearchFilterValue?:(value:any)=>string|number, // 获取去重关键值函数,一般用于组件外触发 activeSearchOptions 且 serachParams 存在 isNoReset为 true 的场景 onHandleFetchResult?:(data:any)=>{data:any,totalSize:number,currentPage:number,pageSize:number} fetchOptionsApi: (params:any) => Promise<{}>, // 下拉选项的 api;必填 noInitFetch?: boolean, // constructor 不触发 fetchOptions;选填,默认为 false } export class ScrollSelect extends React.Component{ private setAsyncState = funToPromise_callbackType(this.setState.bind(this)) constructor(props) { super(props); const { fetchOptionsApi, noInitFetch = false } = this.props; if(fetchOptionsApi && !noInitFetch){ // 当 fetchOptionsApi 存在且允许初始化自动请求时 this.fetchOptions(false,false) } } searchFilterMap=[]; state = { spinning:false, serachValue: "", pagination:{ current: 1, total: 0, }, options:[] }; @loading('spinning') async fetchOptions(isScroll:any,serachParams?:any){ const { fetchOptionsApi, } = this.props; const { total } = this.state.pagination if(!fetchOptionsApi || total < 0) return Promise.reject() const params = this.createFetchParams(isScroll,serachParams) return await fetchOptionsApi(params).then((res:resultProps)=>{ if(res.code !== 2000) return res this.handleFetchResult(res, isScroll, serachParams) return res }) } createFetchParams(isScroll:any,serachParams?:any){ const { fetchParams,paramsPage='page',paramsPageSize='size',paramsSearch='search' } = this.props; const { pagination,serachValue } = this.state const { current } =pagination var params:any = { [paramsPage]:serachParams?1:current, [paramsPageSize]:10 } //select的输入查询 if(serachValue){ params[paramsSearch] = Tools.isString(serachValue) ? serachValue.replace(/\s\n\t\r/g, '') : serachValue; } //搜索参数 if(serachParams && Tools.isPlainObject(serachParams)){ Tools.map(serachParams,(item,key)=>{ params[key] = item }) } //搜索参数可能是字符串 if(serachParams && (Tools.isString(serachParams) || Tools.isNumber(serachParams)) ){ params[paramsSearch] = Tools.isString(serachParams)?serachParams.replace(/\s\n\t\r/g,''):serachParams } // add other params if(Tools.isObject(fetchParams)){ Tools.map(fetchParams,(value,key)=>{ params[key] = fetchParams[key] }) } //isScroll if(isScroll && !serachParams){ params[paramsPage] += 1; } if(serachValue !== params[paramsSearch]) { // 如果 Select 输入查询条件与实际查询条件不一致,则更新输入查询条件展示 this.setState({serachValue: params[paramsSearch]}) } return params } handleFetchResult(result:any, isScroll?: any, serachParams?:any){ const { data,totalSize,currentPage,pageSize } = result const { total } = this.state.pagination const { onFetchOptions,onGetSearchFilterValue } = this.props var { options } = this.state if(onGetSearchFilterValue && serachParams){ this.searchFilterMap = data.map(e=>onGetSearchFilterValue(e)) } const newOptions = !serachParams ? (isScroll ? options.concat(this.filterSearchOptions(data)) : data) : // 滚动场景需要拼接下拉数据 data.concat(onGetSearchFilterValue ? this.filterSearchOptions(options) : []) // 当 serachParams 存在时特殊场景拼接数据,具体应用场景「王凯」 this.setState({ options: newOptions, pagination:{ current: currentPage, total: totalSize===total? -1 :(pageSize*currentPage>totalSize? totalSize :pageSize*currentPage), }, }) if(onFetchOptions){ onFetchOptions(newOptions) } } resetInit(){ this.searchFilterMap = [] return this.setAsyncState({ pagination:{ current: 1, total: 0, }, options:[], }) } activeSearchOptions(serachParams:any,isNoReset?:boolean){ if(isNoReset) return this.fetchOptions(false,serachParams); return this.resetInit().then(()=>this.fetchOptions(false,serachParams)) } filterSearchOptions(newOptions:any){ const { onGetSearchFilterValue } = this.props if(!onGetSearchFilterValue || Tools.isEmptyArray(this.searchFilterMap)) return newOptions return newOptions.filter(item=>{ return this.searchFilterMap.indexOf(onGetSearchFilterValue(item)) === -1 }) } @throttle(300) onHandleSearchOptions(){ return this.resetInit().then(()=>this.fetchOptions(false)) } onHandleScroll(el){ const { target } = el; let { onPopupScroll=false } = this.props as any; let { spinning } = this.state onPopupScroll = onPopupScroll || this.fetchOptions.bind(this) // WARN: Math.ceil 是因为 windows 浏览器 带小数点 target.scrollTop + target.offsetHeight,导致无法进入 if 循环 if (Math.ceil(target.scrollTop + target.offsetHeight) === target.scrollHeight && !spinning) { const result = onPopupScroll(el) if(result instanceof Promise || (result && result.then && Tools.isFunction(result.then) ) ){ this.setState({ spinning:true, }) result.then(()=>this.setState({ spinning:false, }),()=>this.setState({ spinning:false, })) } } } onHandleSearch(value){ const { onSearch } = this.props if(Tools.isFunction(onSearch)){ return onSearch(value) } this.setState({ serachValue: value, }, this.onHandleSearchOptions) } onHandleChange(value,options){ const { onChange } = this.props //clear all if(!value || (value && Tools.isEmptyArray(value))){ this.onHandleSearchOptions() } //notice if(Tools.isFunction(onChange)){ return onChange(value,options) } } // 失去焦点,输入框的关键字会自动被系统清空,这时需要重置搜索关键字,不然再次看到的选项是搜索关键字对应的选项 onHandleBlur(selected) { const { onBlur } = this.props const { serachValue } = this.state if (serachValue) { this.onHandleSearch('') } if (Tools.isFunction(onBlur)) { onBlur(selected) } } render() { const { spinning } = this.state return ( ) } }