Files
xk-admin/apps/web-antd/src/components/canvas-spreadsheet/composables/useAutoMeasure.ts
李琦 a38da5ce92
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
1. Excel交互组件封装优化
2026-06-26 12:52:26 +08:00

220 lines
5.5 KiB
TypeScript

import type { SpreadsheetColumnConfig } from '../types';
import {
COLUMN_LETTER_HEIGHT,
DEFAULT_COL_WIDTH,
DEFAULT_ROW_HEIGHT,
MIN_COL_WIDTH,
ROW_NUMBER_WIDTH,
} from '../types';
import { getDataColX } from '../canvas/columnLayout';
const CELL_PADDING = 16;
const SAMPLE_ROWS = 50;
function measureTextWidth(
ctx: CanvasRenderingContext2D,
text: string,
): number {
return ctx.measureText(text).width + CELL_PADDING;
}
function formatCellDisplay(value: unknown, cellType: import('../types').CellType): string { if (value === null || value === undefined) return '';
if (cellType === 'number') {
const n = Number(value);
return Number.isFinite(n) ? String(n) : String(value);
}
if (typeof value === 'object') return JSON.stringify(value);
return String(value);
}
export function measureColumnWidths(
canvas: HTMLCanvasElement,
headers: Array<{ title: string; col: number }>,
columns: SpreadsheetColumnConfig[],
rows: Record<string, unknown>[],
defaultWidth: number,
resolveDisplayValue?: (
dataRowIndex: number,
colCfg: SpreadsheetColumnConfig,
row: Record<string, unknown>,
) => unknown,
formatWithSuffix?: (
value: unknown,
colCfg: SpreadsheetColumnConfig,
) => string,
): number[] { const ctx = canvas.getContext('2d');
if (!ctx) return headers.map(() => defaultWidth);
ctx.font = '13px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
const maxCol = Math.max(...headers.map((h) => h.col), 0);
const widths = new Array<number>(maxCol + 1).fill(defaultWidth);
for (const header of headers) {
widths[header.col] = Math.max(
widths[header.col] ?? defaultWidth,
measureTextWidth(ctx, header.title),
MIN_COL_WIDTH,
);
}
const sample = rows.slice(0, SAMPLE_ROWS);
for (const colCfg of columns) {
let maxW = widths[colCfg.col] ?? defaultWidth;
sample.forEach((row, dataRowIdx) => {
const raw = resolveDisplayValue
? resolveDisplayValue(dataRowIdx, colCfg, row)
: row[colCfg.fieldId];
const text = formatWithSuffix
? formatWithSuffix(raw, colCfg)
: formatCellDisplay(raw, colCfg.cellType === 'formula' ? 'number' : colCfg.cellType);
maxW = Math.max(maxW, measureTextWidth(ctx, text));
});
widths[colCfg.col] = Math.max(maxW, MIN_COL_WIDTH);
}
return widths;
}
export function getRowHeight(
cellType: import('../types').CellType, configured: number | 'auto' | undefined,
): number {
if (typeof configured === 'number') return configured;
switch (cellType) {
case 'image':
case 'video':
return 48;
default:
return DEFAULT_ROW_HEIGHT;
}
}
export function getTotalWidth(colWidths: number[]): number {
return colWidths.reduce((sum, w) => sum + w, 0);
}
export function getRowTop(rowIndex: number, rowHeight: number): number {
return rowIndex * rowHeight;
}
export function getColLeft(colIndex: number, colWidths: number[]): number {
let left = 0;
for (let i = 0; i < colIndex; i += 1) {
left += colWidths[i] ?? DEFAULT_COL_WIDTH;
}
return left;
}
function getDataColScreenX(
col: number,
colWidths: number[],
scrollLeft: number,
fixedCols: number[],
fixedWidth: number,
): number {
return getDataColX(col, colWidths, scrollLeft, fixedCols, fixedWidth);
}
export function hitTestCell(
x: number,
y: number,
scrollTop: number,
scrollLeft: number,
colWidths: number[],
rowHeight: number,
rowCount: number,
colCount: number,
fixedCols: number[] = [],
fixedWidth = ROW_NUMBER_WIDTH,
): { row: number; col: number } | null {
const bodyY = y + scrollTop;
if (bodyY < 0) return null;
const row = Math.floor(bodyY / rowHeight);
if (row < 0 || row >= rowCount) return null;
for (let col = 0; col < colCount; col += 1) {
const cellX = getDataColScreenX(
col,
colWidths,
scrollLeft,
fixedCols,
fixedWidth,
);
const w = colWidths[col] ?? DEFAULT_COL_WIDTH;
if (x >= cellX && x < cellX + w) {
return { row, col };
}
}
return null;
}
export function hitTestRowNumber(
x: number,
y: number,
scrollTop: number,
rowHeight: number,
rowCount: number,
): number | null {
if (x < 0 || x >= ROW_NUMBER_WIDTH) return null;
const bodyY = y + scrollTop;
if (bodyY < 0) return null;
const row = Math.floor(bodyY / rowHeight);
if (row < 0 || row >= rowCount) return null;
return row;
}
export type HeaderHitArea = 'corner' | 'columnLetter' | 'columnTitle';
export function hitTestHeaderArea(
x: number,
y: number,
scrollLeft: number,
colWidths: number[],
colCount: number,
fixedCols: number[] = [],
fixedWidth = ROW_NUMBER_WIDTH,
): { area: HeaderHitArea; col?: number } | null {
if (x >= 0 && x < ROW_NUMBER_WIDTH) {
return { area: 'corner' };
}
const col = hitTestHeaderCol(
x,
scrollLeft,
colWidths,
colCount,
fixedCols,
fixedWidth,
);
if (col === null) return null;
if (y < COLUMN_LETTER_HEIGHT) {
return { area: 'columnLetter', col };
}
return { area: 'columnTitle', col };
}
export function hitTestHeaderCol(
x: number,
scrollLeft: number,
colWidths: number[],
colCount: number,
fixedCols: number[] = [],
fixedWidth = ROW_NUMBER_WIDTH,
): number | null {
for (let col = 0; col < colCount; col += 1) {
const cellX = getDataColScreenX(
col,
colWidths,
scrollLeft,
fixedCols,
fixedWidth,
);
const w = colWidths[col] ?? DEFAULT_COL_WIDTH;
if (x >= cellX && x < cellX + w) return col;
}
return null;
}
export { getDataColScreenX };
export { formatCellDisplay, CELL_PADDING };