@gmfe/table-x 2.14.30-beta.5 → 2.14.30
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 +226 -0
- package/package.json +4 -4
- package/src/index.js +0 -4
- package/src/index.less +3 -160
- package/src/util/index.js +5 -7
- package/src/hoc/two_level_header_table_x/table_x.js +0 -162
- package/src/hoc/two_level_header_table_x/td.js +0 -126
- package/src/hoc/two_level_header_table_x/thead.js +0 -293
- package/src/hoc/two_level_header_table_x/tr.js +0 -53
- package/src/hoc/two_level_header_table_x/transform_helper.js +0 -170
- package/src/hoc/two_level_header_table_x.js +0 -73
package/README.md
CHANGED
|
@@ -1,3 +1,229 @@
|
|
|
1
|
+
# @gmfe/table-x
|
|
2
|
+
|
|
3
|
+
## 简介
|
|
4
|
+
|
|
5
|
+
`@gmfe/table-x` 是基于 [react-table v7](https://github.com/tannerlinsley/react-table)(7.0.0-rc.11)封装的高级表格组件包,是 `@gmfe/table` 的升级版本。采用 Hooks API 和 Context API,提供更现代的表格功能扩展。支持虚拟滚动、选择、展开、固定列、排序、子表格、自定义列和可编辑表格等功能。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @gmfe/table-x
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 使用
|
|
14
|
+
|
|
15
|
+
### 基础表格
|
|
16
|
+
|
|
17
|
+
```jsx
|
|
18
|
+
import React from 'react'
|
|
19
|
+
import { TableX } from '@gmfe/table-x'
|
|
20
|
+
|
|
21
|
+
const columns = [
|
|
22
|
+
{ Header: '名称', accessor: 'name' },
|
|
23
|
+
{ Header: '年龄', accessor: 'age' }
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
const data = [
|
|
27
|
+
{ value: 1, name: '张三', age: 28 },
|
|
28
|
+
{ value: 2, name: '李四', age: 32 }
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
function App() {
|
|
32
|
+
return <TableX columns={columns} data={data} />
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 带选择功能的表格
|
|
37
|
+
|
|
38
|
+
```jsx
|
|
39
|
+
import React, { useState } from 'react'
|
|
40
|
+
import { TableX, selectTableXHOC } from '@gmfe/table-x'
|
|
41
|
+
|
|
42
|
+
const SelectTableX = selectTableXHOC(TableX)
|
|
43
|
+
|
|
44
|
+
function App() {
|
|
45
|
+
const [selected, setSelected] = useState([])
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<SelectTableX
|
|
49
|
+
columns={columns}
|
|
50
|
+
data={data}
|
|
51
|
+
selected={selected}
|
|
52
|
+
onSelect={(selected) => setSelected(selected)}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 虚拟滚动表格
|
|
59
|
+
|
|
60
|
+
```jsx
|
|
61
|
+
import { TableXVirtualized } from '@gmfe/table-x'
|
|
62
|
+
|
|
63
|
+
function App() {
|
|
64
|
+
return (
|
|
65
|
+
<TableXVirtualized
|
|
66
|
+
columns={columns}
|
|
67
|
+
data={largeData}
|
|
68
|
+
virtualizedHeight={600}
|
|
69
|
+
virtualizedItemSize={40}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## API
|
|
76
|
+
|
|
77
|
+
### TableX
|
|
78
|
+
|
|
79
|
+
基础表格组件。
|
|
80
|
+
|
|
81
|
+
| 属性 | 说明 | 类型 | 默认值 | 必填 |
|
|
82
|
+
|------|------|------|--------|------|
|
|
83
|
+
| columns | 列定义 | `array` | - | 是 |
|
|
84
|
+
| data | 表格数据 | `array` | - | 是 |
|
|
85
|
+
| loading | 是否加载中 | `boolean` | `false` | 否 |
|
|
86
|
+
| keyField | 行唯一标识字段 | `string` | `'value'` | 否 |
|
|
87
|
+
| tiled | 是否平铺 | `boolean` | `false` | 否 |
|
|
88
|
+
| isTrDisable | 行禁用判断函数 | `function(row): boolean` | `() => false` | 否 |
|
|
89
|
+
| isTrHighlight | 行高亮判断函数 | `function(row): boolean` | `() => false` | 否 |
|
|
90
|
+
| SubComponent | 展开行渲染函数 | `function(row)` | - | 否 |
|
|
91
|
+
| onScroll | 滚动回调 | `function` | - | 否 |
|
|
92
|
+
| className | 自定义类名 | `string` | - | 否 |
|
|
93
|
+
| style | 自定义样式 | `object` | - | 否 |
|
|
94
|
+
|
|
95
|
+
### TableXVirtualized
|
|
96
|
+
|
|
97
|
+
虚拟滚动表格组件,继承 TableX 的所有属性,额外支持:
|
|
98
|
+
|
|
99
|
+
| 额外属性 | 说明 | 类型 | 默认值 | 必填 |
|
|
100
|
+
|----------|------|------|--------|------|
|
|
101
|
+
| virtualizedHeight | 表格容器高度 | `number` | - | 是 |
|
|
102
|
+
| virtualizedItemSize | 行高(数字或函数) | `number \| function` | - | 是 |
|
|
103
|
+
| refVirtualized | 虚拟列表 ref | `object \| function` | - | 否 |
|
|
104
|
+
|
|
105
|
+
### HOC 组件
|
|
106
|
+
|
|
107
|
+
#### selectTableXHOC(TableX)
|
|
108
|
+
|
|
109
|
+
选择功能表格 HOC。
|
|
110
|
+
|
|
111
|
+
| 额外属性 | 说明 | 类型 | 默认值 | 必填 |
|
|
112
|
+
|----------|------|------|--------|------|
|
|
113
|
+
| selected | 已选中项数组 | `array` | - | 是 |
|
|
114
|
+
| onSelect | 选中回调 | `function(selected: array)` | - | 是 |
|
|
115
|
+
| selectType | 选择类型 | `'checkbox' \| 'radio'` | `'checkbox'` | 否 |
|
|
116
|
+
| keyField | 行唯一标识字段 | `string` | `'value'` | 否 |
|
|
117
|
+
| isSelectorDisable | 行选择禁用函数 | `function(row): boolean` | `() => false` | 否 |
|
|
118
|
+
| batchActionBar | 自定义批量操作栏 | `ReactElement` | - | 否 |
|
|
119
|
+
| fixedSelect | 是否固定选择列 | `boolean` | `false` | 否 |
|
|
120
|
+
|
|
121
|
+
#### expandTableXHOC(TableX)
|
|
122
|
+
|
|
123
|
+
展开行表格 HOC。
|
|
124
|
+
|
|
125
|
+
| 额外属性 | 说明 | 类型 | 默认值 | 必填 |
|
|
126
|
+
|----------|------|------|--------|------|
|
|
127
|
+
| SubComponent | 展开内容渲染函数 | `function(row)` | - | 是 |
|
|
128
|
+
| expanded | 展开行对象(受控模式) | `object` | - | 否 |
|
|
129
|
+
| onExpand | 展开/折叠回调 | `function(expanded: object)` | - | 否 |
|
|
130
|
+
| fixedExpand | 是否固定展开列 | `boolean` | `false` | 否 |
|
|
131
|
+
|
|
132
|
+
#### fixedColumnsTableXHOC(TableX)
|
|
133
|
+
|
|
134
|
+
固定列 HOC。列定义中通过 `fixed: 'left'` 或 `fixed: 'right'` 指定固定方向。固定列需要设置 `width` 属性。
|
|
135
|
+
|
|
136
|
+
#### sortableTableXHOC(TableX)
|
|
137
|
+
|
|
138
|
+
可拖拽排序表格 HOC。
|
|
139
|
+
|
|
140
|
+
| 额外属性 | 说明 | 类型 | 默认值 | 必填 |
|
|
141
|
+
|----------|------|------|--------|------|
|
|
142
|
+
| onSortChange | 排序变化回调 | `function(data: array)` | - | 是 |
|
|
143
|
+
| keyField | 行唯一标识字段 | `string` | `'value'` | 否 |
|
|
144
|
+
|
|
145
|
+
#### editTableXHOC(TableX)
|
|
146
|
+
|
|
147
|
+
可编辑表格 HOC,添加 `.gm-table-x-edit-table` 样式类。
|
|
148
|
+
|
|
149
|
+
#### subTableXHOC(TableX)
|
|
150
|
+
|
|
151
|
+
子表格 HOC,添加缩进列。
|
|
152
|
+
|
|
153
|
+
| 额外属性 | 说明 | 类型 | 默认值 | 必填 |
|
|
154
|
+
|----------|------|------|--------|------|
|
|
155
|
+
| subTableIndent | 子表格缩进宽度 | `number` | `TABLE_X.WIDTH_FUN` | 否 |
|
|
156
|
+
|
|
157
|
+
#### diyTableXHOC(TableX)
|
|
158
|
+
|
|
159
|
+
自定义列管理 HOC。
|
|
160
|
+
|
|
161
|
+
| 额外属性 | 说明 | 类型 | 默认值 | 必填 |
|
|
162
|
+
|----------|------|------|--------|------|
|
|
163
|
+
| id | 唯一标识,用于保存列配置到 localStorage | `string` | - | 是 |
|
|
164
|
+
| diyGroupSorting | 分组排序配置 | `array` | - | 是 |
|
|
165
|
+
|
|
166
|
+
- 列定义中需要 `diyGroupName` 和 `Header`(或 `diyItemText`)字段。
|
|
167
|
+
|
|
168
|
+
### TableXUtil
|
|
169
|
+
|
|
170
|
+
表格工具集。
|
|
171
|
+
|
|
172
|
+
| 属性/方法 | 说明 |
|
|
173
|
+
|-----------|------|
|
|
174
|
+
| `TABLE_X` | 表格常量配置(宽度、高度等) |
|
|
175
|
+
| `BatchActionBar` | 批量操作栏组件 |
|
|
176
|
+
| `OperationHeader` | 操作列表头组件 |
|
|
177
|
+
| `OperationDelete` | 删除操作组件 |
|
|
178
|
+
| `OperationRecover` | 恢复操作组件 |
|
|
179
|
+
| `OperationDetail` | 详情操作组件 |
|
|
180
|
+
| `OperationCell` | 操作单元格组件 |
|
|
181
|
+
| `OperationRowEdit` | 行编辑操作组件 |
|
|
182
|
+
| `OperationIconTip` | 图标提示操作组件 |
|
|
183
|
+
| `EditButton` | 编辑按钮组件 |
|
|
184
|
+
| `EditOperation` | 编辑操作组件 |
|
|
185
|
+
| `SortHeader` | 排序表头组件 |
|
|
186
|
+
|
|
187
|
+
## 示例
|
|
188
|
+
|
|
189
|
+
### 多功能组合
|
|
190
|
+
|
|
191
|
+
```jsx
|
|
192
|
+
import { TableX, selectTableXHOC, expandTableXHOC, fixedColumnsTableXHOC } from '@gmfe/table-x'
|
|
193
|
+
|
|
194
|
+
// HOC 组合:选择 + 展开 + 固定列
|
|
195
|
+
const EnhancedTable = fixedColumnsTableXHOC(selectTableXHOC(expandTableXHOC(TableX)))
|
|
196
|
+
|
|
197
|
+
const columns = [
|
|
198
|
+
{ Header: '名称', accessor: 'name', fixed: 'left', width: 200 },
|
|
199
|
+
{ Header: '描述', accessor: 'desc' },
|
|
200
|
+
{ Header: '操作', accessor: 'action', fixed: 'right', width: 150 }
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
function App() {
|
|
204
|
+
return (
|
|
205
|
+
<EnhancedTable
|
|
206
|
+
columns={columns}
|
|
207
|
+
data={data}
|
|
208
|
+
selected={selected}
|
|
209
|
+
onSelect={setSelected}
|
|
210
|
+
SubComponent={(row) => <div>展开内容:{row.original.name}</div>}
|
|
211
|
+
/>
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## 注意事项
|
|
217
|
+
|
|
218
|
+
- `keyField` 默认为 `'value'`,确保数据中有该字段作为行唯一标识。
|
|
219
|
+
- 固定列必须设置 `width` 属性,否则会报错。
|
|
220
|
+
- `show: false` 的列会被自动过滤。
|
|
221
|
+
- 虚拟列表(`TableXVirtualized`)适用于大数据量场景,需要指定 `virtualizedHeight` 和 `virtualizedItemSize`。
|
|
222
|
+
- `selectTableXHOC` 不再提供 `onSelectAll` 回调,选中全部的逻辑通过 `isSelectorDisable` 和内部计算实现。
|
|
223
|
+
- 此包需要 `styled-components ^5.1.0` 作为 peer dependency。
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
1
227
|
## tableX 踩的坑,阅读后改代码更自信
|
|
2
228
|
|
|
3
229
|
- 注意不要随意升级版本,目前使用 7.0.0-rc.11
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gmfe/table-x",
|
|
3
|
-
"version": "2.14.30
|
|
3
|
+
"version": "2.14.30",
|
|
4
4
|
"description": "",
|
|
5
5
|
"author": "liyatang <liyatang@qq.com>",
|
|
6
6
|
"homepage": "https://github.com/gmfe/gmfe#readme",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@gm-common/tool": "^1.0.0",
|
|
30
|
-
"@gmfe/locales": "^2.14.
|
|
31
|
-
"@gmfe/react": "2.14.
|
|
30
|
+
"@gmfe/locales": "^2.14.30",
|
|
31
|
+
"@gmfe/react": "^2.14.30",
|
|
32
32
|
"classnames": "^2.2.5",
|
|
33
33
|
"lodash": "^4.17.14",
|
|
34
34
|
"prop-types": "^15.7.2",
|
|
@@ -36,5 +36,5 @@
|
|
|
36
36
|
"react-window": "^1.8.5",
|
|
37
37
|
"sortablejs": "^1.10.1"
|
|
38
38
|
},
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "ecae7979e69fc5e3bb1185fe84f82f00dfca1159"
|
|
40
40
|
}
|
package/src/index.js
CHANGED
|
@@ -7,8 +7,6 @@ import sortableTableXHOC from './hoc/sortable_table_x'
|
|
|
7
7
|
import subTableXHOC from './hoc/sub_table_x'
|
|
8
8
|
import editTableXHOC from './hoc/edit_table_x'
|
|
9
9
|
import diyTableXHOC from './hoc/diy_table_x'
|
|
10
|
-
import twoLevelHeaderTableXHOC from './hoc/two_level_header_table_x'
|
|
11
|
-
import TwoLevelTableX from './hoc/two_level_header_table_x/table_x'
|
|
12
10
|
|
|
13
11
|
import {
|
|
14
12
|
TABLE_X,
|
|
@@ -44,8 +42,6 @@ const TableXUtil = {
|
|
|
44
42
|
export {
|
|
45
43
|
TableXUtil,
|
|
46
44
|
TableX,
|
|
47
|
-
TwoLevelTableX,
|
|
48
|
-
twoLevelHeaderTableXHOC,
|
|
49
45
|
TableXVirtualized,
|
|
50
46
|
selectTableXHOC,
|
|
51
47
|
expandTableXHOC,
|
package/src/index.less
CHANGED
|
@@ -19,70 +19,6 @@
|
|
|
19
19
|
transform: translateZ(0);
|
|
20
20
|
display: block; // 覆盖table 默认display
|
|
21
21
|
|
|
22
|
-
// 当有两级表头时,表格需要使用表格布局来支持 rowspan
|
|
23
|
-
&.gm-table-x-table-two-level {
|
|
24
|
-
display: table !important;
|
|
25
|
-
.gm-table-x-tbody {
|
|
26
|
-
display: table-row-group !important;
|
|
27
|
-
.gm-table-x-tr {
|
|
28
|
-
display: table-row !important;
|
|
29
|
-
height: 60px;
|
|
30
|
-
.gm-table-x-td {
|
|
31
|
-
display: table-cell !important;
|
|
32
|
-
flex: none !important;
|
|
33
|
-
flex-grow: unset !important;
|
|
34
|
-
flex-shrink: unset !important;
|
|
35
|
-
flex-basis: unset !important;
|
|
36
|
-
box-sizing: border-box;
|
|
37
|
-
padding: 8px;
|
|
38
|
-
white-space: normal;
|
|
39
|
-
word-wrap: break-word;
|
|
40
|
-
word-break: break-all;
|
|
41
|
-
vertical-align: middle;
|
|
42
|
-
text-align: center;
|
|
43
|
-
&:first-child {
|
|
44
|
-
padding-left: 20px;
|
|
45
|
-
}
|
|
46
|
-
&:last-child {
|
|
47
|
-
padding-right: 20px;
|
|
48
|
-
}
|
|
49
|
-
&.gm-table-x-icon-column {
|
|
50
|
-
padding-right: 5px;
|
|
51
|
-
padding-left: 5px;
|
|
52
|
-
.gm-table-x-icon.gm-popover-active {
|
|
53
|
-
color: @brand-primary;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
img {
|
|
57
|
-
max-width: 100%;
|
|
58
|
-
max-height: 60px;
|
|
59
|
-
object-fit: contain;
|
|
60
|
-
vertical-align: middle;
|
|
61
|
-
display: block;
|
|
62
|
-
margin: 0 auto;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// 保持与原版 TableX 一致的 expand 样式
|
|
66
|
-
.gm-table-x-expand {
|
|
67
|
-
font-size: 14px;
|
|
68
|
-
padding-top: 2px;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// 二级表头的固定列边框处理(使用 inset box-shadow 创建边框,确保滚动时边框始终可见)
|
|
72
|
-
&.gm-table-x-fixed-left {
|
|
73
|
-
border-right: none !important;
|
|
74
|
-
box-shadow: inset -1px 0 0 0 @gm-table-x-border-color;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
&.gm-table-x-fixed-right {
|
|
78
|
-
border-left: none !important;
|
|
79
|
-
box-shadow: inset 1px 0 0 0 @gm-table-x-border-color;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
22
|
.gm-table-x-tr {
|
|
87
23
|
display: flex;
|
|
88
24
|
min-height: 60px;
|
|
@@ -144,77 +80,6 @@
|
|
|
144
80
|
}
|
|
145
81
|
}
|
|
146
82
|
|
|
147
|
-
// 二级表头样式
|
|
148
|
-
// 注意:为了支持 rowspan,二级表头需要使用原生的表格布局模型
|
|
149
|
-
.gm-table-x-thead-two-level {
|
|
150
|
-
display: table-header-group !important;
|
|
151
|
-
|
|
152
|
-
.gm-table-x-tr-first-level {
|
|
153
|
-
display: table-row !important;
|
|
154
|
-
|
|
155
|
-
.gm-table-x-th-first-level {
|
|
156
|
-
// 第一级表头样式
|
|
157
|
-
display: table-cell !important;
|
|
158
|
-
border: none !important;
|
|
159
|
-
vertical-align: middle;
|
|
160
|
-
text-align: center;
|
|
161
|
-
padding: 8px;
|
|
162
|
-
flex: none !important;
|
|
163
|
-
// 确保固定列有背景色,这样边框才能正确显示
|
|
164
|
-
background: @gm-table-x-header-bg !important;
|
|
165
|
-
position: relative;
|
|
166
|
-
|
|
167
|
-
// 使用伪元素创建所有边框
|
|
168
|
-
&::before {
|
|
169
|
-
content: '';
|
|
170
|
-
position: absolute;
|
|
171
|
-
top: 0;
|
|
172
|
-
left: 0;
|
|
173
|
-
right: 0;
|
|
174
|
-
bottom: 0;
|
|
175
|
-
border: 1px solid #d8dee7;
|
|
176
|
-
border-bottom-width: 2px;
|
|
177
|
-
pointer-events: none;
|
|
178
|
-
z-index: 1;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
.gm-table-x-tr-second-level {
|
|
184
|
-
display: table-row !important;
|
|
185
|
-
.gm-table-x-th-second-level {
|
|
186
|
-
// 第二级表头样式
|
|
187
|
-
display: table-cell !important;
|
|
188
|
-
border: none !important;
|
|
189
|
-
vertical-align: middle;
|
|
190
|
-
text-align: center;
|
|
191
|
-
padding: 8px;
|
|
192
|
-
flex: none !important;
|
|
193
|
-
flex-grow: unset !important;
|
|
194
|
-
flex-shrink: unset !important;
|
|
195
|
-
flex-basis: unset !important;
|
|
196
|
-
box-sizing: border-box;
|
|
197
|
-
// 确保固定列有背景色,这样边框才能正确显示
|
|
198
|
-
background: @gm-table-x-header-bg !important;
|
|
199
|
-
position: relative;
|
|
200
|
-
|
|
201
|
-
// 使用伪元素创建所有边框(除了顶部,因为顶部由一级表头的底部边框覆盖)
|
|
202
|
-
&::before {
|
|
203
|
-
content: '';
|
|
204
|
-
position: absolute;
|
|
205
|
-
top: 0;
|
|
206
|
-
left: 0;
|
|
207
|
-
right: 0;
|
|
208
|
-
bottom: 0;
|
|
209
|
-
border: 1px solid #d8dee7;
|
|
210
|
-
border-top: none;
|
|
211
|
-
pointer-events: none;
|
|
212
|
-
z-index: 1;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
83
|
.gm-table-x-tbody {
|
|
219
84
|
display: block; // 覆盖tbody 默认display
|
|
220
85
|
|
|
@@ -346,41 +211,19 @@
|
|
|
346
211
|
position: sticky !important;
|
|
347
212
|
z-index: 1;
|
|
348
213
|
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// 表头固定列需要更高的 z-index
|
|
352
|
-
.gm-table-x-th {
|
|
353
|
-
&.gm-table-x-fixed-left,
|
|
354
|
-
&.gm-table-x-fixed-right {
|
|
355
|
-
z-index: 11 !important;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
214
|
|
|
359
|
-
// tbody 单元格固定列使用 inset box-shadow
|
|
360
|
-
.gm-table-x-td {
|
|
361
215
|
&.gm-table-x-fixed-left {
|
|
362
|
-
border-right:
|
|
363
|
-
// 使用 box-shadow 创建边框,确保在滚动时边框始终可见
|
|
364
|
-
// inset box-shadow 会创建内部阴影,不会随内容滚动
|
|
365
|
-
box-shadow: inset -1px 0 0 0 @gm-table-x-border-color !important;
|
|
216
|
+
border-right: 1px solid @gm-table-x-border-color !important;
|
|
366
217
|
}
|
|
367
218
|
|
|
368
219
|
&.gm-table-x-fixed-right {
|
|
369
|
-
border-left:
|
|
370
|
-
// 使用 box-shadow 创建边框,确保在滚动时边框始终可见
|
|
371
|
-
// inset box-shadow 会创建内部阴影,不会随内容滚动
|
|
372
|
-
box-shadow: inset 1px 0 0 0 @gm-table-x-border-color !important;
|
|
220
|
+
border-left: 1px solid @gm-table-x-border-color !important;
|
|
373
221
|
}
|
|
374
222
|
}
|
|
375
223
|
}
|
|
376
224
|
|
|
377
225
|
&.gm-table-x-empty {
|
|
378
226
|
min-height: 166px;
|
|
379
|
-
|
|
380
|
-
// 两级表头的空状态需要更大的最小高度(表头高度 92px + 内容区域)
|
|
381
|
-
&.gm-table-x-two-level {
|
|
382
|
-
min-height: 212px;
|
|
383
|
-
}
|
|
384
227
|
}
|
|
385
228
|
|
|
386
229
|
&.gm-table-x-tiled {
|
|
@@ -589,7 +432,7 @@
|
|
|
589
432
|
}
|
|
590
433
|
}
|
|
591
434
|
|
|
592
|
-
.gm-react-table-x-diy-modal-content
|
|
435
|
+
.gm-react-table-x-diy-modal-content{
|
|
593
436
|
border-top: 1px solid @gm-table-x-border-color;
|
|
594
437
|
border-bottom: 1px solid @gm-table-x-border-color;
|
|
595
438
|
}
|
package/src/util/index.js
CHANGED
|
@@ -59,7 +59,7 @@ const Mask = ({ style, children }) => {
|
|
|
59
59
|
bottom: 0,
|
|
60
60
|
left: 0,
|
|
61
61
|
right: 0,
|
|
62
|
-
marginTop: '
|
|
62
|
+
marginTop: '46px',
|
|
63
63
|
...style
|
|
64
64
|
}}
|
|
65
65
|
>
|
|
@@ -72,9 +72,9 @@ Mask.propTypes = {
|
|
|
72
72
|
style: PropTypes.object
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
const Empty =
|
|
75
|
+
const Empty = () => {
|
|
76
76
|
return (
|
|
77
|
-
<Mask
|
|
77
|
+
<Mask>
|
|
78
78
|
<div style={{ padding: '10px' }}>
|
|
79
79
|
<SVGEmpty style={{ width: '70px', height: '70px' }} />
|
|
80
80
|
<div className='gm-text-desc'>{getLocale('没有数据了')}</div>
|
|
@@ -83,14 +83,12 @@ const Empty = props => {
|
|
|
83
83
|
)
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
const Loading = (
|
|
86
|
+
const Loading = () => {
|
|
87
87
|
return (
|
|
88
88
|
<Mask
|
|
89
89
|
style={{
|
|
90
|
-
backgroundColor: 'rgba(255,255,255,0.8)'
|
|
91
|
-
...style
|
|
90
|
+
backgroundColor: 'rgba(255,255,255,0.8)'
|
|
92
91
|
}}
|
|
93
|
-
{...rest}
|
|
94
92
|
>
|
|
95
93
|
{getLocale('加载数据中...')}
|
|
96
94
|
</Mask>
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import React, { useMemo } from 'react'
|
|
2
|
-
import PropTypes from 'prop-types'
|
|
3
|
-
import { useTable } from 'react-table'
|
|
4
|
-
import { Empty, Loading, afterScroll, __DEFAULT_COLUMN } from '../../util'
|
|
5
|
-
import classNames from 'classnames'
|
|
6
|
-
import _ from 'lodash'
|
|
7
|
-
import TwoLevelTHead from './thead'
|
|
8
|
-
import Tr from './tr'
|
|
9
|
-
import { transformColumnsForTwoLevel } from './transform_helper'
|
|
10
|
-
|
|
11
|
-
const defaultColumn = __DEFAULT_COLUMN
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* 支持两级表头的 TableX 组件
|
|
15
|
-
*/
|
|
16
|
-
const TwoLevelTableX = ({
|
|
17
|
-
columns,
|
|
18
|
-
data,
|
|
19
|
-
loading,
|
|
20
|
-
SubComponent,
|
|
21
|
-
keyField = 'value',
|
|
22
|
-
className,
|
|
23
|
-
tiled = false,
|
|
24
|
-
onScroll,
|
|
25
|
-
isTrDisable = () => false,
|
|
26
|
-
isTrHighlight = () => false,
|
|
27
|
-
...rest
|
|
28
|
-
}) => {
|
|
29
|
-
const { transformedColumns, firstLevelHeaders } = useMemo(() => {
|
|
30
|
-
return transformColumnsForTwoLevel(columns)
|
|
31
|
-
}, [columns])
|
|
32
|
-
|
|
33
|
-
const visibleColumns = useMemo(() => {
|
|
34
|
-
return transformedColumns.filter(c => c.show !== false)
|
|
35
|
-
}, [transformedColumns])
|
|
36
|
-
|
|
37
|
-
const {
|
|
38
|
-
getTableProps,
|
|
39
|
-
headerGroups,
|
|
40
|
-
getTableBodyProps,
|
|
41
|
-
rows,
|
|
42
|
-
prepareRow
|
|
43
|
-
} = useTable({
|
|
44
|
-
columns: visibleColumns,
|
|
45
|
-
data,
|
|
46
|
-
defaultColumn
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
let totalWidth = 0
|
|
50
|
-
if (rows[0] && rows[0].cells.length > 0) {
|
|
51
|
-
prepareRow(rows[0])
|
|
52
|
-
const last = rows[0].cells[rows[0].cells.length - 1].column
|
|
53
|
-
totalWidth = last.totalLeft + last.totalWidth
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const gtp = getTableProps()
|
|
57
|
-
const tableStyle =
|
|
58
|
-
totalWidth > 0
|
|
59
|
-
? { width: '100%', minWidth: totalWidth + 'px' }
|
|
60
|
-
: { width: '100%' }
|
|
61
|
-
|
|
62
|
-
const tableProps = {
|
|
63
|
-
...gtp,
|
|
64
|
-
style: tableStyle,
|
|
65
|
-
className: classNames(
|
|
66
|
-
'gm-table-x-table',
|
|
67
|
-
'gm-table-x-table-two-level',
|
|
68
|
-
gtp.className
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const gtbp = getTableBodyProps()
|
|
73
|
-
const tableBodyProps = {
|
|
74
|
-
...gtbp,
|
|
75
|
-
className: 'gm-table-x-tbody'
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const handleScroll = e => {
|
|
79
|
-
onScroll && onScroll(e)
|
|
80
|
-
afterScroll()
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const RenderRow = ({ index, style }) => {
|
|
84
|
-
const row = rows[index]
|
|
85
|
-
prepareRow(row)
|
|
86
|
-
|
|
87
|
-
return (
|
|
88
|
-
<Tr
|
|
89
|
-
key={row.index}
|
|
90
|
-
row={row}
|
|
91
|
-
SubComponent={SubComponent}
|
|
92
|
-
keyField={keyField}
|
|
93
|
-
style={style}
|
|
94
|
-
totalWidth={totalWidth}
|
|
95
|
-
isTrDisable={isTrDisable}
|
|
96
|
-
isTrHighlight={isTrHighlight}
|
|
97
|
-
/>
|
|
98
|
-
)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
<div
|
|
103
|
-
{...rest}
|
|
104
|
-
className={classNames(
|
|
105
|
-
'gm-table-x',
|
|
106
|
-
'gm-table-x-two-level',
|
|
107
|
-
{
|
|
108
|
-
'gm-table-x-empty': data.length === 0,
|
|
109
|
-
'gm-table-x-tiled': tiled
|
|
110
|
-
},
|
|
111
|
-
className
|
|
112
|
-
)}
|
|
113
|
-
onScroll={handleScroll}
|
|
114
|
-
style={{
|
|
115
|
-
// 设置 CSS 变量,用于 Mask 组件
|
|
116
|
-
'--gm-table-x-header-height': '92px',
|
|
117
|
-
...rest.style
|
|
118
|
-
}}
|
|
119
|
-
>
|
|
120
|
-
<table {...tableProps}>
|
|
121
|
-
<TwoLevelTHead
|
|
122
|
-
headerGroups={headerGroups}
|
|
123
|
-
firstLevelHeaders={firstLevelHeaders}
|
|
124
|
-
totalWidth={totalWidth}
|
|
125
|
-
/>
|
|
126
|
-
<tbody {...tableBodyProps}>
|
|
127
|
-
{_.map(rows, row =>
|
|
128
|
-
RenderRow({
|
|
129
|
-
index: row.index,
|
|
130
|
-
style: {}
|
|
131
|
-
})
|
|
132
|
-
)}
|
|
133
|
-
</tbody>
|
|
134
|
-
</table>
|
|
135
|
-
{loading && <Loading />}
|
|
136
|
-
{!loading && data.length === 0 && <Empty />}
|
|
137
|
-
</div>
|
|
138
|
-
)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
TwoLevelTableX.propTypes = {
|
|
142
|
-
columns: PropTypes.array.isRequired,
|
|
143
|
-
data: PropTypes.array.isRequired,
|
|
144
|
-
loading: PropTypes.bool,
|
|
145
|
-
SubComponent: PropTypes.func,
|
|
146
|
-
keyField: PropTypes.string,
|
|
147
|
-
tiled: PropTypes.bool,
|
|
148
|
-
isTrDisable: PropTypes.func,
|
|
149
|
-
isTrHighlight: PropTypes.func,
|
|
150
|
-
onScroll: PropTypes.func,
|
|
151
|
-
className: PropTypes.string,
|
|
152
|
-
style: PropTypes.object
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
TwoLevelTableX.defaultProps = {
|
|
156
|
-
keyField: 'value',
|
|
157
|
-
tiled: false,
|
|
158
|
-
isTrDisable: () => false,
|
|
159
|
-
isTrHighlight: () => false
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export default TwoLevelTableX
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import classNames from 'classnames'
|
|
2
|
-
import { getColumnStyle } from '../../util'
|
|
3
|
-
import PropTypes from 'prop-types'
|
|
4
|
-
import React from 'react'
|
|
5
|
-
|
|
6
|
-
// cell.render('Cell') 是一个react组件,如果这个组件return undefined,那就就会报错
|
|
7
|
-
// 这里是为了兼容 cell.render('Cell') 返回undefined的情况
|
|
8
|
-
class TdCatchErr extends React.Component {
|
|
9
|
-
state = {
|
|
10
|
-
hasError: false
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
static getDerivedStateFromError(error) {
|
|
14
|
-
// 捕获子组件树中的错误,返回新的 state
|
|
15
|
-
return { hasError: true }
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
componentDidCatch(error, errorInfo) {
|
|
19
|
-
// 静默处理错误,不打印日志
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
render() {
|
|
23
|
-
if (this.state.hasError) {
|
|
24
|
-
return null
|
|
25
|
-
}
|
|
26
|
-
const children = this.props.children
|
|
27
|
-
// 如果 children 是 undefined 或 null,返回 null(React 允许返回 null)
|
|
28
|
-
if (children === undefined || children === null) {
|
|
29
|
-
return null
|
|
30
|
-
}
|
|
31
|
-
return children
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// 创建一个包装组件,用于包装 Cell 组件的 type,确保它总是返回有效值
|
|
36
|
-
// 这个组件会包装原始的 Cell 组件,如果原始组件返回 undefined,就返回 null
|
|
37
|
-
const SafeCellWrapper = OriginalComponent => {
|
|
38
|
-
return function WrappedCell(props) {
|
|
39
|
-
try {
|
|
40
|
-
const result = OriginalComponent(props)
|
|
41
|
-
// 如果原始组件返回 undefined,返回 null
|
|
42
|
-
return result === undefined ? null : result
|
|
43
|
-
} catch (error) {
|
|
44
|
-
return null
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// 错误边界组件,专门用于捕获 Cell 组件返回 undefined 的错误
|
|
50
|
-
class SafeCellErrorBoundary extends React.Component {
|
|
51
|
-
state = {
|
|
52
|
-
hasError: false
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
static getDerivedStateFromError(error) {
|
|
56
|
-
return { hasError: true }
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
componentDidCatch(error, errorInfo) {
|
|
60
|
-
// 静默处理错误
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
render() {
|
|
64
|
-
if (this.state.hasError) {
|
|
65
|
-
return null
|
|
66
|
-
}
|
|
67
|
-
return this.props.children
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const Td = ({ cell, totalWidth }) => {
|
|
72
|
-
const cp = cell.getCellProps()
|
|
73
|
-
const { tdClassName } = cell.column
|
|
74
|
-
const tdProps = {
|
|
75
|
-
...cp,
|
|
76
|
-
className: classNames('gm-table-x-td', tdClassName, {
|
|
77
|
-
'gm-table-x-fixed-left': cell.column.fixed === 'left',
|
|
78
|
-
'gm-table-x-fixed-right': cell.column.fixed === 'right'
|
|
79
|
-
}),
|
|
80
|
-
style: {
|
|
81
|
-
...cp.style,
|
|
82
|
-
...getColumnStyle(cell.column)
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (cell.column.fixed === 'left') {
|
|
87
|
-
// 用到 fixed,可以利用 totalLeft
|
|
88
|
-
tdProps.style.left = cell.column.totalLeft
|
|
89
|
-
} else if (cell.column.fixed === 'right') {
|
|
90
|
-
tdProps.style.right =
|
|
91
|
-
totalWidth - cell.column.totalLeft - cell.column.totalWidth
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// 如果 Cell 返回 undefined,转换为 null(null 不会报错,undefined 会)
|
|
95
|
-
let cellContent = cell.render('Cell')
|
|
96
|
-
|
|
97
|
-
// cellContent 是一个 React 元素,但它的 type(组件)在渲染时可能返回 undefined
|
|
98
|
-
// 我们需要用一个包装组件来替换原始的 type,确保总是返回有效值
|
|
99
|
-
if (cellContent && React.isValidElement(cellContent)) {
|
|
100
|
-
// 创建一个新的 React 元素,用包装组件替换原始的 type
|
|
101
|
-
const OriginalComponent = cellContent.type
|
|
102
|
-
const WrappedComponent = SafeCellWrapper(OriginalComponent)
|
|
103
|
-
cellContent = React.createElement(
|
|
104
|
-
SafeCellErrorBoundary,
|
|
105
|
-
null,
|
|
106
|
-
React.createElement(WrappedComponent, cellContent.props)
|
|
107
|
-
)
|
|
108
|
-
} else if (cellContent === undefined || cellContent === null) {
|
|
109
|
-
cellContent = null
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return (
|
|
113
|
-
<td {...tdProps}>
|
|
114
|
-
<TdCatchErr>{cellContent}</TdCatchErr>
|
|
115
|
-
</td>
|
|
116
|
-
)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
Td.whyDidYouRender = true
|
|
120
|
-
|
|
121
|
-
Td.propTypes = {
|
|
122
|
-
cell: PropTypes.object.isRequired,
|
|
123
|
-
totalWidth: PropTypes.number.isRequired
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export default React.memo(Td)
|
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
import PropTypes from 'prop-types'
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import classNames from 'classnames'
|
|
4
|
-
import Th from '../../base/th'
|
|
5
|
-
import { getColumnStyle, SortHeader } from '../../util'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* 二级表头组件
|
|
9
|
-
* 渲染两级表头:
|
|
10
|
-
* - 第一行:一级表头(有子列的显示一级表头,没有子列的占两行)
|
|
11
|
-
* - 第二行:二级表头(只有有子列的一级表头才显示)
|
|
12
|
-
*/
|
|
13
|
-
const TwoLevelTHead = ({ headerGroups, firstLevelHeaders, totalWidth }) => {
|
|
14
|
-
const hasNestedHeaders = headerGroups.length > 1
|
|
15
|
-
const firstLevelGroup = hasNestedHeaders ? headerGroups[0] : null
|
|
16
|
-
const secondLevelGroup = hasNestedHeaders ? headerGroups[1] : headerGroups[0]
|
|
17
|
-
|
|
18
|
-
// 构建第一级表头的渲染信息
|
|
19
|
-
const firstLevelCells = []
|
|
20
|
-
let secondLevelIndex = 0 // 在第二级表头中的索引
|
|
21
|
-
|
|
22
|
-
firstLevelHeaders.forEach((firstLevelHeader, idx) => {
|
|
23
|
-
if (firstLevelHeader.hasSubColumns) {
|
|
24
|
-
// 有子列:从第二级表头中获取对应的列
|
|
25
|
-
const subColumnCount = firstLevelHeader.subColumnCount
|
|
26
|
-
const subColumns = secondLevelGroup.headers.slice(
|
|
27
|
-
secondLevelIndex,
|
|
28
|
-
secondLevelIndex + subColumnCount
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
// 获取对应的第一级分组列对象
|
|
32
|
-
const groupColumn = firstLevelGroup
|
|
33
|
-
? firstLevelGroup.headers.find(col => {
|
|
34
|
-
// 通过 id 或 Header 匹配
|
|
35
|
-
return (
|
|
36
|
-
col.id === firstLevelHeader.id ||
|
|
37
|
-
(col.columns && col.columns.length === subColumnCount)
|
|
38
|
-
)
|
|
39
|
-
}) || firstLevelGroup.headers[idx]
|
|
40
|
-
: null
|
|
41
|
-
|
|
42
|
-
// 计算分组列的总宽度(所有子列的宽度之和)
|
|
43
|
-
let totalSubWidth = 0
|
|
44
|
-
subColumns.forEach(subCol => {
|
|
45
|
-
const style = getColumnStyle(subCol)
|
|
46
|
-
const width = parseFloat(style.width) || subCol.totalWidth || 0
|
|
47
|
-
totalSubWidth += width
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
firstLevelCells.push({
|
|
51
|
-
type: 'group',
|
|
52
|
-
header: firstLevelHeader,
|
|
53
|
-
colSpan: subColumnCount,
|
|
54
|
-
subColumns: subColumns,
|
|
55
|
-
totalWidth: totalSubWidth, // 添加总宽度
|
|
56
|
-
column: groupColumn,
|
|
57
|
-
secondLevelStartIndex: secondLevelIndex
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
secondLevelIndex += subColumnCount
|
|
61
|
-
} else {
|
|
62
|
-
let singleColumn = null
|
|
63
|
-
|
|
64
|
-
// 优先从 firstLevelGroup 中查找单列(用于 getHeaderProps 等)
|
|
65
|
-
if (firstLevelGroup) {
|
|
66
|
-
singleColumn = firstLevelGroup.headers.find(col => {
|
|
67
|
-
// 通过 id 或 accessor 匹配
|
|
68
|
-
const matchId = col.id === firstLevelHeader.id
|
|
69
|
-
const matchAccessor =
|
|
70
|
-
col.accessor === firstLevelHeader.accessor ||
|
|
71
|
-
(typeof col.accessor === 'function' &&
|
|
72
|
-
typeof firstLevelHeader.accessor === 'function' &&
|
|
73
|
-
col.accessor.toString() === firstLevelHeader.accessor.toString())
|
|
74
|
-
return matchId || matchAccessor
|
|
75
|
-
})
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// 如果 firstLevelGroup 中没找到,从 secondLevelGroup 获取
|
|
79
|
-
if (!singleColumn) {
|
|
80
|
-
singleColumn = secondLevelGroup.headers[secondLevelIndex]
|
|
81
|
-
}
|
|
82
|
-
firstLevelCells.push({
|
|
83
|
-
type: 'single',
|
|
84
|
-
header: firstLevelHeader,
|
|
85
|
-
colSpan: 1,
|
|
86
|
-
rowSpan: 2, // 明确设置为 2,占两行
|
|
87
|
-
column: singleColumn,
|
|
88
|
-
secondLevelIndex: secondLevelIndex
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
secondLevelIndex += 1
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
return (
|
|
96
|
-
<thead className='gm-table-x-thead gm-table-x-thead-two-level'>
|
|
97
|
-
{/* 第一级表头行 */}
|
|
98
|
-
<tr className='gm-table-x-tr gm-table-x-tr-first-level'>
|
|
99
|
-
{firstLevelCells.map((cell, idx) => {
|
|
100
|
-
const { column, header, colSpan, rowSpan, type } = cell
|
|
101
|
-
|
|
102
|
-
// 对于分组列,使用分组列对象;对于单列,使用单列对象
|
|
103
|
-
const headerColumn = column
|
|
104
|
-
const hp = headerColumn ? headerColumn.getHeaderProps() : {}
|
|
105
|
-
const { thClassName, style } = headerColumn || {}
|
|
106
|
-
|
|
107
|
-
// 计算样式
|
|
108
|
-
let cellStyle = {
|
|
109
|
-
...hp.style,
|
|
110
|
-
...style
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const fixed =
|
|
114
|
-
type === 'group'
|
|
115
|
-
? header.fixed // 分组列直接使用 header.fixed,不 fallback 到 headerColumn
|
|
116
|
-
: headerColumn?.fixed !== undefined
|
|
117
|
-
? headerColumn.fixed
|
|
118
|
-
: header.fixed
|
|
119
|
-
|
|
120
|
-
// 判断是否是最后一列(通过检查是否是 firstLevelCells 的最后一个元素)
|
|
121
|
-
const isLastCell = idx === firstLevelCells.length - 1
|
|
122
|
-
|
|
123
|
-
if (type === 'group') {
|
|
124
|
-
const { flex, width, minWidth, maxWidth, ...restStyle } = cellStyle
|
|
125
|
-
cellStyle = restStyle
|
|
126
|
-
} else if (headerColumn) {
|
|
127
|
-
const columnStyle = getColumnStyle(headerColumn)
|
|
128
|
-
const { flex, ...restColumnStyle } = columnStyle
|
|
129
|
-
cellStyle = {
|
|
130
|
-
...cellStyle,
|
|
131
|
-
...restColumnStyle // 只使用 width 和 maxWidth,不使用 flex
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 如果有固定列,确保宽度不被压缩
|
|
135
|
-
if (fixed === 'left' || fixed === 'right') {
|
|
136
|
-
const width = restColumnStyle.width
|
|
137
|
-
if (width) {
|
|
138
|
-
cellStyle.minWidth = width
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if (fixed === 'left') {
|
|
143
|
-
if (
|
|
144
|
-
type === 'group' &&
|
|
145
|
-
cell.subColumns &&
|
|
146
|
-
cell.subColumns.length > 0
|
|
147
|
-
) {
|
|
148
|
-
cellStyle.left = cell.subColumns[0].totalLeft
|
|
149
|
-
} else if (headerColumn) {
|
|
150
|
-
cellStyle.left = headerColumn.totalLeft
|
|
151
|
-
}
|
|
152
|
-
} else if (fixed === 'right') {
|
|
153
|
-
// 对于分组列,使用最后一个子列的位置
|
|
154
|
-
if (
|
|
155
|
-
type === 'group' &&
|
|
156
|
-
cell.subColumns &&
|
|
157
|
-
cell.subColumns.length > 0
|
|
158
|
-
) {
|
|
159
|
-
const lastSubCol = cell.subColumns[cell.subColumns.length - 1]
|
|
160
|
-
// 如果是最后一列,直接设置 right: 0
|
|
161
|
-
if (isLastCell) {
|
|
162
|
-
cellStyle.right = 0
|
|
163
|
-
} else {
|
|
164
|
-
cellStyle.right =
|
|
165
|
-
totalWidth - lastSubCol.totalLeft - lastSubCol.totalWidth
|
|
166
|
-
}
|
|
167
|
-
} else if (headerColumn) {
|
|
168
|
-
// 如果是最后一列,直接设置 right: 0
|
|
169
|
-
if (isLastCell) {
|
|
170
|
-
cellStyle.right = 0
|
|
171
|
-
} else {
|
|
172
|
-
cellStyle.right =
|
|
173
|
-
totalWidth - headerColumn.totalLeft - headerColumn.totalWidth
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
const {
|
|
178
|
-
colSpan: hpColSpan,
|
|
179
|
-
rowSpan: hpRowSpan,
|
|
180
|
-
key: hpKey,
|
|
181
|
-
...restHp
|
|
182
|
-
} = hp || {}
|
|
183
|
-
const finalRowSpan =
|
|
184
|
-
rowSpan !== undefined ? Number(rowSpan) : undefined
|
|
185
|
-
const finalColSpan =
|
|
186
|
-
colSpan !== undefined
|
|
187
|
-
? Number(colSpan)
|
|
188
|
-
: hpColSpan !== undefined
|
|
189
|
-
? Number(hpColSpan)
|
|
190
|
-
: undefined
|
|
191
|
-
|
|
192
|
-
const thProps = {
|
|
193
|
-
...restHp,
|
|
194
|
-
...(finalColSpan !== undefined && { colSpan: finalColSpan }),
|
|
195
|
-
...(finalRowSpan !== undefined && { rowSpan: finalRowSpan }), // 只有定义了才添加
|
|
196
|
-
className: classNames(
|
|
197
|
-
'gm-table-x-th',
|
|
198
|
-
'gm-table-x-th-first-level',
|
|
199
|
-
hp?.className,
|
|
200
|
-
thClassName,
|
|
201
|
-
{
|
|
202
|
-
'gm-table-x-fixed-left': fixed === 'left',
|
|
203
|
-
'gm-table-x-fixed-right': fixed === 'right'
|
|
204
|
-
}
|
|
205
|
-
),
|
|
206
|
-
style: cellStyle
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// 生成唯一的 key:结合 id/accessor 和索引,确保即使 id/accessor 相同,key 也是唯一的
|
|
210
|
-
// 如果有 hpKey,也加上索引以确保唯一性(因为 hpKey 可能也会重复)
|
|
211
|
-
const baseKey = hpKey || header.id || header.accessor
|
|
212
|
-
const headerKey = baseKey ? `${baseKey}-${idx}` : `header-${idx}`
|
|
213
|
-
return (
|
|
214
|
-
<th key={headerKey} {...thProps}>
|
|
215
|
-
{typeof header.Header === 'function' ? (
|
|
216
|
-
<header.Header />
|
|
217
|
-
) : (
|
|
218
|
-
header.Header
|
|
219
|
-
)}
|
|
220
|
-
{headerColumn?.canSort && (
|
|
221
|
-
<SortHeader
|
|
222
|
-
{...headerColumn.getSortByToggleProps()}
|
|
223
|
-
type={
|
|
224
|
-
headerColumn.isSorted
|
|
225
|
-
? headerColumn.isSortedDesc
|
|
226
|
-
? 'desc'
|
|
227
|
-
: 'asc'
|
|
228
|
-
: null
|
|
229
|
-
}
|
|
230
|
-
/>
|
|
231
|
-
)}
|
|
232
|
-
</th>
|
|
233
|
-
)
|
|
234
|
-
})}
|
|
235
|
-
</tr>
|
|
236
|
-
|
|
237
|
-
{/* 第二级表头行(只有有子列的情况才显示) */}
|
|
238
|
-
{hasNestedHeaders && secondLevelGroup && (
|
|
239
|
-
<tr className='gm-table-x-tr gm-table-x-tr-second-level'>
|
|
240
|
-
{secondLevelGroup.headers.map((column, idx) => {
|
|
241
|
-
// 找到这个列对应的第一级表头
|
|
242
|
-
const correspondingFirstLevel = firstLevelCells.find(cell => {
|
|
243
|
-
if (cell.type === 'single') {
|
|
244
|
-
return cell.secondLevelIndex === idx
|
|
245
|
-
} else if (cell.type === 'group') {
|
|
246
|
-
return (
|
|
247
|
-
idx >= cell.secondLevelStartIndex &&
|
|
248
|
-
idx < cell.secondLevelStartIndex + cell.colSpan
|
|
249
|
-
)
|
|
250
|
-
}
|
|
251
|
-
return false
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
if (
|
|
255
|
-
correspondingFirstLevel &&
|
|
256
|
-
correspondingFirstLevel.type === 'single'
|
|
257
|
-
) {
|
|
258
|
-
return null
|
|
259
|
-
}
|
|
260
|
-
// 生成唯一的 key:结合 id/accessor 和索引,确保即使 id/accessor 相同,key 也是唯一的
|
|
261
|
-
const columnKey = column.id
|
|
262
|
-
? `${column.id}-${idx}`
|
|
263
|
-
: column.accessor
|
|
264
|
-
? `${column.accessor}-${idx}`
|
|
265
|
-
: `col-${idx}`
|
|
266
|
-
return (
|
|
267
|
-
<Th
|
|
268
|
-
key={columnKey}
|
|
269
|
-
column={{
|
|
270
|
-
...column,
|
|
271
|
-
// column.fixed 已经在 transformColumnsForTwoLevel 中正确设置
|
|
272
|
-
thClassName: classNames(
|
|
273
|
-
column.thClassName,
|
|
274
|
-
'gm-table-x-th-second-level'
|
|
275
|
-
)
|
|
276
|
-
}}
|
|
277
|
-
totalWidth={totalWidth}
|
|
278
|
-
/>
|
|
279
|
-
)
|
|
280
|
-
})}
|
|
281
|
-
</tr>
|
|
282
|
-
)}
|
|
283
|
-
</thead>
|
|
284
|
-
)
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
TwoLevelTHead.propTypes = {
|
|
288
|
-
headerGroups: PropTypes.array.isRequired,
|
|
289
|
-
firstLevelHeaders: PropTypes.array.isRequired,
|
|
290
|
-
totalWidth: PropTypes.number.isRequired
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
export default React.memo(TwoLevelTHead)
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import classNames from 'classnames'
|
|
2
|
-
import PropTypes from 'prop-types'
|
|
3
|
-
import React from 'react'
|
|
4
|
-
import Td from './td'
|
|
5
|
-
|
|
6
|
-
const Tr = ({
|
|
7
|
-
row,
|
|
8
|
-
SubComponent,
|
|
9
|
-
keyField,
|
|
10
|
-
style,
|
|
11
|
-
totalWidth,
|
|
12
|
-
isTrDisable,
|
|
13
|
-
isTrHighlight
|
|
14
|
-
}) => {
|
|
15
|
-
const props = {
|
|
16
|
-
...row.getRowProps(),
|
|
17
|
-
style,
|
|
18
|
-
className: classNames('gm-table-x-tr', {
|
|
19
|
-
'gm-table-x-tr-disable': isTrDisable(row.original, row.index),
|
|
20
|
-
'gm-table-x-tr-highlight': isTrHighlight(row.original, row.index),
|
|
21
|
-
'gm-table-x-tr-odd': row.index % 2 === 0,
|
|
22
|
-
'gm-table-x-tr-even': row.index % 2 !== 0
|
|
23
|
-
})
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// 目前视为了 sortable 用。值可能是 undefined,keyField 没作用的情况
|
|
27
|
-
const dataId = row.original[keyField]
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<>
|
|
31
|
-
<tr data-id={dataId} {...props}>
|
|
32
|
-
{row.cells.map((cell, cellIndex) => (
|
|
33
|
-
<Td key={cellIndex} cell={cell} totalWidth={totalWidth} />
|
|
34
|
-
))}
|
|
35
|
-
</tr>
|
|
36
|
-
{SubComponent && SubComponent(row)}
|
|
37
|
-
</>
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
Tr.whyDidYouRender = true
|
|
42
|
-
|
|
43
|
-
Tr.propTypes = {
|
|
44
|
-
row: PropTypes.object.isRequired,
|
|
45
|
-
SubComponent: PropTypes.func,
|
|
46
|
-
keyField: PropTypes.string.isRequired,
|
|
47
|
-
style: PropTypes.object.isRequired,
|
|
48
|
-
totalWidth: PropTypes.number.isRequired,
|
|
49
|
-
isTrDisable: PropTypes.func,
|
|
50
|
-
isTrHighlight: PropTypes.func
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export default React.memo(Tr)
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
TABLE_X_SELECT_ID,
|
|
3
|
-
TABLE_X_DIY_ID,
|
|
4
|
-
TABLE_X_EXPAND_ID
|
|
5
|
-
} from '../../util'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* 检查一级表头下的子列 fixed 状态是否一致
|
|
9
|
-
*/
|
|
10
|
-
function validateSubColumnsFixed(column) {
|
|
11
|
-
if (!column.subColumns || column.subColumns.length === 0) {
|
|
12
|
-
return { valid: true }
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const subColumns = column.subColumns
|
|
16
|
-
// 重要:二级分类的 fixed 应该与一级分类保持一致
|
|
17
|
-
// 所以验证时,所有子列应该使用一级表头的 fixed(column.fixed)
|
|
18
|
-
const expectedFixed = column.fixed !== undefined ? column.fixed : undefined
|
|
19
|
-
const fixedStates = subColumns.map(subCol => {
|
|
20
|
-
// 子列的 fixed 应该与一级表头一致,如果有不一致的,记录实际的 fixed 用于检查
|
|
21
|
-
return subCol.fixed !== undefined ? subCol.fixed : expectedFixed
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
const allSame = fixedStates.every(fixed => fixed === expectedFixed)
|
|
25
|
-
|
|
26
|
-
if (!allSame) {
|
|
27
|
-
return {
|
|
28
|
-
valid: false,
|
|
29
|
-
error:
|
|
30
|
-
`一级表头 "${column.Header || column.id}" 下的子列 fixed 状态不一致。` +
|
|
31
|
-
`为了避免滚动时表头宽度错位,请确保同一一级表头下的所有子列要么全部固定,要么全部不固定。`
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return { valid: true }
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 将带有 subColumns 的 columns 转换为 react-table 可以理解的嵌套结构
|
|
40
|
-
* 输入:[{ Header: '菜品信息', subColumns: [...] }]
|
|
41
|
-
* 输出:[{ Header: '菜品信息', columns: [...] }] (react-table 格式)
|
|
42
|
-
*/
|
|
43
|
-
export function transformColumnsForTwoLevel(columns) {
|
|
44
|
-
const transformedColumns = []
|
|
45
|
-
const firstLevelHeaders = []
|
|
46
|
-
const specialColumnIds = [
|
|
47
|
-
TABLE_X_SELECT_ID,
|
|
48
|
-
TABLE_X_DIY_ID,
|
|
49
|
-
TABLE_X_EXPAND_ID
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
// 分离普通列和特殊列(特殊列需要单独处理)
|
|
53
|
-
const normalColumns = []
|
|
54
|
-
const specialColumns = []
|
|
55
|
-
|
|
56
|
-
columns.forEach(column => {
|
|
57
|
-
if (specialColumnIds.includes(column.id)) {
|
|
58
|
-
specialColumns.push(column)
|
|
59
|
-
} else {
|
|
60
|
-
normalColumns.push(column)
|
|
61
|
-
}
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
// 处理普通列(保持原始顺序)
|
|
65
|
-
normalColumns.forEach((column, index) => {
|
|
66
|
-
if (column.subColumns && column.subColumns.length > 0) {
|
|
67
|
-
// 过滤可见的子列
|
|
68
|
-
const visibleSubCols = column.subColumns.filter(
|
|
69
|
-
subCol => subCol.show !== false
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
// 如果所有子列都被隐藏,跳过这个一级表头
|
|
73
|
-
if (visibleSubCols.length === 0) {
|
|
74
|
-
return
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// 验证 fixed 状态(使用可见的子列)
|
|
78
|
-
const validation = validateSubColumnsFixed({
|
|
79
|
-
...column,
|
|
80
|
-
subColumns: visibleSubCols
|
|
81
|
-
})
|
|
82
|
-
if (!validation.valid) {
|
|
83
|
-
console.error(
|
|
84
|
-
`[TwoLevelTableX] 配置错误 (第 ${index + 1} 个列):`,
|
|
85
|
-
validation.error
|
|
86
|
-
)
|
|
87
|
-
if (process.env.NODE_ENV === 'development') {
|
|
88
|
-
throw new Error(validation.error)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
firstLevelHeaders.push({
|
|
93
|
-
...column,
|
|
94
|
-
hasSubColumns: true,
|
|
95
|
-
subColumnCount: visibleSubCols.length // 使用可见子列的数量
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
// 确定统一的 fixed 状态
|
|
99
|
-
// 重要:只使用一级表头的 fixed 属性,二级分类的 fixed 应该与一级分类保持一致
|
|
100
|
-
const unifiedFixed = column.fixed !== undefined ? column.fixed : undefined
|
|
101
|
-
|
|
102
|
-
// 转换为 react-table 的嵌套结构(只使用可见的子列)
|
|
103
|
-
transformedColumns.push({
|
|
104
|
-
Header: column.Header,
|
|
105
|
-
id: column.id || `group_${transformedColumns.length}`,
|
|
106
|
-
fixed: unifiedFixed,
|
|
107
|
-
columns: visibleSubCols.map(subCol => {
|
|
108
|
-
// 移除子列自己的 fixed 属性,强制使用一级表头的 fixed
|
|
109
|
-
const { fixed: _subColFixed, ...subColWithoutFixed } = subCol
|
|
110
|
-
return {
|
|
111
|
-
...subColWithoutFixed,
|
|
112
|
-
fixed: unifiedFixed // 二级分类的 fixed 与一级分类保持一致
|
|
113
|
-
}
|
|
114
|
-
})
|
|
115
|
-
})
|
|
116
|
-
} else {
|
|
117
|
-
// 没有 subColumns,直接添加(会占两行)
|
|
118
|
-
firstLevelHeaders.push({
|
|
119
|
-
...column,
|
|
120
|
-
hasSubColumns: false,
|
|
121
|
-
subColumnCount: 1
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
transformedColumns.push(column)
|
|
125
|
-
}
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
// 处理特殊列(select、diy、expand)
|
|
129
|
-
// 特殊列需要根据它们的 fixed 属性来决定位置
|
|
130
|
-
// fixed: 'left' 的特殊列(如 diy)需要放在最前面
|
|
131
|
-
// 其他特殊列放在最后
|
|
132
|
-
const leftFixedSpecialColumns = specialColumns.filter(
|
|
133
|
-
col => col.fixed === 'left'
|
|
134
|
-
)
|
|
135
|
-
const otherSpecialColumns = specialColumns.filter(col => col.fixed !== 'left')
|
|
136
|
-
|
|
137
|
-
// 处理 fixed: 'left' 的特殊列,放在最前面(使用倒序遍历 + unshift 保持顺序)
|
|
138
|
-
for (let i = leftFixedSpecialColumns.length - 1; i >= 0; i--) {
|
|
139
|
-
const column = leftFixedSpecialColumns[i]
|
|
140
|
-
transformedColumns.unshift(column)
|
|
141
|
-
firstLevelHeaders.unshift({
|
|
142
|
-
...column,
|
|
143
|
-
hasSubColumns: false,
|
|
144
|
-
subColumnCount: 1,
|
|
145
|
-
isSpecialColumn: true
|
|
146
|
-
})
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// 处理其他特殊列,放在最后
|
|
150
|
-
// 注意:移除特殊列的 fixed: 'left' 属性,让它们按数组顺序显示在最后(不固定)
|
|
151
|
-
// 如果用户需要特殊列固定在右边,可以手动设置 fixed: 'right'
|
|
152
|
-
otherSpecialColumns.forEach(column => {
|
|
153
|
-
const { fixed, ...columnWithoutFixed } = column
|
|
154
|
-
const adjustedColumn = {
|
|
155
|
-
...columnWithoutFixed,
|
|
156
|
-
// 特殊列应该在最后,移除 fixed: 'left',让它们按数组顺序显示
|
|
157
|
-
// 如果需要固定在右边,可以改为 fixed: 'right'
|
|
158
|
-
fixed: fixed === 'left' ? undefined : fixed
|
|
159
|
-
}
|
|
160
|
-
transformedColumns.push(adjustedColumn)
|
|
161
|
-
firstLevelHeaders.push({
|
|
162
|
-
...adjustedColumn,
|
|
163
|
-
hasSubColumns: false,
|
|
164
|
-
subColumnCount: 1,
|
|
165
|
-
isSpecialColumn: true
|
|
166
|
-
})
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
return { transformedColumns, firstLevelHeaders }
|
|
170
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import React, { useMemo } from 'react'
|
|
2
|
-
import PropTypes from 'prop-types'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* 递归过滤嵌套结构中的 show === false 的列
|
|
6
|
-
* 如果一级分类的 show === false,递归设置所有子列的 show = false
|
|
7
|
-
*/
|
|
8
|
-
function filterColumnsByShow(columns) {
|
|
9
|
-
return columns
|
|
10
|
-
.map(column => {
|
|
11
|
-
// 如果有 subColumns,递归处理
|
|
12
|
-
if (column.subColumns && column.subColumns.length > 0) {
|
|
13
|
-
// 如果一级分类 show === false,递归设置所有子列 show = false
|
|
14
|
-
if (column.show === false) {
|
|
15
|
-
const filteredSubCols = column.subColumns.map(subCol => ({
|
|
16
|
-
...subCol,
|
|
17
|
-
show: false
|
|
18
|
-
}))
|
|
19
|
-
return {
|
|
20
|
-
...column,
|
|
21
|
-
subColumns: filteredSubCols
|
|
22
|
-
}
|
|
23
|
-
} else {
|
|
24
|
-
// 如果一级分类显示,递归过滤子列
|
|
25
|
-
const filteredSubCols = filterColumnsByShow(column.subColumns).filter(
|
|
26
|
-
subCol => subCol.show !== false
|
|
27
|
-
)
|
|
28
|
-
// 如果所有子列都被隐藏,整个一级分类也应该隐藏
|
|
29
|
-
if (filteredSubCols.length === 0) {
|
|
30
|
-
return null
|
|
31
|
-
}
|
|
32
|
-
return {
|
|
33
|
-
...column,
|
|
34
|
-
subColumns: filteredSubCols
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
} else {
|
|
38
|
-
// 没有 subColumns,直接返回(如果是 show === false,会在后面被过滤掉)
|
|
39
|
-
return column
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
.filter(column => column !== null && column.show !== false)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* 两级表头 HOC
|
|
47
|
-
* 功能:过滤嵌套结构中的 show === false 的列,然后传递给内部 Component 处理
|
|
48
|
-
*
|
|
49
|
-
* 使用方式
|
|
50
|
-
* const TwoLevelTable = diyTableXHOC(
|
|
51
|
-
* twoLevelHeaderTableXHOC(TwoLevelTableX)
|
|
52
|
-
* )
|
|
53
|
-
*/
|
|
54
|
-
function twoLevelHeaderTableXHOC(Component) {
|
|
55
|
-
const TwoLevelHeaderTableXWrapper = props => {
|
|
56
|
-
const { columns: originalColumns, ...rest } = props
|
|
57
|
-
const filteredColumns = useMemo(() => {
|
|
58
|
-
// 过滤掉 show === false 的列(递归处理嵌套结构)
|
|
59
|
-
// 返回的仍然是嵌套结构
|
|
60
|
-
return filterColumnsByShow(originalColumns)
|
|
61
|
-
}, [originalColumns])
|
|
62
|
-
|
|
63
|
-
return <Component {...rest} columns={filteredColumns} />
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
TwoLevelHeaderTableXWrapper.propTypes = {
|
|
67
|
-
columns: PropTypes.array.isRequired
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return TwoLevelHeaderTableXWrapper
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export default twoLevelHeaderTableXHOC
|