From c4a268f670b44e09f4bbd937d47d2235a04ec6b9 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Fri, 22 May 2026 20:08:16 +0800 Subject: [PATCH] fix(app): normalize grid JSON smart quotes --- .../src/composables/useDataGridEditor.ts | 21 ++++++- packages/app-tests/dataGridEditor.test.ts | 56 +++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/composables/useDataGridEditor.ts b/apps/desktop/src/composables/useDataGridEditor.ts index 7f142867b..e0d29562a 100644 --- a/apps/desktop/src/composables/useDataGridEditor.ts +++ b/apps/desktop/src/composables/useDataGridEditor.ts @@ -269,7 +269,26 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) { if (typeof oldVal === "boolean") { return value === "true" || value === "1"; } - return value; + return normalizeSmartQuotedJsonInput(value); + } + + function normalizeSmartQuotedJsonInput(value: string): string { + if (!/[“”]/.test(value)) return value; + const trimmed = value.trim(); + if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return value; + try { + JSON.parse(value); + return value; + } catch { + // macOS smart punctuation can turn JSON delimiters into Chinese-style quotes. + } + const normalized = value.replace(/[“”]/g, '"'); + try { + JSON.parse(normalized); + return normalized; + } catch { + return value; + } } function canEditColumn(columnIndex: number): boolean { diff --git a/packages/app-tests/dataGridEditor.test.ts b/packages/app-tests/dataGridEditor.test.ts index 366cd76bb..3eb3a4b1a 100644 --- a/packages/app-tests/dataGridEditor.test.ts +++ b/packages/app-tests/dataGridEditor.test.ts @@ -379,3 +379,59 @@ test("saving edited rows without deletes does not reload table data", async () = assert.deepEqual(emitted, []); }); + +test("saving manually typed JSON from a MySQL grid normalizes smart quotes", async () => { + setActivePinia(createPinia()); + installBrowserTestGlobals(); + + const result = computed(() => ({ + columns: ["id", "payload"], + rows: [[1, "{}"] as CellValue[]], + })); + const rowStatusFilter = ref<"all" | "changed" | "edited" | "new" | "deleted">("all"); + const executedSql: string[] = []; + + const editor = useDataGridEditor({ + result, + editable: computed(() => true), + databaseType: computed(() => "mysql"), + connectionId: computed(() => undefined), + database: computed(() => undefined), + tableMeta: computed(() => ({ + tableName: "settings", + columns: [column("id", true), { ...column("payload"), data_type: "json" }], + primaryKeys: ["id"], + })), + onExecuteSql: computed(() => async (sql: string) => { + executedSql.push(sql); + }), + customSave: computed(() => undefined), + sql: computed(() => "SELECT id, payload FROM settings"), + searchText: ref(""), + whereFilterInput: ref(""), + orderByInput: ref(""), + rowStatusFilter, + pageSize: ref(50), + currentPage: ref(1), + getRowItem: (rowId) => { + if (rowId !== 0) return undefined; + return { + id: 0, + sourceIndex: 0, + data: result.value.rows[0], + isNew: false, + isDeleted: false, + isDirtyCol: [false, false], + status: "clean", + }; + }, + emit: () => {}, + }); + + editor.applyCellValue(0, 1, "{“2:3”:“3:4”,“3:2”:“4:3”,“21:9”:“16:9”}"); + await editor.saveChanges(); + + assert.deepEqual(executedSql, [ + `UPDATE "settings" SET "payload" = '{"2:3":"3:4","3:2":"4:3","21:9":"16:9"}' WHERE "id" = 1;`, + ]); +});