最近因為使用列表展示的需求有點多,就想著把列表分頁篩選的邏輯抽象一下。看了umi的一個useTable的hook,也不能滿足業務需要,於是就自己寫了一個,支持本地分頁篩選和介面分頁篩選。 思路就是,篩選的欄位都使用form表單控制,然後在hook裡面將form和table聯合起來。 下麵貼出源碼 1 ...
最近因為使用列表展示的需求有點多,就想著把列表分頁篩選的邏輯抽象一下。看了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 }useFormTable.tsx
下麵是業務代碼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);Demo.tsx
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 }useDeepCompareEffect.ts