最近因为使用列表展示的需求有点多,就想着把列表分页筛选的逻辑抽象一下。看了umi的一个useTable的hook,也不能满足业务需要,于是就自己写了一个,支持本地分页筛选和接口分页筛选。
思路就是,筛选的字段都使用form表单控制,然后在hook里面将form和table联合起来。
下面贴出源码

1 import { TableProps, PaginationProps } from '@slardar/antd';
2 import React, { useEffect } from 'react';
3 import {
4 PaginationConfig,
5 SorterResult,
6 TableCurrentDataSource
7 } from 'antd/lib/table';
8 import { useDeepCompareEffect } from '@byted-woody/slardar';
9
10 export type WrappedFormUtils = any;
11
12 export interface TableControlState<T> extends TableProps<DataRow> {
13 pagination: PaginationProps;
14 sorter?: SorterResult<DataRow>;
15 loading?: boolean;
16 }
17
18 // 搜索参数描述
19 export interface SearchDescribe {
20 // 字段名
21 fieldName: string;
22 iniValue?: any;
23 // 输入值解析,比如日期输入解析
24 decodeFn?: any;
25 // 自定义搜索函数
26 searchFn?: (record: DataRow, desc: SearchDescribe) => boolean;
27 // 解析后的值
28 searchValue?: any;
29 // 调用接口或者只在本地过滤
30 searchMod?: 'api' | 'local';
31 // 搜索的字段,默认只搜索当前字段
32 searchFields?: string[];
33 }
34
35 export interface DataReceive {
36 pageSize?: number;
37 pageIndex?: number;
38 pageTotal?: number;
39 pageData: any[];
40 }
41
42 export type DataRow = { [key: string]: any };
43 export type DataApiGet = (
44 apiParams,
45 pagination?: { pageIndex?: number; pageSize?: number }
46 ) => Promise<DataReceive>;
47
48 export interface FormTableReq {
49 form: WrappedFormUtils;
50 getDataApi: DataApiGet;
51 getDataParam?: { [key: string]: any };
52 // 表单的字段解析
53 includeFormFields?: (SearchDescribe | string)[];
54 // 本地分页
55 localPagination?: boolean;
56 // 本地搜索
57 localSearch?: boolean;
58 // 本地分页+搜索
59 local?: boolean;
60 afterFetchData?: (v: any) => void;
61 validateParam?: (param: any) => boolean;
62 }
63
64 const defaultTableState: TableControlState<DataRow> = {
65 pagination: { current: 1, total: 0, pageSize: 10 },
66 dataSource: [],
67 loading: true
68 };
69
70 export type FormTableRet = [
71 TableControlState<DataRow>,
72 { fetchData: () => void }
73 ];
74
75 export function useFormTable(options: FormTableReq): FormTableRet {
76 if (options.local) {
77 options?.includeFormFields?.forEach(d => (d.searchMod = 'local'));
78 return useFormTableLocal(options);
79 } else {
80 return useFormTableDB(options);
81 }
82 }
83
84 // 本地分页筛选版本
85 export function useFormTableLocal(options: FormTableReq): FormTableRet {
86 let { form, getDataApi, includeFormFields } = options;
87 let currentFormValue = form.getFieldsValue();
88 // 缓存数据
89 let cacheDataListRef = React.useRef<DataRow[]>([]);
90 let [tableState, setTableState] = React.useState<TableControlState<DataRow>>(
91 defaultTableState
92 );
93 let searchApiParam = {};
94 let searchLocalParam: SearchDescribe[] = [];
95 if (Array.isArray(includeFormFields)) {
96 includeFormFields?.forEach(describe => {
97 if (typeof describe === 'string') {
98 let value = currentFormValue[describe];
99 searchApiParam[describe] = value;
100 } else {
101 let value = currentFormValue[describe.fieldName];
102 if (describe.decodeFn) {
103 value = describe.decodeFn(value);
104 }
105 if (describe.searchMod === 'api') {
106 searchApiParam[describe.fieldName] = value;
107 } else {
108 searchLocalParam.push(
109 Object.assign({ searchValue: value }, describe)
110 );
111 }
112 }
113 });
114 } else {
115 searchApiParam = currentFormValue;
116 }
117
118 function getTableApiData() {
119 getDataApi(searchApiParam).then(data => {
120 cacheDataListRef.current = data.pageData;
121 setTableState(prevState => {
122 return Object.assign({}, prevState, { dataSource: [] });
123 });
124 });
125 }
126
127 useEffect(getTableApiData, []);
128
129 let { data, total } = calculatePageData(
130 tableState,
131 cacheDataListRef.current,
132 searchLocalParam
133 );
134
135 function onSorterChange(
136 _pagination: PaginationConfig,
137 _filters: Record<keyof DataRow, string[]>,
138 _sorter: SorterResult<DataRow>,
139 _extra: TableCurrentDataSource<DataRow>
140 ) {
141 setTableState(prevState => {
142 return Object.assign({}, prevState, { sorter: _sorter });
143 });
144 }
145
146 let newPage: PaginationProps = {
147 total: total,
148 onChange: (page, pageSize) => {
149 setTableState(prevState => {
150 prevState.pagination.pageSize = pageSize;
151 prevState.pagination.current = page;
152 return Object.assign({}, prevState);
153 });
154 }
155 };
156
157 let finalPagination: PaginationProps = Object.assign(
158 {},
159 tableState.pagination,
160 newPage
161 );
162
163 return [
164 { pagination: finalPagination, dataSource: data, onChange: onSorterChange },
165 { fetchData: getTableApiData }
166 ];
167 }
168
169 // 接口分页筛选版本 待完善
170 export function useFormTableDB(options: FormTableReq): FormTableRet {
171 let { form, getDataApi, includeFormFields } = options;
172 let currentFormValue = form.getFieldsValue();
173 let [state, setState] = React.useState<TableControlState<DataRow>>(
174 defaultTableState
175 );
176 let searchApiParam: { [key: string]: any } = {};
177 let onceRef = React.useRef(false);
178 // 计算接口参数
179 if (Array.isArray(includeFormFields)) {
180 includeFormFields?.forEach(describe => {
181 if (typeof describe === 'string') {
182 let value = currentFormValue[describe];
183 searchApiParam[describe] = value;
184 } else {
185 let value = currentFormValue[describe.fieldName];
186 if (!onceRef.current && describe.iniValue) {
187 value = describe.iniValue;
188 }
189 if (describe.decodeFn) {
190 value = describe.decodeFn(value);
191 Object.assign(searchApiParam, value);
192 } else {
193 searchApiParam[describe.fieldName] = value;
194 }
195 }
196 });
197 } else {
198 searchApiParam = currentFormValue;
199 }
200 Object.assign(searchApiParam, options.getDataParam);
201 const pageParam = {
202 pageIndex: state.pagination.current,
203 pageSize: state.pagination.pageSize
204 };
205
206 function getTableApiData() {
207 if (options.validateParam && !options.validateParam(searchApiParam)) {
208 return;
209 }
210 setState(prevState => {
211 return Object.assign({}, prevState, {
212 loading: true
213 } as TableControlState<any>);
214 });
215 getDataApi(searchApiParam, pageParam).then(data => {
216 const { pageData, pageTotal } = data;
217 onceRef.current = true;
218 setState(prevState => {
219 return Object.assign({}, prevState, {
220 dataSource: pageData,
221 pagination: {
222 current: pageParam.pageIndex,
223 total: pageTotal || 0,
224 pageSize: pageParam.pageSize
225 },
226 loading: false
227 } as TableControlState<any>);
228 });
229 // 将表单数据同步到query
230 if (options.afterFetchData) {
231 options.afterFetchData(currentFormValue);
232 }
233 });
234 }
235 useDeepCompareEffect(getTableApiData, [searchApiParam, pageParam]);
236
237 function onSorterChange(
238 _pagination: PaginationConfig,
239 _filters: Record<keyof DataRow, string[]>,
240 _sorter: SorterResult<DataRow>,
241 _extra: TableCurrentDataSource<DataRow>
242 ) {
243 setState(prevState => {
244 return Object.assign({}, prevState, { sorter: _sorter });
245 });
246 }
247
248 let finalPagination: PaginationProps = Object.assign(
249 {
250 total: state.pagination.total,
251 onChange: (page, pageSize) => {
252 setState(prevState => {
253 prevState.pagination.pageSize = pageSize;
254 prevState.pagination.current = page;
255 return Object.assign({}, prevState);
256 });
257 }
258 },
259 state.pagination
260 );
261 let dataSource = state.dataSource;
262 if (options.localPagination) {
263 let { data, total } = calculatePageData(state, state.dataSource as any, []);
264 finalPagination.total = total;
265 dataSource = data;
266 }
267
268 return [
269 {
270 pagination: finalPagination,
271 dataSource: dataSource,
272 onChange: onSorterChange,
273 loading: state.loading
274 },
275 { fetchData: getTableApiData },
276 state
277 ] as any;
278 }
279
280 // 排序,筛选,计算分页数据
281 function calculatePageData(
282 state: TableControlState<DataRow>,
283 dataList: DataRow[],
284 param: SearchDescribe[]
285 ) {
286 let { pagination, sorter } = state;
287 let { current = 1, pageSize = 10 } = pagination;
288 let copyDataList = Array.from(dataList);
289 // 排序
290 if (sorter?.column) {
291 let order = sorter.order;
292 let sortField = sorter.columnKey;
293 copyDataList = copyDataList.sort((a, b) => {
294 if (order === 'ascend') {
295 return a[sortField] - b[sortField];
296 } else {
297 return b[sortField] - a[sortField];
298 }
299 });
300 }
301 // 筛选
302 if (Array.isArray(param) && param.length > 0) {
303 copyDataList = copyDataList.filter(function filter(v) {
304 return param.every(desc => {
305 let { fieldName, searchValue, searchFields, searchFn } = desc;
306 let fieldValue = v[fieldName];
307 let searchString = searchValue;
308 if (!searchString) {
309 return true;
310 }
311 if (searchFn) {
312 return searchFn(v, desc);
313 }
314 if (
315 typeof fieldValue !== 'string' ||
316 typeof searchString !== 'string'
317 ) {
318 return true;
319 }
320 if (searchFields?.length) {
321 return searchFields?.some(fieldName => {
322 let value = v[fieldName];
323 if (typeof value === 'string') {
324 value.includes(searchString);
325 }
326 return false;
327 });
328 } else {
329 return fieldValue.includes(searchString);
330 }
331 });
332 });
333 }
334 // 分页
335 let displayData = copyDataList.slice(
336 (current - 1) * pageSize,
337 current * pageSize
338 );
339 // 默认空数据的展示
340 displayData.forEach(d => {
341 Object.entries(d).forEach(([k, v]) => {
342 if (v !== 0 && !v) {
343 d[k] = '---';
344 }
345 });
346 return d;
347 });
348 return { data: displayData, total: copyDataList.length };
349 }
下面是业务代码demo

