基础业务组件封装方法

0. 起因

最近工作中要对系统做整体重构,需要封装一些基础的组件,封装基础组件参照如下准则:

  1. 组件基本样式统一,留有可修改空间;
  2. 把默认要的一些基本功能配置,直接写到基础组件配置中,避免重复配置;
  3. 具备和原本组件一致的接口定义;

下面就通过封装 ProTable 表格基础组件的例子.

1. 分析业务的默认需求配置

  1. 在业务中有很多默认的设置,比如单行文本过长折叠,页脚设置统一等;
  2. 通过封装使功能简单就可以调用,比如 设置表头的排序展示本地存储;
  3. 搜索展示多行的功能;

27-10-18-0oNfH4-xpDHz0

2. 代码实现

2.1 单行文本过长折叠默认配置

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

// 1. 默认全部折叠,新增一个 excludeEllipsis 排除折叠的数组配置
/** ProTable 的类型定义 继承自 antd 的 Table */
export declare type CSProTableProps<DataSource, U, ValueType = 'text'> = {
excludeEllipsis?: string[]; // 不需要折叠的数组,默认所有的都折叠 option 不折叠
} & ProTableProps<DataSource, U, ValueType>;


/****** 2. 设置默认配置 *******/
const newColumns = useMemo(() => {
const nc: any = columns?.map?.(it => {
const { dataIndex = '', valueType = '' }: any = it
const tempIt = { ...it, ellipsis: true }
if (excludeEllipsis?.includes?.(dataIndex)) {
tempIt.ellipsis = false
}
if (it?.hasOwnProperty('ellipsis')) {
tempIt.ellipsis = it?.ellipsis
}
return tempIt
})
return nc
}, [columns, excludeEllipsis])

/****** 3. 传入到原有里面 *******/

const getTableProps = () => ({
...rest,
columns: newColumns,
})
return (
<ProTable<DataType, Params, ValueType>
{...getTableProps()}
>{props?.children}</ProTable>
);

2.2 简单配置设置表头的排序展示本地存储

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
// 1. 添加一个新传参字段 persistenceKey
/** ProTable 的类型定义 继承自 antd 的 Table */
export declare type CSProTableProps<DataSource, U, ValueType = 'text'> = {
persistenceKey?: string; // 表头的排序持久化 key,提出来传参,实现简单调用
} & ProTableProps<DataSource, U, ValueType>;

/******** 2. 设置默认传参 *********/
const newColumnsState = useMemo(() => {
let ncs: any = {
persistenceType: 'localStorage',
}
if (persistenceKey && !columnsState) {
ncs.persistenceKey = persistenceKey
}
if (columnsState) {
ncs = { ...ncs, columnsState }
if (!ncs?.persistenceKey && persistenceKey) {
ncs.persistenceKey = persistenceKey
}
}
return ncs
}, [persistenceKey, columnsState])

/****** 3. 传入到原有里面 *******/
const getTableProps = () => ({
...rest,
columnsState: newColumnsState,
})
return (
<ProTable<DataType, Params, ValueType>
{...getTableProps()}
>{props?.children}</ProTable>
);

2.3 封装前后调用对比

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

const columns = [
{
title: '序号',
valueType: 'index',
width: 65,
},
{
dataIndex: 'name',
title: '用户名称',
},
{
dataIndex: 'mobile',
title: '联系电话',
},
{
dataIndex: 'type',
title: '好友类型',
hideInSearch: true,
valueEnum: [],
},
{
title: '操作',
valueType: 'option',
width: 180,
render: (_, record) => [<a key="manage" onClick={() => {}}>管理</a>]
},
]

<ProTable
columns={columns}
columnsState={{
persistenceType: 'localStorage',
persistenceKey: 'persistenceKey'
}}
rowKey="id"
scroll={{ x: 800 }}
actionRef={actionRef}
request={getContactListApi}
/>
<CSProTable
persistenceKey="persistenceKey"
columns={columns}
rowKey="id"
scroll={{ x: 800 }}
actionRef={actionRef}
request={getContactListApi}
/>

两者调用基本差不多,设置标题持久化 CSProTable 的配置会简单些,还有他会默认对单行文本过长折叠

3. 完整封装代码

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/*
* @Description: CSProTable,对原有的做一些定制化的东西,方便调用
*/
import type { ParamsType, ProTableProps } from '@ant-design/pro-components';
import { ProTable } from '@ant-design/pro-components';
import { Icon } from 'antd';
import { isBoolean } from 'lodash';
import React, { ReactNode, useMemo } from 'react';

