From dddb813fc694a0ba2338149f3b5cfed45189569f Mon Sep 17 00:00:00 2001 From: Abeautifulsnow Date: Mon, 11 May 2026 18:56:08 +0800 Subject: [PATCH] =?UTF-8?q?refactor(grid):=20=E6=8F=90=E5=8F=96=20cellValu?= =?UTF-8?q?e=20=E6=A0=BC=E5=BC=8F=E5=8C=96=E5=88=B0=E5=85=B1=E4=BA=AB?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=B9=B6=E5=A2=9E=E5=8A=A0=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=20(#218)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 CellValue 类型和 displayCellValue(原 formatCell)提取到 src/lib/cellValue.ts,DataGrid.vue 和 useDataGridExport.ts 统一引用, 移除 composable 中冗余的 formatCell 参数传递。 --- src/components/grid/DataGrid.vue | 16 +++++---- src/composables/useDataGridExport.ts | 11 +++--- src/lib/cellValue.ts | 8 +++++ tests/cellValue.test.ts | 52 ++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 src/lib/cellValue.ts create mode 100644 tests/cellValue.test.ts diff --git a/src/components/grid/DataGrid.vue b/src/components/grid/DataGrid.vue index 0bd87cd10..99f92a989 100644 --- a/src/components/grid/DataGrid.vue +++ b/src/components/grid/DataGrid.vue @@ -63,6 +63,7 @@ import { buildTableSelectSql, quoteTableIdentifier } from "@/lib/tableSelectSql" import { isHiddenGridColumn, usesSyntheticRowIdKey } from "@/lib/tableEditing"; import { formatGridSqlLiteral } from "@/lib/dataGridSql"; import { matchesRowStatusFilter, type RowStatus, type RowStatusFilter } from "@/lib/gridRowStatus"; +import { displayCellValue, type CellValue } from "@/lib/cellValue"; import { useToast } from "@/composables/useToast"; import { useDataGridExport } from "@/composables/useDataGridExport"; @@ -817,7 +818,6 @@ function changePageSize(size: number) { } // --- Editing (composable) --- -type CellValue = string | number | boolean | null; interface RowItem { id: number; @@ -1041,7 +1041,7 @@ const activeCellDetail = computed(() => { const column = props.result.columns[cell.col]; if (!item || !column) return null; const value = item.data[cell.col] ?? null; - const rawValue = formatCell(value); + const rawValue = displayCellValue(value); const valueText = value === null ? "" : typeof value === "object" ? JSON.stringify(value) : String(value); const trimmed = valueText.trim(); const maybeJson = typeof value === "string" && (trimmed.startsWith("{") || trimmed.startsWith("[")); @@ -1339,7 +1339,6 @@ const { selectedCells, contextCell: exportContextCell, getRowItem: (rowId: number) => visibleDisplayItems.value.find((item) => item.id === rowId), - formatCell, quoteIdent, escapeVal, }); @@ -1419,8 +1418,10 @@ async function onGridKeydown(event: KeyboardEvent) { } function copyDetailValue() { - if (!activeCellDetail.value) return; - copyText(activeCellDetail.value.rawValue); + const detail = activeCellDetail.value; + if (!detail) return; + const text = detail.value === null ? "" : displayCellValue(detail.value); + copyText(text); } function copyDetailColumnName() { @@ -2326,7 +2327,8 @@ defineExpose({
{{ t("grid.formattedJson") }}
{{ activeCellDetail.formattedJson }}
+ {{ activeCellDetail.formattedJson }} @@ -2618,9 +2620,11 @@ defineExpose({ color: oklch(0.6 0.15 250); font-weight: 600; } + .ddl-code :deep(.ddl-ident) { color: oklch(0.65 0.15 150); } + .ddl-code :deep(.ddl-str) { color: oklch(0.65 0.15 50); } diff --git a/src/composables/useDataGridExport.ts b/src/composables/useDataGridExport.ts index be253548c..0c9a9f002 100644 --- a/src/composables/useDataGridExport.ts +++ b/src/composables/useDataGridExport.ts @@ -10,8 +10,7 @@ import { type SelectionData, } from "@/lib/gridSelection"; import { useToast } from "@/composables/useToast"; - -type CellValue = string | number | boolean | null; +import { displayCellValue, type CellValue } from "@/lib/cellValue"; interface RowItem { id: number; @@ -36,7 +35,6 @@ export interface UseDataGridExportOptions { | Ref<{ rowId: number; rowIndex: number; col: number } | null> | ComputedRef<{ rowId: number; rowIndex: number; col: number } | null>; getRowItem: (rowId: number) => RowItem | undefined; - formatCell: (value: CellValue) => string; quoteIdent: (name: string) => string; escapeVal: (value: CellValue) => string; } @@ -54,7 +52,6 @@ export function useDataGridExport(options: UseDataGridExportOptions) { selectedCells, contextCell, getRowItem, - formatCell, quoteIdent, escapeVal, } = options; @@ -90,7 +87,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) { if (!contextCell.value || contextCell.value.col < 0) return; const item = getRowItem(contextCell.value.rowId); const val = item?.data[contextCell.value.col] ?? null; - copyText(formatCell(val)); + copyText(displayCellValue(val)); } function copyRow() { @@ -118,7 +115,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) { function copyAll() { const header = columns.value.join("\t"); - const body = displayItems.value.map((item) => item.data.map((c) => formatCell(c)).join("\t")).join("\n"); + const body = displayItems.value.map((item) => item.data.map((c) => displayCellValue(c)).join("\t")).join("\n"); copyText(`${header}\n${body}`); } @@ -184,7 +181,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) { // --- Export functions --- async function exportCsv() { try { - const rows = displayItems.value.map((item) => item.data.map((c) => formatCell(c))); + const rows = displayItems.value.map((item) => item.data.map((c) => displayCellValue(c))); if (await saveFileContent(formatCsv(columns.value, rows), "export.csv", "CSV", "csv")) { toast(t("grid.exported")); } diff --git a/src/lib/cellValue.ts b/src/lib/cellValue.ts new file mode 100644 index 000000000..b3403622b --- /dev/null +++ b/src/lib/cellValue.ts @@ -0,0 +1,8 @@ +export type CellValue = string | number | boolean | null; + +export function displayCellValue(value: CellValue): string { + if (value === null) return "NULL"; + if (typeof value === "boolean") return value ? "true" : "false"; + if (typeof value === "object") return JSON.stringify(value); + return String(value); +} diff --git a/tests/cellValue.test.ts b/tests/cellValue.test.ts new file mode 100644 index 000000000..c35fc063c --- /dev/null +++ b/tests/cellValue.test.ts @@ -0,0 +1,52 @@ +import { strict as assert } from "node:assert"; +import test from "node:test"; +import { displayCellValue } from "../src/lib/cellValue.ts"; + +test("displayCellValue returns NULL for null", () => { + assert.equal(displayCellValue(null), "NULL"); +}); + +test("displayCellValue returns string representation for booleans", () => { + assert.equal(displayCellValue(true), "true"); + assert.equal(displayCellValue(false), "false"); +}); + +test("displayCellValue returns String for numbers", () => { + assert.equal(displayCellValue(42), "42"); + assert.equal(displayCellValue(-3.14), "-3.14"); + assert.equal(displayCellValue(0), "0"); +}); + +test("displayCellValue returns String for strings", () => { + assert.equal(displayCellValue("hello"), "hello"); + assert.equal(displayCellValue(""), ""); +}); + +test("displayCellValue does not truncate long strings", () => { + const long = "a".repeat(1000); + assert.equal(displayCellValue(long), long); +}); + +test("displayCellValue serializes JSON strings containing complex nested objects", () => { + const payload = '{"metadata":{"version":"2.1.0","tags":["alpha","beta"],"flags":{"enabled":true,"nested":{"depth":3,"items":[{"id":1,"label":"a"},{"id":2,"label":"b"}]}}},"data":{"records":[{"key":"k-001","value":42},{"key":"k-002","value":-999}],"summary":{"total":3,"valid":2}}}'; + assert.equal(displayCellValue(payload), payload); +}); + +test("displayCellValue handles strings with unicode and special characters", () => { + const unicodeStr = "你好世界 🚀🎉
test
Line1\nLine2\tTabbed"; + assert.equal(displayCellValue(unicodeStr), unicodeStr); +}); + +test("displayCellValue handles large integer and scientific notation as strings", () => { + const bigIntStr = "9007199254740991"; + const sciStr = "1e-10"; + assert.equal(displayCellValue(bigIntStr), bigIntStr); + assert.equal(displayCellValue(sciStr), sciStr); +}); + +test("displayCellValue handles empty JSON-like strings without alteration", () => { + assert.equal(displayCellValue("{}"), "{}"); + assert.equal(displayCellValue("[]"), "[]"); + assert.equal(displayCellValue('{"key":"value"}'), '{"key":"value"}'); + assert.equal(displayCellValue("[1,2,3]"), "[1,2,3]"); +}); \ No newline at end of file