+
+
}
- aria-label="定位当前打开表"
- data-sidebar-locate-current-tab-action="true"
- disabled={!canLocateActiveTab}
- onClick={handleLocateActiveTabInSidebar}
- style={{ color: darkMode ? 'rgba(255,255,255,0.65)' : 'rgba(0,0,0,0.65)' }}
+ icon={}
+ aria-label="新建组"
+ data-sidebar-create-group-action="true"
+ onClick={() => { setRenameViewTarget(null); createTagForm.resetFields(); setIsCreateTagModalOpen(true); }}
+ style={{ color: legacyToolbarButtonColor }}
/>
-
-
+
+
+
+
+ }
+ aria-label="批量操作表"
+ data-sidebar-batch-table-action="true"
+ onClick={() => openBatchOperationModal()}
+ style={{ color: legacyToolbarButtonColor }}
+ />
+
+
+
+
+ }
+ aria-label="批量操作库"
+ data-sidebar-batch-database-action="true"
+ onClick={() => openBatchDatabaseModal()}
+ style={{ color: legacyToolbarButtonColor }}
+ />
+
+
+
+
+ }
+ aria-label="运行外部 SQL 文件"
+ data-sidebar-open-external-sql-file-action="true"
+ onClick={handleOpenSQLFileFromToolbar}
+ style={{ color: legacyToolbarButtonColor }}
+ />
+
+
+
+
+
+ }
+ aria-label="定位当前打开表"
+ data-sidebar-locate-current-tab-action="true"
+ disabled={!canLocateActiveTab}
+ onClick={handleLocateActiveTabInSidebar}
+ style={{ color: legacyToolbarButtonColor }}
+ />
+
+
+
)}
diff --git a/frontend/src/components/Sidebar.v2-metadata.test.tsx b/frontend/src/components/Sidebar.v2-metadata.test.tsx
new file mode 100644
index 0000000..cb5138a
--- /dev/null
+++ b/frontend/src/components/Sidebar.v2-metadata.test.tsx
@@ -0,0 +1,13 @@
+import { describe, expect, it } from 'vitest';
+
+import { formatSidebarRowCount } from './Sidebar';
+
+describe('Sidebar v2 metadata', () => {
+ it('formats table row counts for sidebar labels', () => {
+ expect(formatSidebarRowCount(-1)).toBe('');
+ expect(formatSidebarRowCount(0)).toBe('0');
+ expect(formatSidebarRowCount(27)).toBe('27');
+ expect(formatSidebarRowCount(1532)).toBe('1.5K');
+ expect(formatSidebarRowCount(2_450_000)).toBe('2.5M');
+ });
+});
diff --git a/frontend/src/store.test.ts b/frontend/src/store.test.ts
index db86cd0..81b8097 100644
--- a/frontend/src/store.test.ts
+++ b/frontend/src/store.test.ts
@@ -773,6 +773,78 @@ describe('store appearance persistence', () => {
expect(reloaded.useStore.getState().activeTabId).toBe('query-tab-1');
});
+ it('updates activeContext when switching between tabs with different host or database', async () => {
+ const { useStore } = await importStore();
+
+ useStore.getState().addTab({
+ id: 'table-main',
+ title: 'users',
+ type: 'table',
+ connectionId: 'conn-1',
+ dbName: 'sys',
+ tableName: 'users',
+ });
+ expect(useStore.getState().activeContext).toEqual({
+ connectionId: 'conn-1',
+ dbName: 'sys',
+ });
+
+ useStore.getState().addTab({
+ id: 'query-bot',
+ title: '新建查询',
+ type: 'query',
+ connectionId: 'conn-2',
+ dbName: 'missav_bot',
+ query: 'select 1;',
+ });
+ expect(useStore.getState().activeContext).toEqual({
+ connectionId: 'conn-2',
+ dbName: 'missav_bot',
+ });
+
+ useStore.getState().setActiveTab('table-main');
+ expect(useStore.getState().activeTabId).toBe('table-main');
+ expect(useStore.getState().activeContext).toEqual({
+ connectionId: 'conn-1',
+ dbName: 'sys',
+ });
+ });
+
+ it('falls back activeContext to the new active tab after closing the current tab', async () => {
+ const { useStore } = await importStore();
+
+ useStore.getState().addTab({
+ id: 'query-sys',
+ title: '新建查询',
+ type: 'query',
+ connectionId: 'conn-1',
+ dbName: 'sys',
+ query: 'select 1;',
+ });
+ useStore.getState().addTab({
+ id: 'query-bot',
+ title: '新建查询',
+ type: 'query',
+ connectionId: 'conn-2',
+ dbName: 'missav_bot',
+ query: 'select 2;',
+ });
+
+ expect(useStore.getState().activeTabId).toBe('query-bot');
+ expect(useStore.getState().activeContext).toEqual({
+ connectionId: 'conn-2',
+ dbName: 'missav_bot',
+ });
+
+ useStore.getState().closeTab('query-bot');
+
+ expect(useStore.getState().activeTabId).toBe('query-sys');
+ expect(useStore.getState().activeContext).toEqual({
+ connectionId: 'conn-1',
+ dbName: 'sys',
+ });
+ });
+
it('only restores persisted query tabs with useful SQL state', async () => {
storage.setItem('lite-db-storage', JSON.stringify({
state: {
diff --git a/frontend/src/store.ts b/frontend/src/store.ts
index 40adbae..8ea9057 100644
--- a/frontend/src/store.ts
+++ b/frontend/src/store.ts
@@ -1385,6 +1385,34 @@ const sanitizeActiveTabId = (activeTabId: unknown, tabs: TabData[]): string | nu
return tabs[0]?.id || null;
};
+const resolveActiveContextFromTab = (
+ tab: TabData | null | undefined,
+): { connectionId: string; dbName: string } | null => {
+ if (!tab) return null;
+ const connectionId = toTrimmedString(tab.connectionId);
+ if (!connectionId) return null;
+ return {
+ connectionId,
+ dbName: toTrimmedString(tab.dbName),
+ };
+};
+
+const resolveActiveContextForTabId = (
+ tabs: TabData[],
+ activeTabId: string | null | undefined,
+ fallbackContext: { connectionId: string; dbName: string } | null,
+): { connectionId: string; dbName: string } | null => {
+ const normalizedActiveTabId = toTrimmedString(activeTabId);
+ if (normalizedActiveTabId) {
+ const activeTab = tabs.find((tab) => tab.id === normalizedActiveTabId);
+ const contextFromTab = resolveActiveContextFromTab(activeTab);
+ if (contextFromTab) {
+ return contextFromTab;
+ }
+ }
+ return fallbackContext;
+};
+
const sanitizeSqlLogs = (value: unknown, limit = MAX_PERSISTED_SQL_LOGS): SqlLog[] => {
if (!Array.isArray(value)) return [];
const result: SqlLog[] = [];
@@ -2204,7 +2232,15 @@ export const useStore = create