/** ProTable 的类型定义 继承自 antd 的 Table */
export declare type CSProTableProps<DataSource, U, ValueType = 'text'> = {
searchTitle?: string | ReactNode; // 搜索标题
excludeEllipsis?: string[]; // 不需要折叠的数组,默认所有的都折叠 option 不折叠
persistenceKey?: string; // 表格列表持久化 key,提前出来传参
} & ProTableProps<DataSource, U, ValueType>;

const CSProTable = <
DataType extends Record<string, any>,
Params extends ParamsType = ParamsType,
ValueType = 'text',
>(
props: CSProTableProps<DataType, Params, ValueType>
) => {
const {
searchTitle,
excludeEllipsis = [],
columns = [],
pagination = {},
search,
scroll = {},
searchFormRender,
persistenceKey,
columnsState,
options,
cardBordered = true,
bordered = true,
...rest } = props
const newColumns = useMemo(() => {
const nc: any = columns?.map?.(it => {
const { dataIndex = '', valueType = '' }: any = it
const tempIt = { ...it, ellipsis: true }
if (excludeEllipsis?.includes?.(dataIndex)) {
tempIt.ellipsis = false
}
if (it?.hasOwnProperty('ellipsis')) {
tempIt.ellipsis = it?.ellipsis
}
if (it?.hasOwnProperty('valueEnum') || valueType === 'select') {
tempIt.fieldProps = { showSearch: true, ...tempIt?.fieldProps }
}
if (dataIndex === 'option' || valueType === 'option') {
tempIt.fixed = 'right'
tempIt.hideInSetting = true
}
if (dataIndex === 'index' || valueType === 'index') {
tempIt.fixed = 'left'
}
if (dataIndex === 'indexBorder' || valueType === 'indexBorder') {
tempIt.fixed = 'left'
}
return tempIt
})
return nc
}, [columns, excludeEllipsis])
const newOptions = useMemo(() => {
let no = {}
if (options) {
no = options
if (!options?.hasOwnProperty?.('reloadIcon')) {
no = { ...no, reloadIcon: <Icon type="icon-shuaxin" /> }
}
if (!options?.hasOwnProperty?.('density')) {
no = { ...no, density: false }
}
if (!options?.hasOwnProperty?.('setting')) {
let setting = options?.setting
if (isBoolean(setting)) {
no = { ...no, setting }
} else {
if (!setting?.hasOwnProperty?.('settingIcon')) {
setting = { ...setting, settingIcon: <CFIcon type="icon-shezhi" /> }
}
no = { ...no, setting }
}
}
} else if (isBoolean(options)) {
no = options
} else {
no = {
setting: {
draggable: true,
settingIcon: <Icon type="icon-shezhi" />
},
reloadIcon: <Icon type="icon-shuaxin" />,
density: false
}
}
return no
}, [])
const newPagination = useMemo(() => {
let np = {}
if (pagination) {
np = pagination
if (!pagination?.hasOwnProperty?.('showSizeChanger')) {
np = { ...np, showSizeChanger: true }
}
if (!pagination?.hasOwnProperty?.('showQuickJumper')) {
np = { ...np, showQuickJumper: true }
}
} else if (isBoolean(pagination)) {
np = options
} else {
np = {
showSizeChanger: true,
showQuickJumper: true
}
}
return np
}, [])
const newColumnsState = useMemo(() => {
let ncs: any = {
persistenceType: 'localStorage',
}
if (persistenceKey && !columnsState) {
ncs.persistenceKey = persistenceKey
}
if (columnsState) {
ncs = { ...ncs, columnsState }
if (!ncs?.persistenceKey && persistenceKey) {
ncs.persistenceKey = persistenceKey
}
}
return ncs
}, [persistenceKey, columnsState])
const newSearch = useMemo(() => {
let ns: any = {
span: {
xs: 24,
sm: 24,
md: 12,
lg: 8,
xl: 6,
xxl: 6,
}
}
if (isBoolean(search)) {
ns = search
} else {
ns = { ...ns, ...search }
}
return ns
}, [search])
const newScroll = useMemo(() => {
return { x: 1300, ...scroll }
}, [scroll])
const newSearchFormRender = (props, defaultDom) => {
return <>
{searchTitle && <div className='searchTitle absolute text-base z-50 font-medium text-[#333]' style={{ top: '40px', left: '44px' }}>{searchTitle}</div>}
{defaultDom}
</>
}
const getTableProps = () => ({
...rest,
cardBordered,
bordered,
columnsState: newColumnsState,
search: newSearch,
scroll: newScroll,
pagination: newPagination,
columns: newColumns,
options: newOptions,
searchFormRender: searchFormRender ? searchFormRender : newSearchFormRender
})
return (
<ProTable<DataType, Params, ValueType>
{...getTableProps()}
>{props?.children}</ProTable>
);
};

export { CSProTable };

4. 参考引用

Pro-Components 组件 ProTable 文档