@gmfe/table-x 2.14.30-alpha.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmfe/table-x",
3
- "version": "2.14.30-alpha.0",
3
+ "version": "2.14.30-beta.1",
4
4
  "description": "",
5
5
  "author": "liyatang <liyatang@qq.com>",
6
6
  "homepage": "https://github.com/gmfe/gmfe#readme",
@@ -27,9 +27,8 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@gm-common/tool": "^1.0.0",
30
- "@gmfe/business": "^2.14.30-alpha.0",
31
- "@gmfe/locales": "^2.14.30-alpha.0",
32
- "@gmfe/react": "^2.14.30-alpha.0",
30
+ "@gmfe/locales": "^2.14.29",
31
+ "@gmfe/react": "^2.14.29",
33
32
  "classnames": "^2.2.5",
34
33
  "lodash": "^4.17.14",
35
34
  "prop-types": "^15.7.2",
@@ -37,5 +36,5 @@
37
36
  "react-window": "^1.8.5",
38
37
  "sortablejs": "^1.10.1"
39
38
  },
40
- "gitHead": "f7da14010d37550f0a19949fe4a5b3d65301545a"
39
+ "gitHead": "8d291ad2d8b321f27918ac65cf201bd1eb8872a8"
41
40
  }
package/src/base/index.js CHANGED
@@ -6,7 +6,6 @@ import classNames from 'classnames'
6
6
  import _ from 'lodash'
7
7
  import THead from './thead'
8
8
  import Tr from './tr'
9
- import { StickyLayout } from '@gmfe/react'
10
9
 
11
10
  // 给定初始值,交由getColumnStyle控制。width逻辑保持跟react-table(v6)的用法一致。
12
11
  const defaultColumn = __DEFAULT_COLUMN
@@ -140,4 +139,4 @@ TableX.defaultProps = {
140
139
  isTrHighlight: () => false
141
140
  }
142
141
 
143
- export default StickyLayout(TableX)
142
+ export default TableX
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/thead.js CHANGED
@@ -4,7 +4,7 @@ import Th from './th'
4
4
 
