本文最后更新于:2021年6月21日 中午
场景
GitHub 源码
在后台项目中,即便使用了 antd,仍然存在太多太多的列表页面。这些列表页面大多数又是非常相似的,所以吾辈需要解决重复的简单列表的编写,避免每次都手动控制过滤器/分页之类的东西,将之抽象成配置项,然后通过配置生成列表页面。或许已经有很多人做过了这件事情,但于吾辈而言,这仍然是全新的体验,所以也便于此记录,并供之以他人参考。
理念 使用逐级递进的方式进行封装,使用者可以根据需求停留在一个合适的封装层次,使用不同封装层次的组件。
BasicList
:高层列表封装组件
ListHeader
:列表页顶部工具栏组件
ListFilter
:过滤器组件
FilterSelect
:单选过滤器
FilterTimeRange
:时间区间过滤器
FilterSlot
:自定义过滤器
ListTable
:列表封装组件
BasicList 使用步骤
graph TD;
A[添加 api class 实现 BasicListApi] --> B[添加配置项]
B --> C{是否需要动态修改};
C --> |是| C1[使用 useMemo 声明]
C --> |否| C2[将之抽离到组件外部声明为常量]
C1 --> D
C2 --> D
D{是否有非通用 Filter}
D --> |是| D1[自定义 slot filter]
D --> |否| D2[使用 select/time filter]
D1 --> E
D2 --> E
E{是否需要自定义表格相关功能}
E --> |是| E1[使用 filterOperate 配置]
E --> |否| E2[不声明 filter 配置]
E1 --> F
E2 --> F
F[列表渲染]
BasicList 渲染流程
graph TB
A[从 props 拿到配置项列表] --> B{header 是否为 ReactElement}
B --> |是| B1[直接渲染]
B --> |否| B2[使用 ListHeader 渲染 header 配置项]
B1 --> C
B2 --> C
C{filters 是否存在}
C --> |是| C1{filters 是否为 ReactElement}
C1 --> |是| C1A[直接渲染]
C1 --> |否| C1B[使用 ListFilter 渲染 filters 配置项] --> C1C[监听 initialParams 变化, 以随时修改 form]
C --> |否| C2[不渲染]
C1A --> D
C1B --> D
C2 --> D
D[渲染列表]
使用示例 使用基本 API
GitHub 代码示例
如下所示,我们想要构造下面这样一个简单的列表页面,包含一个面包屑导航列表、搜索框、过滤条件选择器和一个表格。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 import * as React from 'react' import { Button } from 'antd' import { Link } from 'react-router-dom' import { Moment } from 'moment' import { LabeledValue } from 'antd/es/select' import { userApi } from './api/UserApi' import { BasicList , BasicListPropsType , FilterFieldTypeEnum , } from '../../components/list' type PropsType = {}type Config = Omit <BasicListPropsType , 'params' > & { params?: { keyword?: string test?: number birthdayTimeRange?: [Moment , Moment ] } }const testOptionList : LabeledValue [] = [ { value : 0 , label : '测试 0' }, { value : 1 , label : '测试 1' }, { value : 2 , label : '测试 2' }, ]const config : Config = { header : { placeholder : '用户名/住址' , list : ['用户' , '列表' ], }, filters : [ { type : FilterFieldTypeEnum .Select , label : '测试字段' , field : 'test' , options : testOptionList, }, { type : FilterFieldTypeEnum .TimeRange , label : '生日' , field : 'birthdayTimeRange' , }, ], columns : [ { field : 'id' , title : 'ID' }, { field : 'name' , title : '姓名' }, { field : 'birthday' , title : '生日' }, { field : 'operate' , title : '操作' , slot : (param ) => <Link to ={ `/system /user /${param.record.id }`}> 详情</Link > , }, ], api : userApi, }const BasicListExample : React .FC <PropsType > = () => { return <BasicList {...config } /> }export default BasicListExample
使用自定义过滤器组件
GitHub 代码示例
事实上,总有各种奇怪的过滤器无法满足,这时候就需要添加一个自定义的过滤器了。 例如下面这个过滤器,包含了年龄的值和单位,是不是感觉很奇怪
关键代码配置如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 const config : Config = { filters : [ { type : FilterFieldTypeEnum .Slot , label : '年龄' , field : 'age' , children : ( <Input.Group compact > <Form.Item name ={[ 'age ', 'value ']} noStyle > <InputNumber style ={{ width: 'calc (100 % - 64px )' }} /> </Form.Item > <Form.Item name ={[ 'age ', 'unit ']} noStyle > <Select style ={{ width: 64 }} options ={ageUnitOptionList} /> </Form.Item > </Input.Group > ), }, ], params : { age : { unit : 0 , }, }, }
添加表格的额外操作
GitHub 代码示例
有时候,我们需要添加一个额外的表格操作,例如导出/导入数据/删除选中数据。
关键代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 async function handleBatchDelete ({ selectedRowKeys, setSelectedRowKeys, searchPage, }: ListTableOperateParam ) { if (selectedRowKeys.length === 0 ) { return } await userApi.batchDelete (selectedRowKeys) setSelectedRowKeys ([]) await searchPage () }const config = useMemo<Config >( () => ({ tableOperate : (params ) => ( <Button onClick ={() => handleBatchDelete(params)}>删除选中</Button > ), }), [], )
过滤器的下拉框数据来源是异步的
GitHub 代码示例
很多时候,我们的数据来源并不是由前端写死,而是从后台获取的,这就要求我们传入的值是 react 的一个 State 而非一个固定值。
关键代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const testOptionList = useAsyncMemo ([], dictApi.list )const config = useMemo<Config >( () => ({ filters : [ { type : FilterFieldTypeEnum .Select , label : '测试字段' , field : 'test' , options : testOptionList, }, ], }), [testOptionList], )
组件 API BasicList 参考 src/components/common/table/js/BasicListOptions.d.ts
prop
类型
说明
header
BasicList.Header
标题栏相关配置
[filters]
BaseFilterField[]
过滤器列表
((params: any, onChange: (params: any) => void) => ReactElement)
[params]
Params
查询参数
columns
TableColumn[]
列字段列表
api
BaseListApi
api 对象
[tableOptions]
TableOptions
一些其他选项
[tableOperate]
ListTableOperate
一些其他操作
ListFilter | prop
| 类型 | 说明 | | —————- | ——————– | ———————— | —————— | ———- | | [initialValue]
| any
| 查询参数 | | filters
| (FilterSelectType | FilterTimeRangeType | FilterSlotType)[]
| 过滤器列表 | | onChange
| (value: T) => void
| 当过滤器的参数发生改变时 |
ListTable
prop
类型
说明
columns
TableColumn[]
列字段列表
api
BaseListApi
api 对象
params
Params
查询参数
[tableOptions]
TableOptions
一些其他选项
[tableOperate]
ListTableOperate
一些其他选项
其他类型定义 下面是类型定义,所有的类型定义都有对应的 .d.ts
文件,请使用 C-N
搜索 class
。
HeaderNavItem
类型
说明
string
导航的名字
name
string
导航的名字
[link]
string
如果是 route 的话必须有值
Header
类型
说明
list
HeaderNavItem[]
导航元素列表
placeholder
string
搜索框的提示文本
过滤器相关
FilterFieldTypeEnum
类型
说明
Slot
1
自定义 slot
Select
2
普通选择框
TimeRange
3
日期区间选择器
FilterFieldBase
类型
说明
type
FilterFieldTypeEnum
过滤器元素类型
label
string
显示的标题
FilterSelectType
类型
说明
extends
FilterFieldBase
继承基本过滤器配置
field
string
字段名
options
LabeledValue[]
值列表
FilterTimeRangeType
类型
说明
extends
FilterFieldBase
继承基本过滤器配置
field
string
字段名
FilterSlotType
类型
说明
extends
FilterFieldBase
继承基本过滤器配置
field
string
字段名
children
ReactElement
Form.Item
的子元素
[computed]
(res: Record<string, any>, value: any) => Record<string, any>
自定义计算方法
Params
类型
说明
keyword
string
查询关键字
...args
any[]
其他查询参数
表格相关
TableColumn
类型
说明
field
string
在数据项中对应的字段名
title
string
列标题
[formatter]
(v: any, record: any) => any
自定义字段格式化函数
[slot]
(param: { text: string; record: any; i: number }) => ReactNode
自定义 slot
BaseListApi
类型
说明
pageList
(params: any) => Promise<PageRes<any>>
所有 ListTable 中的 api 对象必须实现该类型
ListTableOperateParam
类型
说明
searchPage
(page?: PageParam) => Promise<void>
导航元素列表
selectedRowKeys
string[]
当前选中行的主键
setSelectedRowKeys
(selectedRowKeys: string[]) => void
设置当前选中行的主键
page
PageData<any>
分页数据信息
params
Params
过滤器及搜索参数
TableOptions
类型
说明
[isSelect]
boolean
是否可选,默认为 false
[rowKey]
string
行的唯一键,默认为 id
总结 相比于之前吾辈 Vue 表格封装 BasicTableVue ,嗯,差距非常明显!