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 } interface ScrollSelectProps { loading?: boolean, fetchParams?: Object, // 自定义的请求参数 paramsPage?: string, // 默认为page paramsPageSize?: string, // 默认为size paramsSearch?: string, // 搜索关键字 默认为search onFetchOptions: Function, onGetSearchFilterValue: (value: any) => string | number, // 配合内部searchFilterMap过滤用 fetchOptionsApi: (params: any) => Promise<{}>, // 接口 noInitFetch?: boolean, // constructor 不触发 fetchOptions;选填,默认为 false } export class ScrollSelectV2 extends Component{ private setAsyncState = funToPromise_callbackType(this.setState.bind(this)) constructor(props) { super(props); // 无论是页面新建还是页面编辑状态下调用该ScrollSelect组件,先发起一次只带分页信息的请求 // 后续页面编辑状态下再调用activeSearchOptions方法,恢复已选择的选项,isNoReset if(!props.noInitFetch) 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 (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 let 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 } filterSearchOptions(newOptions: any) { const { onGetSearchFilterValue } = this.props if (Tools.isEmptyArray(this.searchFilterMap)) return newOptions return newOptions.filter(item => { return this.searchFilterMap.indexOf(onGetSearchFilterValue(item)) === -1 }) } handleFetchResult(result: any, isScroll?: any, serachParams?: any) { const { data, totalSize, currentPage, pageSize } = result const { total } = this.state.pagination const { onFetchOptions, onGetSearchFilterValue } = this.props let { options } = this.state if (serachParams) { // 这表示页面编辑状态下再调用activeSearchOptions方法传的参数 this.searchFilterMap = data.map(e => onGetSearchFilterValue(e)) } const newOptions = serachParams ? data.concat(this.filterSearchOptions(options)) //页面编辑状态下,恢复已选择的选项+之前第一次请求获取的options : (isScroll ? options.concat(this.filterSearchOptions(data)) : data) // 滚动场景需要拼接下拉数据 this.setState({ newOptions, pagination: { current: currentPage, total: totalSize === total ? -1 : (pageSize * currentPage > totalSize ? totalSize : pageSize * currentPage), }, }) if (onFetchOptions) { // 传递options到外部,供外部使用 onFetchOptions(newOptions) } } resetInit() { this.searchFilterMap = [] return this.setAsyncState({ pagination: { current: 1, total: 0, }, options: [], }) } // 此方法供外部编辑状态下恢复已选择的选项用,isNoReset为true,表示拼接初始化组件时请求获取的选项,false表示不拼接 activeSearchOptions(serachParams: any, isNoReset?: boolean) { if (isNoReset) return this.fetchOptions(false, serachParams); return this.resetInit().then(() => this.fetchOptions(false, serachParams)) } // 滚动 onHandleScroll(el) { const { target } = el; const { onPopupScroll } = this.props let { spinning } = this.state // WARN: Math.ceil 是因为 windows 浏览器 带小数点 target.scrollTop + target.offsetHeight,导致无法进入 if 循环 if (Math.ceil(target.scrollTop + target.offsetHeight) === target.scrollHeight && !spinning) { const result = this.fetchOptions.bind(this)(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, })) } } if (Tools.isFunction(onPopupScroll)) { onPopupScroll(el) } } onHandleSearchOptions() { return this.resetInit().then(() => this.fetchOptions(false)) } // 搜索关键字,需要存储搜索关键字、重置分页信息和重置之前存储的选项 @throttle(300) onHandleSearch(value) { const { onSearch } = this.props this.setState({ serachValue: value, }, this.onHandleSearchOptions) if (Tools.isFunction(onSearch)) { onSearch(value) } } // 清空全部,需要重置分页信息和重置之前存储的选项 onHandleChange(value, options) { const { onChange } = this.props //clear all if(!value || (value && Tools.isEmptyArray(value))){ this.onHandleSearchOptions() } //notice if (Tools.isFunction(onChange)) { 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 ( ) } }