Skip to content

Commit 1fef292

Browse files
authored
perf: optimize table rendering with virtualization (#5)
* perf: optimize table rendering with virtualization - Replace HeroUI Table with custom virtualized table using @tanstack/react-virtual - Remove useMemo/useCallback optimizations in favor of direct virtualization - Implement fixed column widths and sticky row number column - Add destroyInactiveTabPanel prop to Tabs component - Add "export" translation key for Chinese and English - Simplify query status logic and remove unnecessary memoization - Improve table performance for large datasets by rendering * feat: improve empty state UI and reposition total rows counter - Add table header with row number column to empty state - Update empty state styling with better colors and spacing - Move total rows counter to bottom-left as floating badge - Only show total counter when data exists - Adjust toolbar padding from top to right - Improve visual consistency between empty and populated states * refactor: replace div-based table with semantic HTML table * refactor: change row number column alignment from center to left * refactor: adjust z-index values for table header and total counter * feat: add column resizing support to data result table
1 parent 8066aed commit 1fef292

File tree

6 files changed

+2915
-3036
lines changed

6 files changed

+2915
-3036
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@fortawesome/free-solid-svg-icons": "^6.7.2",
1919
"@fortawesome/react-fontawesome": "^0.2.2",
2020
"@heroui/react": "^2.7.8",
21+
"@tanstack/react-table": "^8.21.3",
2122
"@tauri-apps/api": "^2.8.0",
2223
"@tauri-apps/plugin-dialog": "^2.4.0",
2324
"ace-builds": "^1.37.4",
@@ -53,4 +54,4 @@
5354
"typescript-eslint": "^8.18.2",
5455
"vite": "^6.0.5"
5556
}
56-
}
57+
}

