From 6ae49d4b849e20695ef62fba7b8f00ddf74fb98c Mon Sep 17 00:00:00 2001 From: tianqijiuyun-latiao <69459608+tianqijiuyun-latiao@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:42:33 +0800 Subject: [PATCH] =?UTF-8?q?fix(kingbase-data-grid):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=87=91=E4=BB=93=E6=89=93=E5=BC=80=E8=A1=A8=E5=8D=A1=E9=A1=BF?= =?UTF-8?q?=E5=B9=B6=E9=99=8D=E4=BD=8E=E5=AF=B9=E8=B1=A1=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E5=BC=80=E9=94=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refs #178 --- frontend/src/components/DataGrid.tsx | 31 +++++++++++++++++++++++++++- internal/db/query_value.go | 11 ++++++++++ internal/db/query_value_test.go | 30 +++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/DataGrid.tsx b/frontend/src/components/DataGrid.tsx index 5adf70f..981173c 100644 --- a/frontend/src/components/DataGrid.tsx +++ b/frontend/src/components/DataGrid.tsx @@ -142,10 +142,19 @@ const formatCellValue = (val: any) => { try { if (val === null) return NULL; if (typeof val === 'object') { + if (!Array.isArray(val) && !isPlainObject(val)) { + return String(val); + } const cached = objectCellPreviewCache.get(val); if (cached !== undefined) { return cached; } + const topLevelSize = Array.isArray(val) ? val.length : Object.keys(val || {}).length; + if (topLevelSize > 80) { + const summary = Array.isArray(val) ? `[Array(${topLevelSize})]` : `{Object(${topLevelSize})}`; + objectCellPreviewCache.set(val, summary); + return summary; + } try { const nextText = JSON.stringify(val); const previewText = nextText.length > TABLE_CELL_PREVIEW_MAX_CHARS ? `${nextText.slice(0, TABLE_CELL_PREVIEW_MAX_CHARS)}…` : nextText; @@ -191,6 +200,26 @@ const isCellValueEqualForDiff = (left: any, right: any): boolean => { return toFormText(left) === toFormText(right); }; +// 渲染阶段轻量比较:避免对象值在 shouldCellUpdate 中反复深度序列化导致卡顿。 +const isCellValueEqualForRender = (left: any, right: any): boolean => { + if (left === right) return true; + const leftNullish = left === null || left === undefined; + const rightNullish = right === null || right === undefined; + if (leftNullish || rightNullish) return leftNullish && rightNullish; + + const leftType = typeof left; + const rightType = typeof right; + if (leftType === 'object' || rightType === 'object') { + // 对象仅按引用比较;真正的值差异在提交保存时再做严格比对。 + return false; + } + + if (leftType === 'string' || rightType === 'string') { + return normalizeDateTimeString(String(left)) === normalizeDateTimeString(String(right)); + } + return left === right; +}; + const INLINE_EDIT_MAX_CHARS = 2000; const shouldOpenModalEditor = (val: any): boolean => { @@ -2067,7 +2096,7 @@ const DataGrid: React.FC = ({ shouldCellUpdate: (record: Item, prevRecord: Item) => { const rowKeyChanged = record?.[GONAVI_ROW_KEY] !== prevRecord?.[GONAVI_ROW_KEY]; if (rowKeyChanged) return true; - return !isCellValueEqualForDiff(record?.[key], prevRecord?.[key]); + return !isCellValueEqualForRender(record?.[key], prevRecord?.[key]); }, onHeaderCell: (column: any) => ({ width: column.width, diff --git a/internal/db/query_value.go b/internal/db/query_value.go index 83fdf7f..fa28bd7 100644 --- a/internal/db/query_value.go +++ b/internal/db/query_value.go @@ -8,6 +8,7 @@ import ( "reflect" "strconv" "strings" + "time" "unicode" "unicode/utf8" ) @@ -86,6 +87,16 @@ func normalizeCompositeQueryValue(v interface{}) interface{} { items[i] = normalizeQueryValue(rv.Index(i).Interface()) } return items + case reflect.Struct: + // 部分驱动(如 Kingbase)会返回复杂结构体值,直接透传会导致前端渲染和比较开销激增。 + // 统一降级为可读字符串,避免对象深层序列化触发 UI 卡顿。 + if tm, ok := v.(time.Time); ok { + return tm.Format(time.RFC3339Nano) + } + if stringer, ok := v.(fmt.Stringer); ok { + return stringer.String() + } + return fmt.Sprintf("%v", v) default: return normalizeUnsafeIntegerForJS(rv, v) } diff --git a/internal/db/query_value_test.go b/internal/db/query_value_test.go index b05977e..285344e 100644 --- a/internal/db/query_value_test.go +++ b/internal/db/query_value_test.go @@ -2,7 +2,9 @@ package db import ( "encoding/json" + "fmt" "testing" + "time" ) type duckMapLike map[any]any @@ -165,3 +167,31 @@ func TestNormalizeQueryValueWithDBType_JSONNumber(t *testing.T) { }) } } + +type customStructValue struct { + Name string + Age int +} + +func (v customStructValue) String() string { + return fmt.Sprintf("%s-%d", v.Name, v.Age) +} + +func TestNormalizeQueryValueWithDBType_StructToString(t *testing.T) { + got := normalizeQueryValueWithDBType(customStructValue{Name: "alice", Age: 18}, "") + if got != "alice-18" { + t.Fatalf("结构体应降级为可读字符串,实际=%v(%T)", got, got) + } +} + +func TestNormalizeQueryValueWithDBType_TimeStructToRFC3339(t *testing.T) { + input := time.Date(2026, 3, 5, 18, 30, 15, 123456789, time.UTC) + got := normalizeQueryValueWithDBType(input, "") + text, ok := got.(string) + if !ok { + t.Fatalf("time.Time 应转为字符串,实际=%v(%T)", got, got) + } + if text != "2026-03-05T18:30:15.123456789Z" { + t.Fatalf("time.Time 规整值异常,实际=%s", text) + } +}