@gmfe/react 2.14.30-alpha.0 → 2.15.5-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmfe/react",
3
- "version": "2.14.30-alpha.0",
3
+ "version": "2.15.5-alpha.0",
4
4
  "description": "",
5
5
  "author": "liyatang <liyatang@qq.com>",
6
6
  "homepage": "https://github.com/gmfe/gmfe#readme",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@gm-common/tool": "^1.0.0",
30
- "@gmfe/locales": "^2.14.30-alpha.0",
30
+ "@gmfe/locales": "^2.15.5-alpha.0",
31
31
  "big.js": "^5.2.2",
32
32
  "classnames": "^2.2.5",
33
33
  "lodash": "^4.17.14",
@@ -35,5 +35,5 @@
35
35
  "prop-types": "^15.7.2",
36
36
  "react-window": "^1.8.5"
37
37
  },
38
- "gitHead": "f7da14010d37550f0a19949fe4a5b3d65301545a"
38
+ "gitHead": "43c05b1972463cd83ab9d7b40956c28dacaa119c"
39
39
  }
@@ -1,31 +1,63 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import classNames from 'classnames'
4
+ import Flex from '../flex'
5
+ import StickyLayout from '../../hoc/sticky_layout'
4
6
 
5
- /** Box,用来包裹一块内容 */
6
- const Box = props => {
7
- const { hasGap, className, children, ...rest } = props
8
-
7
+ // 暂时没什么用
8
+ const Info = props => {
9
9
  return (
10
10
  <div
11
- {...rest}
11
+ {...props}
12
+ className={classNames('gm-box-table-info', props.className)}
13
+ />
14
+ )
15
+ }
16
+
17
+ Info.propTypes = {
18
+ className: PropTypes.string,
19
+ style: PropTypes.object
20
+ }
21
+
22
+ const BoxHeader = StickyLayout(props => {
23
+ const { info, action, headerProps = {} } = props
24
+ const { className: headerClassName } = headerProps
25
+
26
+ return (
27
+ <Flex
28
+ {...headerProps}
12
29
  className={classNames(
13
- 'gm-box',
14
- {
15
- 'gm-padding-tb-10 gm-padding-lr-20 ': hasGap
16
- },
17
- className
30
+ 'gm-box-table-header common-sticky-header',
31
+ headerClassName
18
32
  )}
33
+ alignCenter
19
34
  >
20
- {children}
35
+ <Flex>{info}</Flex>
36
+ <Flex flex />
37
+ <Flex>{action}</Flex>
38
+ </Flex>
39
+ )
40
+ })
41
+
42
+ const BoxTable = props => {
43
+ const { children, className, ...rest } = props
44
+
45
+ return (
46
+ <div {...rest} className={classNames('gm-box gm-box-table', className)}>
47
+ <BoxHeader {...props} />
48
+ <div>{children}</div>
21
49
  </div>
22
50
  )
23
51
  }
24
52
 
25
- Box.propTypes = {
26
- hasGap: PropTypes.bool,
53
+ BoxTable.Info = Info
54
+
55
+ BoxTable.propTypes = {
56
+ info: PropTypes.element,
57
+ action: PropTypes.element,
27
58
  className: PropTypes.string,
28
- style: PropTypes.object
59
+ style: PropTypes.object,
60
+ headerProps: PropTypes.object
29
61
  }
30
62
 
31
- export default Box
63
+ export default BoxTable
@@ -2,7 +2,7 @@ import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import classNames from 'classnames'
4
4
  import Flex from '../flex'
5
- import StickyLayout from '../../hoc/sticky_layout'
5
+ import { StickyLayout } from '@gmfe/react'
6
6
 
7
7
  // 暂时没什么用
8
8
  const Info = props => {
@@ -19,20 +19,25 @@ Info.propTypes = {
19
19
  style: PropTypes.object
20
20
  }
21
21
 
22
- const BoxHeader = StickyLayout((props) => {
22
+ const BoxHeader = StickyLayout(props => {
23
23
  const { info, action, headerProps = {} } = props
24
24
  const { className: headerClassName } = headerProps
25
25
 
26
- return <Flex
27
- {...headerProps}
28
- className={classNames('gm-box-table-header common-sticky-header', headerClassName)}
29
- alignCenter
30
- >
31
- <Flex>{info}</Flex>
32
- <Flex flex />
33
- <Flex>{action}</Flex>
34
- </Flex>
35
- });
26
+ return (
27
+ <Flex
28
+ {...headerProps}
29
+ className={classNames(
30
+ 'gm-box-table-header common-sticky-header',
31
+ headerClassName
32
+ )}
33
+ alignCenter
34
+ >
35
+ <Flex>{info}</Flex>
36
+ <Flex flex />
37
+ <Flex>{action}</Flex>
38
+ </Flex>
39
+ )
40
+ })
36
41
 
37
42
  const BoxTable = props => {
38
43
  const { children, className, ...rest } = props
@@ -1,157 +1,229 @@
1
1
  /**
2
2
  * @author: stanfer
3
- * @description:
3
+ * @description: 粘性布局高阶组件,用于实现表格等组件的粘性头部效果
4
4
  * @createDate: 2025/11/24 10:05
5
5
  * @Version: 1.0
6
- * @last modify time:
7
6
  **/
8
- import React, { useRef, useEffect } from 'react'
7
+ import React, { useRef, useEffect, useMemo } from 'react'
8
+ import PropTypes from 'prop-types'
9
9
 
10
- function stickyLayout(Component) {
11
- const StickyLayout = ({ sticky, ...rest }) => {
12
- if (!sticky) { // 不传的话直接返回上个组件
13
- return <Component {...rest} />
10
+ // 常量配置
11
+ const CONSTANTS = {
12
+ PADDING: {
13
+ RT_THEAD: 10,
14
+ BOX_TABLE_HEADER: 10,
15
+ TABLE_X_THEAD: 8
16
+ },
17
+ Z_INDEX: {
18
+ RT_THEAD: 10,
19
+ STICKY_LAYOUT: 101
20
+ },
21
+ HEIGHT: {
22
+ FULL_TAB: 40,
23
+ TOP_NAV: 50,
24
+ DEFAULT_MAX: '50%'
25
+ }
26
+ }
27
+
28
+ // IntersectionObserver 配置
29
+ const OBSERVER_OPTIONS = {
30
+ root: null, // 相对于浏览器视口
31
+ rootMargin: '0px',
32
+ threshold: 0 // 只要有一个像素进入/离开视口就触发
33
+ }
34
+
35
+ // 哨兵元素样式
36
+ const SENTINEL_STYLE = {
37
+ position: 'absolute',
38
+ left: '0',
39
+ width: '100%',
40
+ height: '1px',
41
+ pointerEvents: 'none',
42
+ backgroundColor: 'transparent'
43
+ }
44
+
45
+ /**
46
+ * 计算最大高度
47
+ * @param {number} heightSum - 高度总和
48
+ * @param {number} fullTabCount - 全屏标签页数量
49
+ * @returns {string} 计算后的最大高度值
50
+ */
51
+ const calculateMaxHeight = (heightSum, fullTabCount = 0) => {
52
+ const extraHeight =
53
+ fullTabCount * CONSTANTS.HEIGHT.FULL_TAB + CONSTANTS.HEIGHT.TOP_NAV
54
+ return `calc(100vh - ${heightSum + extraHeight}px)`
55
+ }
56
+
57
+ /**
58
+ * 设置表格最大高度
59
+ * @param {NodeList|Element[]} elements - 需要设置的元素列表
60
+ * @param {number} heightSum - 高度总和
61
+ * @param {number} fullTabCount - 全屏标签页数量
62
+ */
63
+ const setElementsMaxHeight = (elements, heightSum, fullTabCount) => {
64
+ const maxHeight = calculateMaxHeight(heightSum, fullTabCount)
65
+ elements.forEach(el => {
66
+ el.style.maxHeight = maxHeight
67
+ })
68
+ }
69
+
70
+ /**
71
+ * 计算粘性元素的总高度
72
+ * @returns {{heightSum: number, tableRoot: Element|null, fullTabCount: number}}
73
+ */
74
+ const calculateStickyHeight = () => {
75
+ let heightSum = 0
76
+ let tableRoot = null
77
+
78
+ // 查询 DOM 元素
79
+ const fullTab = document.querySelectorAll('.gm-framework-full-tabs')
80
+ const commonStickyHeader = document.querySelectorAll('.common-sticky-header')
81
+ const rtTheadHeader = document.querySelectorAll('.rt-thead')
82
+
83
+ // 处理 rt-thead 元素
84
+ rtTheadHeader.forEach(el => {
85
+ el.style.position = 'sticky'
86
+ el.style.top = '0'
87
+ el.style.zIndex = CONSTANTS.Z_INDEX.RT_THEAD
88
+ heightSum += el.offsetHeight + CONSTANTS.PADDING.RT_THEAD
89
+ })
90
+
91
+ // 处理 common-sticky-header 元素
92
+ commonStickyHeader.forEach(item => {
93
+ if (fullTab.length) {
94
+ const currentSticky = document.querySelector('.common-sticky-layout')
95
+ if (currentSticky) {
96
+ currentSticky.style.zIndex = CONSTANTS.Z_INDEX.STICKY_LAYOUT
97
+ }
14
98
  }
15
99
 
16
- const currentStickyRef = useRef(null);
17
- const topSentinelRef = useRef(null);
18
- const bottomSentinelRef = useRef(null);
19
- // 统一管理
20
- const observerRef = useRef(null);
100
+ if (item.className.includes('gm-box-table-header')) {
101
+ heightSum += item.offsetHeight + CONSTANTS.PADDING.BOX_TABLE_HEADER
102
+ }
103
+
104
+ if (item.className.includes('gm-table-x-thead')) {
105
+ heightSum += item.offsetHeight + CONSTANTS.PADDING.TABLE_X_THEAD
106
+ tableRoot = item.parentElement?.parentElement || null
107
+ }
108
+ })
109
+
110
+ return {
111
+ heightSum,
112
+ tableRoot,
113
+ fullTabCount: fullTab.length
114
+ }
115
+ }
116
+
117
+ /**
118
+ * 处理顶部哨兵进入视口
119
+ */
120
+ const handleTopSentinelEnter = currentStickyRef => {
121
+ const { heightSum, tableRoot, fullTabCount } = calculateStickyHeight()
122
+
123
+ // 设置 rt-table 元素的最大高度
124
+ const rtTable = document.querySelectorAll('.rt-table')
125
+ if (rtTable.length) {
126
+ setElementsMaxHeight(rtTable, heightSum, fullTabCount)
127
+ }
128
+
129
+ // 设置 tableRoot 的最大高度
130
+ if (tableRoot) {
131
+ tableRoot.style.maxHeight = calculateMaxHeight(heightSum, fullTabCount)
132
+ }
133
+ }
134
+
135
+ /**
136
+ * 处理顶部哨兵离开视口
137
+ */
138
+ const handleTopSentinelLeave = currentStickyRef => {
139
+ if (currentStickyRef.current) {
140
+ currentStickyRef.current.style.maxHeight = CONSTANTS.HEIGHT.DEFAULT_MAX
141
+ }
142
+ }
143
+
144
+ function StickyLayout(Component) {
145
+ const _StickyLayout = ({ sticky, ...rest }) => {
146
+ const currentStickyRef = useRef(null)
147
+ const topSentinelRef = useRef(null)
148
+ const bottomSentinelRef = useRef(null)
149
+ const observerRef = useRef(null)
150
+
151
+ // 哨兵元素样式
152
+ const topSentinelStyle = useMemo(
153
+ () => ({ ...SENTINEL_STYLE, top: '0' }),
154
+ []
155
+ )
156
+ const bottomSentinelStyle = useMemo(
157
+ () => ({ ...SENTINEL_STYLE, bottom: '0' }),
158
+ []
159
+ )
21
160
 
22
161
  useEffect(() => {
23
- if (!currentStickyRef.current) return;
162
+ if (!sticky || !currentStickyRef.current) return undefined
24
163
 
25
- const options = {
26
- root: null, // 相对于浏览器视口
27
- rootMargin: '0px',
28
- threshold: 0, // 只要有一个像素进入/离开视口就触发
29
- };
164
+ if (typeof window.IntersectionObserver === 'undefined') {
165
+ return undefined
166
+ }
30
167
 
31
- observerRef.current = new IntersectionObserver((entries) => {
32
- entries.forEach((entry) => {
33
- const target = entry.target;
168
+ observerRef.current = new window.IntersectionObserver(entries => {
169
+ entries.forEach(entry => {
170
+ const target = entry.target
34
171
 
35
172
  if (target === topSentinelRef.current) {
36
173
  if (entry.isIntersecting) {
37
- // console.log('=======> 顶部进入');
38
- let heightSum = 0, tableRoot;
39
- const fullTab = document.querySelectorAll('.gm-framework-full-tabs')
40
- const commonStickyHeader = document.querySelectorAll('.common-sticky-header')
41
- const rtTable = document.querySelectorAll('.rt-table')
42
- const rtTheadHeader = document.querySelectorAll('.rt-thead')
43
- rtTheadHeader.forEach((el) => {
44
- el.style.position = 'sticky';
45
- el.style.top = 0;
46
- el.style.zIndex = 10;
47
- heightSum += el.offsetHeight + 10; // 元素本身高 + 上下 10 padding
48
- })
49
-
50
- commonStickyHeader.forEach((item, idx) => {
51
- if (fullTab.length) {
52
- currentStickyRef.current.style.zIndex = 101;
53
- }
54
- // console.log(item.className);
55
- if (item.className.includes('gm-box-table-header')) { // 表格筛选项
56
- heightSum += item.offsetHeight + 10; // 元素本身高 + 上 10 padding
57
- // console.log(idx, item.getBoundingClientRect(), 'gm-box-table-header: ', item.getBoundingClientRect().height + 20);
58
- }
59
- if (item.className.includes('gm-table-x-thead')) { //
60
- heightSum += item.offsetHeight + 8; // 元素本身高 + 上 8 padding
61
- tableRoot = item.parentElement.parentElement;
62
- // console.log(idx, item.getBoundingClientRect(), 'gm-table-x-thead: ', item.getBoundingClientRect().height + 16);
63
- }
64
- })
65
-
66
- if (rtTable.length) {
67
- rtTable.forEach((el) => {
68
- if (fullTab.length) {
69
- el.style.maxHeight = `calc(100vh - ${heightSum + (fullTab.length * 40) + 50}px)`; // +50 顶部导航栏
70
- return;
71
- }
72
- el.style.maxHeight = `calc(100vh - ${heightSum + 50}px)`; // +50 顶部导航栏
73
- })
74
- }
75
- if (tableRoot) {
76
- if (fullTab.length) {
77
- tableRoot.style.maxHeight = `calc(100vh - ${heightSum + (fullTab.length * 40) + 50}px)`; // +50 顶部导航栏
78
- return;
79
- }
80
- tableRoot.style.maxHeight = `calc(100vh - ${heightSum + 50}px)`; // +50 顶部导航栏
81
- }
82
- // currentStickyRef.current.style.maxHeight = 'unset';
83
- // console.log('currentStickyRef 实例:', currentStickyRef.current.style);
84
- return
85
- }
86
- currentStickyRef.current.style.maxHeight = '50%';
87
- // console.log('顶部离开 <========');
88
- return;
89
- }
90
- if (target === bottomSentinelRef.current) {
91
- if (entry.isIntersecting) {
92
- // console.log('=======> 底部进入');
93
- return;
174
+ handleTopSentinelEnter(currentStickyRef)
175
+ } else {
176
+ handleTopSentinelLeave(currentStickyRef)
94
177
  }
95
- // console.log('底部离开 <========');
96
178
  }
97
- });
98
- }, options);
179
+ })
180
+ }, OBSERVER_OPTIONS)
99
181
 
182
+ // 观察哨兵元素
100
183
  if (topSentinelRef.current) {
101
- observerRef.current.observe(topSentinelRef.current);
184
+ observerRef.current.observe(topSentinelRef.current)
102
185
  }
103
186
  if (bottomSentinelRef.current) {
104
- observerRef.current.observe(bottomSentinelRef.current);
187
+ observerRef.current.observe(bottomSentinelRef.current)
105
188
  }
106
189
 
107
190
  return () => {
108
191
  if (observerRef.current) {
109
- observerRef.current.disconnect();
192
+ observerRef.current.disconnect()
110
193
  }
111
- };
112
- }, [])
194
+ }
195
+ }, [sticky])
196
+
197
+ if (!sticky) {
198
+ return <Component {...rest} />
199
+ }
113
200
 
114
201
  return (
115
202
  <div ref={currentStickyRef} className='common-sticky-layout'>
116
203
  <div
117
204
  ref={topSentinelRef}
118
- sentinel="topSentinel"
119
- style={{
120
- position: 'absolute',
121
- top: '0',
122
- left: '0',
123
- width: '100%',
124
- height: '1px',
125
- pointerEvents: 'none',
126
- backgroundColor: 'transparent',
127
- }}
205
+ data-sentinel='topSentinel'
206
+ style={topSentinelStyle}
128
207
  />
129
208
  <Component {...rest} />
130
209
  <div
131
210
  ref={bottomSentinelRef}
132
- sentinel="bottomSentinel"
133
- style={{
134
- position: 'absolute',
135
- bottom: '0',
136
- left: '0',
137
- width: '100%',
138
- height: '1px',
139
- pointerEvents: 'none',
140
- backgroundColor: 'transparent',
141
- }}
211
+ data-sentinel='bottomSentinel'
212
+ style={bottomSentinelStyle}
142
213
  />
143
214
  </div>
144
215
  )
145
216
  }
146
217
 
147
- StickyLayout.defaultProps = {
148
- // sticky: {
149
- // offsetHeight: 0,
150
- // },
151
- sticky: false
218
+ _StickyLayout.defaultProps = {
219
+ sticky: true
220
+ }
221
+
222
+ _StickyLayout.propTypes = {
223
+ sticky: PropTypes.oneOfType([PropTypes.bool, PropTypes.object])
152
224
  }
153
225
 
154
- return StickyLayout
226
+ return _StickyLayout
155
227
  }
156
228
 
157
- export default stickyLayout
229
+ export default StickyLayout
package/src/index.js CHANGED
@@ -133,8 +133,8 @@ Object.assign(Checkbox, {
133
133
  Object.assign(Select, {
134
134
  Option
135
135
  })
136
-
137
136
  export {
137
+ StickyLayout,
138
138
  // 库之间用
139
139
  EVENT_TYPE,
140
140
  PaginationBase,
@@ -234,6 +234,5 @@ export {
234
234
  Selection,
235
235
  TreeV2,
236
236
  RecommendInput,
237
- PicturePreview,
238
- StickyLayout
237
+ PicturePreview
239
238
  }