import type { TableColumnsType } from 'antd' type Column = TableColumnsType[number] type WidthContext = { leafPercent: WeakMap, number> firstLeaf: Column | undefined } /** * 把业务列宽(px 数值)按相对权重归一化为百分比。 * rowSelection / expandable 等内置列由调用方传入需要预留的百分比。 */ export function normalizePercentColumns( columns: TableColumnsType, options: { reservedPercent?: number } = {}, ): TableColumnsType { const leaves: Column[] = [] collectLeaves(columns, leaves) const explicitWeights = leaves .map((column) => columnWeight(column)) .filter((width): width is number => width !== null) const fallbackWeight = explicitWeights.length > 0 ? explicitWeights.reduce((sum, width) => sum + width, 0) / explicitWeights.length : 100 const totalWeight = leaves.reduce( (sum, column) => sum + (columnWeight(column) ?? fallbackWeight), 0, ) const budgetPercent = Math.max(60, 100 - Math.max(0, options.reservedPercent ?? 0)) const leafPercent = new WeakMap, number>() for (const column of leaves) { const weight = columnWeight(column) ?? fallbackWeight leafPercent.set(column, (weight / totalWeight) * budgetPercent) } return applyPercentWidths(columns, { leafPercent, firstLeaf: leaves[0] }) } function applyPercentWidths( columns: TableColumnsType, context: WidthContext, ): TableColumnsType { return columns.map((column) => { const children = 'children' in column ? column.children : undefined if (Array.isArray(children) && children.length > 0) { const normalizedChildren = applyPercentWidths(children, context) const width = normalizedChildren.reduce((sum, child) => sum + percentOf(child.width), 0) return { ...withoutMinWidth(column), width: `${width.toFixed(4)}%`, align: 'center', children: normalizedChildren, } } return { ...withoutMinWidth(column), width: `${(context.leafPercent.get(column) ?? 0).toFixed(4)}%`, align: column === context.firstLeaf ? 'left' : 'center', ...(column === context.firstLeaf ? { className: appendClassName(column.className, 'percent-table-first-column') } : {}), } }) } function collectLeaves( columns: TableColumnsType, leaves: Column[], ) { for (const column of columns) { const children = 'children' in column ? column.children : undefined if (Array.isArray(children) && children.length > 0) { collectLeaves(children, leaves) } else { leaves.push(column) } } } function columnWeight(column: Column) { return numericWidth(column.width) ?? numericWidth(column.minWidth) } function withoutMinWidth(column: Column) { const { minWidth: _minWidth, ...rest } = column return rest } function appendClassName(className: string | undefined, next: string) { return [className, next].filter(Boolean).join(' ') } function numericWidth(width: unknown) { if (typeof width === 'number' && Number.isFinite(width) && width > 0) return width if (typeof width === 'string') { const parsed = Number.parseFloat(width) if (Number.isFinite(parsed) && parsed > 0) return parsed } return null } function percentOf(width: unknown) { if (typeof width !== 'string' || !width.endsWith('%')) return 0 const parsed = Number.parseFloat(width) return Number.isFinite(parsed) ? parsed : 0 }