src/i18n/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export interface Translations {
3737
noResultsDescription: string;
3838
};
3939
export: {
40+
export: string;
4041
completed: string;
4142
queryTime: string;
4243
fileName: string;
@@ -207,6 +208,7 @@ const translations: Record<Language, Translations> = {
207208
noResultsDescription: "请尝试使用其他关键词搜索",
208209
},
209210
export: {
211+
export: "导出",
210212
completed: "下载完成",
211213
queryTime: "查询时间",
212214
fileName: "文件名",
@@ -385,6 +387,7 @@ const translations: Record<Language, Translations> = {
385387
noResultsDescription: "Try using different search keywords",
386388
},
387389
export: {
390+
export: "Export",
388391
completed: "Download Completed",
389392
queryTime: "Query Time",
390393
fileName: "File Name",

src/pages/notebook/notebook-middle/notebook-mddle-bottom.tsx

Lines changed: 46 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Tabs, Tab } from "@heroui/react";
2-
import { memo, useState, useCallback, useMemo } from "react";
2+
import { memo, useState } from "react";
33
import DataTable from "./notebook-middle-table";
44
import QueryHistory from "./notebook-middle-history";
55
import { invoke } from "@tauri-apps/api/core";
@@ -27,16 +27,11 @@ function NotebookMiddleBottom({
2727
sql,
2828
}: NotebookMiddleBottomProps) {
2929
const [queryHistory, setQueryHistory] = useState<
30-
{
31-
sql: string;
32-
created_at: string;
33-
status: string;
34-
}[]
30+
{ sql: string; created_at: string; status: string }[]
3531
>([]);
3632
const [isHistoryLoaded, setIsHistoryLoaded] = useState(false);
3733

38-
// 使用 useCallback 缓存历史数据获取函数
39-
const loadQueryHistory = useCallback(async () => {
34+
const loadQueryHistory = async () => {
4035
if (!isHistoryLoaded) {
4136
try {
4237
const history = (await invoke("sql_history", {})) as {
@@ -50,87 +45,66 @@ function NotebookMiddleBottom({
5045
console.error("Failed to load query history:", error);
5146
}
5247
}
53-
}, [isHistoryLoaded]);
54-
55-
// 使用 useCallback 缓存标签页切换处理函数
56-
const handleTabChange = useCallback(
57-
async (key: string | number) => {
58-
if (key === "history") {
59-
await loadQueryHistory();
60-
}
61-
},
62-
[loadQueryHistory]
63-
);
64-
65-
// 使用 useMemo 缓存查询时间显示
66-
const queryTimeTitle = useMemo(
67-
() => (
68-
<span className="text-gray-500 cursor-default">
69-
Query Time (
70-
<span className="text-green-600 font-medium">
71-
{data.query_time ?? "-"}
72-
</span>
73-
)
74-
</span>
75-
),
76-
[data.query_time]
77-
);
48+
};
7849

79-
// 判断查询是否成功或失败
80-
const queryStatus = useMemo(() => {
81-
// 如果数据为空,返回 null(初始状态或加载中)
82-
if (data.header.length === 0 && data.rows.length === 0) {
83-
return null;
50+
const handleTabChange = async (key: string | number) => {
51+
if (key === "history") {
52+
await loadQueryHistory();
8453
}
54+
};
8555

86-
// 如果 header 是 "Error" 或 "Status"(且包含错误信息),则是失败
56+
// 判断查询状态
57+
const getQueryStatus = () => {
58+
if (data.header.length === 0 && data.rows.length === 0) return null;
8759
if (
8860
data.header[0] === "Error" ||
8961
(data.header[0] === "Status" &&
90-
data.rows.length > 0 &&
91-
data.rows[0][0]?.toLowerCase().includes("cancelled"))
62+
data.rows[0]?.[0]?.toLowerCase().includes("cancelled"))
9263
) {
9364
return "failed";
9465
}
95-
96-
// 否则是成功
9766
return "success";
98-
}, [data.header, data.rows]);
67+
};
9968

100-
// 使用 useMemo 缓存 Results 标签页标题
101-
const resultsTitle = useMemo(() => {
102-
if (queryStatus === "success") {
103-
return (
104-
<span className="flex items-center gap-2">
105-
Results
106-
<FontAwesomeIcon
107-
icon={faCheckCircle}
108-
className="text-green-500"
109-
style={{ fontSize: "0.9em" }}
110-
/>
111-
</span>
112-
);
113-
} else if (queryStatus === "failed") {
114-
return (
115-
<span className="flex items-center gap-2">
116-
Results
117-
<FontAwesomeIcon
118-
icon={faTimesCircle}
119-
className="text-red-500"
120-
style={{ fontSize: "0.9em" }}
121-
/>
122-
</span>
123-
);
124-
}
125-
return "Results";
126-
}, [queryStatus]);
69+
const queryStatus = getQueryStatus();
70+
71+
const resultsTitle = (
72+
<span className="flex items-center gap-2">
73+
Results
74+
{queryStatus === "success" && (
75+
<FontAwesomeIcon
76+
icon={faCheckCircle}
77+
className="text-green-500"
78+
style={{ fontSize: "0.9em" }}
79+
/>
80+
)}
81+
{queryStatus === "failed" && (
82+
<FontAwesomeIcon
83+
icon={faTimesCircle}
84+
className="text-red-500"
85+
style={{ fontSize: "0.9em" }}
86+
/>
87+
)}
88+
</span>
89+
);
90+
91+
const queryTimeTitle = (
92+
<span className="text-gray-500 cursor-default">
93+
Query Time (
94+
<span className="text-green-600 font-medium">
95+
{data.query_time ?? "-"}
96+
</span>
97+
)
98+
</span>
99+
);
127100

128101
return (
129102
<div className="flex w-full flex-col">
130103
<Tabs
131104
variant="underlined"
132105
defaultSelectedKey="results"
133106
onSelectionChange={handleTabChange}
107+
destroyInactiveTabPanel={false}
134108
>
135109
<Tab key="history" title="Query History">
136110
<QueryHistory setSql={setSql} data={queryHistory} />
@@ -147,7 +121,7 @@ function NotebookMiddleBottom({
147121
? "opacity-100"
148122
: "opacity-60"
149123
}`}
150-
></Tab>
124+
/>
151125
</Tabs>
152126
</div>
153127
);

0 commit comments

Comments
 (0)