@@ -14,6 +14,7 @@ import {
1414 generateAxisCombinations ,
1515 generateDimensionPath ,
1616} from './CrosstabUtils'
17+ import './crosstab-table.css'
1718
1819const { Text } = Typography
1920
@@ -284,7 +285,7 @@ export default function CrosstabTable({
284285 style = { {
285286 display : 'grid' ,
286287 gridTemplateColumns : `repeat(${ colDimensions } , 120px) repeat(${ horizontalCombinations . length } , 200px)` ,
287- gridTemplateRows : `repeat(${ rowDimensions } , 40px ) repeat(${ verticalCombinations . length } , 60px )` ,
288+ gridTemplateRows : `repeat(${ rowDimensions } , auto ) repeat(${ verticalCombinations . length } , auto )` ,
288289 gap : '1px' ,
289290 backgroundColor : '#f0f0f0' ,
290291 border : '1px solid #d9d9d9' ,
@@ -311,87 +312,237 @@ export default function CrosstabTable({
311312 </ span >
312313 </ div >
313314
314- { /* 列头区域 */ }
315- { horizontalCombinations . map ( ( hCombination , colIndex ) => {
316- const hPath = generateDimensionPath ( hCombination )
317- const hasColumnData = Object . keys ( tableData ) . some ( cellKey =>
318- cellKey . startsWith ( hPath + '|' ) && Object . keys ( tableData [ cellKey ] ) . length > 0
319- )
320-
321- return hCombination . map ( ( value , dimIndex ) => (
322- < div
323- key = { `col-${ colIndex } -${ dimIndex } ` }
324- className = "grid-column-header"
325- style = { {
326- gridColumn : colDimensions + colIndex + 1 ,
327- gridRow : dimIndex + 1 ,
328- backgroundColor : 'white' ,
329- border : '1px solid #d9d9d9' ,
330- padding : '8px' ,
331- display : 'flex' ,
332- alignItems : 'center' ,
333- justifyContent : 'center' ,
334- position : 'relative' ,
335- fontSize : dimIndex === 0 ? '12px' : '11px' ,
336- fontWeight : dimIndex === 0 ? 'bold' : 'normal'
337- } }
338- >
339- { value }
340- { /* 只在最后一个维度显示菜单 */ }
341- { dimIndex === hCombination . length - 1 && ( onGenerateColumn || onClearColumn ) && (
342- < Dropdown
343- menu = { { items : createColumnMenu ( hPath , hasColumnData ) } }
344- trigger = { [ 'hover' ] }
345- placement = "bottomRight"
315+ { /* 列头区域 */ }
316+ { ( ( ) => {
317+ const renderedHeaders = new Set < string > ( )
318+ const headerElements : React . ReactNode [ ] = [ ]
319+
320+ // 为每个维度层级生成表头
321+ for ( let dimIndex = 0 ; dimIndex < metadata . horizontalDimensions . length ; dimIndex ++ ) {
322+ const dimension = metadata . horizontalDimensions [ dimIndex ]
323+ const remainingDimensions = metadata . horizontalDimensions . slice ( dimIndex + 1 )
324+ const isLastDimension = dimIndex === metadata . horizontalDimensions . length - 1
325+
326+ if ( isLastDimension ) {
327+ // 叶子节点:为每个具体的组合生成表头
328+ horizontalCombinations . forEach ( ( hCombination , colIndex ) => {
329+ const value = hCombination [ dimIndex ]
330+ const headerKey = `${ dimIndex } -${ colIndex } -${ value } `
331+
332+ headerElements . push (
333+ < div
334+ key = { headerKey }
335+ className = "grid-column-header"
336+ style = { {
337+ gridColumn : colDimensions + colIndex + 1 ,
338+ gridRow : dimIndex + 1 ,
339+ backgroundColor : 'white' ,
340+ border : '1px solid #d9d9d9' ,
341+ fontSize : dimIndex === 0 ? '12px' : '11px' ,
342+ fontWeight : dimIndex === 0 ? 'bold' : 'normal'
343+ } }
346344 >
347- < div className = "cell-menu-trigger" />
348- </ Dropdown >
349- ) }
350- </ div >
351- ) )
352- } ) }
345+ { value }
346+ </ div >
347+ )
348+ } )
349+ } else {
350+ // 非叶子节点:为每个维度值生成合并的表头
351+ const spanCount = remainingDimensions . reduce ( ( acc , dim ) => acc * dim . values . length , 1 )
352+
353+ dimension . values . forEach ( ( value , valueIndex ) => {
354+ const headerKey = `${ dimIndex } -${ value } `
355+
356+ if ( ! renderedHeaders . has ( headerKey ) ) {
357+ renderedHeaders . add ( headerKey )
358+
359+ // 计算起始列位置
360+ const startCol = colDimensions + 1 + valueIndex * spanCount
361+
362+ headerElements . push (
363+ < div
364+ key = { headerKey }
365+ className = "grid-column-header"
366+ style = { {
367+ gridColumn : `${ startCol } / ${ startCol + spanCount } ` ,
368+ gridRow : dimIndex + 1 ,
369+ backgroundColor : 'white' ,
370+ border : '1px solid #d9d9d9' ,
371+ fontSize : dimIndex === 0 ? '12px' : '11px' ,
372+ fontWeight : dimIndex === 0 ? 'bold' : 'normal'
373+ } }
374+ >
375+ { value }
376+ </ div >
377+ )
378+ }
379+ } )
380+ }
381+ }
382+
383+ // 为最后一个维度的每个组合添加菜单
384+ if ( onGenerateColumn || onClearColumn ) {
385+ horizontalCombinations . forEach ( ( hCombination , colIndex ) => {
386+ const hPath = generateDimensionPath ( hCombination )
387+ const hasColumnData = Object . keys ( tableData ) . some ( cellKey =>
388+ cellKey . startsWith ( hPath + '|' ) && Object . keys ( tableData [ cellKey ] ) . length > 0
389+ )
390+
391+ headerElements . push (
392+ < div
393+ key = { `menu-${ colIndex } ` }
394+ className = "grid-column-menu"
395+ style = { {
396+ gridColumn : colDimensions + colIndex + 1 ,
397+ gridRow : metadata . horizontalDimensions . length ,
398+ backgroundColor : 'transparent' ,
399+ border : 'none' ,
400+ padding : '0' ,
401+ position : 'relative' ,
402+ height : '100%' ,
403+ width : '100%' ,
404+ pointerEvents : 'none'
405+ } }
406+ >
407+ < Dropdown
408+ menu = { { items : createColumnMenu ( hPath , hasColumnData ) } }
409+ trigger = { [ 'hover' ] }
410+ placement = "bottomRight"
411+ >
412+ < div
413+ className = "cell-menu-trigger"
414+ style = { {
415+ position : 'absolute' ,
416+ top : 0 ,
417+ right : 0 ,
418+ pointerEvents : 'auto'
419+ } }
420+ />
421+ </ Dropdown >
422+ </ div >
423+ )
424+ } )
425+ }
426+
427+ return headerElements
428+ } ) ( ) }
353429
354430 { /* 行头区域 */ }
355- { verticalCombinations . map ( ( vCombination , rowIndex ) => {
356- const vPath = generateDimensionPath ( vCombination )
357- const hasRowData = horizontalCombinations . some ( ( hCombination ) => {
358- const hPath = generateDimensionPath ( hCombination )
359- const cellKey = `${ hPath } |${ vPath } `
360- return tableData [ cellKey ] && Object . keys ( tableData [ cellKey ] ) . length > 0
361- } )
362-
363- return vCombination . map ( ( value , dimIndex ) => (
364- < div
365- key = { `row-${ rowIndex } -${ dimIndex } ` }
366- className = "grid-row-header"
367- style = { {
368- gridColumn : dimIndex + 1 ,
369- gridRow : rowDimensions + rowIndex + 1 ,
370- backgroundColor : 'white' ,
371- border : '1px solid #d9d9d9' ,
372- padding : '8px' ,
373- display : 'flex' ,
374- alignItems : 'center' ,
375- justifyContent : 'center' ,
376- position : 'relative' ,
377- fontSize : '12px' ,
378- fontWeight : dimIndex === 0 ? 'bold' : 'normal'
379- } }
380- >
381- { value }
382- { /* 只在最后一个维度显示菜单 */ }
383- { dimIndex === vCombination . length - 1 && ( onGenerateRow || onClearRow ) && (
384- < Dropdown
385- menu = { { items : createRowMenu ( vPath , hasRowData ) } }
386- trigger = { [ 'hover' ] }
387- placement = "bottomRight"
431+ { ( ( ) => {
432+ const renderedHeaders = new Set < string > ( )
433+ const headerElements : React . ReactNode [ ] = [ ]
434+
435+ // 为每个维度层级生成表头
436+ for ( let dimIndex = 0 ; dimIndex < metadata . verticalDimensions . length ; dimIndex ++ ) {
437+ const dimension = metadata . verticalDimensions [ dimIndex ]
438+ const remainingDimensions = metadata . verticalDimensions . slice ( dimIndex + 1 )
439+ const isLastDimension = dimIndex === metadata . verticalDimensions . length - 1
440+
441+ if ( isLastDimension ) {
442+ // 叶子节点:为每个具体的组合生成表头
443+ verticalCombinations . forEach ( ( vCombination , rowIndex ) => {
444+ const value = vCombination [ dimIndex ]
445+ const headerKey = `${ dimIndex } -${ rowIndex } -${ value } `
446+
447+ headerElements . push (
448+ < div
449+ key = { headerKey }
450+ className = "grid-row-header"
451+ style = { {
452+ gridColumn : dimIndex + 1 ,
453+ gridRow : rowDimensions + rowIndex + 1 ,
454+ backgroundColor : 'white' ,
455+ border : '1px solid #d9d9d9' ,
456+ fontSize : '12px' ,
457+ fontWeight : dimIndex === 0 ? 'bold' : 'normal'
458+ } }
388459 >
389- < div className = "cell-menu-trigger" />
390- </ Dropdown >
391- ) }
392- </ div >
393- ) )
394- } ) }
460+ { value }
461+ </ div >
462+ )
463+ } )
464+ } else {
465+ // 非叶子节点:为每个维度值生成合并的表头
466+ const spanCount = remainingDimensions . reduce ( ( acc , dim ) => acc * dim . values . length , 1 )
467+
468+ dimension . values . forEach ( ( value , valueIndex ) => {
469+ const headerKey = `${ dimIndex } -${ value } `
470+
471+ if ( ! renderedHeaders . has ( headerKey ) ) {
472+ renderedHeaders . add ( headerKey )
473+
474+ // 计算起始行位置
475+ const startRow = rowDimensions + 1 + valueIndex * spanCount
476+
477+ headerElements . push (
478+ < div
479+ key = { headerKey }
480+ className = "grid-row-header"
481+ style = { {
482+ gridColumn : dimIndex + 1 ,
483+ gridRow : `${ startRow } / ${ startRow + spanCount } ` ,
484+ backgroundColor : 'white' ,
485+ border : '1px solid #d9d9d9' ,
486+ fontSize : '12px' ,
487+ fontWeight : dimIndex === 0 ? 'bold' : 'normal'
488+ } }
489+ >
490+ { value }
491+ </ div >
492+ )
493+ }
494+ } )
495+ }
496+ }
497+
498+ // 为最后一个维度的每个组合添加菜单
499+ if ( onGenerateRow || onClearRow ) {
500+ verticalCombinations . forEach ( ( vCombination , rowIndex ) => {
501+ const vPath = generateDimensionPath ( vCombination )
502+ const hasRowData = horizontalCombinations . some ( ( hCombination ) => {
503+ const hPath = generateDimensionPath ( hCombination )
504+ const cellKey = `${ hPath } |${ vPath } `
505+ return tableData [ cellKey ] && Object . keys ( tableData [ cellKey ] ) . length > 0
506+ } )
507+
508+ headerElements . push (
509+ < div
510+ key = { `menu-${ rowIndex } ` }
511+ className = "grid-row-menu"
512+ style = { {
513+ gridColumn : metadata . verticalDimensions . length ,
514+ gridRow : rowDimensions + rowIndex + 1 ,
515+ backgroundColor : 'transparent' ,
516+ border : 'none' ,
517+ padding : '0' ,
518+ position : 'relative' ,
519+ height : '100%' ,
520+ width : '100%' ,
521+ pointerEvents : 'none'
522+ } }
523+ >
524+ < Dropdown
525+ menu = { { items : createRowMenu ( vPath , hasRowData ) } }
526+ trigger = { [ 'hover' ] }
527+ placement = "bottomRight"
528+ >
529+ < div
530+ className = "cell-menu-trigger"
531+ style = { {
532+ position : 'absolute' ,
533+ top : 0 ,
534+ right : 0 ,
535+ pointerEvents : 'auto'
536+ } }
537+ />
538+ </ Dropdown >
539+ </ div >
540+ )
541+ } )
542+ }
543+
544+ return headerElements
545+ } ) ( ) }
395546
396547 { /* 数据单元格区域 */ }
397548 { verticalCombinations . map ( ( vCombination , rowIndex ) => {
@@ -412,31 +563,24 @@ export default function CrosstabTable({
412563 gridRow : rowDimensions + rowIndex + 1 ,
413564 backgroundColor : isGenerating ? '#f0f0f0' : 'white' ,
414565 border : '1px solid #d9d9d9' ,
415- padding : '8px' ,
416- position : 'relative' ,
417- cursor : 'pointer' ,
418- minHeight : '44px' ,
419- display : 'flex' ,
420- alignItems : 'center'
566+ cursor : 'pointer'
421567 } }
422568 onClick = { ( ) => {
423569 if ( ! isGenerating && onGenerateCell ) {
424570 onGenerateCell ( hPath , vPath )
425571 }
426572 } }
427573 >
428- < div style = { {
429- fontSize : '12px' ,
430- wordBreak : 'break-word' ,
431- width : '100%'
432- } } >
433- { isGenerating ? (
434- < Text type = "secondary" > 生成中...</ Text >
435- ) : cellContent ? (
436- < Text > { cellContent } </ Text >
437- ) : (
438- < Text type = "secondary" > 点击生成</ Text >
439- ) }
574+ < div className = "cell-content" >
575+ < div className = "cell-text" style = { { fontSize : '12px' } } >
576+ { isGenerating ? (
577+ < Text type = "secondary" > 生成中...</ Text >
578+ ) : cellContent ? (
579+ < Text > { cellContent } </ Text >
580+ ) : (
581+ < Text type = "secondary" > 点击生成</ Text >
582+ ) }
583+ </ div >
440584 </ div >
441585 < Dropdown
442586 menu = { { items : createCellMenu ( hPath , vPath , cellContent ) } }
0 commit comments