112 lines
3.7 KiB
TypeScript
112 lines
3.7 KiB
TypeScript
import type { TableColumnsType } from 'antd'
|
|
|
|
type Column<RecordType extends object> = TableColumnsType<RecordType>[number]
|
|
|
|
type WidthContext<RecordType extends object> = {
|
|
leafPercent: WeakMap<Column<RecordType>, number>
|
|
firstLeaf: Column<RecordType> | undefined
|
|
}
|
|
|
|
/**
|
|
* 把业务列宽(px 数值)按相对权重归一化为百分比。
|
|
* rowSelection / expandable 等内置列由调用方传入需要预留的百分比。
|
|
*/
|
|
export function normalizePercentColumns<RecordType extends object>(
|
|
columns: TableColumnsType<RecordType>,
|
|
options: { reservedPercent?: number } = {},
|
|
): TableColumnsType<RecordType> {
|
|
const leaves: Column<RecordType>[] = []
|
|
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<Column<RecordType>, 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<RecordType extends object>(
|
|
columns: TableColumnsType<RecordType>,
|
|
context: WidthContext<RecordType>,
|
|
): TableColumnsType<RecordType> {
|
|
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<RecordType extends object>(
|
|
columns: TableColumnsType<RecordType>,
|
|
leaves: Column<RecordType>[],
|
|
) {
|
|
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<RecordType extends object>(column: Column<RecordType>) {
|
|
return numericWidth(column.width) ?? numericWidth(column.minWidth)
|
|
}
|
|
|
|
function withoutMinWidth<RecordType extends object>(column: Column<RecordType>) {
|
|
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
|
|
}
|