5
5
  const THead = ({ headerGroups, totalWidth }) => {
6
6
  return (
7
- <thead className='gm-table-x-thead common-sticky-header'>
7
+ <thead className='gm-table-x-thead'>
8
8
  {headerGroups.map((headerGroup, i) => (
9
9
  <tr key={i} className='gm-table-x-tr'>
10
10
  {headerGroup.headers.map((column, i) => (
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(),
@@ -0,0 +1,29 @@
1
+ /**
2
+ * 扁平化 columns(用于 select 和 diy 处理)
3
+ * 将带有 subColumns 的 columns 展开为扁平结构
4
+ *
5
+ * 对于有 subColumns 的列,会给每个 subColumn 添加:
6
+ * - parentKey: 父级列的 id(用于重建时识别分组)
7
+ * - parentHeader: 父级列的 Header(用于重建时显示一级表头)
8
+ * - parentFixed: 父级列的 fixed 属性(用于重建时设置 fixed)
9
+ */
10
+ export function flattenColumnsForSelectAndDiy(columns) {
11
+ const flattened = []
12
+ columns.forEach(column => {
13
+ if (column.subColumns && column.subColumns.length > 0) {
14
+ column.subColumns.forEach(subCol => {
15
+ const { fixed: _subColFixed, ...subColWithoutFixed } = subCol
16
+ flattened.push({
17
+ ...subColWithoutFixed,
18
+ parentKey: column.id || column.Header, // 父级标识
19
+ parentHeader: column.Header,
20
+ parentFixed: column.fixed
21
+ })
22
+ })
23
+ } else {
24
+ // 没有 subColumns,直接添加
25
+ flattened.push(column)
26
+ }
27
+ })
28
+ return flattened
29
+ }
@@ -0,0 +1,92 @@
1
+ import {
2
+ TABLE_X_SELECT_ID,
3
+ TABLE_X_DIY_ID,
4
+ TABLE_X_EXPAND_ID
5
+ } from '../../util'
6
+
7
+ /**
8
+ * 根据扁平化的 columns 重建嵌套结构
9
+ * 输入:
10
+ * - flatColumns: 处理后的扁平 columns(每个 subColumn 都带有 parentKey、parentHeader、parentFixed 属性)
11
+ * 输出:重建的嵌套结构(严格按照 flatColumns 的顺序输出)
12
+ */
13
+ export function rebuildNestedColumnsFromFlat(flatColumns) {
14
+ const nested = []
15
+ const specialColumnIds = [
16
+ TABLE_X_SELECT_ID,
17
+ TABLE_X_DIY_ID,
18
+ TABLE_X_EXPAND_ID
19
+ ]
20
+
21
+ // 普通列(排除特殊列)
22
+ const normalCols = flatColumns.filter(
23
+ col => !specialColumnIds.includes(col.id)
24
+ )
25
+ // 收集分组数据:parentKey -> { parentHeader, parentFixed, subCols: [] }
26
+ const groupMap = new Map()
27
+
28
+ // 遍历一次:收集所有分组的数据
29
+ normalCols.forEach(col => {
30
+ if (col.parentKey) {
31
+ // 有 parentKey:二级分类的子列
32
+ if (!groupMap.has(col.parentKey)) {
33
+ groupMap.set(col.parentKey, {
34
+ parentHeader: col.parentHeader,
35
+ parentFixed: col.parentFixed,
36
+ subCols: []
37
+ })
38
+ }
39
+ // 移除临时属性并添加到分组
40
+ const {
41
+ parentKey: _p,
42
+ parentHeader: _h,
43
+ parentFixed: parentFixedValue,
44
+ ...colWithoutParent
45
+ } = col
46
+ const { fixed: _subColFixed, ...colWithoutFixed } = colWithoutParent
47
+ groupMap.get(col.parentKey).subCols.push({
48
+ ...colWithoutFixed,
49
+ fixed: parentFixedValue
50
+ })
51
+ }
52
+ })
53
+
54
+ // 再次遍历:按 flatColumns 的顺序输出普通列
55
+ const processedGroups = new Set()
56
+
57
+ normalCols.forEach(col => {
58
+ if (col.parentKey) {
59
+ // 有 parentKey:在第一次遇到的位置输出分组
60
+ if (!processedGroups.has(col.parentKey)) {
61
+ processedGroups.add(col.parentKey)
62
+ const group = groupMap.get(col.parentKey)
63
+ const visibleSubCols = group.subCols.filter(
64
+ subCol => subCol.show !== false
65
+ )
66
+ if (visibleSubCols.length > 0) {
67
+ nested.push({
68
+ Header: group.parentHeader,
69
+ id: col.parentKey,
70
+ fixed: group.parentFixed,
71
+ subColumns: visibleSubCols
72
+ })
73
+ }
74
+ }
75
+ } else {
76
+ // 无 parentKey:单独列,直接输出
77
+ if (col.show !== false) {
78
+ nested.push(col)
79
+ }
80
+ }
81
+ })
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
+
91
+ return nested
92
+ }
@@ -0,0 +1,169 @@
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 { rebuildNestedColumnsFromFlat } from './rebuild_helper'
10
+ import { transformColumnsForTwoLevel } from './transform_helper'
11
+
12
+ const defaultColumn = __DEFAULT_COLUMN
13
+
14
+ /**
15
+ * 支持两级表头的 TableX 组件
16
+ *
17
+ * 接收:
18
+ * - columns: 处理后的扁平 columns(经过 select、diy HOC 处理,每个 subColumn 都带有 parentKey、parentHeader、parentFixed 属性)
19
+ *
20
+ * 功能:
21
+ * 1. 根据 parentKey 重建嵌套结构
22
+ * 2. 转换为 react-table 格式
23
+ * 3. 使用 TwoLevelTHead 渲染两级表头
24
+ */
25
+ const TwoLevelTableX = ({
26
+ columns,
27
+ data,
28
+ loading,
29
+ SubComponent,
30
+ keyField = 'value',
31
+ className,
32
+ tiled = false,
33
+ onScroll,
34
+ isTrDisable = () => false,
35
+ isTrHighlight = () => false,
36
+ ...rest
37
+ }) => {
38
+ const rebuiltColumns = useMemo(() => {
39
+ return rebuildNestedColumnsFromFlat(columns)
40
+ }, [columns])
41
+
42
+ const { transformedColumns, firstLevelHeaders } = useMemo(() => {
43
+ return transformColumnsForTwoLevel(rebuiltColumns)
44
+ }, [rebuiltColumns])
45
+
46
+ const visibleColumns = useMemo(() => {
47
+ return transformedColumns.filter(c => c.show !== false)
48
+ }, [transformedColumns])
49
+
50
+ const {
51
+ getTableProps,
52
+ headerGroups,
53
+ getTableBodyProps,
54
+ rows,
55
+ prepareRow
56
+ } = useTable({
57
+ columns: visibleColumns,
58
+ data,
59
+ defaultColumn
60
+ })
61
+
62
+ let totalWidth = 0
63
+ if (rows[0] && rows[0].cells.length > 0) {
64
+ prepareRow(rows[0])
65
+ const last = rows[0].cells[rows[0].cells.length - 1].column
66
+ totalWidth = last.totalLeft + last.totalWidth
67
+ }
68
+
69
+ const gtp = getTableProps()
70
+ const tableStyle =
71
+ totalWidth > 0
72
+ ? { width: '100%', minWidth: totalWidth + 'px' }
73
+ : { width: '100%' }
74
+
75
+ const tableProps = {
76
+ ...gtp,
77
+ style: tableStyle,
78
+ className: classNames(
79
+ 'gm-table-x-table',
80
+ 'gm-table-x-table-two-level',
81
+ gtp.className
82
+ )
83
+ }
84
+
85
+ const gtbp = getTableBodyProps()
86
+ const tableBodyProps = {
87
+ ...gtbp,
88
+ className: 'gm-table-x-tbody'
89
+ }
90
+
91
+ const handleScroll = e => {
92
+ onScroll && onScroll(e)
93
+ afterScroll()
94
+ }
95
+
96
+ const RenderRow = ({ index, style }) => {
97
+ const row = rows[index]
98
+ prepareRow(row)
99
+
100
+ return (
101
+ <Tr
102
+ key={row.index}
103
+ row={row}
104
+ SubComponent={SubComponent}
105
+ keyField={keyField}
106
+ style={style}
107
+ totalWidth={totalWidth}
108
+ isTrDisable={isTrDisable}
109
+ isTrHighlight={isTrHighlight}
110
+ />
111
+ )
112
+ }
113
+
114
+ return (
115
+ <div
116
+ {...rest}
117
+ className={classNames(
118
+ 'gm-table-x',
119
+ {
120
+ 'gm-table-x-empty': data.length === 0,
121
+ 'gm-table-x-tiled': tiled
122
+ },
123
+ className
124
+ )}
125
+ onScroll={handleScroll}
126
+ >
127
+ <table {...tableProps}>
128
+ <TwoLevelTHead
129
+ headerGroups={headerGroups}
130
+ firstLevelHeaders={firstLevelHeaders}
131
+ totalWidth={totalWidth}
132
+ />
133
+ <tbody {...tableBodyProps}>
134
+ {_.map(rows, row =>
135
+ RenderRow({
136
+ index: row.index,
137
+ style: {}
138
+ })
139
+ )}
140
+ </tbody>
141
+ </table>
142
+ {loading && <Loading style={{ marginTop: '92px' }} />}
143
+ {!loading && data.length === 0 && <Empty />}
144
+ </div>
145
+ )
146
+ }
147
+
148
+ TwoLevelTableX.propTypes = {
149
+ columns: PropTypes.array.isRequired,
150
+ data: PropTypes.array.isRequired,
151
+ loading: PropTypes.bool,
152
+ SubComponent: PropTypes.func,
153
+ keyField: PropTypes.string,
154
+ tiled: PropTypes.bool,
155
+ isTrDisable: PropTypes.func,
156
+ isTrHighlight: PropTypes.func,
157
+ onScroll: PropTypes.func,
158
+ className: PropTypes.string,
159
+ style: PropTypes.object
160
+ }
161
+
162
+ TwoLevelTableX.defaultProps = {
163
+ keyField: 'value',
164
+ tiled: false,
165
+ isTrDisable: () => false,
166
+ isTrHighlight: () => false
167
+ }
168
+
169
+ export default TwoLevelTableX
@@ -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)
@@ -0,0 +1,278 @@
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 { colSpan: hpColSpan, rowSpan: hpRowSpan, ...restHp } = hp || {}
178
+ const finalRowSpan =
179
+ rowSpan !== undefined ? Number(rowSpan) : undefined
180
+ const finalColSpan =
181
+ colSpan !== undefined
182
+ ? Number(colSpan)
183
+ : hpColSpan !== undefined
184
+ ? Number(hpColSpan)
185
+ : undefined
186
+
187
+ const thProps = {
188
+ ...restHp,
189
+ ...(finalColSpan !== undefined && { colSpan: finalColSpan }),
190
+ ...(finalRowSpan !== undefined && { rowSpan: finalRowSpan }), // 只有定义了才添加
191
+ className: classNames(
192
+ 'gm-table-x-th',
193
+ 'gm-table-x-th-first-level',
194
+ hp?.className,
195
+ thClassName,
196
+ {
197
+ 'gm-table-x-fixed-left': fixed === 'left',
198
+ 'gm-table-x-fixed-right': fixed === 'right'
199
+ }
200
+ ),
201
+ style: cellStyle
202
+ }
203
+
204
+ return (
205
+ <th key={idx} {...thProps}>
206
+ {typeof header.Header === 'function' ? (
207
+ <header.Header />
208
+ ) : (
209
+ header.Header
210
+ )}
211
+ {headerColumn?.canSort && (
212
+ <SortHeader
213
+ {...headerColumn.getSortByToggleProps()}
214
+ type={
215
+ headerColumn.isSorted
216
+ ? headerColumn.isSortedDesc
217
+ ? 'desc'
218
+ : 'asc'
219
+ : null
220
+ }
221
+ />
222
+ )}
223
+ </th>
224
+ )
225
+ })}
226
+ </tr>
227
+
228
+ {/* 第二级表头行(只有有子列的情况才显示) */}
229
+ {hasNestedHeaders && secondLevelGroup && (
230
+ <tr className='gm-table-x-tr gm-table-x-tr-second-level'>
231
+ {secondLevelGroup.headers.map((column, idx) => {
232
+ // 找到这个列对应的第一级表头
233
+ const correspondingFirstLevel = firstLevelCells.find(cell => {
234
+ if (cell.type === 'single') {
235
+ return cell.secondLevelIndex === idx
236
+ } else if (cell.type === 'group') {
237
+ return (
238
+ idx >= cell.secondLevelStartIndex &&
239
+ idx < cell.secondLevelStartIndex + cell.colSpan
240
+ )
241
+ }
242
+ return false
243
+ })
244
+
245
+ if (
246
+ correspondingFirstLevel &&
247
+ correspondingFirstLevel.type === 'single'
248
+ ) {
249
+ return null
250
+ }
251
+ return (
252
+ <Th
253
+ key={idx}
254
+ column={{
255
+ ...column,
256
+ // column.fixed 已经在 transformColumnsForTwoLevel 中正确设置
257
+ thClassName: classNames(
258
+ column.thClassName,
259
+ 'gm-table-x-th-second-level'
260
+ )
261
+ }}
262
+ totalWidth={totalWidth}
263
+ />
264
+ )
265
+ })}
266
+ </tr>
267
+ )}
268
+ </thead>
269
+ )
270
+ }
271
+
272
+ TwoLevelTHead.propTypes = {
273
+ headerGroups: PropTypes.array.isRequired,
274
+ firstLevelHeaders: PropTypes.array.isRequired,
275
+ totalWidth: PropTypes.number.isRequired
276
+ }
277
+
278
+ export default React.memo(TwoLevelTHead)
@@ -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)
@@ -0,0 +1,149 @@
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: '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
+
148
+ return { transformedColumns, firstLevelHeaders }
149
+ }
@@ -0,0 +1,36 @@
1
+ import React, { useMemo } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { flattenColumnsForSelectAndDiy } from './two_level_header_table_x/flatten_helper'
4
+
5
+ /**
6
+ * 两级表头 HOC
7
+ * 功能:将带 subColumns 的 columns 扁平化,传递给内部 Component 处理
8
+ *
9
+ * 使用方式:
10
+ * const TwoLevelTable = twoLevelHeaderTableXHOC(
11
+ * selectTableXHOC(
12
+ * diyTableXHOC(TwoLevelTableX)
13
+ * )
14
+ * )
15
+ *
16
+ * 注意:当存在一级表头相同的列时,在column配置中加id标识区分,避免混乱
17
+ */
18
+ function twoLevelHeaderTableXHOC(Component) {
19
+ const TwoLevelHeaderTableXWrapper = props => {
20
+ const { columns: originalColumns, ...rest } = props
21
+
22
+ const flattenedColumns = useMemo(() => {
23
+ return flattenColumnsForSelectAndDiy(originalColumns)
24
+ }, [originalColumns])
25
+
26
+ return <Component {...rest} columns={flattenedColumns} />
27
+ }
28
+
29
+ TwoLevelHeaderTableXWrapper.propTypes = {
30
+ columns: PropTypes.array.isRequired
31
+ }
32
+
33
+ return TwoLevelHeaderTableXWrapper
34
+ }
35
+
36
+ export default twoLevelHeaderTableXHOC
package/src/index.js CHANGED
@@ -7,6 +7,8 @@ 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'
10
12
 
