@kne/table-page 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1923 -0
- package/dist/index.css +819 -0
- package/dist/index.css.map +1 -0
- package/dist/index.js +3539 -0
- package/dist/index.js.map +1 -0
- package/dist/index.modern.js +3453 -0
- package/dist/index.modern.js.map +1 -0
- package/dist/locale/en-US.js +17 -0
- package/dist/locale/en-US.js.map +1 -0
- package/dist/locale/en-US.modern.js +17 -0
- package/dist/locale/en-US.modern.js.map +1 -0
- package/dist/locale/zh-CN.js +17 -0
- package/dist/locale/zh-CN.js.map +1 -0
- package/dist/locale/zh-CN.modern.js +17 -0
- package/dist/locale/zh-CN.modern.js.map +1 -0
- package/package.json +119 -0
package/README.md
ADDED
|
@@ -0,0 +1,1923 @@
|
|
|
1
|
+
# table-page
|
|
2
|
+
|
|
3
|
+
### 描述
|
|
4
|
+
|
|
5
|
+
A React table page component based on Ant Design 5, supporting column config, filter, sort and batch operations.
|
|
6
|
+
|
|
7
|
+
### 安装
|
|
8
|
+
|
|
9
|
+
```shell
|
|
10
|
+
npm i --save @kne/table-page
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### 概述
|
|
14
|
+
|
|
15
|
+
`@kne/table-page` 是一个基于 React 和 Ant Design 的表格页面组件库,提供开箱即用的数据表格解决方案。组件库围绕表格的常见业务场景,封装了数据加载、分页、排序、行选择、列配置、筛选搜索、批量操作等能力,帮助开发者快速构建功能完善的表格管理页面。
|
|
16
|
+
|
|
17
|
+
### 核心组件
|
|
18
|
+
|
|
19
|
+
#### TablePage
|
|
20
|
+
|
|
21
|
+
表格页面主组件,基于 `@kne/react-fetch` 封装数据请求与分页逻辑。内置两种渲染模式:
|
|
22
|
+
|
|
23
|
+
- **`Table` 模式**(默认):基于 antd `Table`,支持列宽拖动、字段显示/隐藏、分组表头、粘性表头等
|
|
24
|
+
- **`TableView` 模式**:基于 antd Row/Col 栅格布局,适合移动端或卡片式表格场景
|
|
25
|
+
|
|
26
|
+
通过 `loader` 或 `url` 配置数据源,通过 `dataFormat` 适配不同的接口数据结构。分页器渲染在表格外侧,翻页默认采用 `reload` 方式(不显示全屏 loading)。
|
|
27
|
+
|
|
28
|
+
同时内置了顶部工具栏(`TableToolbar`),整合筛选、搜索、批量操作三大能力:
|
|
29
|
+
|
|
30
|
+
- **筛选(filter)**:基于 `@kne/react-filter` 的 `FilterLines`,支持多行多字段组合筛选,筛选值变化时自动 `reload` 并回到第 1 页
|
|
31
|
+
- **搜索(search)**:基于 `@kne/react-filter` 的 `SearchInput`,支持关键词搜索与防抖自动提交,与筛选器共享筛选值状态
|
|
32
|
+
- **批量操作(batchActions)**:配合 `rowSelection` 和 `useSelectedRow`,提供下拉菜单形式的批量操作(如批量导出、批量通知),未选中时自动禁用
|
|
33
|
+
- **已选筛选值展示**:工具栏下方展示当前生效的筛选条件标签,支持快速清除
|
|
34
|
+
|
|
35
|
+
#### Table
|
|
36
|
+
|
|
37
|
+
基于 antd `Table` 的表格组件,与 `TableView` 共享相同的 `columns`、`rowSelection` 等 API。额外提供以下增强能力:
|
|
38
|
+
|
|
39
|
+
- **列宽拖动**:悬停表头列右侧拖动手柄可调整列宽,支持 `min`/`max` 限制
|
|
40
|
+
- **列配置面板**:通过表头最后一列的设置图标,可显示/隐藏字段、拖拽排序
|
|
41
|
+
- **配置持久化**:设置 `name` 后,列宽和显示状态自动保存到 localStorage
|
|
42
|
+
- **分组表头**:通过 `groupHeader` 配置实现多级表头结构
|
|
43
|
+
- **浮动横向滚动条**:当表格宽度超出容器时,底部自动显示横向滚动条(通过 `horizontalScroller` 控制)
|
|
44
|
+
|
|
45
|
+
#### TableView
|
|
46
|
+
|
|
47
|
+
基于 CSS Grid 的表格视图组件,以 antd Row/Col 布局为基础。相比于 `Table`,它更轻量灵活,适合需要自定义渲染的场景。支持:
|
|
48
|
+
|
|
49
|
+
- 基于 24 栅格的列宽分配(`span` 属性)
|
|
50
|
+
- CSS Grid 自动布局,内容超出时自动撑开
|
|
51
|
+
- 行选择(checkbox 多选 / radio 单选)
|
|
52
|
+
- 行点击事件
|
|
53
|
+
- 通过 `render` 属性自定义渲染,可拆分表头和表体
|
|
54
|
+
|
|
55
|
+
### 核心 Hooks
|
|
56
|
+
|
|
57
|
+
#### useSelectedRow
|
|
58
|
+
|
|
59
|
+
行选择 Hook,支持多选(checkbox)和单选(radio)两种模式。提供:
|
|
60
|
+
|
|
61
|
+
- `getRowSelection(dataSource)` 生成标准 `rowSelection` 配置,可直接传入 `Table` 或 `TableView`
|
|
62
|
+
- `selectedRowKeys` 和 `selectedRows` 追踪选中状态
|
|
63
|
+
- `setSelectedRowKeys(keys, dataSource)` 从 key 列表反查完整行数据
|
|
64
|
+
- `clearSelectedRows()` 一键清空选择
|
|
65
|
+
|
|
66
|
+
适用于批量操作(批量删除、批量导出等)和单选场景(详情查看、关联选择等)。
|
|
67
|
+
|
|
68
|
+
#### useSort
|
|
69
|
+
|
|
70
|
+
排序 Hook,配合 `Table`/`TableView` 的 `sortRender` 实现表头排序交互。支持:
|
|
71
|
+
|
|
72
|
+
- **单列排序**(`sort: true` 或 `sort: { single: true }`):切换列时自动清除其他列的排序
|
|
73
|
+
- **多列排序**(`sort: { single: false }`):允许多列同时排序
|
|
74
|
+
- 排序状态循环切换:DESC → ASC → 取消
|
|
75
|
+
- `sortDataSource(dataSource, sort, columns)` 工具函数,支持本地排序(包含中文排序)
|
|
76
|
+
|
|
77
|
+
### 列渲染类型系统
|
|
78
|
+
|
|
79
|
+
通过 `renderType` 属性,可以用声明式的方式定义列的渲染样式,无需手写 `render` 函数。内置以下 render 类型:
|
|
80
|
+
|
|
81
|
+
| 类型 | 说明 |
|
|
82
|
+
|------|------|
|
|
83
|
+
| `main` | 主要内容列,自动省略号,较大宽度 |
|
|
84
|
+
| `options` | 操作列,铺满单元格 |
|
|
85
|
+
| `enum` | 枚举值渲染,自动映射 color/text |
|
|
86
|
+
| `tag` | 标签渲染,单个 Tag 组件 |
|
|
87
|
+
| `tagList` | 标签列表渲染,多个 Tag 组件 |
|
|
88
|
+
| `amount` | 金额列,右对齐,自动省略号 |
|
|
89
|
+
| `list` | 列表渲染,自动省略号 |
|
|
90
|
+
| `description` | 描述文本,大宽度,自动省略号 |
|
|
91
|
+
|
|
92
|
+
支持尺寸修饰符:
|
|
93
|
+
|
|
94
|
+
- `short`:缩小宽度(约 120px)
|
|
95
|
+
- `small`:最小宽度(约 100px)
|
|
96
|
+
- `large`:放大宽度(约 300px)
|
|
97
|
+
|
|
98
|
+
例如 `renderType: "enum-small"` 表示枚举值 + 小尺寸列。维度(width、min、max、ellipsis)可通过 `globalParams.renderTypeSize` 全局定制。
|
|
99
|
+
|
|
100
|
+
默认导出 `getTagColor`、`renderTagItem`、`renderTagList` 工具函数,用于 Tag 相关渲染。
|
|
101
|
+
|
|
102
|
+
### 其他导出
|
|
103
|
+
|
|
104
|
+
| 导出项 | 说明 |
|
|
105
|
+
|--------|------|
|
|
106
|
+
| `tableLocalApis` | 基于 localStorage 的列配置存取 API,可替换为服务端存储 |
|
|
107
|
+
| `useTableConfig` | 列配置 Hook,提供列宽、显示状态的管理能力 |
|
|
108
|
+
| `preset` / `globalParams` | 全局参数预设,用于设置 renderType 映射和标签颜色等全局配置 |
|
|
109
|
+
| `Ellipsis` | 超出省略组件,基于 antd Typography |
|
|
110
|
+
| `label` | 标签组件 |
|
|
111
|
+
| `sortDataSource` | 客户端排序工具函数 |
|
|
112
|
+
|
|
113
|
+
### 使用场景
|
|
114
|
+
|
|
115
|
+
- **后台管理系统**:订单管理、用户列表、商品管理等 CRUD 页面
|
|
116
|
+
- **数据报表**:配合排序、分页、总结栏展示统计数据
|
|
117
|
+
- **列表配置页**:需要用户自定义列宽、显示字段的表格场景
|
|
118
|
+
- **移动端适配**:使用 `TableView` 模式实现栅格式数据展示
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
### 示例
|
|
122
|
+
|
|
123
|
+
#### 示例代码
|
|
124
|
+
|
|
125
|
+
- TablePage
|
|
126
|
+
- 表格页面组件,基于 @kne/react-fetch 实现数据加载与分页,支持 useSort 服务端排序、列配置、总结栏等
|
|
127
|
+
- _TablePage(@kne/current-lib_table-page)[import * as _TablePage from "@kne/table-page"],(@kne/current-lib_table-page/dist/index.css),antd(antd),_ReactFilter(@kne/react-filter)[import * as _ReactFilter from "@kne/react-filter"],(@kne/react-filter/dist/index.css)
|
|
128
|
+
|
|
129
|
+
```jsx
|
|
130
|
+
const { default: TablePage, Table } = _TablePage;
|
|
131
|
+
const { fields } = _ReactFilter;
|
|
132
|
+
const { SuperSelectFilterItem } = fields;
|
|
133
|
+
const { Table: AntTable, Flex, Badge, Tag, Button, Space, message } = antd;
|
|
134
|
+
const { useMemo } = React;
|
|
135
|
+
|
|
136
|
+
const TOTAL = 156;
|
|
137
|
+
|
|
138
|
+
const range = (start, end) => Array.from({ length: end - start }, (_, i) => start + i);
|
|
139
|
+
|
|
140
|
+
const surnames = ['张', '李', '王', '刘', '陈'];
|
|
141
|
+
const givenNames = ['伟', '强', '敏', '磊', '杰', '婷', '娜', '静', '丽', '娟'];
|
|
142
|
+
const departments = ['技术研发部', '产品设计部', '市场营销部', '人力资源部', '财务部'];
|
|
143
|
+
const positions = ['工程师', '高级工程师', '经理', '总监', '专员'];
|
|
144
|
+
const educations = ['本科', '硕士', '博士', '大专'];
|
|
145
|
+
const performances = ['A', 'B', 'C', 'S'];
|
|
146
|
+
|
|
147
|
+
const statusMap = {
|
|
148
|
+
active: { color: 'success', text: '在职' },
|
|
149
|
+
vacation: { color: 'warning', text: '休假' },
|
|
150
|
+
resigned: { color: 'default', text: '离职' },
|
|
151
|
+
probation: { color: 'processing', text: '试用期' }
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const perfMap = {
|
|
155
|
+
S: { color: 'success', text: 'S' },
|
|
156
|
+
A: { color: 'processing', text: 'A' },
|
|
157
|
+
B: { color: 'warning', text: 'B' },
|
|
158
|
+
C: { color: 'error', text: 'C' }
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const departmentOptions = departments.map(item => ({ value: item, label: item }));
|
|
162
|
+
const statusOptions = Object.entries(statusMap).map(([value, { text }]) => ({ value, label: text }));
|
|
163
|
+
|
|
164
|
+
const buildEmployee = index => {
|
|
165
|
+
const statusKeys = ['active', 'vacation', 'resigned', 'probation'];
|
|
166
|
+
return {
|
|
167
|
+
id: `EMP${String(index + 1).padStart(4, '0')}`,
|
|
168
|
+
employeeNo: `EMP-2024-${String(index + 1).padStart(4, '0')}`,
|
|
169
|
+
name: `${surnames[index % surnames.length]}${givenNames[index % givenNames.length]}`,
|
|
170
|
+
department: departments[index % departments.length],
|
|
171
|
+
position: positions[index % positions.length],
|
|
172
|
+
status: statusKeys[index % statusKeys.length],
|
|
173
|
+
email: `employee${index + 1}@company.com`,
|
|
174
|
+
phone: `138${String(index).padStart(8, '0')}`,
|
|
175
|
+
joinDate: `2023-${String((index % 12) + 1).padStart(2, '0')}-${String((index % 28) + 1).padStart(2, '0')}`,
|
|
176
|
+
workYears: Math.floor(index / 12) + 1,
|
|
177
|
+
salary: `${15 + (index % 20)}K-${20 + (index % 20)}K`,
|
|
178
|
+
education: educations[index % educations.length],
|
|
179
|
+
performance: performances[index % performances.length]
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const statusRender = value => {
|
|
184
|
+
const { color, text } = statusMap[value] || { color: 'default', text: value };
|
|
185
|
+
return <Badge status={color} text={text} />;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const perfRender = value => {
|
|
189
|
+
const { color, text } = perfMap[value] || { color: 'default', text: value };
|
|
190
|
+
return <Tag color={color === 'processing' ? 'blue' : color === 'success' ? 'green' : color === 'warning' ? 'orange' : color === 'error' ? 'red' : 'default'}>{text}</Tag>;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const columns = [
|
|
194
|
+
{ name: 'employeeNo', title: '工号', width: 180, min: 120, max: 240, fixed: 'left', sort: { single: true } },
|
|
195
|
+
{ name: 'name', title: '姓名', width: 100, min: 80, max: 160, sort: true },
|
|
196
|
+
{ name: 'department', title: '部门', width: 150, min: 120, max: 240, sort: true },
|
|
197
|
+
{ name: 'position', title: '职位', width: 120, min: 100, max: 200 },
|
|
198
|
+
{ name: 'status', title: '状态', width: 100, min: 80, max: 140, render: statusRender },
|
|
199
|
+
{ name: 'performance', title: '绩效', width: 80, min: 70, max: 120, render: perfRender },
|
|
200
|
+
{ name: 'phone', title: '手机号', width: 140, min: 120, max: 180, render: value => value.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3') },
|
|
201
|
+
{ name: 'email', title: '邮箱', width: 200, min: 160, max: 320, ellipsis: true },
|
|
202
|
+
{ name: 'joinDate', title: '入职日期', width: 120, min: 100, max: 160, format: 'date', sort: true },
|
|
203
|
+
{ name: 'workYears', title: '工龄', width: 90, min: 70, max: 120, sort: true, render: value => `${value}年` },
|
|
204
|
+
{ name: 'salary', title: '薪资范围', width: 120, min: 100, max: 180, hidden: true },
|
|
205
|
+
{ name: 'education', title: '学历', width: 90, min: 70, max: 120, hidden: true },
|
|
206
|
+
{
|
|
207
|
+
name: 'options',
|
|
208
|
+
title: '操作',
|
|
209
|
+
renderType: 'options',
|
|
210
|
+
fixed: 'right',
|
|
211
|
+
width: 160,
|
|
212
|
+
min: 120,
|
|
213
|
+
max: 200,
|
|
214
|
+
getValueOf: item => {
|
|
215
|
+
const actions = [
|
|
216
|
+
{ children: '查看', onClick: () => message.info(`查看 ${item.name}`) },
|
|
217
|
+
{ children: '编辑', onClick: () => message.info(`编辑 ${item.name}`) }
|
|
218
|
+
];
|
|
219
|
+
if (item.status !== 'resigned') {
|
|
220
|
+
actions.push({
|
|
221
|
+
children: '离职办理',
|
|
222
|
+
onClick: () => message.warning(`办理离职 ${item.name}`)
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return actions;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
const sortFieldLabels = {
|
|
231
|
+
employeeNo: '工号',
|
|
232
|
+
name: '姓名',
|
|
233
|
+
department: '部门',
|
|
234
|
+
joinDate: '入职日期',
|
|
235
|
+
workYears: '工龄'
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const normalizeFilterValue = value => {
|
|
239
|
+
if (value == null) {
|
|
240
|
+
return value;
|
|
241
|
+
}
|
|
242
|
+
return Array.isArray(value) ? value[0] : value;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const applyFilters = (employees, data, requestParams) => {
|
|
246
|
+
const params = Object.assign({}, requestParams?.data, data);
|
|
247
|
+
let result = employees;
|
|
248
|
+
|
|
249
|
+
if (params.keyword) {
|
|
250
|
+
const keyword = String(params.keyword).toLowerCase();
|
|
251
|
+
result = result.filter(item => item.employeeNo.toLowerCase().includes(keyword) || item.name.includes(params.keyword));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const department = normalizeFilterValue(params.department);
|
|
255
|
+
if (department) {
|
|
256
|
+
result = result.filter(item => item.department === department);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const status = normalizeFilterValue(params.status);
|
|
260
|
+
if (status) {
|
|
261
|
+
result = result.filter(item => item.status === status);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return result;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const SortState = ({ sort }) => (
|
|
268
|
+
<div style={{ padding: '12px', background: '#f5f5f5', borderRadius: 8, fontSize: 13 }}>
|
|
269
|
+
当前排序:
|
|
270
|
+
{sort.length ? (
|
|
271
|
+
sort.map(item => (
|
|
272
|
+
<Tag key={item.name} color="blue" style={{ marginLeft: 8 }}>
|
|
273
|
+
{sortFieldLabels[item.name] || item.name} {item.sort}
|
|
274
|
+
</Tag>
|
|
275
|
+
))
|
|
276
|
+
) : (
|
|
277
|
+
<span style={{ marginLeft: 8, color: '#999' }}>无</span>
|
|
278
|
+
)}
|
|
279
|
+
</div>
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
const Tips = () => (
|
|
283
|
+
<div style={{ color: '#666', fontSize: 13, lineHeight: 1.8 }}>
|
|
284
|
+
<div>
|
|
285
|
+
<Tag color="blue">数据加载</Tag>
|
|
286
|
+
通过 <code>loader</code> 模拟分页接口,请求参数为 <code>data.currentPage</code>、<code>data.perPage</code>。
|
|
287
|
+
</div>
|
|
288
|
+
<div>
|
|
289
|
+
<Tag color="green">分页</Tag>
|
|
290
|
+
分页器渲染在表格外侧,翻页时以 <code>reload</code> 方式请求;<code>pageSize</code> 会持久化到 localStorage。
|
|
291
|
+
</div>
|
|
292
|
+
<div>
|
|
293
|
+
<Tag color="gold">筛选</Tag>
|
|
294
|
+
顶部工具栏集成 <code>filter</code>、<code>search</code>、<code>batchActions</code>;筛选变化自动 <code>reload</code> 并回到第 1 页。
|
|
295
|
+
</div>
|
|
296
|
+
<div>
|
|
297
|
+
<Tag color="orange">列配置</Tag>
|
|
298
|
+
设置 <code>name</code> 开启列宽拖动与显示/隐藏,「薪资范围」「学历」默认隐藏;操作列使用 <code>renderType="options"</code> 且 <code>fixed="right"</code>。
|
|
299
|
+
</div>
|
|
300
|
+
<div>
|
|
301
|
+
<Tag color="cyan">排序</Tag>
|
|
302
|
+
配合 <code>Table.useSort</code> 与 <code>sortRender</code>,在 <code>onSortChange</code> 中调用 <code>reload</code> 传排序参数,与翻页一样不闪烁。
|
|
303
|
+
</div>
|
|
304
|
+
<div>
|
|
305
|
+
<Tag color="purple">总结栏</Tag>
|
|
306
|
+
<code>summary</code> 回调可拿到 <code>data</code>、<code>requestParams</code> 等 fetch 上下文。
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
const BaseExample = () => {
|
|
312
|
+
const tableRef = React.useRef();
|
|
313
|
+
const allEmployees = useMemo(() => range(0, TOTAL).map(buildEmployee), []);
|
|
314
|
+
const { selectedRows, getRowSelection } = Table.useSelectedRow({ rowKey: 'id' });
|
|
315
|
+
const { sort, sortRender } = Table.useSort({
|
|
316
|
+
defaultSort: [{ name: 'joinDate', sort: 'DESC' }],
|
|
317
|
+
onSortChange: newSort => {
|
|
318
|
+
tableRef.current?.reload({
|
|
319
|
+
data: { currentPage: 1, sort: newSort }
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
return (
|
|
325
|
+
<Flex vertical gap={16}>
|
|
326
|
+
<Tips />
|
|
327
|
+
<SortState sort={sort} />
|
|
328
|
+
<Space>
|
|
329
|
+
<Button
|
|
330
|
+
onClick={() => {
|
|
331
|
+
tableRef.current?.reload({
|
|
332
|
+
data: { currentPage: 1 }
|
|
333
|
+
});
|
|
334
|
+
}}
|
|
335
|
+
>
|
|
336
|
+
重新加载(回到第 1 页)
|
|
337
|
+
</Button>
|
|
338
|
+
<Button
|
|
339
|
+
onClick={() => {
|
|
340
|
+
tableRef.current?.refresh();
|
|
341
|
+
}}
|
|
342
|
+
>
|
|
343
|
+
刷新当前页
|
|
344
|
+
</Button>
|
|
345
|
+
</Space>
|
|
346
|
+
<TablePage
|
|
347
|
+
ref={tableRef}
|
|
348
|
+
name="demo-employee-table"
|
|
349
|
+
sticky
|
|
350
|
+
sortRender={sortRender}
|
|
351
|
+
scroll={{ x: 1600 }}
|
|
352
|
+
rowSelection={getRowSelection(allEmployees)}
|
|
353
|
+
selectedRows={selectedRows}
|
|
354
|
+
search={{ name: 'keyword', label: '关键词', placeholder: '搜索工号/姓名', style: { width: 220 } }}
|
|
355
|
+
filter={{
|
|
356
|
+
list: [
|
|
357
|
+
[
|
|
358
|
+
{
|
|
359
|
+
type: SuperSelectFilterItem,
|
|
360
|
+
props: { name: 'department', label: '部门', single: true, options: departmentOptions }
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
type: SuperSelectFilterItem,
|
|
364
|
+
props: { name: 'status', label: '状态', single: true, options: statusOptions }
|
|
365
|
+
}
|
|
366
|
+
]
|
|
367
|
+
],
|
|
368
|
+
displayLine: 1
|
|
369
|
+
}}
|
|
370
|
+
batchActions={[
|
|
371
|
+
{
|
|
372
|
+
key: 'export',
|
|
373
|
+
label: '批量导出',
|
|
374
|
+
onClick: ({ selectedRowKeys }) => {
|
|
375
|
+
message.info(`正在导出 ${selectedRowKeys.length} 名员工`);
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
key: 'notify',
|
|
380
|
+
label: '批量通知',
|
|
381
|
+
onClick: ({ selectedRowKeys }) => {
|
|
382
|
+
message.success(`已通知 ${selectedRowKeys.length} 名员工`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
]}
|
|
386
|
+
pagination={{
|
|
387
|
+
open: true,
|
|
388
|
+
pageSize: 10,
|
|
389
|
+
showSizeChanger: true,
|
|
390
|
+
showQuickJumper: true,
|
|
391
|
+
pageSizeOptions: ['10', '20', '50', '100']
|
|
392
|
+
}}
|
|
393
|
+
dataFormat={data => ({
|
|
394
|
+
list: data.pageData,
|
|
395
|
+
total: data.totalCount,
|
|
396
|
+
data
|
|
397
|
+
})}
|
|
398
|
+
loader={({ data, requestParams }) => {
|
|
399
|
+
const currentPage = Number(data?.currentPage ?? requestParams?.data?.currentPage) || 1;
|
|
400
|
+
const perPage = Number(data?.perPage ?? requestParams?.data?.perPage) || 20;
|
|
401
|
+
const sortParams = data?.sort ?? requestParams?.data?.sort ?? [{ name: 'joinDate', sort: 'DESC' }];
|
|
402
|
+
const filteredEmployees = applyFilters(allEmployees, data, requestParams);
|
|
403
|
+
const sortedEmployees = sortParams.length ? Table.sortDataSource(filteredEmployees, sortParams, columns) : filteredEmployees;
|
|
404
|
+
const startIndex = (currentPage - 1) * perPage;
|
|
405
|
+
|
|
406
|
+
return new Promise(resolve => {
|
|
407
|
+
setTimeout(() => {
|
|
408
|
+
resolve({
|
|
409
|
+
pageData: sortedEmployees.slice(startIndex, startIndex + perPage),
|
|
410
|
+
totalCount: filteredEmployees.length
|
|
411
|
+
});
|
|
412
|
+
}, 400);
|
|
413
|
+
});
|
|
414
|
+
}}
|
|
415
|
+
columns={columns}
|
|
416
|
+
summary={({ pageData, data }) => {
|
|
417
|
+
const totalCount = data?.totalCount || 0;
|
|
418
|
+
return (
|
|
419
|
+
<AntTable.Summary fixed>
|
|
420
|
+
<AntTable.Summary.Row>
|
|
421
|
+
<AntTable.Summary.Cell index={0} colSpan={5}>
|
|
422
|
+
<strong>当前页统计</strong>
|
|
423
|
+
</AntTable.Summary.Cell>
|
|
424
|
+
<AntTable.Summary.Cell index={5}>
|
|
425
|
+
<strong>{pageData.length} 人</strong>
|
|
426
|
+
</AntTable.Summary.Cell>
|
|
427
|
+
<AntTable.Summary.Cell index={6} colSpan={7}>
|
|
428
|
+
<strong>总员工数: {totalCount} 人</strong>
|
|
429
|
+
</AntTable.Summary.Cell>
|
|
430
|
+
</AntTable.Summary.Row>
|
|
431
|
+
</AntTable.Summary>
|
|
432
|
+
);
|
|
433
|
+
}}
|
|
434
|
+
/>
|
|
435
|
+
</Flex>
|
|
436
|
+
);
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
render(<BaseExample />);
|
|
440
|
+
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
- TableView
|
|
444
|
+
- 表格视图组件,支持行选择、列宽设置等
|
|
445
|
+
- _TablePage(@kne/current-lib_table-page)[import * as _TablePage from "@kne/table-page"],(@kne/current-lib_table-page/dist/index.css),antd(antd)
|
|
446
|
+
|
|
447
|
+
```jsx
|
|
448
|
+
const { TableView } = _TablePage;
|
|
449
|
+
const { Flex, Tag, Badge } = antd;
|
|
450
|
+
const { useState } = React;
|
|
451
|
+
|
|
452
|
+
const dataSource = [
|
|
453
|
+
{
|
|
454
|
+
id: 'ORD20240115001',
|
|
455
|
+
customerName: '深圳市腾讯计算机系统有限公司',
|
|
456
|
+
contact: '张三',
|
|
457
|
+
phone: '138-0013-8000',
|
|
458
|
+
amount: 42500,
|
|
459
|
+
status: '已完成',
|
|
460
|
+
orderDate: '2024-01-15',
|
|
461
|
+
deliveryDate: '2024-01-17'
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
id: 'ORD20240115002',
|
|
465
|
+
customerName: '华为技术有限公司',
|
|
466
|
+
contact: '李四',
|
|
467
|
+
phone: '139-0014-9000',
|
|
468
|
+
amount: 85000,
|
|
469
|
+
status: '处理中',
|
|
470
|
+
orderDate: '2024-01-15',
|
|
471
|
+
deliveryDate: '2024-01-20'
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
id: 'ORD20240115003',
|
|
475
|
+
customerName: '阿里巴巴集团控股有限公司',
|
|
476
|
+
contact: '王五',
|
|
477
|
+
phone: '137-0015-7000',
|
|
478
|
+
amount: 120000,
|
|
479
|
+
status: '待发货',
|
|
480
|
+
orderDate: '2024-01-14',
|
|
481
|
+
deliveryDate: '2024-01-22'
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
id: 'ORD20240115004',
|
|
485
|
+
customerName: '北京字节跳动科技有限公司',
|
|
486
|
+
contact: '赵六',
|
|
487
|
+
phone: '136-0016-6000',
|
|
488
|
+
amount: 65000,
|
|
489
|
+
status: '已完成',
|
|
490
|
+
orderDate: '2024-01-13',
|
|
491
|
+
deliveryDate: '2024-01-16'
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
id: 'ORD20240115005',
|
|
495
|
+
customerName: '百度在线网络技术(北京)有限公司',
|
|
496
|
+
contact: '钱七',
|
|
497
|
+
phone: '135-0017-5000',
|
|
498
|
+
amount: 95000,
|
|
499
|
+
status: '已取消',
|
|
500
|
+
orderDate: '2024-01-12',
|
|
501
|
+
deliveryDate: ''
|
|
502
|
+
}
|
|
503
|
+
];
|
|
504
|
+
|
|
505
|
+
const columns = [
|
|
506
|
+
{ name: 'id', title: '订单编号', width: 180 },
|
|
507
|
+
{ name: 'customerName', title: '客户名称', span: 10 },
|
|
508
|
+
{ name: 'contact', title: '联系人', width: 80 },
|
|
509
|
+
{ name: 'phone', title: '联系电话', width: '130px', render: (value) => value.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3') },
|
|
510
|
+
{ name: 'amount', title: '订单金额(元)', render: (value) => <strong style={{ color: '#f5222d' }}>¥{value.toLocaleString()}</strong> },
|
|
511
|
+
{ name: 'orderDate', title: '下单日期', format: 'date' },
|
|
512
|
+
{ name: 'deliveryDate', title: '预计送达', format: 'date' },
|
|
513
|
+
{ name: 'status', title: '订单状态', width: 100, render: (value) => {
|
|
514
|
+
const config = {
|
|
515
|
+
'已完成': { color: 'success', text: '已完成' },
|
|
516
|
+
'处理中': { color: 'processing', text: '处理中' },
|
|
517
|
+
'待发货': { color: 'warning', text: '待发货' },
|
|
518
|
+
'已取消': { color: 'default', text: '已取消' }
|
|
519
|
+
};
|
|
520
|
+
const { color, text } = config[value] || { color: 'default', text: value };
|
|
521
|
+
return <Badge status={color} text={text} />;
|
|
522
|
+
}}
|
|
523
|
+
];
|
|
524
|
+
|
|
525
|
+
const WithCheckbox = () => {
|
|
526
|
+
const [selectKeys, setSelectKeys] = useState([]);
|
|
527
|
+
const totalAmount = selectKeys.reduce((sum, id) => sum + (dataSource.find(d => d.id === id)?.amount || 0), 0);
|
|
528
|
+
return (
|
|
529
|
+
<div>
|
|
530
|
+
<Flex justify="space-between" align="center" style={{ marginBottom: 12 }}>
|
|
531
|
+
<span>已选 <strong>{selectKeys.length}</strong> 个订单,总金额 <strong style={{ color: '#52c41a' }}>¥{totalAmount.toLocaleString()}</strong></span>
|
|
532
|
+
</Flex>
|
|
533
|
+
<TableView dataSource={dataSource} columns={columns} rowSelection={{
|
|
534
|
+
type: 'checkbox', allowSelectedAll: true, selectedRowKeys: selectKeys, onChange: setSelectKeys
|
|
535
|
+
}} />
|
|
536
|
+
</div>
|
|
537
|
+
);
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
const WithSelected = () => {
|
|
541
|
+
const [selectKeys, setSelectKeys] = useState([]);
|
|
542
|
+
const selectedOrder = dataSource.find(d => d.id === selectKeys[0]);
|
|
543
|
+
return (
|
|
544
|
+
<div>
|
|
545
|
+
<Flex justify="space-between" align="center" style={{ marginBottom: 12 }}>
|
|
546
|
+
<span>已选订单:{selectedOrder ? `${selectedOrder.id} (${selectedOrder.customerName})` : '无'}</span>
|
|
547
|
+
{selectedOrder && <Tag color="blue">¥{selectedOrder.amount.toLocaleString()}</Tag>}
|
|
548
|
+
</Flex>
|
|
549
|
+
<TableView dataSource={dataSource} columns={columns} rowSelection={{
|
|
550
|
+
type: 'radio', selectedRowKeys: selectKeys, onChange: setSelectKeys
|
|
551
|
+
}} />
|
|
552
|
+
</div>
|
|
553
|
+
);
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
const WithColumnWidth = () => {
|
|
557
|
+
const widthColumns = [
|
|
558
|
+
{ name: 'id', title: '订单编号', width: 180 },
|
|
559
|
+
{ name: 'customerName', title: '客户名称', width: '200px' },
|
|
560
|
+
{ name: 'amount', title: '订单金额(元)', width: 120, render: (value) => <strong style={{ color: '#f5222d' }}>¥{value.toLocaleString()}</strong> },
|
|
561
|
+
{ name: 'status', title: '订单状态', width: '100px', render: (value) => {
|
|
562
|
+
const config = {
|
|
563
|
+
'已完成': { color: 'success', text: '已完成' },
|
|
564
|
+
'处理中': { color: 'processing', text: '处理中' },
|
|
565
|
+
'待发货': { color: 'warning', text: '待发货' },
|
|
566
|
+
'已取消': { color: 'default', text: '已取消' }
|
|
567
|
+
};
|
|
568
|
+
const { color, text } = config[value] || { color: 'default', text: value };
|
|
569
|
+
return <Badge status={color} text={text} />;
|
|
570
|
+
}}
|
|
571
|
+
];
|
|
572
|
+
return (
|
|
573
|
+
<div>
|
|
574
|
+
<div style={{ marginBottom: 12, color: '#666', fontSize: 13 }}>
|
|
575
|
+
通过 columns 的 <code>width</code> 设置列最小宽度,支持数字(如 <code>180</code>)或字符串(如 <code>'100px'</code>),内容超出时会自动撑开
|
|
576
|
+
</div>
|
|
577
|
+
<TableView dataSource={dataSource.slice(0, 3)} columns={widthColumns} />
|
|
578
|
+
</div>
|
|
579
|
+
);
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
const BaseExample = () => {
|
|
583
|
+
return (
|
|
584
|
+
<Flex vertical gap={16}>
|
|
585
|
+
<div style={{ background: '#f5f5f5', padding: '12px', borderRadius: '8px' }}>
|
|
586
|
+
订单列表 - 共 <strong>{dataSource.length}</strong> 个订单
|
|
587
|
+
</div>
|
|
588
|
+
<WithColumnWidth />
|
|
589
|
+
<TableView dataSource={dataSource} columns={columns} />
|
|
590
|
+
<WithCheckbox />
|
|
591
|
+
<WithSelected />
|
|
592
|
+
<div style={{ padding: '16px', background: '#fafafa', border: '1px dashed #d9d9d9', borderRadius: '8px' }}>
|
|
593
|
+
暂无订单数据
|
|
594
|
+
</div>
|
|
595
|
+
</Flex>
|
|
596
|
+
);
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
render(<BaseExample />);
|
|
600
|
+
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
- Table
|
|
604
|
+
- 基于 antd Table 的表格组件,支持列宽拖动、字段显示/隐藏,与 TableView 使用一致的 columns、rowSelection 等 API
|
|
605
|
+
- _TablePage(@kne/current-lib_table-page)[import * as _TablePage from "@kne/table-page"],(@kne/current-lib_table-page/dist/index.css),antd(antd)
|
|
606
|
+
|
|
607
|
+
```jsx
|
|
608
|
+
const { Table } = _TablePage;
|
|
609
|
+
const { Flex, Tag, Badge } = antd;
|
|
610
|
+
const { useState } = React;
|
|
611
|
+
|
|
612
|
+
const dataSource = [
|
|
613
|
+
{
|
|
614
|
+
id: 'ORD20240115001',
|
|
615
|
+
customerName: '深圳市腾讯计算机系统有限公司',
|
|
616
|
+
contact: '张三',
|
|
617
|
+
phone: '13800138000',
|
|
618
|
+
amount: 42500,
|
|
619
|
+
status: '已完成',
|
|
620
|
+
orderDate: '2024-01-15',
|
|
621
|
+
deliveryDate: '2024-01-17'
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
id: 'ORD20240115002',
|
|
625
|
+
customerName: '华为技术有限公司',
|
|
626
|
+
contact: '李四',
|
|
627
|
+
phone: '13900149000',
|
|
628
|
+
amount: 85000,
|
|
629
|
+
status: '处理中',
|
|
630
|
+
orderDate: '2024-01-15',
|
|
631
|
+
deliveryDate: '2024-01-20'
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
id: 'ORD20240115003',
|
|
635
|
+
customerName: '阿里巴巴集团控股有限公司',
|
|
636
|
+
contact: '王五',
|
|
637
|
+
phone: '13700157000',
|
|
638
|
+
amount: 120000,
|
|
639
|
+
status: '待发货',
|
|
640
|
+
orderDate: '2024-01-14',
|
|
641
|
+
deliveryDate: '2024-01-22'
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
id: 'ORD20240115004',
|
|
645
|
+
customerName: '北京字节跳动科技有限公司',
|
|
646
|
+
contact: '赵六',
|
|
647
|
+
phone: '13600166000',
|
|
648
|
+
amount: 65000,
|
|
649
|
+
status: '已完成',
|
|
650
|
+
orderDate: '2024-01-13',
|
|
651
|
+
deliveryDate: '2024-01-16'
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
id: 'ORD20240115005',
|
|
655
|
+
customerName: '百度在线网络技术(北京)有限公司',
|
|
656
|
+
contact: '钱七',
|
|
657
|
+
phone: '13500175000',
|
|
658
|
+
amount: 95000,
|
|
659
|
+
status: '已取消',
|
|
660
|
+
orderDate: '2024-01-12',
|
|
661
|
+
deliveryDate: ''
|
|
662
|
+
}
|
|
663
|
+
];
|
|
664
|
+
|
|
665
|
+
const columns = [
|
|
666
|
+
{ name: 'id', title: '订单编号', width: 180 },
|
|
667
|
+
{ name: 'customerName', title: '客户名称', width: 200 },
|
|
668
|
+
{ name: 'contact', title: '联系人', width: 80 },
|
|
669
|
+
{ name: 'phone', title: '联系电话', width: 130, render: value => value.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3') },
|
|
670
|
+
{ name: 'amount', title: '订单金额(元)', width: 120, render: value => <strong style={{ color: '#f5222d' }}>¥{value.toLocaleString()}</strong> },
|
|
671
|
+
{ name: 'orderDate', title: '下单日期', width: 110, format: 'date' },
|
|
672
|
+
{ name: 'deliveryDate', title: '预计送达', width: 110, format: 'date' },
|
|
673
|
+
{
|
|
674
|
+
name: 'status',
|
|
675
|
+
title: '订单状态',
|
|
676
|
+
width: 100,
|
|
677
|
+
render: value => {
|
|
678
|
+
const config = {
|
|
679
|
+
已完成: { color: 'success', text: '已完成' },
|
|
680
|
+
处理中: { color: 'processing', text: '处理中' },
|
|
681
|
+
待发货: { color: 'warning', text: '待发货' },
|
|
682
|
+
已取消: { color: 'default', text: '已取消' }
|
|
683
|
+
};
|
|
684
|
+
const { color, text } = config[value] || { color: 'default', text: value };
|
|
685
|
+
return <Badge status={color} text={text} />;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
];
|
|
689
|
+
|
|
690
|
+
const WithCheckbox = () => {
|
|
691
|
+
const [selectKeys, setSelectKeys] = useState([]);
|
|
692
|
+
const totalAmount = selectKeys.reduce((sum, id) => sum + (dataSource.find(d => d.id === id)?.amount || 0), 0);
|
|
693
|
+
return (
|
|
694
|
+
<div>
|
|
695
|
+
<Flex justify="space-between" align="center" style={{ marginBottom: 12 }}>
|
|
696
|
+
<span>
|
|
697
|
+
已选 <strong>{selectKeys.length}</strong> 个订单,总金额 <strong style={{ color: '#52c41a' }}>¥{totalAmount.toLocaleString()}</strong>
|
|
698
|
+
</span>
|
|
699
|
+
</Flex>
|
|
700
|
+
<Table
|
|
701
|
+
dataSource={dataSource}
|
|
702
|
+
columns={columns}
|
|
703
|
+
rowSelection={{
|
|
704
|
+
type: 'checkbox',
|
|
705
|
+
allowSelectedAll: true,
|
|
706
|
+
selectedRowKeys: selectKeys,
|
|
707
|
+
onChange: setSelectKeys
|
|
708
|
+
}}
|
|
709
|
+
/>
|
|
710
|
+
</div>
|
|
711
|
+
);
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
const WithSelected = () => {
|
|
715
|
+
const [selectKeys, setSelectKeys] = useState([]);
|
|
716
|
+
const selectedOrder = dataSource.find(d => d.id === selectKeys[0]);
|
|
717
|
+
return (
|
|
718
|
+
<div>
|
|
719
|
+
<Flex justify="space-between" align="center" style={{ marginBottom: 12 }}>
|
|
720
|
+
<span>已选订单:{selectedOrder ? `${selectedOrder.id} (${selectedOrder.customerName})` : '无'}</span>
|
|
721
|
+
{selectedOrder && <Tag color="blue">¥{selectedOrder.amount.toLocaleString()}</Tag>}
|
|
722
|
+
</Flex>
|
|
723
|
+
<Table
|
|
724
|
+
dataSource={dataSource}
|
|
725
|
+
columns={columns}
|
|
726
|
+
rowSelection={{
|
|
727
|
+
type: 'radio',
|
|
728
|
+
selectedRowKeys: selectKeys,
|
|
729
|
+
onChange: setSelectKeys
|
|
730
|
+
}}
|
|
731
|
+
/>
|
|
732
|
+
</div>
|
|
733
|
+
);
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
const WithScroll = () => {
|
|
737
|
+
return (
|
|
738
|
+
<div>
|
|
739
|
+
<div style={{ marginBottom: 12, color: '#666', fontSize: 13 }}>
|
|
740
|
+
基于 antd Table 渲染,支持 <code>scroll</code>、<code>sticky</code> 等原生表格能力
|
|
741
|
+
</div>
|
|
742
|
+
<Table
|
|
743
|
+
dataSource={dataSource}
|
|
744
|
+
columns={columns}
|
|
745
|
+
sticky
|
|
746
|
+
scroll={{ x: 1200, y: 240 }}
|
|
747
|
+
/>
|
|
748
|
+
</div>
|
|
749
|
+
);
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
const BaseExample = () => {
|
|
753
|
+
return (
|
|
754
|
+
<Flex vertical gap={16}>
|
|
755
|
+
<div style={{ background: '#f5f5f5', padding: '12px', borderRadius: '8px' }}>
|
|
756
|
+
订单列表(antd Table)- 共 <strong>{dataSource.length}</strong> 个订单,与 TableView 使用相同的 columns / rowSelection API
|
|
757
|
+
</div>
|
|
758
|
+
<Table dataSource={dataSource} columns={columns} />
|
|
759
|
+
<WithCheckbox />
|
|
760
|
+
<WithSelected />
|
|
761
|
+
<Table dataSource={[]} columns={columns} />
|
|
762
|
+
<WithScroll />
|
|
763
|
+
</Flex>
|
|
764
|
+
);
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
render(<BaseExample />);
|
|
768
|
+
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
- useSelectedRow
|
|
772
|
+
- 行选择 Hook,配合 Table / TableView 实现多选、全选、批量操作与单选
|
|
773
|
+
- _TablePage(@kne/current-lib_table-page)[import * as _TablePage from "@kne/table-page"],(@kne/current-lib_table-page/dist/index.css),antd(antd)
|
|
774
|
+
|
|
775
|
+
```jsx
|
|
776
|
+
const { Table, TableView } = _TablePage;
|
|
777
|
+
const { Button, Flex, Space, Badge, message } = antd;
|
|
778
|
+
|
|
779
|
+
const dataSource = [
|
|
780
|
+
{
|
|
781
|
+
id: 'ORD20240115001',
|
|
782
|
+
customerName: '深圳市腾讯计算机系统有限公司',
|
|
783
|
+
contact: '张三',
|
|
784
|
+
amount: 42500,
|
|
785
|
+
status: '待发货',
|
|
786
|
+
orderDate: '2024-01-15'
|
|
787
|
+
},
|
|
788
|
+
{
|
|
789
|
+
id: 'ORD20240115002',
|
|
790
|
+
customerName: '华为技术有限公司',
|
|
791
|
+
contact: '李四',
|
|
792
|
+
amount: 85000,
|
|
793
|
+
status: '处理中',
|
|
794
|
+
orderDate: '2024-01-15'
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
id: 'ORD20240115003',
|
|
798
|
+
customerName: '阿里巴巴集团控股有限公司',
|
|
799
|
+
contact: '王五',
|
|
800
|
+
amount: 120000,
|
|
801
|
+
status: '待发货',
|
|
802
|
+
orderDate: '2024-01-14'
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
id: 'ORD20240115004',
|
|
806
|
+
customerName: '北京字节跳动科技有限公司',
|
|
807
|
+
contact: '赵六',
|
|
808
|
+
amount: 65000,
|
|
809
|
+
status: '已完成',
|
|
810
|
+
orderDate: '2024-01-13'
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
id: 'ORD20240115005',
|
|
814
|
+
customerName: '百度在线网络技术(北京)有限公司',
|
|
815
|
+
contact: '钱七',
|
|
816
|
+
amount: 95000,
|
|
817
|
+
status: '已取消',
|
|
818
|
+
orderDate: '2024-01-12'
|
|
819
|
+
}
|
|
820
|
+
];
|
|
821
|
+
|
|
822
|
+
const columns = [
|
|
823
|
+
{ name: 'id', title: '订单编号', width: 180 },
|
|
824
|
+
{ name: 'customerName', title: '客户名称', width: 220 },
|
|
825
|
+
{ name: 'contact', title: '联系人', width: 100 },
|
|
826
|
+
{ name: 'amount', title: '订单金额(元)', width: 130, render: value => <strong style={{ color: '#f5222d' }}>¥{value.toLocaleString()}</strong> },
|
|
827
|
+
{ name: 'orderDate', title: '下单日期', width: 120, format: 'date' },
|
|
828
|
+
{
|
|
829
|
+
name: 'status',
|
|
830
|
+
title: '订单状态',
|
|
831
|
+
width: 100,
|
|
832
|
+
render: value => {
|
|
833
|
+
const config = {
|
|
834
|
+
已完成: { color: 'success', text: '已完成' },
|
|
835
|
+
处理中: { color: 'processing', text: '处理中' },
|
|
836
|
+
待发货: { color: 'warning', text: '待发货' },
|
|
837
|
+
已取消: { color: 'default', text: '已取消' }
|
|
838
|
+
};
|
|
839
|
+
const { color, text } = config[value] || { color: 'default', text: value };
|
|
840
|
+
return <Badge status={color} text={text} />;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
];
|
|
844
|
+
|
|
845
|
+
const BatchToolbar = ({ selectedRowKeys, selectedRows, clearSelectedRows, onBatchShip, onBatchExport }) => {
|
|
846
|
+
const totalAmount = selectedRows.reduce((sum, item) => sum + (item.amount || 0), 0);
|
|
847
|
+
return (
|
|
848
|
+
<Flex justify="space-between" align="center" style={{ marginBottom: 12, padding: '12px', background: '#f5f5f5', borderRadius: 8 }}>
|
|
849
|
+
<Space>
|
|
850
|
+
<span>
|
|
851
|
+
已选 <strong>{selectedRowKeys.length}</strong> 个订单,总金额 <strong style={{ color: '#52c41a' }}>¥{totalAmount.toLocaleString()}</strong>
|
|
852
|
+
</span>
|
|
853
|
+
<Button type="primary" size="small" disabled={!selectedRowKeys.length} onClick={onBatchShip}>
|
|
854
|
+
批量发货
|
|
855
|
+
</Button>
|
|
856
|
+
<Button size="small" disabled={!selectedRowKeys.length} onClick={onBatchExport}>
|
|
857
|
+
批量导出
|
|
858
|
+
</Button>
|
|
859
|
+
<Button size="small" disabled={!selectedRowKeys.length} onClick={clearSelectedRows}>
|
|
860
|
+
清空选择
|
|
861
|
+
</Button>
|
|
862
|
+
</Space>
|
|
863
|
+
</Flex>
|
|
864
|
+
);
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
const TableExample = () => {
|
|
868
|
+
const { selectedRowKeys, selectedRows, getRowSelection, clearSelectedRows } = Table.useSelectedRow({ rowKey: 'id' });
|
|
869
|
+
|
|
870
|
+
return (
|
|
871
|
+
<div>
|
|
872
|
+
<div style={{ marginBottom: 8, color: '#666' }}>Table + useSelectedRow</div>
|
|
873
|
+
<BatchToolbar
|
|
874
|
+
selectedRowKeys={selectedRowKeys}
|
|
875
|
+
selectedRows={selectedRows}
|
|
876
|
+
clearSelectedRows={clearSelectedRows}
|
|
877
|
+
onBatchShip={() => {
|
|
878
|
+
message.success(`已批量发货 ${selectedRowKeys.length} 个订单`);
|
|
879
|
+
clearSelectedRows();
|
|
880
|
+
}}
|
|
881
|
+
onBatchExport={() => message.info(`正在导出 ${selectedRowKeys.length} 个订单`)}
|
|
882
|
+
/>
|
|
883
|
+
<Table dataSource={dataSource} columns={columns} rowSelection={getRowSelection(dataSource)} />
|
|
884
|
+
</div>
|
|
885
|
+
);
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
const TableViewExample = () => {
|
|
889
|
+
const { selectedRowKeys, selectedRows, getRowSelection, clearSelectedRows } = TableView.useSelectedRow({ rowKey: 'id' });
|
|
890
|
+
|
|
891
|
+
return (
|
|
892
|
+
<div>
|
|
893
|
+
<div style={{ marginBottom: 8, color: '#666' }}>TableView + useSelectedRow</div>
|
|
894
|
+
<BatchToolbar
|
|
895
|
+
selectedRowKeys={selectedRowKeys}
|
|
896
|
+
selectedRows={selectedRows}
|
|
897
|
+
clearSelectedRows={clearSelectedRows}
|
|
898
|
+
onBatchShip={() => {
|
|
899
|
+
message.success(`已批量发货 ${selectedRowKeys.length} 个订单`);
|
|
900
|
+
clearSelectedRows();
|
|
901
|
+
}}
|
|
902
|
+
onBatchExport={() => message.info(`正在导出 ${selectedRowKeys.length} 个订单`)}
|
|
903
|
+
/>
|
|
904
|
+
<TableView dataSource={dataSource} columns={columns} rowSelection={getRowSelection(dataSource)} />
|
|
905
|
+
</div>
|
|
906
|
+
);
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
const RadioExample = () => {
|
|
910
|
+
const { selectedRowKeys, selectedRows, getRowSelection } = Table.useSelectedRow({ rowKey: 'id', type: 'radio' });
|
|
911
|
+
const selectedOrder = selectedRows[0];
|
|
912
|
+
|
|
913
|
+
return (
|
|
914
|
+
<div>
|
|
915
|
+
<div style={{ marginBottom: 8, color: '#666' }}>单选模式 type: 'radio'</div>
|
|
916
|
+
<div style={{ marginBottom: 12 }}>
|
|
917
|
+
当前选中:{selectedOrder ? `${selectedOrder.id}(${selectedOrder.customerName})` : '无'}
|
|
918
|
+
</div>
|
|
919
|
+
<Table dataSource={dataSource} columns={columns} rowSelection={getRowSelection(dataSource)} />
|
|
920
|
+
</div>
|
|
921
|
+
);
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
const BaseExample = () => {
|
|
925
|
+
return (
|
|
926
|
+
<Flex vertical gap={24}>
|
|
927
|
+
<TableExample />
|
|
928
|
+
<TableViewExample />
|
|
929
|
+
<RadioExample />
|
|
930
|
+
</Flex>
|
|
931
|
+
);
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
render(<BaseExample />);
|
|
935
|
+
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
- useSort
|
|
939
|
+
- 排序 Hook,配合 Table / TableView 实现表头排序、单列/多列排序与 sortDataSource 本地排序
|
|
940
|
+
- _TablePage(@kne/current-lib_table-page)[import * as _TablePage from "@kne/table-page"],(@kne/current-lib_table-page/dist/index.css),antd(antd)
|
|
941
|
+
|
|
942
|
+
```jsx
|
|
943
|
+
const { Table, TableView } = _TablePage;
|
|
944
|
+
const { Flex, Badge, Tag } = antd;
|
|
945
|
+
const { useMemo } = React;
|
|
946
|
+
|
|
947
|
+
const dataSource = [
|
|
948
|
+
{ id: 'ORD001', customerName: '深圳市腾讯计算机系统有限公司', amount: 42500, status: '已完成', orderDate: '2024-01-15' },
|
|
949
|
+
{ id: 'ORD002', customerName: '华为技术有限公司', amount: 85000, status: '处理中', orderDate: '2024-01-14' },
|
|
950
|
+
{ id: 'ORD003', customerName: '阿里巴巴集团控股有限公司', amount: 120000, status: '待发货', orderDate: '2024-01-16' },
|
|
951
|
+
{ id: 'ORD004', customerName: '北京字节跳动科技有限公司', amount: 65000, status: '已完成', orderDate: '2024-01-13' },
|
|
952
|
+
{ id: 'ORD005', customerName: '百度在线网络技术(北京)有限公司', amount: 95000, status: '已取消', orderDate: '2024-01-12' }
|
|
953
|
+
];
|
|
954
|
+
|
|
955
|
+
const statusRender = value => {
|
|
956
|
+
const config = {
|
|
957
|
+
已完成: { color: 'success', text: '已完成' },
|
|
958
|
+
处理中: { color: 'processing', text: '处理中' },
|
|
959
|
+
待发货: { color: 'warning', text: '待发货' },
|
|
960
|
+
已取消: { color: 'default', text: '已取消' }
|
|
961
|
+
};
|
|
962
|
+
const { color, text } = config[value] || { color: 'default', text: value };
|
|
963
|
+
return <Badge status={color} text={text} />;
|
|
964
|
+
};
|
|
965
|
+
|
|
966
|
+
const columns = [
|
|
967
|
+
{ name: 'id', title: '订单编号', width: 140, sort: { single: true } },
|
|
968
|
+
{ name: 'customerName', title: '客户名称', width: 240, sort: true },
|
|
969
|
+
{ name: 'amount', title: '订单金额(元)', width: 130, sort: true, render: value => <strong style={{ color: '#f5222d' }}>¥{value.toLocaleString()}</strong> },
|
|
970
|
+
{ name: 'orderDate', title: '下单日期', width: 120, sort: true, format: 'date' },
|
|
971
|
+
{ name: 'status', title: '订单状态', width: 100, render: statusRender }
|
|
972
|
+
];
|
|
973
|
+
|
|
974
|
+
const SortState = ({ sort }) => (
|
|
975
|
+
<div style={{ marginBottom: 12, padding: '12px', background: '#f5f5f5', borderRadius: 8 }}>
|
|
976
|
+
当前排序:
|
|
977
|
+
{sort.length ? (
|
|
978
|
+
<span>
|
|
979
|
+
{sort.map(item => (
|
|
980
|
+
<Tag key={item.name} color="blue" style={{ marginLeft: 8 }}>
|
|
981
|
+
{item.name} {item.sort}
|
|
982
|
+
</Tag>
|
|
983
|
+
))}
|
|
984
|
+
</span>
|
|
985
|
+
) : (
|
|
986
|
+
<span style={{ marginLeft: 8, color: '#999' }}>无</span>
|
|
987
|
+
)}
|
|
988
|
+
</div>
|
|
989
|
+
);
|
|
990
|
+
|
|
991
|
+
const TableExample = () => {
|
|
992
|
+
const { sort, sortRender } = Table.useSort({
|
|
993
|
+
onSortChange: value => console.log('Table 排序变更:', value)
|
|
994
|
+
});
|
|
995
|
+
const sortedData = useMemo(() => Table.sortDataSource(dataSource, sort, columns), [sort]);
|
|
996
|
+
|
|
997
|
+
return (
|
|
998
|
+
<div>
|
|
999
|
+
<div style={{ marginBottom: 8, color: '#666' }}>Table + useSort(金额、日期支持多列排序)</div>
|
|
1000
|
+
<SortState sort={sort} />
|
|
1001
|
+
<Table dataSource={sortedData} columns={columns} sortRender={sortRender} />
|
|
1002
|
+
</div>
|
|
1003
|
+
);
|
|
1004
|
+
};
|
|
1005
|
+
|
|
1006
|
+
const TableViewExample = () => {
|
|
1007
|
+
const { sort, sortRender } = TableView.useSort({
|
|
1008
|
+
defaultSort: [{ name: 'orderDate', sort: 'DESC' }],
|
|
1009
|
+
onSortChange: value => console.log('TableView 排序变更:', value)
|
|
1010
|
+
});
|
|
1011
|
+
const sortedData = useMemo(() => TableView.sortDataSource(dataSource, sort, columns), [sort]);
|
|
1012
|
+
|
|
1013
|
+
return (
|
|
1014
|
+
<div>
|
|
1015
|
+
<div style={{ marginBottom: 8, color: '#666' }}>TableView + useSort(默认按下单日期降序)</div>
|
|
1016
|
+
<SortState sort={sort} />
|
|
1017
|
+
<TableView dataSource={sortedData} columns={columns} sortRender={sortRender} />
|
|
1018
|
+
</div>
|
|
1019
|
+
);
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
const BaseExample = () => {
|
|
1023
|
+
return (
|
|
1024
|
+
<Flex vertical gap={24}>
|
|
1025
|
+
<div style={{ color: '#666', fontSize: 13 }}>
|
|
1026
|
+
列配置 <code>sort: true</code> 开启排序,<code>sort: {'{ single: true }'}</code> 为单列排序。点击表头三角切换 DESC → ASC → 取消。
|
|
1027
|
+
</div>
|
|
1028
|
+
<TableExample />
|
|
1029
|
+
<TableViewExample />
|
|
1030
|
+
</Flex>
|
|
1031
|
+
);
|
|
1032
|
+
};
|
|
1033
|
+
|
|
1034
|
+
render(<BaseExample />);
|
|
1035
|
+
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
- column ellipsis
|
|
1039
|
+
- 列 ellipsis 配置,基于 antd Typography 实现超出省略与 tooltip 展示
|
|
1040
|
+
- _TablePage(@kne/current-lib_table-page)[import * as _TablePage from "@kne/table-page"],(@kne/current-lib_table-page/dist/index.css),antd(antd)
|
|
1041
|
+
|
|
1042
|
+
```jsx
|
|
1043
|
+
const { Table, TableView } = _TablePage;
|
|
1044
|
+
const { Flex, Badge } = antd;
|
|
1045
|
+
|
|
1046
|
+
const dataSource = [
|
|
1047
|
+
{
|
|
1048
|
+
id: 'ORD001',
|
|
1049
|
+
customerName: '深圳市腾讯计算机系统有限公司深圳总部研发中心',
|
|
1050
|
+
remark: '客户要求春节前完成交付,需协调物流加急处理,并同步更新合同附件与验收标准说明文档。',
|
|
1051
|
+
amount: 42500,
|
|
1052
|
+
status: '待发货'
|
|
1053
|
+
},
|
|
1054
|
+
{
|
|
1055
|
+
id: 'ORD002',
|
|
1056
|
+
customerName: '华为技术有限公司坂田基地采购中心',
|
|
1057
|
+
remark: '项目处于需求评审阶段,待客户确认最终配置清单后安排发货。',
|
|
1058
|
+
amount: 85000,
|
|
1059
|
+
status: '处理中'
|
|
1060
|
+
},
|
|
1061
|
+
{
|
|
1062
|
+
id: 'ORD003',
|
|
1063
|
+
customerName: '阿里巴巴集团控股有限公司滨江园区',
|
|
1064
|
+
remark: '已完成付款,仓库正在拣货,预计两个工作日内发出第一批货物。',
|
|
1065
|
+
amount: 120000,
|
|
1066
|
+
status: '待发货'
|
|
1067
|
+
}
|
|
1068
|
+
];
|
|
1069
|
+
|
|
1070
|
+
const statusRender = value => {
|
|
1071
|
+
const config = {
|
|
1072
|
+
已完成: { color: 'success', text: '已完成' },
|
|
1073
|
+
处理中: { color: 'processing', text: '处理中' },
|
|
1074
|
+
待发货: { color: 'warning', text: '待发货' }
|
|
1075
|
+
};
|
|
1076
|
+
const { color, text } = config[value] || { color: 'default', text: value };
|
|
1077
|
+
return <Badge status={color} text={text} />;
|
|
1078
|
+
};
|
|
1079
|
+
|
|
1080
|
+
const columns = [
|
|
1081
|
+
{ name: 'id', title: '订单编号', width: 120 },
|
|
1082
|
+
{
|
|
1083
|
+
name: 'customerName',
|
|
1084
|
+
title: '客户名称',
|
|
1085
|
+
width: 180,
|
|
1086
|
+
ellipsis: true
|
|
1087
|
+
},
|
|
1088
|
+
{
|
|
1089
|
+
name: 'remark',
|
|
1090
|
+
title: '备注',
|
|
1091
|
+
width: 220,
|
|
1092
|
+
ellipsis: { showTitle: true }
|
|
1093
|
+
},
|
|
1094
|
+
{
|
|
1095
|
+
name: 'amount',
|
|
1096
|
+
title: '金额',
|
|
1097
|
+
width: 100,
|
|
1098
|
+
render: value => `¥${value.toLocaleString()}`
|
|
1099
|
+
},
|
|
1100
|
+
{ name: 'status', title: '状态', width: 90, render: statusRender }
|
|
1101
|
+
];
|
|
1102
|
+
|
|
1103
|
+
const BaseExample = () => {
|
|
1104
|
+
return (
|
|
1105
|
+
<Flex vertical gap={24}>
|
|
1106
|
+
<div style={{ color: '#666', fontSize: 13 }}>
|
|
1107
|
+
列配置 <code>ellipsis: true</code> 或 <code>ellipsis: {'{ showTitle: true }'}</code>,超出宽度自动省略,悬停显示完整内容(基于 antd Typography)。
|
|
1108
|
+
</div>
|
|
1109
|
+
<div>
|
|
1110
|
+
<div style={{ marginBottom: 8, color: '#666' }}>Table</div>
|
|
1111
|
+
<Table dataSource={dataSource} columns={columns} />
|
|
1112
|
+
</div>
|
|
1113
|
+
<div>
|
|
1114
|
+
<div style={{ marginBottom: 8, color: '#666' }}>TableView</div>
|
|
1115
|
+
<TableView dataSource={dataSource} columns={columns} />
|
|
1116
|
+
</div>
|
|
1117
|
+
</Flex>
|
|
1118
|
+
);
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
render(<BaseExample />);
|
|
1122
|
+
|
|
1123
|
+
```
|
|
1124
|
+
|
|
1125
|
+
- renderType
|
|
1126
|
+
- 列 renderType 配置,预设 main / options / enum / description 类型与 short / small / large 尺寸修饰
|
|
1127
|
+
- _TablePage(@kne/current-lib_table-page)[import * as _TablePage from "@kne/table-page"],(@kne/current-lib_table-page/dist/index.css),antd(antd)
|
|
1128
|
+
|
|
1129
|
+
```jsx
|
|
1130
|
+
const { Table, TableView } = _TablePage;
|
|
1131
|
+
const { Flex } = antd;
|
|
1132
|
+
|
|
1133
|
+
const statusMap = {
|
|
1134
|
+
待发货: { type: 'warning', text: '待发货' },
|
|
1135
|
+
处理中: { type: 'processing', text: '处理中' },
|
|
1136
|
+
已完成: { type: 'success', text: '已完成' }
|
|
1137
|
+
};
|
|
1138
|
+
|
|
1139
|
+
const categoryMap = {
|
|
1140
|
+
企业客户: { type: 'default', text: '企业客户' },
|
|
1141
|
+
战略客户: { type: 'processing', text: '战略客户' }
|
|
1142
|
+
};
|
|
1143
|
+
|
|
1144
|
+
const dataSource = [
|
|
1145
|
+
{
|
|
1146
|
+
id: 'ORD001',
|
|
1147
|
+
customerName: '深圳市腾讯计算机系统有限公司',
|
|
1148
|
+
category: '企业客户',
|
|
1149
|
+
tags: ['物流', '加急'],
|
|
1150
|
+
keywords: ['合同', '附件', '春节前'],
|
|
1151
|
+
remark: '客户要求春节前完成交付,需协调物流加急处理,并同步更新合同附件。',
|
|
1152
|
+
amount: 42500,
|
|
1153
|
+
status: '待发货'
|
|
1154
|
+
},
|
|
1155
|
+
{
|
|
1156
|
+
id: 'ORD002',
|
|
1157
|
+
customerName: '华为技术有限公司',
|
|
1158
|
+
category: '战略客户',
|
|
1159
|
+
tags: ['评审', '配置清单'],
|
|
1160
|
+
keywords: ['需求评审', '配置清单'],
|
|
1161
|
+
remark: '项目处于需求评审阶段,待客户确认最终配置清单后安排发货。',
|
|
1162
|
+
amount: 85000,
|
|
1163
|
+
status: '处理中'
|
|
1164
|
+
},
|
|
1165
|
+
{
|
|
1166
|
+
id: 'ORD003',
|
|
1167
|
+
customerName: '阿里巴巴集团控股有限公司',
|
|
1168
|
+
category: '企业客户',
|
|
1169
|
+
tags: ['拣货', '付款完成'],
|
|
1170
|
+
keywords: ['付款', '拣货', '发货'],
|
|
1171
|
+
remark: '已完成付款,仓库正在拣货,预计两个工作日内发出第一批货物。',
|
|
1172
|
+
amount: 120000,
|
|
1173
|
+
status: '已完成'
|
|
1174
|
+
}
|
|
1175
|
+
];
|
|
1176
|
+
|
|
1177
|
+
const columns = [
|
|
1178
|
+
{ name: 'id', title: '编号', renderType: 'small' },
|
|
1179
|
+
{ name: 'customerName', title: '客户名称', renderType: 'main' },
|
|
1180
|
+
{
|
|
1181
|
+
name: 'category',
|
|
1182
|
+
title: '分类',
|
|
1183
|
+
renderType: 'tag-short',
|
|
1184
|
+
getValueOf: item => categoryMap[item.category]
|
|
1185
|
+
},
|
|
1186
|
+
{
|
|
1187
|
+
name: 'tags',
|
|
1188
|
+
title: '标签',
|
|
1189
|
+
renderType: 'tagList',
|
|
1190
|
+
getValueOf: item =>
|
|
1191
|
+
(item.tags || []).map(text => ({
|
|
1192
|
+
type: text === '加急' ? 'error' : 'processing',
|
|
1193
|
+
text
|
|
1194
|
+
}))
|
|
1195
|
+
},
|
|
1196
|
+
{
|
|
1197
|
+
name: 'keywords',
|
|
1198
|
+
title: '关键词',
|
|
1199
|
+
renderType: 'list',
|
|
1200
|
+
split: '、',
|
|
1201
|
+
getValueOf: item => item.keywords
|
|
1202
|
+
},
|
|
1203
|
+
{ name: 'remark', title: '备注', renderType: 'description' },
|
|
1204
|
+
{
|
|
1205
|
+
name: 'amount',
|
|
1206
|
+
title: '金额',
|
|
1207
|
+
renderType: 'amount',
|
|
1208
|
+
format: 'number-style:decimal-maximumFractionDigits:0-useGrouping:true-suffix:元'
|
|
1209
|
+
},
|
|
1210
|
+
{
|
|
1211
|
+
name: 'status',
|
|
1212
|
+
title: '状态',
|
|
1213
|
+
renderType: 'tag',
|
|
1214
|
+
getValueOf: item => statusMap[item.status]
|
|
1215
|
+
},
|
|
1216
|
+
{
|
|
1217
|
+
name: 'options',
|
|
1218
|
+
title: '操作',
|
|
1219
|
+
renderType: 'options',
|
|
1220
|
+
fixed: 'right',
|
|
1221
|
+
getValueOf: item => {
|
|
1222
|
+
const actions = [
|
|
1223
|
+
{ children: '查看', onClick: () => console.log('查看', item.id) },
|
|
1224
|
+
{ children: '编辑', onClick: () => console.log('编辑', item.id) }
|
|
1225
|
+
];
|
|
1226
|
+
if (item.status !== '已完成') {
|
|
1227
|
+
actions.push({
|
|
1228
|
+
children: '删除',
|
|
1229
|
+
isDelete: true,
|
|
1230
|
+
message: `确定删除 ${item.id} 吗?`,
|
|
1231
|
+
onClick: () => console.log('删除', item.id)
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
return actions;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
];
|
|
1238
|
+
|
|
1239
|
+
const BaseExample = () => {
|
|
1240
|
+
return (
|
|
1241
|
+
<Flex vertical gap={24}>
|
|
1242
|
+
<div style={{ color: '#666', fontSize: 13 }}>
|
|
1243
|
+
列配置 <code>renderType</code> 支持 <code>main</code> / <code>amount</code> / <code>tag</code> / <code>tagList</code> / <code>list</code> / <code>options</code> / <code>description</code> 等类型,可与尺寸修饰词组合(如 <code>tag-short</code>、<code>main-small</code>)。
|
|
1244
|
+
通过 <code>getValueOf</code> 返回 render 所需的数据结构,通过 <code>format</code> 做展示格式化(如金额)。
|
|
1245
|
+
</div>
|
|
1246
|
+
<div>
|
|
1247
|
+
<div style={{ marginBottom: 8, color: '#666' }}>Table</div>
|
|
1248
|
+
<Table dataSource={dataSource} columns={columns} scroll={{ x: 1800 }} />
|
|
1249
|
+
</div>
|
|
1250
|
+
<div>
|
|
1251
|
+
<div style={{ marginBottom: 8, color: '#666' }}>TableView</div>
|
|
1252
|
+
<TableView dataSource={dataSource} columns={columns} />
|
|
1253
|
+
</div>
|
|
1254
|
+
</Flex>
|
|
1255
|
+
);
|
|
1256
|
+
};
|
|
1257
|
+
|
|
1258
|
+
render(<BaseExample />);
|
|
1259
|
+
|
|
1260
|
+
```
|
|
1261
|
+
|
|
1262
|
+
- column config
|
|
1263
|
+
- 列宽拖动调整、显示/隐藏字段、列排序与 localStorage 持久化(仅 Table)
|
|
1264
|
+
- _TablePage(@kne/current-lib_table-page)[import * as _TablePage from "@kne/table-page"],(@kne/current-lib_table-page/dist/index.css),antd(antd)
|
|
1265
|
+
|
|
1266
|
+
```jsx
|
|
1267
|
+
const { Table } = _TablePage;
|
|
1268
|
+
const { Flex, Badge, Tag } = antd;
|
|
1269
|
+
|
|
1270
|
+
const dataSource = [
|
|
1271
|
+
{
|
|
1272
|
+
id: 'ORD20240115001',
|
|
1273
|
+
customerName: '深圳市腾讯计算机系统有限公司',
|
|
1274
|
+
contact: '张三',
|
|
1275
|
+
phone: '13800138000',
|
|
1276
|
+
amount: 42500,
|
|
1277
|
+
status: '已完成',
|
|
1278
|
+
orderDate: '2024-01-15',
|
|
1279
|
+
deliveryDate: '2024-01-17',
|
|
1280
|
+
remark: '客户要求春节前完成交付,需协调物流加急处理。'
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
id: 'ORD20240115002',
|
|
1284
|
+
customerName: '华为技术有限公司',
|
|
1285
|
+
contact: '李四',
|
|
1286
|
+
phone: '13900149000',
|
|
1287
|
+
amount: 85000,
|
|
1288
|
+
status: '处理中',
|
|
1289
|
+
orderDate: '2024-01-15',
|
|
1290
|
+
deliveryDate: '2024-01-20',
|
|
1291
|
+
remark: '项目处于需求评审阶段,待客户确认最终配置清单。'
|
|
1292
|
+
},
|
|
1293
|
+
{
|
|
1294
|
+
id: 'ORD20240115003',
|
|
1295
|
+
customerName: '阿里巴巴集团控股有限公司',
|
|
1296
|
+
contact: '王五',
|
|
1297
|
+
phone: '13700157000',
|
|
1298
|
+
amount: 120000,
|
|
1299
|
+
status: '待发货',
|
|
1300
|
+
orderDate: '2024-01-14',
|
|
1301
|
+
deliveryDate: '2024-01-22',
|
|
1302
|
+
remark: '已完成付款,仓库正在拣货。'
|
|
1303
|
+
},
|
|
1304
|
+
{
|
|
1305
|
+
id: 'ORD20240115004',
|
|
1306
|
+
customerName: '北京字节跳动科技有限公司',
|
|
1307
|
+
contact: '赵六',
|
|
1308
|
+
phone: '13600166000',
|
|
1309
|
+
amount: 65000,
|
|
1310
|
+
status: '已完成',
|
|
1311
|
+
orderDate: '2024-01-13',
|
|
1312
|
+
deliveryDate: '2024-01-16',
|
|
1313
|
+
remark: '常规订单,按标准流程处理。'
|
|
1314
|
+
}
|
|
1315
|
+
];
|
|
1316
|
+
|
|
1317
|
+
const statusRender = value => {
|
|
1318
|
+
const config = {
|
|
1319
|
+
已完成: { color: 'success', text: '已完成' },
|
|
1320
|
+
处理中: { color: 'processing', text: '处理中' },
|
|
1321
|
+
待发货: { color: 'warning', text: '待发货' },
|
|
1322
|
+
已取消: { color: 'default', text: '已取消' }
|
|
1323
|
+
};
|
|
1324
|
+
const { color, text } = config[value] || { color: 'default', text: value };
|
|
1325
|
+
return <Badge status={color} text={text} />;
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1328
|
+
const columns = [
|
|
1329
|
+
{ name: 'id', title: '订单编号', width: 160, min: 120, max: 240, fixed: 'left' },
|
|
1330
|
+
{ name: 'customerName', title: '客户名称', width: 200, min: 140, max: 360 },
|
|
1331
|
+
{ name: 'contact', title: '联系人', width: 90, min: 70, max: 160 },
|
|
1332
|
+
{ name: 'phone', title: '联系电话', width: 130, min: 110, max: 180, render: value => value.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3') },
|
|
1333
|
+
{ name: 'amount', title: '订单金额(元)', width: 130, min: 100, max: 200, render: value => <strong style={{ color: '#f5222d' }}>¥{value.toLocaleString()}</strong> },
|
|
1334
|
+
{ name: 'orderDate', title: '下单日期', width: 110, min: 90, max: 160, format: 'date' },
|
|
1335
|
+
{ name: 'deliveryDate', title: '预计送达', width: 110, min: 90, max: 160, format: 'date' },
|
|
1336
|
+
{ name: 'status', title: '订单状态', width: 100, min: 80, max: 140, render: statusRender },
|
|
1337
|
+
{ name: 'remark', title: '备注', width: 200, min: 120, max: 400, hidden: true, ellipsis: true }
|
|
1338
|
+
];
|
|
1339
|
+
|
|
1340
|
+
const Tips = () => (
|
|
1341
|
+
<div style={{ color: '#666', fontSize: 13, lineHeight: 1.8 }}>
|
|
1342
|
+
<div>
|
|
1343
|
+
<Tag color="blue">列宽拖动</Tag>
|
|
1344
|
+
鼠标悬停表头列右侧,出现拖动手柄后可左右拖动调整列宽(受 <code>min</code> / <code>max</code> 约束)。仅 <code>Table</code> 组件支持。
|
|
1345
|
+
</div>
|
|
1346
|
+
<div>
|
|
1347
|
+
<Tag color="green">显示/隐藏</Tag>
|
|
1348
|
+
点击最后一列表头的 <strong>设置图标</strong>,可勾选显示或隐藏列、拖拽排序;配置通过 <code>name</code> 持久化到 localStorage。
|
|
1349
|
+
</div>
|
|
1350
|
+
<div>
|
|
1351
|
+
<Tag color="orange">默认隐藏</Tag>
|
|
1352
|
+
本示例中「备注」列设置了 <code>hidden: true</code>,可在列配置面板中重新显示。
|
|
1353
|
+
</div>
|
|
1354
|
+
<div>
|
|
1355
|
+
<Tag color="purple">固定列</Tag>
|
|
1356
|
+
「订单编号」设置了 <code>fixed: 'left'</code>,固定显示且不可隐藏。
|
|
1357
|
+
</div>
|
|
1358
|
+
</div>
|
|
1359
|
+
);
|
|
1360
|
+
|
|
1361
|
+
const BaseExample = () => {
|
|
1362
|
+
return (
|
|
1363
|
+
<Flex vertical gap={24}>
|
|
1364
|
+
<Tips />
|
|
1365
|
+
<Table name="demo-table-column-config" controllerOpen dataSource={dataSource} columns={columns} />
|
|
1366
|
+
<div>
|
|
1367
|
+
<div style={{ marginBottom: 8, color: '#666' }}>关闭列配置(controllerOpen=false)</div>
|
|
1368
|
+
<Table dataSource={dataSource.slice(0, 2)} columns={columns} controllerOpen={false} />
|
|
1369
|
+
</div>
|
|
1370
|
+
</Flex>
|
|
1371
|
+
);
|
|
1372
|
+
};
|
|
1373
|
+
|
|
1374
|
+
render(<BaseExample />);
|
|
1375
|
+
|
|
1376
|
+
```
|
|
1377
|
+
|
|
1378
|
+
- group header
|
|
1379
|
+
- 分组表头(groupHeader),实现多级表头结构,可与 useSort 配合使用(仅 Table)
|
|
1380
|
+
- _TablePage(@kne/current-lib_table-page)[import * as _TablePage from "@kne/table-page"],(@kne/current-lib_table-page/dist/index.css),antd(antd)
|
|
1381
|
+
|
|
1382
|
+
```jsx
|
|
1383
|
+
const { Table } = _TablePage;
|
|
1384
|
+
const { Flex, Tag } = antd;
|
|
1385
|
+
const { useMemo } = React;
|
|
1386
|
+
|
|
1387
|
+
const dataSource = [
|
|
1388
|
+
{
|
|
1389
|
+
id: 'SALE001',
|
|
1390
|
+
region: '华北区',
|
|
1391
|
+
province: '北京',
|
|
1392
|
+
city: '北京',
|
|
1393
|
+
productName: '企业版 SaaS',
|
|
1394
|
+
productCode: 'SAAS-ENT',
|
|
1395
|
+
salesAmount: 1250000,
|
|
1396
|
+
salesVolume: 50,
|
|
1397
|
+
growthRate: 23.5,
|
|
1398
|
+
marketShare: 18.2,
|
|
1399
|
+
customerCount: 128,
|
|
1400
|
+
newCustomerCount: 32,
|
|
1401
|
+
repurchaseRate: 85.5,
|
|
1402
|
+
avgOrderValue: 9765.6,
|
|
1403
|
+
targetCompletion: 92.5
|
|
1404
|
+
},
|
|
1405
|
+
{
|
|
1406
|
+
id: 'SALE002',
|
|
1407
|
+
region: '华东区',
|
|
1408
|
+
province: '上海',
|
|
1409
|
+
city: '上海',
|
|
1410
|
+
productName: '企业版 SaaS',
|
|
1411
|
+
productCode: 'SAAS-ENT',
|
|
1412
|
+
salesAmount: 1680000,
|
|
1413
|
+
salesVolume: 68,
|
|
1414
|
+
growthRate: 35.2,
|
|
1415
|
+
marketShare: 22.8,
|
|
1416
|
+
customerCount: 156,
|
|
1417
|
+
newCustomerCount: 45,
|
|
1418
|
+
repurchaseRate: 88.6,
|
|
1419
|
+
avgOrderValue: 24705.9,
|
|
1420
|
+
targetCompletion: 105.2
|
|
1421
|
+
},
|
|
1422
|
+
{
|
|
1423
|
+
id: 'SALE003',
|
|
1424
|
+
region: '华南区',
|
|
1425
|
+
province: '广东',
|
|
1426
|
+
city: '深圳',
|
|
1427
|
+
productName: '专业版 SaaS',
|
|
1428
|
+
productCode: 'SAAS-PRO',
|
|
1429
|
+
salesAmount: 980000,
|
|
1430
|
+
salesVolume: 95,
|
|
1431
|
+
growthRate: 28.6,
|
|
1432
|
+
marketShare: 16.3,
|
|
1433
|
+
customerCount: 112,
|
|
1434
|
+
newCustomerCount: 28,
|
|
1435
|
+
repurchaseRate: 82.4,
|
|
1436
|
+
avgOrderValue: 10315.8,
|
|
1437
|
+
targetCompletion: 95.8
|
|
1438
|
+
}
|
|
1439
|
+
];
|
|
1440
|
+
|
|
1441
|
+
const growthRateRender = value => (
|
|
1442
|
+
<span style={{ color: value > 20 ? '#52c41a' : value > 10 ? '#1677ff' : '#faad14' }}>{value}%</span>
|
|
1443
|
+
);
|
|
1444
|
+
|
|
1445
|
+
const columns = [
|
|
1446
|
+
{
|
|
1447
|
+
name: 'region',
|
|
1448
|
+
title: '大区',
|
|
1449
|
+
width: 100,
|
|
1450
|
+
groupHeader: [{ name: 'area', title: '区域信息' }]
|
|
1451
|
+
},
|
|
1452
|
+
{
|
|
1453
|
+
name: 'province',
|
|
1454
|
+
title: '省份',
|
|
1455
|
+
width: 100,
|
|
1456
|
+
groupHeader: [{ name: 'area', title: '区域信息' }]
|
|
1457
|
+
},
|
|
1458
|
+
{
|
|
1459
|
+
name: 'city',
|
|
1460
|
+
title: '城市',
|
|
1461
|
+
width: 100,
|
|
1462
|
+
groupHeader: [{ name: 'area', title: '区域信息' }]
|
|
1463
|
+
},
|
|
1464
|
+
{
|
|
1465
|
+
name: 'productName',
|
|
1466
|
+
title: '产品名称',
|
|
1467
|
+
width: 150,
|
|
1468
|
+
groupHeader: [{ name: 'product', title: '产品信息' }]
|
|
1469
|
+
},
|
|
1470
|
+
{
|
|
1471
|
+
name: 'productCode',
|
|
1472
|
+
title: '产品编码',
|
|
1473
|
+
width: 130,
|
|
1474
|
+
groupHeader: [{ name: 'product', title: '产品信息' }]
|
|
1475
|
+
},
|
|
1476
|
+
{
|
|
1477
|
+
name: 'salesAmount',
|
|
1478
|
+
title: '销售金额',
|
|
1479
|
+
width: 130,
|
|
1480
|
+
sort: { single: true },
|
|
1481
|
+
render: value => <strong style={{ color: '#f5222d' }}>¥{(value / 10000).toFixed(2)}万</strong>,
|
|
1482
|
+
groupHeader: [{ name: 'sales', title: '销售业绩' }]
|
|
1483
|
+
},
|
|
1484
|
+
{
|
|
1485
|
+
name: 'salesVolume',
|
|
1486
|
+
title: '销售数量',
|
|
1487
|
+
width: 110,
|
|
1488
|
+
sort: true,
|
|
1489
|
+
groupHeader: [{ name: 'sales', title: '销售业绩' }]
|
|
1490
|
+
},
|
|
1491
|
+
{
|
|
1492
|
+
name: 'growthRate',
|
|
1493
|
+
title: '增长率',
|
|
1494
|
+
width: 110,
|
|
1495
|
+
sort: true,
|
|
1496
|
+
render: growthRateRender,
|
|
1497
|
+
groupHeader: [{ name: 'sales', title: '销售业绩' }]
|
|
1498
|
+
},
|
|
1499
|
+
{
|
|
1500
|
+
name: 'marketShare',
|
|
1501
|
+
title: '市场份额',
|
|
1502
|
+
width: 110,
|
|
1503
|
+
sort: true,
|
|
1504
|
+
render: value => `${value}%`,
|
|
1505
|
+
groupHeader: [{ name: 'market', title: '市场分析' }]
|
|
1506
|
+
},
|
|
1507
|
+
{
|
|
1508
|
+
name: 'customerCount',
|
|
1509
|
+
title: '客户总数',
|
|
1510
|
+
width: 110,
|
|
1511
|
+
sort: true,
|
|
1512
|
+
groupHeader: [{ name: 'market', title: '市场分析' }]
|
|
1513
|
+
},
|
|
1514
|
+
{
|
|
1515
|
+
name: 'newCustomerCount',
|
|
1516
|
+
title: '新增客户',
|
|
1517
|
+
width: 110,
|
|
1518
|
+
sort: true,
|
|
1519
|
+
groupHeader: [{ name: 'market', title: '市场分析' }]
|
|
1520
|
+
},
|
|
1521
|
+
{
|
|
1522
|
+
name: 'repurchaseRate',
|
|
1523
|
+
title: '复购率',
|
|
1524
|
+
width: 110,
|
|
1525
|
+
render: value => `${value}%`,
|
|
1526
|
+
groupHeader: [{ name: 'customer', title: '客户指标' }]
|
|
1527
|
+
},
|
|
1528
|
+
{
|
|
1529
|
+
name: 'avgOrderValue',
|
|
1530
|
+
title: '客单价',
|
|
1531
|
+
width: 120,
|
|
1532
|
+
render: value => `¥${value.toLocaleString()}`,
|
|
1533
|
+
groupHeader: [{ name: 'customer', title: '客户指标' }]
|
|
1534
|
+
},
|
|
1535
|
+
{
|
|
1536
|
+
name: 'targetCompletion',
|
|
1537
|
+
title: '目标完成率',
|
|
1538
|
+
width: 130,
|
|
1539
|
+
sort: true,
|
|
1540
|
+
render: value => (
|
|
1541
|
+
<span style={{ color: value >= 100 ? '#52c41a' : value >= 90 ? '#1677ff' : '#faad14' }}>{value}%</span>
|
|
1542
|
+
),
|
|
1543
|
+
groupHeader: [{ name: 'target', title: '目标达成' }]
|
|
1544
|
+
}
|
|
1545
|
+
];
|
|
1546
|
+
|
|
1547
|
+
const Tips = () => (
|
|
1548
|
+
<div style={{ color: '#666', fontSize: 13, lineHeight: 1.8 }}>
|
|
1549
|
+
<div>
|
|
1550
|
+
<Tag color="blue">groupHeader</Tag>
|
|
1551
|
+
在列配置中通过 <code>groupHeader</code> 声明所属分组,相同 <code>name</code> 的列会自动合并为多级表头(仅 <code>Table</code> 支持)。
|
|
1552
|
+
</div>
|
|
1553
|
+
<div>
|
|
1554
|
+
<Tag color="green">多级分组</Tag>
|
|
1555
|
+
<code>groupHeader</code> 为数组,按层级嵌套,例如{' '}
|
|
1556
|
+
<code>{`[{ name: 'sales', title: '销售业绩' }, { name: 'detail', title: '明细' }]`}</code>。
|
|
1557
|
+
</div>
|
|
1558
|
+
<div>
|
|
1559
|
+
<Tag color="orange">排序</Tag>
|
|
1560
|
+
分组表头可与 <code>useSort</code> 配合,排序按钮显示在叶子列表头。
|
|
1561
|
+
</div>
|
|
1562
|
+
</div>
|
|
1563
|
+
);
|
|
1564
|
+
|
|
1565
|
+
const BaseExample = () => {
|
|
1566
|
+
const { sort, sortRender } = Table.useSort({
|
|
1567
|
+
onSortChange: value => console.log('排序变更:', value)
|
|
1568
|
+
});
|
|
1569
|
+
const sortedData = useMemo(() => Table.sortDataSource(dataSource, sort, columns), [sort]);
|
|
1570
|
+
|
|
1571
|
+
return (
|
|
1572
|
+
<Flex vertical gap={24}>
|
|
1573
|
+
<Tips />
|
|
1574
|
+
<Table dataSource={sortedData} columns={columns} sortRender={sortRender} scroll={{ x: 1600 }} />
|
|
1575
|
+
</Flex>
|
|
1576
|
+
);
|
|
1577
|
+
};
|
|
1578
|
+
|
|
1579
|
+
render(<BaseExample />);
|
|
1580
|
+
|
|
1581
|
+
```
|
|
1582
|
+
|
|
1583
|
+
### API
|
|
1584
|
+
|
|
1585
|
+
### TablePage
|
|
1586
|
+
|
|
1587
|
+
表格页面组件,基于 `@kne/react-fetch` 的 `withFetch` 封装数据请求逻辑,内部使用 `Table` 渲染列表,并内置分页能力。
|
|
1588
|
+
|
|
1589
|
+
#### 属性
|
|
1590
|
+
|
|
1591
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
1592
|
+
|------|------|--------|------|
|
|
1593
|
+
| loader | function | - | 数据加载函数,参数为 fetch 请求上下文,需返回 `{ pageData, totalCount }` 或自定义结构(配合 `dataFormat`) |
|
|
1594
|
+
| url | string | - | 请求地址,与 `loader` 二选一,透传给 `@kne/react-fetch` |
|
|
1595
|
+
| data | object | - | POST 请求体,默认分页参数挂在 `data.currentPage`、`data.perPage` |
|
|
1596
|
+
| dataFormat | function | `(data) => ({ list: data.pageData, total: data.totalCount })` | 将接口数据转为 `{ list, total }` 供表格使用 |
|
|
1597
|
+
| pagination | object | 见下方 | 分页配置 |
|
|
1598
|
+
| name | string | - | 表格唯一标识,用于列配置持久化,同 `Table` 的 `name` |
|
|
1599
|
+
| columns | array \| function | - | 列配置,见 TableView 的 columns 说明;也可传入函数 `(data) => columns` |
|
|
1600
|
+
| getColumns | function | - | 根据接口数据动态生成列配置 |
|
|
1601
|
+
| sticky | boolean | - | 是否启用粘性表头,仅 `renderType="Table"` 时生效 |
|
|
1602
|
+
| renderType | `'Table'` \| `'TableView'` | `'Table'` | 表格渲染类型 |
|
|
1603
|
+
| horizontalScroller | boolean | `true` | 是否启用底部浮动横向滚动条(仅 `renderType="Table"` 且表格存在横向滚动时生效) |
|
|
1604
|
+
| getScrollContainer | function | - | 浮动滚动条 portal 挂载容器,默认 `document.body` |
|
|
1605
|
+
| summary | function | - | 总结栏,回调参数包含 `data`、`requestParams`、`refresh`、`reload` 等 fetch 上下文 |
|
|
1606
|
+
| columnRenderProps | object | `{}` | 列渲染扩展属性,会合并进列 `render` 的 context |
|
|
1607
|
+
| filter | object | - | 顶部筛选器配置,基于 `@kne/react-filter` 的 `FilterLines`,见下方 |
|
|
1608
|
+
| search | object | - | 顶部搜索框配置,基于 `@kne/react-filter` 的 `SearchInput`,见下方 |
|
|
1609
|
+
| batchActions | array | - | 批量操作下拉菜单项,需配合 `rowSelection` 使用,见下方 |
|
|
1610
|
+
| selectedRows | array | - | 已选行数据,传给 `batchActions` 的 `onClick` 上下文 |
|
|
1611
|
+
| className | string | - | 自定义类名 |
|
|
1612
|
+
| ...fetchProps | - | - | 其余属性透传给 `@kne/react-fetch`(如 `url`、`params`、`auto` 等) |
|
|
1613
|
+
| ...tableProps | - | - | 其余属性透传给内部 `Table`(如 `rowKey`、`rowSelection`、`scroll`) |
|
|
1614
|
+
|
|
1615
|
+
#### pagination
|
|
1616
|
+
|
|
1617
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
1618
|
+
|------|------|--------|------|
|
|
1619
|
+
| open | boolean | `true` | 是否开启分页 |
|
|
1620
|
+
| paramsType | string | `'data'` | 分页参数挂载的请求参数类型 |
|
|
1621
|
+
| currentName | string | `'currentPage'` | 当前页参数字段名 |
|
|
1622
|
+
| pageSizeName | string | `'perPage'` | 每页条数字段名 |
|
|
1623
|
+
| requestType | `'reload'` \| `'refresh'` | `'reload'` | 翻页时的请求方式,`reload` 不切换 loading,`refresh` 会重新 loading |
|
|
1624
|
+
| showSizeChanger | boolean | `true` | 是否展示每页条数切换 |
|
|
1625
|
+
| showQuickJumper | boolean | `true` | 是否展示快速跳转 |
|
|
1626
|
+
| hideOnSinglePage | boolean | `false` | 仅一页时是否隐藏分页器 |
|
|
1627
|
+
| pageSizeOptions | array | - | 每页条数选项 |
|
|
1628
|
+
| pageSize | number | `20` | 默认每页条数,会持久化到 localStorage |
|
|
1629
|
+
| showTotal | function | - | 自定义总数展示 `(total) => ReactNode` |
|
|
1630
|
+
| onChange | function | - | 自定义翻页回调 `(page, size) => void`,传入后覆盖默认请求逻辑 |
|
|
1631
|
+
| onShowSizeChange | function | - | 每页条数变化回调,组件内部已处理持久化 |
|
|
1632
|
+
|
|
1633
|
+
#### filter
|
|
1634
|
+
|
|
1635
|
+
顶部筛选器配置,传入后会在表格上方渲染筛选行(中间区域宽度撑满)。筛选值变化时自动 `reload` 并回到第 1 页,参数通过 `getFilterValue` 合并进 `data`。
|
|
1636
|
+
|
|
1637
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
1638
|
+
|------|------|--------|------|
|
|
1639
|
+
| list | `Array<Array>` | - | 传给 `FilterLines` 的筛选项配置 |
|
|
1640
|
+
| displayLine | number | `1` | 默认展示行数 |
|
|
1641
|
+
| value | array | - | 受控筛选值 |
|
|
1642
|
+
| defaultValue | array | `[]` | 默认筛选值,会合并进首次请求参数 |
|
|
1643
|
+
| onChange | function | - | 筛选值变化回调 `(value) => void` |
|
|
1644
|
+
| mapFilterValue | function | - | 自定义参数转换,默认 `getFilterValue` |
|
|
1645
|
+
|
|
1646
|
+
#### search
|
|
1647
|
+
|
|
1648
|
+
顶部关键词搜索配置,基于 `SearchInput`,与 `filter` 共享筛选值状态。
|
|
1649
|
+
|
|
1650
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
1651
|
+
|------|------|--------|------|
|
|
1652
|
+
| name | string | - | 必填,写入筛选值的字段名 |
|
|
1653
|
+
| label | string | - | 已选展示标签 |
|
|
1654
|
+
| placeholder | string | - | 占位符 |
|
|
1655
|
+
| searchDelay | number | `500` | 自动提交防抖时间(毫秒) |
|
|
1656
|
+
|
|
1657
|
+
#### batchActions
|
|
1658
|
+
|
|
1659
|
+
批量操作下拉菜单,需配合 `rowSelection`(通常来自 `Table.useSelectedRow`)使用。
|
|
1660
|
+
|
|
1661
|
+
| 属性 | 类型 | 说明 |
|
|
1662
|
+
|------|------|------|
|
|
1663
|
+
| key | string | 菜单项 key |
|
|
1664
|
+
| label | string | 菜单文案 |
|
|
1665
|
+
| disabled | boolean | 是否禁用,默认无选中行时禁用 |
|
|
1666
|
+
| danger | boolean | 危险操作样式 |
|
|
1667
|
+
| onClick | function | `({ selectedRowKeys, selectedRows, reload, refresh, requestParams, ... }) => void` |
|
|
1668
|
+
|
|
1669
|
+
#### ref 方法
|
|
1670
|
+
|
|
1671
|
+
通过 `ref` 可调用 `@kne/react-fetch` 暴露的方法:
|
|
1672
|
+
|
|
1673
|
+
| 方法 | 说明 |
|
|
1674
|
+
|------|------|
|
|
1675
|
+
| reload | 重新请求,请求完成前保留当前内容 |
|
|
1676
|
+
| refresh | 重新请求,请求期间显示 loading |
|
|
1677
|
+
| setData | 直接修改当前数据 |
|
|
1678
|
+
| send | 发送自定义请求 |
|
|
1679
|
+
|
|
1680
|
+
#### 与 Table 分页的差异
|
|
1681
|
+
|
|
1682
|
+
`TablePage` 的分页器渲染在表格外侧(`antd Pagination`),不会出现在 `Table` 边框内部。表格本身始终设置 `pagination={false}`。
|
|
1683
|
+
|
|
1684
|
+
#### renderType
|
|
1685
|
+
|
|
1686
|
+
通过 `renderType` 选择内部使用的表格组件,默认为 `Table`:
|
|
1687
|
+
|
|
1688
|
+
```jsx
|
|
1689
|
+
<TablePage renderType="TableView" loader={...} columns={...} />
|
|
1690
|
+
```
|
|
1691
|
+
|
|
1692
|
+
#### 示例
|
|
1693
|
+
|
|
1694
|
+
```jsx
|
|
1695
|
+
<TablePage
|
|
1696
|
+
name="order-list"
|
|
1697
|
+
loader={({ data }) => {
|
|
1698
|
+
const { currentPage = 1, perPage = 20 } = data || {};
|
|
1699
|
+
return fetchOrders({ currentPage, perPage });
|
|
1700
|
+
}}
|
|
1701
|
+
dataFormat={data => ({
|
|
1702
|
+
list: data.pageData,
|
|
1703
|
+
total: data.totalCount
|
|
1704
|
+
})}
|
|
1705
|
+
columns={[
|
|
1706
|
+
{ name: 'id', title: '订单编号', width: 160 },
|
|
1707
|
+
{ name: 'customerName', title: '客户名称', width: 200 }
|
|
1708
|
+
]}
|
|
1709
|
+
pagination={{
|
|
1710
|
+
open: true,
|
|
1711
|
+
pageSizeOptions: ['10', '20', '50', '100']
|
|
1712
|
+
}}
|
|
1713
|
+
/>
|
|
1714
|
+
```
|
|
1715
|
+
|
|
1716
|
+
完整示例见文档 `TablePage`。
|
|
1717
|
+
|
|
1718
|
+
### TableView
|
|
1719
|
+
|
|
1720
|
+
表格视图组件,基于 Ant Design 的 Row/Col 布局实现,支持列配置、行选择等能力。
|
|
1721
|
+
|
|
1722
|
+
#### 属性
|
|
1723
|
+
|
|
1724
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
1725
|
+
|------|------|--------|------|
|
|
1726
|
+
| dataSource | array | - | 表格数据源 |
|
|
1727
|
+
| columns | array | - | 列配置,见下方 columns 说明 |
|
|
1728
|
+
| rowKey | string \| function | `'id'` | 行唯一标识字段名或取值函数 |
|
|
1729
|
+
| rowSelection | object | - | 行选择配置,见下方 rowSelection 说明 |
|
|
1730
|
+
| placeholder | string | `'-'` | 空值占位符 |
|
|
1731
|
+
| emptyIsPlaceholder | boolean | `true` | 空值是否显示占位符 |
|
|
1732
|
+
| empty | ReactNode | `<Empty />` | 无数据时的展示内容 |
|
|
1733
|
+
| headerStyle | object | - | 表头自定义样式,仅在 `render` 自定义渲染时作用于 `header` |
|
|
1734
|
+
| onRowSelect | function | - | 行点击回调 `(item, { columns, dataSource }) => void` |
|
|
1735
|
+
| render | function | - | 自定义渲染 `(props) => ReactNode`,可获取 `header` 和 `renderBody` |
|
|
1736
|
+
| sortRender | function | - | 排序按钮渲染,由 `useSort` 提供 |
|
|
1737
|
+
|
|
1738
|
+
#### columns
|
|
1739
|
+
|
|
1740
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
1741
|
+
|------|------|--------|------|
|
|
1742
|
+
| name | string | - | 字段名,对应 dataSource 中的属性 |
|
|
1743
|
+
| title | ReactNode | - | 列标题 |
|
|
1744
|
+
| width | number \| string | - | 列最小宽度,支持数字(如 `50`,视为 50px)或字符串(如 `'50px'`),参与列宽计算,内容超出时会自动撑开 |
|
|
1745
|
+
| span | number | - | 列占比(基于 24 栅格),未设置时自动均分剩余栅格 |
|
|
1746
|
+
| align | string | `'top'` | 垂直对齐方式 |
|
|
1747
|
+
| justify | string | `'flex-start'` | 水平对齐方式 |
|
|
1748
|
+
| format | string \| function | - | 值格式化 |
|
|
1749
|
+
| render | function | - | 自定义单元格渲染 `(value, { column, dataSource, context }) => ReactNode` |
|
|
1750
|
+
| sort | boolean \| object | - | 是否支持排序,`{ single: true }` 为单列排序 |
|
|
1751
|
+
| ellipsis | boolean \| object | `false` | 超出省略,基于 antd Typography;`true` 开启省略与 tooltip,`{ showTitle: false }` 关闭 tooltip |
|
|
1752
|
+
| display | boolean \| function | - | 是否显示该列 |
|
|
1753
|
+
| emptyIsPlaceholder | boolean | - | 该列空值是否显示占位符 |
|
|
1754
|
+
| placeholder | string | - | 该列空值占位符 |
|
|
1755
|
+
| renderPlaceholder | function | - | 自定义空值渲染 |
|
|
1756
|
+
|
|
1757
|
+
#### rowSelection
|
|
1758
|
+
|
|
1759
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
1760
|
+
|------|------|--------|------|
|
|
1761
|
+
| type | `'checkbox'` \| `'radio'` | - | 选择类型 |
|
|
1762
|
+
| selectedRowKeys | array | - | 已选中的行 key 列表 |
|
|
1763
|
+
| onChange | function | - | 选中变化回调 `(selectedRowKeys, id, { context, checked }) => void` |
|
|
1764
|
+
| allowSelectedAll | boolean | - | 是否允许全选(仅 checkbox 模式) |
|
|
1765
|
+
| isSelectedAll | boolean | - | 是否全选状态 |
|
|
1766
|
+
| onIsSelectAllChange | function | - | 全选状态变化回调 |
|
|
1767
|
+
|
|
1768
|
+
### useSelectedRow
|
|
1769
|
+
|
|
1770
|
+
行选择 Hook,用于配合 `Table` / `TableView` 的 `rowSelection`,API 参考 `@kne/components-core` 同名 Hook。
|
|
1771
|
+
|
|
1772
|
+
#### 参数
|
|
1773
|
+
|
|
1774
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
1775
|
+
|------|------|--------|------|
|
|
1776
|
+
| rowKey | string \| function | `'id'` | 行唯一标识 |
|
|
1777
|
+
| type | `'checkbox'` \| `'radio'` | `'checkbox'` | 选择类型 |
|
|
1778
|
+
|
|
1779
|
+
#### 返回值
|
|
1780
|
+
|
|
1781
|
+
| 属性 | 类型 | 说明 |
|
|
1782
|
+
|------|------|------|
|
|
1783
|
+
| selectedRowKeys | array | 已选行 key 列表 |
|
|
1784
|
+
| selectedRows | array | 已选行数据 |
|
|
1785
|
+
| onSelect | function | `(item, checked) => void` |
|
|
1786
|
+
| onSelectAll | function | `(checked, selected, items) => void` |
|
|
1787
|
+
| setSelectedRows | function | 直接设置已选行数据 |
|
|
1788
|
+
| setSelectedRowKeys | function | `(keys, dataSource) => void` |
|
|
1789
|
+
| clearSelectedRows | function | 清空选择 |
|
|
1790
|
+
| getRowSelection | function | `(dataSource, extra?) => rowSelection` 生成 `rowSelection` 配置 |
|
|
1791
|
+
|
|
1792
|
+
#### 示例
|
|
1793
|
+
|
|
1794
|
+
```jsx
|
|
1795
|
+
const { selectedRowKeys, getRowSelection, clearSelectedRows } = Table.useSelectedRow({ rowKey: 'id' });
|
|
1796
|
+
|
|
1797
|
+
<Table
|
|
1798
|
+
dataSource={dataSource}
|
|
1799
|
+
columns={columns}
|
|
1800
|
+
rowSelection={getRowSelection(dataSource)}
|
|
1801
|
+
/>;
|
|
1802
|
+
```
|
|
1803
|
+
|
|
1804
|
+
### useSort
|
|
1805
|
+
|
|
1806
|
+
排序 Hook,配合 `Table` / `TableView` 的 `sortRender` 实现表头排序。
|
|
1807
|
+
|
|
1808
|
+
#### 参数
|
|
1809
|
+
|
|
1810
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
1811
|
+
|------|------|--------|------|
|
|
1812
|
+
| sort | array | - | 受控排序值 `[{ name, sort: 'ASC' \| 'DESC' }]` |
|
|
1813
|
+
| defaultSort | array | `[]` | 默认排序 |
|
|
1814
|
+
| onSortChange | function | - | 排序变化回调 `(sort) => void` |
|
|
1815
|
+
|
|
1816
|
+
#### 返回值
|
|
1817
|
+
|
|
1818
|
+
| 属性 | 类型 | 说明 |
|
|
1819
|
+
|------|------|------|
|
|
1820
|
+
| sort | array | 当前排序配置 |
|
|
1821
|
+
| setSort | function | 设置排序 |
|
|
1822
|
+
| sortRender | function | `({ name, single }) => ReactNode`,传给 Table / TableView |
|
|
1823
|
+
|
|
1824
|
+
#### columns.sort
|
|
1825
|
+
|
|
1826
|
+
| 值 | 说明 |
|
|
1827
|
+
|----|------|
|
|
1828
|
+
| `true` | 开启排序,默认单列模式 |
|
|
1829
|
+
| `{ single: true }` | 单列排序,切换列时清除其他列 |
|
|
1830
|
+
| `{ single: false }` | 多列排序 |
|
|
1831
|
+
|
|
1832
|
+
#### sortDataSource
|
|
1833
|
+
|
|
1834
|
+
本地排序工具函数:`sortDataSource(dataSource, sort, columns)`。
|
|
1835
|
+
|
|
1836
|
+
#### 示例
|
|
1837
|
+
|
|
1838
|
+
```jsx
|
|
1839
|
+
const { sort, sortRender } = Table.useSort({ onSortChange: console.log });
|
|
1840
|
+
const sortedData = useMemo(() => Table.sortDataSource(dataSource, sort, columns), [sort, dataSource]);
|
|
1841
|
+
|
|
1842
|
+
<Table dataSource={sortedData} columns={columns} sortRender={sortRender} />;
|
|
1843
|
+
```
|
|
1844
|
+
|
|
1845
|
+
### Table
|
|
1846
|
+
|
|
1847
|
+
表格组件,以 antd `Table` 作为展示层,外层 API 与 `TableView` 保持一致,可直接复用相同的 `columns`、`rowSelection` 等配置。此外支持透传 antd Table 的原生属性(如 `scroll`、`pagination`、`size` 等)。
|
|
1848
|
+
|
|
1849
|
+
#### 属性
|
|
1850
|
+
|
|
1851
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
1852
|
+
|------|------|--------|------|
|
|
1853
|
+
| dataSource | array | - | 表格数据源 |
|
|
1854
|
+
| columns | array | - | 列配置,见 TableView 的 columns 说明 |
|
|
1855
|
+
| rowKey | string \| function | `'id'` | 行唯一标识字段名或取值函数 |
|
|
1856
|
+
| rowSelection | object | - | 行选择配置,见 TableView 的 rowSelection 说明 |
|
|
1857
|
+
| placeholder | string | `'-'` | 空值占位符 |
|
|
1858
|
+
| emptyIsPlaceholder | boolean | `true` | 空值是否显示占位符 |
|
|
1859
|
+
| empty | ReactNode | `<Empty />` | 无数据时的展示内容 |
|
|
1860
|
+
| sticky | boolean | - | 是否启用粘性表头 |
|
|
1861
|
+
| headerStyle | object | - | 表头自定义样式 |
|
|
1862
|
+
| onRowSelect | function | - | 行点击回调 `(item, { columns, dataSource }) => void` |
|
|
1863
|
+
| render | function | - | 自定义渲染 `(props) => ReactNode`,`header` 为 `null`,`renderBody` 返回 antd Table |
|
|
1864
|
+
| sortRender | function | - | 排序按钮渲染,由 `useSort` 提供 |
|
|
1865
|
+
| pagination | boolean \| object | `false` | 分页配置,默认不显示;传入对象时使用 antd 分页 |
|
|
1866
|
+
| name | string | - | 表格唯一标识,用于持久化列配置 |
|
|
1867
|
+
| controllerOpen | boolean | `true` | 是否开启列宽拖动与列配置面板 |
|
|
1868
|
+
| tableServerApis | object | - | 自定义列配置存储 API,默认使用 `localStorage` |
|
|
1869
|
+
| ...antdTableProps | - | - | 其余属性透传给 antd `Table`(如 `scroll`、`bordered`) |
|
|
1870
|
+
|
|
1871
|
+
#### 与 TableView 的差异
|
|
1872
|
+
|
|
1873
|
+
| 项目 | TableView | Table |
|
|
1874
|
+
|------|-----------|-------|
|
|
1875
|
+
| 展示层 | Row/Col 自定义布局 | antd `Table` |
|
|
1876
|
+
| `columns.span` | 基于 24 栅格分配列宽 | 不生效,请使用 `width` |
|
|
1877
|
+
| 单选展示 | 右侧勾选图标 | antd 左侧 radio 列 |
|
|
1878
|
+
| 列宽拖动 / 字段显示隐藏 | 不支持 | 支持,见下方说明 |
|
|
1879
|
+
| 扩展能力 | 自定义 `render` 拆分表头/表体 | 支持 antd 原生 `scroll`、`pagination` 等 |
|
|
1880
|
+
|
|
1881
|
+
#### columns 扩展(仅 Table)
|
|
1882
|
+
|
|
1883
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
1884
|
+
|------|------|--------|------|
|
|
1885
|
+
| hidden | boolean | `false` | 默认隐藏该列,可在列配置面板中重新显示 |
|
|
1886
|
+
| min | number | `80` | 列最小宽度(px),拖动调整列宽时的下限,无需手动配置 |
|
|
1887
|
+
| max | number | `600` | 列最大宽度(px),拖动调整列宽时的上限,无需手动配置 |
|
|
1888
|
+
| fixed | `'left'` \| `'right'` \| boolean | - | 固定列,固定列不可隐藏或拖动排序 |
|
|
1889
|
+
| groupHeader | array | - | 分组表头配置,见下方 groupHeader 说明 |
|
|
1890
|
+
|
|
1891
|
+
#### groupHeader
|
|
1892
|
+
|
|
1893
|
+
在列配置中通过 `groupHeader` 声明该列所属的分组表头,支持多级嵌套。相同分组路径的列会自动合并为 antd 嵌套表头(仅 `Table` 支持,`TableView` 不生效)。
|
|
1894
|
+
|
|
1895
|
+
| 属性 | 类型 | 说明 |
|
|
1896
|
+
|------|------|------|
|
|
1897
|
+
| name | string | 分组唯一标识 |
|
|
1898
|
+
| title | ReactNode | 分组标题 |
|
|
1899
|
+
|
|
1900
|
+
完整示例见文档 `group header`。
|
|
1901
|
+
|
|
1902
|
+
#### 列宽拖动与字段显示/隐藏
|
|
1903
|
+
|
|
1904
|
+
设置 `name` 开启列配置持久化;`controllerOpen` 控制是否显示拖动手柄与设置面板(默认 `true`)。列只需配置 `width`,`min` / `max` 有默认值(80 / 600),一般无需手动指定。
|
|
1905
|
+
|
|
1906
|
+
```jsx
|
|
1907
|
+
<Table
|
|
1908
|
+
name="order-list"
|
|
1909
|
+
dataSource={dataSource}
|
|
1910
|
+
columns={[
|
|
1911
|
+
{ name: 'id', title: '订单编号', width: 160, min: 120, max: 240, fixed: 'left' },
|
|
1912
|
+
{ name: 'customerName', title: '客户名称', width: 200, min: 140, max: 360 },
|
|
1913
|
+
{ name: 'remark', title: '备注', width: 200, hidden: true }
|
|
1914
|
+
]}
|
|
1915
|
+
/>
|
|
1916
|
+
```
|
|
1917
|
+
|
|
1918
|
+
- 悬停表头列右侧拖动手柄可调整列宽
|
|
1919
|
+
- 点击最后一列表头设置图标可显示/隐藏列、拖拽排序
|
|
1920
|
+
- `hidden: true` 的列默认隐藏,可在面板中重新显示
|
|
1921
|
+
- `fixed` 列固定显示且不可隐藏
|
|
1922
|
+
|
|
1923
|
+
完整示例见文档 `column config`。
|