@@ -47,13 +47,105 @@ export default function TableExample() {
4747 const [ headerAffixedTop , setHeaderAffixedTop ] = useState ( false ) ;
4848 const [ stickyMultiHeader , setStickyMultiHeader ] = useState ( true ) ;
4949 const [ sort , setSort ] = useState < TableSort > ( { sortBy : 'default' , descending : false } ) ;
50+ const tableContainerRef = useRef < HTMLDivElement > ( null ) ;
5051
5152 const onSortChange : TableProps [ 'onSortChange' ] = ( sortInfo , context ) => {
5253 setSort ( sortInfo ) ;
5354 setData ( [ ...context . currentDataSource ] ) ;
5455 console . log ( context ) ;
5556 } ;
5657
58+ // 多级表头粘性定位逻辑
59+ useEffect ( ( ) => {
60+ if ( ! stickyMultiHeader || ! tableContainerRef . current ) return ;
61+
62+ const tableContainer = tableContainerRef . current ;
63+ const tableContent = tableContainer . querySelector ( '.t-table__content' ) as HTMLElement ;
64+
65+ if ( ! tableContent ) return ;
66+
67+ const handleScroll = ( ) => {
68+ const scrollLeft = tableContent . scrollLeft ;
69+ const thead = tableContainer . querySelector ( '.t-table__header--multiple' ) as HTMLElement ;
70+
71+ if ( ! thead ) return ;
72+
73+ // 获取所有顶级表头单元格(有colspan的)
74+ const topLevelHeaders = thead . querySelectorAll ( 'tr:first-child th[colspan]' ) as NodeListOf < HTMLElement > ;
75+
76+ let accumulatedWidth = 0 ;
77+
78+ topLevelHeaders . forEach ( ( header ) => {
79+ const colKey = header . getAttribute ( 'data-colkey' ) ;
80+ if ( ! colKey ) return ;
81+
82+ const cellInner = header . querySelector ( '.t-table__th-cell-inner' ) as HTMLElement ;
83+ if ( ! cellInner ) return ;
84+
85+ // 获取表头相对于表格容器的位置
86+ const headerLeft = header . offsetLeft ;
87+ const headerWidth = header . offsetWidth ;
88+ const headerRight = headerLeft + headerWidth ;
89+
90+ // 判断是否需要粘性定位:表头的左边已经滚动出视野,但右边还在视野内
91+ const shouldStick = scrollLeft > headerLeft && scrollLeft < headerRight ;
92+
93+ if ( shouldStick ) {
94+ // 计算粘性位置,确保不超出表头边界
95+ const maxLeft = headerWidth - 120 ; // 预留最小显示宽度
96+ const stickyLeft = Math . min ( scrollLeft - headerLeft , maxLeft ) ;
97+
98+ console . log ( 'shouldStick' , headerLeft , headerRight , scrollLeft , stickyLeft ) ;
99+ // 应用粘性样式
100+ cellInner . style . position = 'relative' ;
101+ cellInner . style . left = `${ stickyLeft } px` ;
102+ cellInner . style . zIndex = '10' ;
103+ } else {
104+ // 移除粘性样式
105+ cellInner . style . position = '' ;
106+ cellInner . style . left = '' ;
107+ cellInner . style . zIndex = '' ;
108+ }
109+ } ) ;
110+ } ;
111+
112+ // 节流处理
113+ let ticking = false ;
114+ const throttledScroll = ( ) => {
115+ if ( ! ticking ) {
116+ requestAnimationFrame ( ( ) => {
117+ handleScroll ( ) ;
118+ ticking = false ;
119+ } ) ;
120+ ticking = true ;
121+ }
122+ } ;
123+
124+ tableContent . addEventListener ( 'scroll' , throttledScroll ) ;
125+
126+ // 初始化执行
127+ handleScroll ( ) ;
128+
129+ return ( ) => {
130+ tableContent . removeEventListener ( 'scroll' , throttledScroll ) ;
131+
132+ // 清理样式
133+ const thead = tableContainer . querySelector ( '.t-table__header--multiple' ) as HTMLElement ;
134+ if ( thead ) {
135+ const cellInners = thead . querySelectorAll ( '.t-table__th-cell-inner' ) as NodeListOf < HTMLElement > ;
136+ cellInners . forEach ( ( cellInner ) => {
137+ cellInner . style . position = '' ;
138+ cellInner . style . left = '' ;
139+ cellInner . style . zIndex = '' ;
140+ cellInner . style . backgroundColor = '' ;
141+ cellInner . style . boxShadow = '' ;
142+ cellInner . style . borderRight = '' ;
143+ cellInner . style . minWidth = '' ;
144+ } ) ;
145+ }
146+ } ;
147+ } , [ stickyMultiHeader ] ) ;
148+
57149 const columns : TableProps [ 'columns' ] = [
58150 {
59151 title : '申请人' ,
@@ -66,7 +158,6 @@ export default function TableExample() {
66158 fixed : fixedLeftCol ? 'left' : undefined ,
67159 width : 100 ,
68160 colKey : 'total_info' ,
69- className : stickyMultiHeader ? 'sticky-multi-header' : undefined ,
70161 children : [
71162 {
72163 align : 'left' ,
@@ -113,11 +204,31 @@ export default function TableExample() {
113204 } ,
114205 ] ,
115206 } ,
207+ {
208+ colKey : 'field1' ,
209+ title : '住宿费' ,
210+ width : 100 ,
211+ } ,
212+ {
213+ colKey : 'field3' ,
214+ title : '交通费' ,
215+ width : 100 ,
216+ } ,
217+ {
218+ colKey : 'field4' ,
219+ title : '物料费' ,
220+ width : 100 ,
221+ } ,
222+ {
223+ colKey : 'field2' ,
224+ title : '奖品激励费' ,
225+ width : 120 ,
226+ } ,
116227 {
117228 title : '审批汇总' ,
118229 colKey : 'instruction' ,
119230 fixed : fixedRightCol ? 'right' : undefined ,
120- className : stickyMultiHeader ? 'sticky-multi-header' : undefined ,
231+ width : 100 ,
121232 children : [
122233 {
123234 align : 'left' ,
@@ -160,26 +271,6 @@ export default function TableExample() {
160271 } ,
161272 ] ,
162273 } ,
163- {
164- colKey : 'field1' ,
165- title : '住宿费' ,
166- width : 300 ,
167- } ,
168- {
169- colKey : 'field3' ,
170- title : '交通费' ,
171- width : 300 ,
172- } ,
173- {
174- colKey : 'field4' ,
175- title : '物料费' ,
176- width : 300 ,
177- } ,
178- {
179- colKey : 'field2' ,
180- title : '奖品激励费' ,
181- width : 120 ,
182- } ,
183274 {
184275 colKey : 'createTime' ,
185276 title : '申请时间' ,
@@ -188,14 +279,7 @@ export default function TableExample() {
188279 } ,
189280 ] ;
190281 return (
191- < div >
192- < style > { `
193- /* 多级表头粘性定位CSS */
194- .enable-sticky-multi-header .sticky-multi-header {
195- position: sticky;
196- left: 0;
197- }
198- ` } </ style >
282+ < div ref = { tableContainerRef } >
199283 < Space direction = "vertical" size = "large" style = { { width : '100%' } } >
200284 { /* <!-- 按钮操作区域 --> */ }
201285 < Space >
@@ -231,7 +315,6 @@ export default function TableExample() {
231315 sort = { sort }
232316 onSortChange = { onSortChange }
233317 lazyLoad
234- className = { stickyMultiHeader ? 'enable-sticky-multi-header' : undefined }
235318 />
236319 </ Space >
237320 </ div >
0 commit comments