Files
order_site/apps/frontend/src/utils/percent-table-layout.ts
T

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
}