Skip to content

Commit dfb0846

Browse files
committed
refactor: Optimize CrosstabTable component by enhancing header rendering logic and improving grid layout for better responsiveness and user interaction
1 parent 2e25f1c commit dfb0846

File tree

3 files changed

+613
-437
lines changed

3 files changed

+613
-437
lines changed

src/renderer/src/components/pages/crosstab/CrosstabTable.tsx

Lines changed: 240 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
generateAxisCombinations,
1515
generateDimensionPath,
1616
} from './CrosstabUtils'
17+
import './crosstab-table.css'
1718

1819
const { 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

Comments
 (0)