1 import React, { FC } from 'react';
2 import { Form, FormComponentProps, Input, Table } from '@slardar/antd';
3 import { useFormTable } from '@slardar/common-modules';
4
5 const FormItem = Form.Item;
6
7 interface IProps extends FormComponentProps {}
8 const DemoComponent: FC<IProps> = function(props) {
9 const form = props.form;
10 let [tableState] = useFormTable({
11 form: props.form,
12 getDataApi: () => Promise.resolve([] as any),
13 includeFormFields: ['name', 'search']
14 });
15
16 return (
17 <div className={'alarm-page-content'}>
18 <Form layout={'inline'}>
19 <FormItem>
20 {form.getFieldDecorator('name')(
21 <Input.Search
22 style={{ marginLeft: 16, width: 150 }}
23 placeholder="名称"
24 />
25 )}
26 </FormItem>
27 <FormItem>
28 {form.getFieldDecorator('search')(
29 <Input.Search
30 style={{ marginLeft: 16, width: 150 }}
31 placeholder="评论"
32 />
33 )}
34 </FormItem>
35 </Form>
36 <Table {...tableState} columns={[]} />
37 </div>
38 );
39 };
40
41 export const Demo = Form.create()(DemoComponent);

1 1 import { useRef, useEffect } from 'react';
2 2 import _ from 'lodash';
3 3 export function useDeepCompareEffect<T>(fn, deps: T) {
4 4 // 使用一个数字信号控制是否渲染,简化 react 的计算,也便于调试
5 5 let renderRef = useRef<number | any>(0);
6 6 let depsRef = useRef<T>(deps);
7 7 if (!_.isEqual(deps, depsRef.current)) {
8 8 renderRef.current++;
9 9 }
10 10 depsRef.current = deps;
11 11 return useEffect(fn, [renderRef.current]);
12 12 }
来源:https://www.cnblogs.com/wanqingying/p/12579819.html