11
13
  import {
12
14
  TABLE_X,
@@ -42,6 +44,8 @@ const TableXUtil = {
42
44
  export {
43
45
  TableXUtil,
44
46
  TableX,
47
+ TwoLevelTableX,
48
+ twoLevelHeaderTableXHOC,
45
49
  TableXVirtualized,
46
50
  selectTableXHOC,
47
51
  expandTableXHOC,
package/src/index.less CHANGED
@@ -19,6 +19,70 @@
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
+
22
86
  .gm-table-x-tr {
23
87
  display: flex;
24
88
  min-height: 60px;
@@ -80,6 +144,77 @@
80
144
  }
81
145
  }
82
146
 
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
+
83
218
  .gm-table-x-tbody {
84
219
  display: block; // 覆盖tbody 默认display
85
220
 
@@ -211,13 +346,30 @@
211
346
  position: sticky !important;
212
347
  z-index: 1;
213
348
  }
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
+ }
214
358
 
359
+ // tbody 单元格固定列使用 inset box-shadow
360
+ .gm-table-x-td {
215
361
  &.gm-table-x-fixed-left {
216
- 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;
217
366
  }
218
367
 
219
368
  &.gm-table-x-fixed-right {
220
- 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;
221
373
  }
222
374
  }
223
375
  }
