@gmfe/table-x 2.14.30-beta.0 → 2.14.30-beta.1
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/package.json +1 -1
- package/src/base/td.js +1 -1
- package/src/base/tr.js +2 -2
- package/src/hoc/two_level_header_table_x/flatten_helper.js +4 -4
- package/src/hoc/two_level_header_table_x/rebuild_helper.js +15 -8
- package/src/hoc/two_level_header_table_x/table_x.js +3 -10
- package/src/hoc/two_level_header_table_x/td.js +60 -0
- package/src/hoc/two_level_header_table_x/thead.js +30 -69
- package/src/hoc/two_level_header_table_x/tr.js +53 -0
- package/src/hoc/two_level_header_table_x/transform_helper.js +48 -24
- package/src/hoc/two_level_header_table_x.js +1 -3
- package/src/index.less +95 -34
- package/src/util/index.js +8 -6
package/package.json
CHANGED
package/src/base/td.js
CHANGED
package/src/base/tr.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import classNames from 'classnames'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
3
|
import React from 'react'
|
|
4
|
-
import Td from '
|
|
4
|
+
import Td from '@gmfe/table-x/src/base/td'
|
|
5
5
|
|
|
6
6
|
const Tr = ({
|
|
7
7
|
row,
|
|
@@ -10,7 +10,7 @@ const Tr = ({
|
|
|
10
10
|
style,
|
|
11
11
|
totalWidth,
|
|
12
12
|
isTrDisable,
|
|
13
|
-
isTrHighlight
|
|
13
|
+
isTrHighlight
|
|
14
14
|
}) => {
|
|
15
15
|
const props = {
|
|
16
16
|
...row.getRowProps(),
|
|
@@ -11,13 +11,13 @@ export function flattenColumnsForSelectAndDiy(columns) {
|
|
|
11
11
|
const flattened = []
|
|
12
12
|
columns.forEach(column => {
|
|
13
13
|
if (column.subColumns && column.subColumns.length > 0) {
|
|
14
|
-
// 如果有 subColumns,将子列展开,并添加父级信息
|
|
15
14
|
column.subColumns.forEach(subCol => {
|
|
15
|
+
const { fixed: _subColFixed, ...subColWithoutFixed } = subCol
|
|
16
16
|
flattened.push({
|
|
17
|
-
...
|
|
17
|
+
...subColWithoutFixed,
|
|
18
18
|
parentKey: column.id || column.Header, // 父级标识
|
|
19
|
-
parentHeader: column.Header,
|
|
20
|
-
parentFixed: column.fixed
|
|
19
|
+
parentHeader: column.Header,
|
|
20
|
+
parentFixed: column.fixed
|
|
21
21
|
})
|
|
22
22
|
})
|
|
23
23
|
} else {
|
|
@@ -18,11 +18,6 @@ export function rebuildNestedColumnsFromFlat(flatColumns) {
|
|
|
18
18
|
TABLE_X_EXPAND_ID
|
|
19
19
|
]
|
|
20
20
|
|
|
21
|
-
// 特殊列(select、diy)放在最前面
|
|
22
|
-
const specialCols = flatColumns.filter(col =>
|
|
23
|
-
specialColumnIds.includes(col.id)
|
|
24
|
-
)
|
|
25
|
-
nested.push(...specialCols)
|
|
26
21
|
// 普通列(排除特殊列)
|
|
27
22
|
const normalCols = flatColumns.filter(
|
|
28
23
|
col => !specialColumnIds.includes(col.id)
|
|
@@ -45,14 +40,18 @@ export function rebuildNestedColumnsFromFlat(flatColumns) {
|
|
|
45
40
|
const {
|
|
46
41
|
parentKey: _p,
|
|
47
42
|
parentHeader: _h,
|
|
48
|
-
parentFixed:
|
|
43
|
+
parentFixed: parentFixedValue,
|
|
49
44
|
...colWithoutParent
|
|
50
45
|
} = col
|
|
51
|
-
|
|
46
|
+
const { fixed: _subColFixed, ...colWithoutFixed } = colWithoutParent
|
|
47
|
+
groupMap.get(col.parentKey).subCols.push({
|
|
48
|
+
...colWithoutFixed,
|
|
49
|
+
fixed: parentFixedValue
|
|
50
|
+
})
|
|
52
51
|
}
|
|
53
52
|
})
|
|
54
53
|
|
|
55
|
-
// 再次遍历:按 flatColumns
|
|
54
|
+
// 再次遍历:按 flatColumns 的顺序输出普通列
|
|
56
55
|
const processedGroups = new Set()
|
|
57
56
|
|
|
58
57
|
normalCols.forEach(col => {
|
|
@@ -81,5 +80,13 @@ export function rebuildNestedColumnsFromFlat(flatColumns) {
|
|
|
81
80
|
}
|
|
82
81
|
})
|
|
83
82
|
|
|
83
|
+
// 特殊列(select、diy、expand)必须放在最后
|
|
84
|
+
// 从 flatColumns 中提取特殊列,保持它们在 flatColumns 中的相对顺序
|
|
85
|
+
const specialCols = flatColumns.filter(col =>
|
|
86
|
+
specialColumnIds.includes(col.id)
|
|
87
|
+
)
|
|
88
|
+
// 确保特殊列在最后
|
|
89
|
+
nested.push(...specialCols)
|
|
90
|
+
|
|
84
91
|
return nested
|
|
85
92
|
}
|
|
@@ -5,7 +5,7 @@ import { Empty, Loading, afterScroll, __DEFAULT_COLUMN } from '../../util'
|
|
|
5
5
|
import classNames from 'classnames'
|
|
6
6
|
import _ from 'lodash'
|
|
7
7
|
import TwoLevelTHead from './thead'
|
|
8
|
-
import Tr from '
|
|
8
|
+
import Tr from './tr'
|
|
9
9
|
import { rebuildNestedColumnsFromFlat } from './rebuild_helper'
|
|
10
10
|
import { transformColumnsForTwoLevel } from './transform_helper'
|
|
11
11
|
|
|
@@ -23,7 +23,7 @@ const defaultColumn = __DEFAULT_COLUMN
|
|
|
23
23
|
* 3. 使用 TwoLevelTHead 渲染两级表头
|
|
24
24
|
*/
|
|
25
25
|
const TwoLevelTableX = ({
|
|
26
|
-
columns,
|
|
26
|
+
columns,
|
|
27
27
|
data,
|
|
28
28
|
loading,
|
|
29
29
|
SubComponent,
|
|
@@ -35,22 +35,18 @@ const TwoLevelTableX = ({
|
|
|
35
35
|
isTrHighlight = () => false,
|
|
36
36
|
...rest
|
|
37
37
|
}) => {
|
|
38
|
-
// 步骤1: 根据扁平化的 columns 重建嵌套结构
|
|
39
38
|
const rebuiltColumns = useMemo(() => {
|
|
40
39
|
return rebuildNestedColumnsFromFlat(columns)
|
|
41
40
|
}, [columns])
|
|
42
41
|
|
|
43
|
-
// 步骤2: 转换为 react-table 格式
|
|
44
42
|
const { transformedColumns, firstLevelHeaders } = useMemo(() => {
|
|
45
43
|
return transformColumnsForTwoLevel(rebuiltColumns)
|
|
46
44
|
}, [rebuiltColumns])
|
|
47
45
|
|
|
48
|
-
// 步骤3: 过滤隐藏的列(与原 TableX 逻辑一致)
|
|
49
46
|
const visibleColumns = useMemo(() => {
|
|
50
47
|
return transformedColumns.filter(c => c.show !== false)
|
|
51
48
|
}, [transformedColumns])
|
|
52
49
|
|
|
53
|
-
// 步骤4: 使用 react-table 处理列和数据(与原 TableX 逻辑一致)
|
|
54
50
|
const {
|
|
55
51
|
getTableProps,
|
|
56
52
|
headerGroups,
|
|
@@ -63,7 +59,6 @@ const TwoLevelTableX = ({
|
|
|
63
59
|
defaultColumn
|
|
64
60
|
})
|
|
65
61
|
|
|
66
|
-
// 步骤5: 计算总宽度(与原 TableX 逻辑完全一致)
|
|
67
62
|
let totalWidth = 0
|
|
68
63
|
if (rows[0] && rows[0].cells.length > 0) {
|
|
69
64
|
prepareRow(rows[0])
|
|
@@ -71,8 +66,6 @@ const TwoLevelTableX = ({
|
|
|
71
66
|
totalWidth = last.totalLeft + last.totalWidth
|
|
72
67
|
}
|
|
73
68
|
|
|
74
|
-
// 步骤6: 准备 table props
|
|
75
|
-
// 注意:对于 display: table,minWidth 可能不会自动扩展,需要同时设置 width: 100% 来撑满容器
|
|
76
69
|
const gtp = getTableProps()
|
|
77
70
|
const tableStyle =
|
|
78
71
|
totalWidth > 0
|
|
@@ -146,7 +139,7 @@ const TwoLevelTableX = ({
|
|
|
146
139
|
)}
|
|
147
140
|
</tbody>
|
|
148
141
|
</table>
|
|
149
|
-
{loading && <Loading />}
|
|
142
|
+
{loading && <Loading style={{ marginTop: '92px' }} />}
|
|
150
143
|
{!loading && data.length === 0 && <Empty />}
|
|
151
144
|
</div>
|
|
152
145
|
)
|
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
componentDidCatch(error, errorInfo) {
|
|
10
|
+
console.warn(error)
|
|
11
|
+
console.warn(errorInfo.componentStack)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
render() {
|
|
15
|
+
return this.props.children
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const Td = ({ cell, totalWidth }) => {
|
|
20
|
+
const cp = cell.getCellProps()
|
|
21
|
+
const { tdClassName } = cell.column
|
|
22
|
+
const tdProps = {
|
|
23
|
+
...cp,
|
|
24
|
+
className: classNames('gm-table-x-td', tdClassName, {
|
|
25
|
+
'gm-table-x-fixed-left': cell.column.fixed === 'left',
|
|
26
|
+
'gm-table-x-fixed-right': cell.column.fixed === 'right'
|
|
27
|
+
}),
|
|
28
|
+
style: {
|
|
29
|
+
...cp.style,
|
|
30
|
+
...getColumnStyle(cell.column)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (cell.column.fixed === 'left') {
|
|
35
|
+
// 用到 fixed,可以利用 totalLeft
|
|
36
|
+
tdProps.style.left = cell.column.totalLeft
|
|
37
|
+
} else if (cell.column.fixed === 'right') {
|
|
38
|
+
tdProps.style.right =
|
|
39
|
+
totalWidth - cell.column.totalLeft - cell.column.totalWidth
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 如果 Cell 返回 undefined,转换为 null(null 不会报错,undefined 会)
|
|
43
|
+
const cellContent = cell.render('Cell')
|
|
44
|
+
const safeCellContent = cellContent === undefined ? null : cellContent
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<td {...tdProps}>
|
|
48
|
+
<TdCatchErr>{safeCellContent}</TdCatchErr>
|
|
49
|
+
</td>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Td.whyDidYouRender = true
|
|
54
|
+
|
|
55
|
+
Td.propTypes = {
|
|
56
|
+
cell: PropTypes.object.isRequired,
|
|
57
|
+
totalWidth: PropTypes.number.isRequired
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default React.memo(Td)
|
|
@@ -11,12 +11,6 @@ import { getColumnStyle, SortHeader } from '../../util'
|
|
|
11
11
|
* - 第二行:二级表头(只有有子列的一级表头才显示)
|
|
12
12
|
*/
|
|
13
13
|
const TwoLevelTHead = ({ headerGroups, firstLevelHeaders, totalWidth }) => {
|
|
14
|
-
// react-table 的 headerGroups 结构:
|
|
15
|
-
// - 当 columns 有嵌套结构时:headerGroups[0] 是第一级(分组),headerGroups[1] 是第二级(实际列)
|
|
16
|
-
// - 当 columns 没有嵌套时:headerGroups 只有一行,就是实际列
|
|
17
|
-
// 重要:当混合嵌套列和单列时,headerGroups[0] 只包含有 columns 的分组列,不包含单列
|
|
18
|
-
// 单列只会在 headerGroups[1] 中出现
|
|
19
|
-
|
|
20
14
|
const hasNestedHeaders = headerGroups.length > 1
|
|
21
15
|
const firstLevelGroup = hasNestedHeaders ? headerGroups[0] : null
|
|
22
16
|
const secondLevelGroup = hasNestedHeaders ? headerGroups[1] : headerGroups[0]
|
|
@@ -34,14 +28,6 @@ const TwoLevelTHead = ({ headerGroups, firstLevelHeaders, totalWidth }) => {
|
|
|
34
28
|
secondLevelIndex + subColumnCount
|
|
35
29
|
)
|
|
36
30
|
|
|
37
|
-
// 计算总宽度(所有子列的宽度之和)
|
|
38
|
-
let totalSubWidth = 0
|
|
39
|
-
subColumns.forEach(subCol => {
|
|
40
|
-
const style = getColumnStyle(subCol)
|
|
41
|
-
const width = parseFloat(style.width) || subCol.totalWidth || 0
|
|
42
|
-
totalSubWidth += width
|
|
43
|
-
})
|
|
44
|
-
|
|
45
31
|
// 获取对应的第一级分组列对象
|
|
46
32
|
const groupColumn = firstLevelGroup
|
|
47
33
|
? firstLevelGroup.headers.find(col => {
|
|
@@ -53,21 +39,26 @@ const TwoLevelTHead = ({ headerGroups, firstLevelHeaders, totalWidth }) => {
|
|
|
53
39
|
}) || firstLevelGroup.headers[idx]
|
|
54
40
|
: null
|
|
55
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
|
+
|
|
56
50
|
firstLevelCells.push({
|
|
57
51
|
type: 'group',
|
|
58
52
|
header: firstLevelHeader,
|
|
59
53
|
colSpan: subColumnCount,
|
|
60
54
|
subColumns: subColumns,
|
|
61
|
-
totalWidth: totalSubWidth,
|
|
55
|
+
totalWidth: totalSubWidth, // 添加总宽度
|
|
62
56
|
column: groupColumn,
|
|
63
57
|
secondLevelStartIndex: secondLevelIndex
|
|
64
58
|
})
|
|
65
59
|
|
|
66
60
|
secondLevelIndex += subColumnCount
|
|
67
61
|
} else {
|
|
68
|
-
// 没有子列:占两行(rowspan=2)
|
|
69
|
-
// 重要:当混合嵌套列和单列时,react-table 会将单列同时放在 firstLevelGroup 和 secondLevelGroup 中
|
|
70
|
-
// 我们需要从 firstLevelGroup 中获取单列的列对象(如果存在),否则从 secondLevelGroup 获取
|
|
71
62
|
let singleColumn = null
|
|
72
63
|
|
|
73
64
|
// 优先从 firstLevelGroup 中查找单列(用于 getHeaderProps 等)
|
|
@@ -106,17 +97,10 @@ const TwoLevelTHead = ({ headerGroups, firstLevelHeaders, totalWidth }) => {
|
|
|
106
97
|
{/* 第一级表头行 */}
|
|
107
98
|
<tr className='gm-table-x-tr gm-table-x-tr-first-level'>
|
|
108
99
|
{firstLevelCells.map((cell, idx) => {
|
|
109
|
-
const {
|
|
110
|
-
column,
|
|
111
|
-
header,
|
|
112
|
-
colSpan,
|
|
113
|
-
rowSpan,
|
|
114
|
-
type,
|
|
115
|
-
totalWidth: cellTotalWidth
|
|
116
|
-
} = cell
|
|
100
|
+
const { column, header, colSpan, rowSpan, type } = cell
|
|
117
101
|
|
|
118
102
|
// 对于分组列,使用分组列对象;对于单列,使用单列对象
|
|
119
|
-
const headerColumn = column
|
|
103
|
+
const headerColumn = column
|
|
120
104
|
const hp = headerColumn ? headerColumn.getHeaderProps() : {}
|
|
121
105
|
const { thClassName, style } = headerColumn || {}
|
|
122
106
|
|
|
@@ -126,11 +110,6 @@ const TwoLevelTHead = ({ headerGroups, firstLevelHeaders, totalWidth }) => {
|
|
|
126
110
|
...style
|
|
127
111
|
}
|
|
128
112
|
|
|
129
|
-
// 处理固定列(需要先定义,因为在设置宽度时需要用到)
|
|
130
|
-
// 重要:只有明确设置了 fixed 的列才会被固定
|
|
131
|
-
// 对于分组列,直接使用 header.fixed(一级表头的原始 fixed 状态)
|
|
132
|
-
// 对于单列,使用 headerColumn.fixed(react-table 处理后的状态)或 header.fixed
|
|
133
|
-
// 关键:不要从 headerColumn 推断 fixed 状态,因为 headerColumn 可能是 react-table 处理后的对象
|
|
134
113
|
const fixed =
|
|
135
114
|
type === 'group'
|
|
136
115
|
? header.fixed // 分组列直接使用 header.fixed,不 fallback 到 headerColumn
|
|
@@ -138,22 +117,13 @@ const TwoLevelTHead = ({ headerGroups, firstLevelHeaders, totalWidth }) => {
|
|
|
138
117
|
? headerColumn.fixed
|
|
139
118
|
: header.fixed
|
|
140
119
|
|
|
141
|
-
//
|
|
142
|
-
|
|
120
|
+
// 判断是否是最后一列(通过检查是否是 firstLevelCells 的最后一个元素)
|
|
121
|
+
const isLastCell = idx === firstLevelCells.length - 1
|
|
122
|
+
|
|
143
123
|
if (type === 'group') {
|
|
144
|
-
|
|
145
|
-
// 对于 table-layout: fixed,我们需要确保分组列的宽度正确设置
|
|
146
|
-
// colSpan 会自动处理宽度,但为了确保固定列的宽度不被压缩,我们显式设置宽度
|
|
147
|
-
const { flex, ...restStyle } = cellStyle
|
|
124
|
+
const { flex, width, minWidth, maxWidth, ...restStyle } = cellStyle
|
|
148
125
|
cellStyle = restStyle
|
|
149
|
-
|
|
150
|
-
// 如果有固定列,显式设置宽度以确保不被压缩
|
|
151
|
-
if (fixed === 'left' || fixed === 'right') {
|
|
152
|
-
cellStyle.width = `${cellTotalWidth}px`
|
|
153
|
-
cellStyle.minWidth = `${cellTotalWidth}px`
|
|
154
|
-
}
|
|
155
126
|
} else if (headerColumn) {
|
|
156
|
-
// 对于单列,也需要移除 flex,使用 width
|
|
157
127
|
const columnStyle = getColumnStyle(headerColumn)
|
|
158
128
|
const { flex, ...restColumnStyle } = columnStyle
|
|
159
129
|
cellStyle = {
|
|
@@ -169,10 +139,7 @@ const TwoLevelTHead = ({ headerGroups, firstLevelHeaders, totalWidth }) => {
|
|
|
169
139
|
}
|
|
170
140
|
}
|
|
171
141
|
}
|
|
172
|
-
// 只有当 fixed 明确为 'left' 或 'right' 时才设置 left/right 和添加 fixed 类
|
|
173
|
-
// 这样确保只有明确设置了 fixed 的列才会被固定
|
|
174
142
|
if (fixed === 'left') {
|
|
175
|
-
// 对于分组列,使用第一个子列的 totalLeft
|
|
176
143
|
if (
|
|
177
144
|
type === 'group' &&
|
|
178
145
|
cell.subColumns &&
|
|
@@ -190,25 +157,26 @@ const TwoLevelTHead = ({ headerGroups, firstLevelHeaders, totalWidth }) => {
|
|
|
190
157
|
cell.subColumns.length > 0
|
|
191
158
|
) {
|
|
192
159
|
const lastSubCol = cell.subColumns[cell.subColumns.length - 1]
|
|
193
|
-
|
|
194
|
-
|
|
160
|
+
// 如果是最后一列,直接设置 right: 0
|
|
161
|
+
if (isLastCell) {
|
|
162
|
+
cellStyle.right = 0
|
|
163
|
+
} else {
|
|
164
|
+
cellStyle.right =
|
|
165
|
+
totalWidth - lastSubCol.totalLeft - lastSubCol.totalWidth
|
|
166
|
+
}
|
|
195
167
|
} else if (headerColumn) {
|
|
196
|
-
|
|
197
|
-
|
|
168
|
+
// 如果是最后一列,直接设置 right: 0
|
|
169
|
+
if (isLastCell) {
|
|
170
|
+
cellStyle.right = 0
|
|
171
|
+
} else {
|
|
172
|
+
cellStyle.right =
|
|
173
|
+
totalWidth - headerColumn.totalLeft - headerColumn.totalWidth
|
|
174
|
+
}
|
|
198
175
|
}
|
|
199
176
|
}
|
|
200
|
-
// 注意:如果 fixed 是 undefined 或其他值,不应该设置 left/right,也不应该添加 fixed 类
|
|
201
|
-
|
|
202
|
-
// 确保我们的 colSpan 和 rowSpan 优先级最高,不会被 hp 中的值覆盖
|
|
203
177
|
const { colSpan: hpColSpan, rowSpan: hpRowSpan, ...restHp } = hp || {}
|
|
204
|
-
|
|
205
|
-
// 确保 rowSpan 和 colSpan 是数字类型(不是字符串)
|
|
206
178
|
const finalRowSpan =
|
|
207
|
-
rowSpan !== undefined
|
|
208
|
-
? Number(rowSpan)
|
|
209
|
-
: hpRowSpan !== undefined
|
|
210
|
-
? Number(hpRowSpan)
|
|
211
|
-
: undefined
|
|
179
|
+
rowSpan !== undefined ? Number(rowSpan) : undefined
|
|
212
180
|
const finalColSpan =
|
|
213
181
|
colSpan !== undefined
|
|
214
182
|
? Number(colSpan)
|
|
@@ -274,19 +242,12 @@ const TwoLevelTHead = ({ headerGroups, firstLevelHeaders, totalWidth }) => {
|
|
|
274
242
|
return false
|
|
275
243
|
})
|
|
276
244
|
|
|
277
|
-
// 如果是单列(rowspan=2),这一行不渲染(已经被第一行占用)
|
|
278
245
|
if (
|
|
279
246
|
correspondingFirstLevel &&
|
|
280
247
|
correspondingFirstLevel.type === 'single'
|
|
281
248
|
) {
|
|
282
249
|
return null
|
|
283
250
|
}
|
|
284
|
-
|
|
285
|
-
// 为第二级表头添加特殊类名,以便在 CSS 中覆盖样式
|
|
286
|
-
// 重要:二级表头的 fixed 状态应该从对应的一级表头继承
|
|
287
|
-
// 只有当对应的一级表头设置了 fixed 时,二级表头才应该固定
|
|
288
|
-
// 注意:column.fixed 已经在 transformColumnsForTwoLevel 中根据一级表头的 fixed 状态设置了
|
|
289
|
-
// 所以我们直接使用 column.fixed 即可,不需要再次从 firstLevelHeader 继承
|
|
290
251
|
return (
|
|
291
252
|
<Th
|
|
292
253
|
key={idx}
|
|
@@ -0,0 +1,53 @@
|
|
|
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)
|
|
@@ -13,12 +13,15 @@ function validateSubColumnsFixed(column) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const subColumns = column.subColumns
|
|
16
|
+
// 重要:二级分类的 fixed 应该与一级分类保持一致
|
|
17
|
+
// 所以验证时,所有子列应该使用一级表头的 fixed(column.fixed)
|
|
18
|
+
const expectedFixed = column.fixed !== undefined ? column.fixed : undefined
|
|
16
19
|
const fixedStates = subColumns.map(subCol => {
|
|
17
|
-
|
|
20
|
+
// 子列的 fixed 应该与一级表头一致,如果有不一致的,记录实际的 fixed 用于检查
|
|
21
|
+
return subCol.fixed !== undefined ? subCol.fixed : expectedFixed
|
|
18
22
|
})
|
|
19
23
|
|
|
20
|
-
const
|
|
21
|
-
const allSame = fixedStates.every(fixed => fixed === firstFixed)
|
|
24
|
+
const allSame = fixedStates.every(fixed => fixed === expectedFixed)
|
|
22
25
|
|
|
23
26
|
if (!allSame) {
|
|
24
27
|
return {
|
|
@@ -46,19 +49,20 @@ export function transformColumnsForTwoLevel(columns) {
|
|
|
46
49
|
TABLE_X_EXPAND_ID
|
|
47
50
|
]
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
// 分离普通列和特殊列
|
|
53
|
+
const normalColumns = []
|
|
54
|
+
const specialColumns = []
|
|
55
|
+
|
|
56
|
+
columns.forEach(column => {
|
|
51
57
|
if (specialColumnIds.includes(column.id)) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
hasSubColumns: false,
|
|
56
|
-
subColumnCount: 1,
|
|
57
|
-
isSpecialColumn: true
|
|
58
|
-
})
|
|
59
|
-
return
|
|
58
|
+
specialColumns.push(column)
|
|
59
|
+
} else {
|
|
60
|
+
normalColumns.push(column)
|
|
60
61
|
}
|
|
62
|
+
})
|
|
61
63
|
|
|
64
|
+
// 先处理普通列
|
|
65
|
+
normalColumns.forEach((column, index) => {
|
|
62
66
|
if (column.subColumns && column.subColumns.length > 0) {
|
|
63
67
|
// 过滤可见的子列
|
|
64
68
|
const visibleSubCols = column.subColumns.filter(
|
|
@@ -91,23 +95,23 @@ export function transformColumnsForTwoLevel(columns) {
|
|
|
91
95
|
subColumnCount: visibleSubCols.length // 使用可见子列的数量
|
|
92
96
|
})
|
|
93
97
|
|
|
94
|
-
// 确定统一的 fixed
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
? visibleSubCols[0].fixed
|
|
98
|
-
: column.fixed !== undefined
|
|
99
|
-
? column.fixed
|
|
100
|
-
: undefined
|
|
98
|
+
// 确定统一的 fixed 状态
|
|
99
|
+
// 重要:只使用一级表头的 fixed 属性,二级分类的 fixed 应该与一级分类保持一致
|
|
100
|
+
const unifiedFixed = column.fixed !== undefined ? column.fixed : undefined
|
|
101
101
|
|
|
102
102
|
// 转换为 react-table 的嵌套结构(只使用可见的子列)
|
|
103
103
|
transformedColumns.push({
|
|
104
104
|
Header: column.Header,
|
|
105
105
|
id: column.id || `group_${transformedColumns.length}`,
|
|
106
106
|
fixed: unifiedFixed,
|
|
107
|
-
columns: visibleSubCols.map(subCol =>
|
|
108
|
-
|
|
109
|
-
fixed:
|
|
110
|
-
|
|
107
|
+
columns: visibleSubCols.map(subCol => {
|
|
108
|
+
// 移除子列自己的 fixed 属性,强制使用一级表头的 fixed
|
|
109
|
+
const { fixed: _subColFixed, ...subColWithoutFixed } = subCol
|
|
110
|
+
return {
|
|
111
|
+
...subColWithoutFixed,
|
|
112
|
+
fixed: unifiedFixed // 二级分类的 fixed 与一级分类保持一致
|
|
113
|
+
}
|
|
114
|
+
})
|
|
111
115
|
})
|
|
112
116
|
} else {
|
|
113
117
|
// 没有 subColumns,直接添加(会占两行)
|
|
@@ -121,5 +125,25 @@ export function transformColumnsForTwoLevel(columns) {
|
|
|
121
125
|
}
|
|
122
126
|
})
|
|
123
127
|
|
|
128
|
+
// 最后处理特殊列(select、diy、expand)放在最后
|
|
129
|
+
// 注意:移除特殊列的 fixed 属性,让它们按数组顺序显示在最后(不固定)
|
|
130
|
+
// 如果用户需要特殊列固定在右边,可以手动设置 fixed: 'right'
|
|
131
|
+
specialColumns.forEach(column => {
|
|
132
|
+
const { fixed, ...columnWithoutFixed } = column
|
|
133
|
+
const adjustedColumn = {
|
|
134
|
+
...columnWithoutFixed,
|
|
135
|
+
// 特殊列应该在最后,移除 fixed: 'left',让它们按数组顺序显示
|
|
136
|
+
// 如果需要固定在右边,可以改为 fixed: 'right'
|
|
137
|
+
fixed: fixed === 'left' ? undefined : fixed
|
|
138
|
+
}
|
|
139
|
+
transformedColumns.push(adjustedColumn)
|
|
140
|
+
firstLevelHeaders.push({
|
|
141
|
+
...adjustedColumn,
|
|
142
|
+
hasSubColumns: false,
|
|
143
|
+
subColumnCount: 1,
|
|
144
|
+
isSpecialColumn: true
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
124
148
|
return { transformedColumns, firstLevelHeaders }
|
|
125
149
|
}
|
|
@@ -13,18 +13,16 @@ import { flattenColumnsForSelectAndDiy } from './two_level_header_table_x/flatte
|
|
|
13
13
|
* )
|
|
14
14
|
* )
|
|
15
15
|
*
|
|
16
|
-
*
|
|
16
|
+
* 注意:当存在一级表头相同的列时,在column配置中加id标识区分,避免混乱
|
|
17
17
|
*/
|
|
18
18
|
function twoLevelHeaderTableXHOC(Component) {
|
|
19
19
|
const TwoLevelHeaderTableXWrapper = props => {
|
|
20
20
|
const { columns: originalColumns, ...rest } = props
|
|
21
21
|
|
|
22
|
-
// 扁平化 columns(用于 select 和 diy 处理,每个 subColumn 会添加 parentKey、parentHeader、parentFixed)
|
|
23
22
|
const flattenedColumns = useMemo(() => {
|
|
24
23
|
return flattenColumnsForSelectAndDiy(originalColumns)
|
|
25
24
|
}, [originalColumns])
|
|
26
25
|
|
|
27
|
-
// 传递给内部 Component
|
|
28
26
|
return <Component {...rest} columns={flattenedColumns} />
|
|
29
27
|
}
|
|
30
28
|
|
package/src/index.less
CHANGED
|
@@ -21,28 +21,63 @@
|
|
|
21
21
|
|
|
22
22
|
// 当有两级表头时,表格需要使用表格布局来支持 rowspan
|
|
23
23
|
&.gm-table-x-table-two-level {
|
|
24
|
-
display: table !important;
|
|
25
|
-
// 固定列的宽度保护
|
|
26
|
-
.gm-table-x-th,
|
|
27
|
-
.gm-table-x-td {
|
|
28
|
-
&.gm-table-x-fixed-left,
|
|
29
|
-
&.gm-table-x-fixed-right {
|
|
30
|
-
box-sizing: border-box;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
24
|
+
display: table !important;
|
|
34
25
|
.gm-table-x-tbody {
|
|
35
26
|
display: table-row-group !important;
|
|
36
27
|
.gm-table-x-tr {
|
|
37
28
|
display: table-row !important;
|
|
29
|
+
height: 60px;
|
|
38
30
|
.gm-table-x-td {
|
|
39
31
|
display: table-cell !important;
|
|
40
|
-
// 移除 flex 相关样式
|
|
41
32
|
flex: none !important;
|
|
42
33
|
flex-grow: unset !important;
|
|
43
34
|
flex-shrink: unset !important;
|
|
44
35
|
flex-basis: unset !important;
|
|
45
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
|
+
}
|
|
46
81
|
}
|
|
47
82
|
}
|
|
48
83
|
}
|
|
@@ -120,23 +155,27 @@
|
|
|
120
155
|
.gm-table-x-th-first-level {
|
|
121
156
|
// 第一级表头样式
|
|
122
157
|
display: table-cell !important;
|
|
123
|
-
border:
|
|
124
|
-
border-bottom: 2px solid #d8dee7 !important;
|
|
158
|
+
border: none !important;
|
|
125
159
|
vertical-align: middle;
|
|
126
160
|
text-align: center;
|
|
127
161
|
padding: 8px;
|
|
128
162
|
flex: none !important;
|
|
129
163
|
// 确保固定列有背景色,这样边框才能正确显示
|
|
130
164
|
background: @gm-table-x-header-bg !important;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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;
|
|
140
179
|
}
|
|
141
180
|
}
|
|
142
181
|
}
|
|
@@ -146,8 +185,7 @@
|
|
|
146
185
|
.gm-table-x-th-second-level {
|
|
147
186
|
// 第二级表头样式
|
|
148
187
|
display: table-cell !important;
|
|
149
|
-
border:
|
|
150
|
-
border-top: none !important;
|
|
188
|
+
border: none !important;
|
|
151
189
|
vertical-align: middle;
|
|
152
190
|
text-align: center;
|
|
153
191
|
padding: 8px;
|
|
@@ -158,14 +196,20 @@
|
|
|
158
196
|
box-sizing: border-box;
|
|
159
197
|
// 确保固定列有背景色,这样边框才能正确显示
|
|
160
198
|
background: @gm-table-x-header-bg !important;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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;
|
|
169
213
|
}
|
|
170
214
|
}
|
|
171
215
|
}
|
|
@@ -302,13 +346,30 @@
|
|
|
302
346
|
position: sticky !important;
|
|
303
347
|
z-index: 1;
|
|
304
348
|
}
|
|
349
|
+
}
|
|
305
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
|
+
|
|
359
|
+
// tbody 单元格固定列使用 inset box-shadow
|
|
360
|
+
.gm-table-x-td {
|
|
306
361
|
&.gm-table-x-fixed-left {
|
|
307
|
-
border-right:
|
|
362
|
+
border-right: none !important;
|
|
363
|
+
// 使用 box-shadow 创建边框,确保在滚动时边框始终可见
|
|
364
|
+
// inset box-shadow 会创建内部阴影,不会随内容滚动
|
|
365
|
+
box-shadow: inset -1px 0 0 0 @gm-table-x-border-color !important;
|
|
308
366
|
}
|
|
309
367
|
|
|
310
368
|
&.gm-table-x-fixed-right {
|
|
311
|
-
border-left:
|
|
369
|
+
border-left: none !important;
|
|
370
|
+
// 使用 box-shadow 创建边框,确保在滚动时边框始终可见
|
|
371
|
+
// inset box-shadow 会创建内部阴影,不会随内容滚动
|
|
372
|
+
box-shadow: inset 1px 0 0 0 @gm-table-x-border-color !important;
|
|
312
373
|
}
|
|
313
374
|
}
|
|
314
375
|
}
|
package/src/util/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import PropTypes from 'prop-types'
|
|
|
5
5
|
import _ from 'lodash'
|
|
6
6
|
import SVGEmpty from '../../svg/empty.svg'
|
|
7
7
|
import { Flex, EVENT_TYPE } from '@gmfe/react'
|
|
8
|
-
import BatchActionBar from '
|
|
9
|
-
import SortHeader from '
|
|
8
|
+
import BatchActionBar from '@gmfe/table-x/src/util/batch_action_bar'
|
|
9
|
+
import SortHeader from '@gmfe/table-x/src/util/sort_header'
|
|
10
10
|
import {
|
|
11
11
|
OperationHeader,
|
|
12
12
|
OperationDelete,
|
|
@@ -15,8 +15,8 @@ import {
|
|
|
15
15
|
OperationCell,
|
|
16
16
|
OperationRowEdit,
|
|
17
17
|
OperationIconTip
|
|
18
|
-
} from '
|
|
19
|
-
import { EditButton, EditOperation } from '
|
|
18
|
+
} from '@gmfe/table-x/src/util/operation'
|
|
19
|
+
import { EditButton, EditOperation } from '@gmfe/table-x/src/util/edit'
|
|
20
20
|
|
|
21
21
|
const TABLE_X_SELECT_ID = 'table_x_select_id'
|
|
22
22
|
const TABLE_X_EXPAND_ID = 'table_x_expand_id'
|
|
@@ -83,12 +83,14 @@ const Empty = () => {
|
|
|
83
83
|
)
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
const Loading = () => {
|
|
86
|
+
const Loading = ({ style, ...rest }) => {
|
|
87
87
|
return (
|
|
88
88
|
<Mask
|
|
89
89
|
style={{
|
|
90
|
-
backgroundColor: 'rgba(255,255,255,0.8)'
|
|
90
|
+
backgroundColor: 'rgba(255,255,255,0.8)',
|
|
91
|
+
...style
|
|
91
92
|
}}
|
|
93
|
+
{...rest}
|
|
92
94
|
>
|
|
93
95
|
{getLocale('加载数据中...')}
|
|
94
96
|
</Mask>
|