@gmfe/table-x 2.14.30-beta.0 → 2.14.30-beta.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmfe/table-x",
3
- "version": "2.14.30-beta.0",
3
+ "version": "2.14.30-beta.2",
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.29",
31
- "@gmfe/react": "^2.14.29",
30
+ "@gmfe/locales": "^2.14.17",
31
+ "@gmfe/react": "2.14.17",
32
32
  "classnames": "^2.2.5",
33
33
  "lodash": "^4.17.14",
34
34
  "prop-types": "^15.7.2",
package/src/base/td.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import classNames from 'classnames'
2
- import { getColumnStyle } from '../util'
2
+ import { getColumnStyle } from '@gmfe/table-x/src/util'
3
3
  import PropTypes from 'prop-types'
4
4
  import React from 'react'
5
5
 
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 './td'
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
- ...subCol,
17
+ ...subColWithoutFixed,
18
18
  parentKey: column.id || column.Header, // 父级标识
19
- parentHeader: column.Header, // 父级表头文本
20
- parentFixed: column.fixed // 父级 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: _f,
43
+ parentFixed: parentFixedValue,
49
44
  ...colWithoutParent
50
45
  } = col
51
- groupMap.get(col.parentKey).subCols.push(colWithoutParent)
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 '../../base/tr'
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, // 处理后的扁平 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 || cell.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
- // 如果是分组列,使用 colSpan 时不需要手动设置宽度(由浏览器自动计算)
142
- // 但我们需要移除 flex 相关的样式,因为使用 table-cell flex 不起作用
120
+ // 判断是否是最后一列(通过检查是否是 firstLevelCells 的最后一个元素)
121
+ const isLastCell = idx === firstLevelCells.length - 1
122
+
143
123
  if (type === 'group') {
144
- // 移除 flex 相关样式,只保留 width maxWidth(如果需要)
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
- cellStyle.right =
194
- totalWidth - lastSubCol.totalLeft - lastSubCol.totalWidth
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
- cellStyle.right =
197
- totalWidth - headerColumn.totalLeft - headerColumn.totalWidth
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
- return subCol.fixed !== undefined ? subCol.fixed : column.fixed
20
+ // 子列的 fixed 应该与一级表头一致,如果有不一致的,记录实际的 fixed 用于检查
21
+ return subCol.fixed !== undefined ? subCol.fixed : expectedFixed
18
22
  })
19
23
 
20
- const firstFixed = fixedStates[0]
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
- columns.forEach((column, index) => {
50
- // 特殊列(由其他 HOC 添加)直接添加,不进行转换
52
+ // 分离普通列和特殊列
53
+ const normalColumns = []
54
+ const specialColumns = []
55
+
56
+ columns.forEach(column => {
51
57
  if (specialColumnIds.includes(column.id)) {
52
- transformedColumns.push(column)
53
- firstLevelHeaders.push({
54
- ...column,
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
- const unifiedFixed =
96
- visibleSubCols[0]?.fixed !== undefined
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
- ...subCol,
109
- fixed: subCol.fixed !== undefined ? subCol.fixed : unifiedFixed
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
- * 注意:内部 Component(TwoLevelTableX)只需要接收 columns prop(已扁平化,包含 parentKey、parentHeader、parentFixed)
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; // 使用表格布局以支持 rowspan
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: 1px solid #d8dee7 !important;
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
- &.gm-table-x-fixed-left {
133
- background: @gm-table-x-header-bg !important;
134
- border-right: 1px solid @gm-table-x-border-color !important;
135
- }
136
-
137
- &.gm-table-x-fixed-right {
138
- background: @gm-table-x-header-bg !important;
139
- border-left: 1px solid @gm-table-x-border-color !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;
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: 1px solid #d8dee7 !important;
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
- &.gm-table-x-fixed-left {
163
- background: @gm-table-x-header-bg !important;
164
- border-right: 1px solid @gm-table-x-border-color !important;
165
- }
166
- &.gm-table-x-fixed-right {
167
- background: @gm-table-x-header-bg !important;
168
- border-left: 1px solid @gm-table-x-border-color !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;
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: 1px solid @gm-table-x-border-color !important;
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: 1px solid @gm-table-x-border-color !important;
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 './batch_action_bar'
9
- import SortHeader from './sort_header'
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 './operation'
19
- import { EditButton, EditOperation } from './edit'
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>