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)
+ }
+}