@@ -432,7 +584,7 @@
432
584
  }
433
585
  }
434
586
 
435
- .gm-react-table-x-diy-modal-content{
587
+ .gm-react-table-x-diy-modal-content {
436
588
  border-top: 1px solid @gm-table-x-border-color;
437
589
  border-bottom: 1px solid @gm-table-x-border-color;
438
590
  }
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>
package/LICENSE DELETED
@@ -1,16 +0,0 @@
1
- ISC License
2
-
3
- Copyright (c) 2020-present, gmfe contributors
4
-
5
- Permission to use, copy, modify, and/or distribute this software for any
6
- purpose with or without fee is hereby granted, provided that the above
7
- copyright notice and this permission notice appear in all copies.
8
-
9
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
-
@@ -1,149 +0,0 @@
1
- /**
2
- * @author: stanfer
3
- * @description:
4
- * @createDate: 2025/11/24 11:05
5
- * @Version: 1.0
6
- * @last modify time:
7
- **/
8
- import React, { useRef, useEffect } from 'react'
9
-
10
- function stickyLayout(Component) {
11
- const StickyLayout = ({ ...rest }) => {
12
- const currentStickyRef = useRef(null);
13
- const topSentinelRef = useRef(null);
14
- const bottomSentinelRef = useRef(null);
15
- // 统一管理
16
- const observerRef = useRef(null);
17
-
18
- useEffect(() => {
19
- if (!currentStickyRef.current) return;
20
-
21
- const options = {
22
- root: null, // 相对于浏览器视口
23
- rootMargin: '0px',
24
- threshold: 0, // 只要有一个像素进入/离开视口就触发
25
- };
26
-
27
- observerRef.current = new IntersectionObserver((entries) => {
28
- entries.forEach((entry) => {
29
- const target = entry.target;
30
-
31
- if (target === topSentinelRef.current) {
32
- if (entry.isIntersecting) {
33
- console.log('=======> 顶部进入');
34
- let heightSum = 0, tableRoot;
35
- const fullTab = document.querySelectorAll('.gm-framework-full-tabs')
36
- const commonStickyHeader = document.querySelectorAll('.common-sticky-header')
37
- const rtTable = document.querySelectorAll('.rt-table')
38
- const rtTheadHeader = document.querySelectorAll('.rt-thead')
39
- rtTheadHeader.forEach((el) => {
40
- el.style.position = 'sticky';
41
- el.style.top = 0;
42
- el.style.zIndex = 10;
43
- heightSum += el.offsetHeight + 10; // 元素本身高 + 上下 10 padding
44
- })
45
-
46
- commonStickyHeader.forEach((item, idx) => {
47
- if (fullTab.length) {
48
- currentStickyRef.current.style.zIndex = 101;
49
- }
50
- // console.log(item.className);
51
- if (item.className.includes('gm-box-table-header')) {
52
- heightSum += item.offsetHeight + 10; // 元素本身高 + 上下 10 padding
53
- // console.log(idx, item.getBoundingClientRect(), 'gm-box-table-header: ', item.getBoundingClientRect().height + 20);
54
- }
55
- if (item.className.includes('gm-table-x-thead')) {
56
- heightSum += item.offsetHeight + 8; // 元素本身高 + 上下 8 padding
57
- tableRoot = item.parentElement.parentElement;
58
- // console.log(idx, item.getBoundingClientRect(), 'gm-table-x-thead: ', item.getBoundingClientRect().height + 16);
59
- }
60
- })
61
-
62
- if (rtTable.length) {
63
- rtTable.forEach((el) => {
64
- if (fullTab.length) {
65
- el.style.maxHeight = `calc(100vh - ${heightSum + (fullTab.length * 40) + 50}px)`; // +50 顶部导航栏
66
- return;
67
- }
68
- el.style.maxHeight = `calc(100vh - ${heightSum + 50}px)`; // +50 顶部导航栏
69
- })
70
- }
71
- if (tableRoot) {
72
- if (fullTab.length) {
73
- tableRoot.style.maxHeight = `calc(100vh - ${heightSum + (fullTab.length * 40) + 50}px)`; // +50 顶部导航栏
74
- return;
75
- }
76
- tableRoot.style.maxHeight = `calc(100vh - ${heightSum + 50}px)`; // +50 顶部导航栏
77
- }
78
- // currentStickyRef.current.style.maxHeight = 'unset';
79
- console.log('currentStickyRef 实例:', currentStickyRef.current.style);
80
- return
81
- }
82
- currentStickyRef.current.style.maxHeight = '50%'; // TODO: 这块需要之后要走外部参数
83
- console.log('顶部离开 <========');
84
- return;
85
- }
86
- if (target === bottomSentinelRef.current) {
87
- if (entry.isIntersecting) {
88
- console.log('=======> 底部进入');
89
- return;
90
- }
91
- console.log('底部离开 <========');
92
- }
93
- });
94
- }, options);
95
-
96
- if (topSentinelRef.current) {
97
- observerRef.current.observe(topSentinelRef.current);
98
- }
99
- if (bottomSentinelRef.current) {
100
- observerRef.current.observe(bottomSentinelRef.current);
101
- }
102
-
103
- return () => {
104
- if (observerRef.current) {
105
- observerRef.current.disconnect();
106
- }
107
- };
108
- }, [])
109
-
110
- return (
111
- <div ref={currentStickyRef} className='common-sticky-layout'>
112
- <div
113
- ref={topSentinelRef}
114
- sentinel="topSentinel"
115
- style={{
116
- position: 'absolute',
117
- top: '0',
118
- left: '0',
119
- width: '100%',
120
- height: '1px', // 尽量小,不占空间
121
- pointerEvents: 'none', // 防止它拦截任何鼠标事件
122
- backgroundColor: 'transparent', // 完全透明
123
- }}
124
- />
125
- <Component {...rest} />
126
- <div
127
- ref={bottomSentinelRef}
128
- sentinel="bottomSentinel"
129
- style={{
130
- position: 'absolute',
131
- bottom: '0',
132
- left: '0',
133
- width: '100%',
134
- height: '1px',
135
- pointerEvents: 'none',
136
- backgroundColor: 'transparent',
137
- }}
138
- />
139
- </div>
140
- )
141
- }
142
-
143
- StickyLayout.propTypes = StickyLayout.propTypes;
144
- StickyLayout.defaultProps = StickyLayout.defaultProps;
145
-
146
- return StickyLayout
147
- }
148
-
149
- export default stickyLayout