refactor(grid): 提取 cellValue 格式化到共享模块并增加测试 (#218)
将 CellValue 类型和 displayCellValue(原 formatCell)提取到 src/lib/cellValue.ts,DataGrid.vue 和 useDataGridExport.ts 统一引用, 移除 composable 中冗余的 formatCell 参数传递。
This commit is contained in:
parent
e4c1d6639b
commit
dddb813fc6
|
|
@ -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({
|
|||
<div class="text-muted-foreground">{{ t("grid.formattedJson") }}</div>
|
||||
<pre
|
||||
class="max-h-72 overflow-auto rounded border bg-muted/20 p-2 font-mono text-xs whitespace-pre-wrap break-words"
|
||||
>{{ activeCellDetail.formattedJson }}</pre
|
||||
>
|
||||
{{ activeCellDetail.formattedJson }}</pre
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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 = "你好世界 🚀🎉 <div>test</div> 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]");
|
||||
});
|
||||
Loading…
Reference in New Issue