@gmfe/react 2.14.26 → 2.15.4-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/LICENSE +16 -0
- package/package.json +3 -3
- package/src/component/box/box_table.js +23 -11
- package/src/hoc/sticky_layout.js +229 -0
- package/src/index.js +2 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gmfe/react",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.4-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.
|
|
30
|
+
"@gmfe/locales": "^2.15.4-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": "
|
|
38
|
+
"gitHead": "2c13925c3699572748de29cf0256ca1431c30f01"
|
|
39
39
|
}
|
|
@@ -2,6 +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 '@gmfe/react'
|
|
5
6
|
|
|
6
7
|
// 暂时没什么用
|
|
7
8
|
const Info = props => {
|
|
@@ -18,21 +19,32 @@ Info.propTypes = {
|
|
|
18
19
|
style: PropTypes.object
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
const
|
|
22
|
-
const { info, action,
|
|
22
|
+
const BoxHeader = StickyLayout(props => {
|
|
23
|
+
const { info, action, headerProps = {} } = props
|
|
23
24
|
const { className: headerClassName } = headerProps
|
|
24
25
|
|
|
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
|
+
})
|
|
41
|
+
|
|
42
|
+
const BoxTable = props => {
|
|
43
|
+
const { children, className, ...rest } = props
|
|
44
|
+
|
|
25
45
|
return (
|
|
26
46
|
<div {...rest} className={classNames('gm-box gm-box-table', className)}>
|
|
27
|
-
<
|
|
28
|
-
{...headerProps}
|
|
29
|
-
className={classNames('gm-box-table-header', headerClassName)}
|
|
30
|
-
alignCenter
|
|
31
|
-
>
|
|
32
|
-
<Flex>{info}</Flex>
|
|
33
|
-
<Flex flex />
|
|
34
|
-
<Flex>{action}</Flex>
|
|
35
|
-
</Flex>
|
|
47
|
+
<BoxHeader {...props} />
|
|
36
48
|
<div>{children}</div>
|
|
37
49
|
</div>
|
|
38
50
|
)
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author: stanfer
|
|
3
|
+
* @description: 粘性布局高阶组件,用于实现表格等组件的粘性头部效果
|
|
4
|
+
* @createDate: 2025/11/24 10:05
|
|
5
|
+
* @Version: 1.0
|
|
6
|
+
**/
|
|
7
|
+
import React, { useRef, useEffect, useMemo } from 'react'
|
|
8
|
+
import PropTypes from 'prop-types'
|
|
9
|
+
|
|
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
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
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
|
+
)
|
|
160
|
+
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (!sticky || !currentStickyRef.current) return undefined
|
|
163
|
+
|
|
164
|
+
if (typeof window.IntersectionObserver === 'undefined') {
|
|
165
|
+
return undefined
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
observerRef.current = new window.IntersectionObserver(entries => {
|
|
169
|
+
entries.forEach(entry => {
|
|
170
|
+
const target = entry.target
|
|
171
|
+
|
|
172
|
+
if (target === topSentinelRef.current) {
|
|
173
|
+
if (entry.isIntersecting) {
|
|
174
|
+
handleTopSentinelEnter(currentStickyRef)
|
|
175
|
+
} else {
|
|
176
|
+
handleTopSentinelLeave(currentStickyRef)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
}, OBSERVER_OPTIONS)
|
|
181
|
+
|
|
182
|
+
// 观察哨兵元素
|
|
183
|
+
if (topSentinelRef.current) {
|
|
184
|
+
observerRef.current.observe(topSentinelRef.current)
|
|
185
|
+
}
|
|
186
|
+
if (bottomSentinelRef.current) {
|
|
187
|
+
observerRef.current.observe(bottomSentinelRef.current)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return () => {
|
|
191
|
+
if (observerRef.current) {
|
|
192
|
+
observerRef.current.disconnect()
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}, [sticky])
|
|
196
|
+
|
|
197
|
+
if (!sticky) {
|
|
198
|
+
return <Component {...rest} />
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<div ref={currentStickyRef} className='common-sticky-layout'>
|
|
203
|
+
<div
|
|
204
|
+
ref={topSentinelRef}
|
|
205
|
+
data-sentinel='topSentinel'
|
|
206
|
+
style={topSentinelStyle}
|
|
207
|
+
/>
|
|
208
|
+
<Component {...rest} />
|
|
209
|
+
<div
|
|
210
|
+
ref={bottomSentinelRef}
|
|
211
|
+
data-sentinel='bottomSentinel'
|
|
212
|
+
style={bottomSentinelStyle}
|
|
213
|
+
/>
|
|
214
|
+
</div>
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
StickyLayout.defaultProps = {
|
|
219
|
+
sticky: false
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
StickyLayout.propTypes = {
|
|
223
|
+
sticky: PropTypes.oneOfType([PropTypes.bool, PropTypes.object])
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return StickyLayout
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export default StickyLayout
|
package/src/index.js
CHANGED
|
@@ -103,6 +103,7 @@ import Selection from './component/selection'
|
|
|
103
103
|
import TreeV2 from './component/tree_v2'
|
|
104
104
|
import RecommendInput from './component/recommend_input'
|
|
105
105
|
import PicturePreview from './component/picture_preview'
|
|
106
|
+
import StickyLayout from './hoc/sticky_layout'
|
|
106
107
|
|
|
107
108
|
Object.assign(Sheet, {
|
|
108
109
|
SheetColumn,
|
|
@@ -132,8 +133,8 @@ Object.assign(Checkbox, {
|
|
|
132
133
|
Object.assign(Select, {
|
|
133
134
|
Option
|
|
134
135
|
})
|
|
135
|
-
|
|
136
136
|
export {
|
|
137
|
+
StickyLayout,
|
|
137
138
|
// 库之间用
|
|
138
139
|
EVENT_TYPE,
|
|
139
140
|
PaginationBase,
|