基于vue3.0和element-plus的组件库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

162 lines
4.9 KiB

/**
* usePretextColumnWidths
*
* Computes flex-based column width parameters using pretext text measurement.
* - flexBasis: mean (μ) of measured widths + padding
* - flexGrow/flexShrink: derived from variance (σ²)
* - minWidth: max(μ - 2*σ, 50) + padding
* - maxWidth: 300px
*
* User can override any parameter via column definition.
*/
import { computed, type Ref } from "vue";
import { measureShrinkWrapWidth } from "./measureText";
import type { ListTableColumn } from "./types";
const DEFAULT_FONT = "14px Inter, sans-serif";
const DEFAULT_HEADER_FONT = "bold 14px Inter, sans-serif";
const DEFAULT_PADDING = 16;
const CELL_PADDING = DEFAULT_PADDING * 2; // left + right
const MAX_WIDTH = 300;
const MIN_BASE_WIDTH = 50;
export interface ColumnFlexConfig {
key: string;
flexBasis: number;
flexGrow: number;
flexShrink: number;
minWidth: number;
maxWidth: number;
measuredMean: number;
measuredVariance: number;
measuredSampleCount: number;
}
/**
* Sample rows evenly distributed across the dataset.
* Takes first, last, and evenly spaced rows in between.
*/
function sampleRows<T>(data: T[], sampleSize: number): T[] {
if (data.length <= sampleSize) return data;
const result: T[] = [];
const step = (data.length - 1) / (sampleSize - 1);
for (let i = 0; i < sampleSize; i++) {
result.push(data[Math.round(i * step)]);
}
return result;
}
/**
* Compute mean and variance of an array of numbers
*/
function computeStats(widths: number[]): { mean: number; variance: number } {
if (widths.length === 0) return { mean: 0, variance: 0 };
const mean = widths.reduce((sum, w) => sum + w, 0) / widths.length;
const variance =
widths.reduce((sum, w) => sum + Math.pow(w - mean, 2), 0) / widths.length;
return { mean, variance };
}
/**
* Derive flexGrow/flexShrink from variance score
* varianceScore = min(1, σ / (μ * 0.5))
* flex = 0.1 + varianceScore * 1.9 → range [0.1, 2.0]
*/
function varianceToFlex(mean: number, variance: number): number {
if (mean <= 0) return 0.1;
const stdDev = Math.sqrt(variance);
const varianceScore = Math.min(1, stdDev / (mean * 0.5));
return 0.1 + varianceScore * 1.9;
}
export function usePretextColumnWidths<T>(
data: Ref<T[]>,
columns: Ref<ListTableColumn<T>[]>,
containerWidth: Ref<number>,
options?: {
font?: string;
headerFont?: string;
sampleSize?: number;
}
) {
const font = options?.font ?? DEFAULT_FONT;
const headerFont = options?.headerFont ?? DEFAULT_HEADER_FONT;
const sampleSize = options?.sampleSize ?? 100;
const computedConfigs = computed<ColumnFlexConfig[]>(() => {
if (!columns.value.length) return [];
const sampled = sampleRows(data.value, sampleSize);
const configs: ColumnFlexConfig[] = [];
for (const col of columns.value) {
const colKey = String(col.key);
// User overrides (convert string values to numbers)
const userFlexGrow = col.flexGrow;
const userFlexShrink = col.flexShrink;
const userFlexBasis =
col.flexBasis !== undefined && col.flexBasis !== "auto"
? Number(col.flexBasis)
: undefined;
const userMinWidth =
col.minWidth !== undefined ? Number(col.minWidth) : undefined;
const userMaxWidth =
col.maxWidth !== undefined ? Number(col.maxWidth) : undefined;
// Measure header width
const headerText = col.name || col.i18n || colKey;
const headerWidth = measureShrinkWrapWidth(headerText, headerFont) + CELL_PADDING;
// Measure sampled cell widths
const cellWidths: number[] = [];
for (const row of sampled) {
const rawValue = (row as any)[col.dataKey || colKey];
const cellText = rawValue == null ? "" : String(rawValue);
if (cellText) {
const w = measureShrinkWrapWidth(cellText, font) + CELL_PADDING;
cellWidths.push(w);
}
}
// Include header in stats
const allWidths = [headerWidth, ...cellWidths];
const { mean, variance } = computeStats(allWidths);
// Derived values
const flexBasis = userFlexBasis ?? mean;
const minWidth = userMinWidth
? Number(userMinWidth)
: Math.max(mean - 2 * Math.sqrt(variance), MIN_BASE_WIDTH) + CELL_PADDING;
const maxWidth = userMaxWidth ? Number(userMaxWidth) : MAX_WIDTH;
const flexGrow = userFlexGrow ?? varianceToFlex(mean, variance);
const flexShrink = userFlexShrink ?? varianceToFlex(mean, variance);
configs.push({
key: colKey,
flexBasis,
flexGrow,
flexShrink,
minWidth,
maxWidth,
measuredMean: mean,
measuredVariance: variance,
measuredSampleCount: allWidths.length,
});
}
return configs;
});
// Total flex basis (sum of all flexBasis values)
const totalFlexBasis = computed(() =>
computedConfigs.value.reduce((sum, c) => sum + c.flexBasis, 0)
);
return {
computedConfigs,
totalFlexBasis,
};
}