From b85e7491a94528553a6de190deecf65a7620e8d0 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Mon, 1 Jun 2026 11:05:06 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(shortcuts):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=A0=87=E7=AD=BE=E9=A1=B5=E5=88=87=E6=8D=A2=E5=BF=AB?= =?UTF-8?q?=E6=8D=B7=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增切换到下一个标签页动作,默认 Ctrl+Tab - 新增切换到上一个标签页动作,默认 Ctrl+Shift+Tab - 接入全局快捷键处理,按当前标签顺序首尾循环切换 - 补充快捷键默认值与全局执行链路测试 Refs #399 --- frontend/src/App.tool-center.test.ts | 7 ++++++- frontend/src/App.tsx | 17 ++++++++++++++++- frontend/src/utils/shortcuts.test.ts | 8 ++++++++ frontend/src/utils/shortcuts.ts | 22 ++++++++++++++++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/frontend/src/App.tool-center.test.ts b/frontend/src/App.tool-center.test.ts index e85f16d..5ad5a57 100644 --- a/frontend/src/App.tool-center.test.ts +++ b/frontend/src/App.tool-center.test.ts @@ -162,6 +162,8 @@ describe('tool center menu entries', () => { ['runQuery', 'gonavi:run-active-query'], ['focusSidebarSearch', 'gonavi:focus-sidebar-search'], ['newQueryTab', 'handleNewQuery();'], + ['switchToNextTab', 'switchActiveTabByOffset(1);'], + ['switchToPreviousTab', 'switchActiveTabByOffset(-1);'], ['newConnection', 'handleCreateConnection();'], ['toggleAIPanel', 'toggleAIPanel();'], ['toggleLogPanel', 'handleToggleLogPanel();'], @@ -174,8 +176,11 @@ describe('tool center menu entries', () => { for (const [action, handler] of expectedHandlers) { expect(getGlobalShortcutCaseBlock(action)).toContain(handler); } + expect(appSource).toContain('const switchActiveTabByOffset = useCallback((offset: 1 | -1) => {'); + expect(appSource).toContain('const nextIndex = (baseIndex + offset + tabs.length) % tabs.length;'); + expect(appSource).toContain('setActiveTab(tabs[nextIndex].id);'); expect(appSource).toContain('handleCreateConnection, handleManualResetWindowZoom'); - expect(appSource).toContain('setTheme, toggleAIPanel, useNativeMacWindowControls'); + expect(appSource).toContain('switchActiveTabByOffset, themeMode'); }); it('captures global shortcuts before Monaco/editor defaults consume them', () => { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 21ef1ba..a7757bb 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1141,6 +1141,7 @@ function App() { const connections = useStore(state => state.connections); const tabs = useStore(state => state.tabs); const activeTabId = useStore(state => state.activeTabId); + const setActiveTab = useStore(state => state.setActiveTab); const openSecurityUpdateSettings = useCallback((focusTarget: SecurityUpdateSettingsFocusTarget | null = null) => { setIsSecurityUpdateIntroOpen(false); setSecurityUpdateSettingsFocusTarget(focusTarget); @@ -1893,6 +1894,14 @@ function App() { }); }, [activeTabId, tabs, connections, activeContext, addTab]); + const switchActiveTabByOffset = useCallback((offset: 1 | -1) => { + if (tabs.length < 2) return; + const activeIndex = tabs.findIndex(tab => tab.id === activeTabId); + const baseIndex = activeIndex >= 0 ? activeIndex : 0; + const nextIndex = (baseIndex + offset + tabs.length) % tabs.length; + setActiveTab(tabs[nextIndex].id); + }, [activeTabId, setActiveTab, tabs]); + const closeConnectionPackageDialog = useCallback(() => { setConnectionPackageDialog(createClosedConnectionPackageDialogState()); setPendingConnectionImportPayload(null); @@ -2927,6 +2936,12 @@ function App() { case 'newQueryTab': handleNewQuery(); break; + case 'switchToNextTab': + switchActiveTabByOffset(1); + break; + case 'switchToPreviousTab': + switchActiveTabByOffset(-1); + break; case 'newConnection': handleCreateConnection(); break; @@ -2957,7 +2972,7 @@ function App() { return () => { window.removeEventListener('keydown', handleGlobalShortcut, true); }; - }, [activeShortcutPlatform, handleCreateConnection, handleManualResetWindowZoom, handleNewQuery, handleTitleBarWindowToggle, handleToggleLogPanel, isMacRuntime, shortcutOptions, themeMode, setTheme, toggleAIPanel, useNativeMacWindowControls]); + }, [activeShortcutPlatform, handleCreateConnection, handleManualResetWindowZoom, handleNewQuery, handleTitleBarWindowToggle, handleToggleLogPanel, isMacRuntime, shortcutOptions, switchActiveTabByOffset, themeMode, setTheme, toggleAIPanel, useNativeMacWindowControls]); useEffect(() => { if (!capturingShortcutAction) { diff --git a/frontend/src/utils/shortcuts.test.ts b/frontend/src/utils/shortcuts.test.ts index 1bf18c3..c896a3e 100644 --- a/frontend/src/utils/shortcuts.test.ts +++ b/frontend/src/utils/shortcuts.test.ts @@ -200,6 +200,14 @@ describe('shortcut defaults', () => { mac: { combo: 'Meta+N', enabled: true }, windows: { combo: 'Ctrl+N', enabled: true }, }); + expect(DEFAULT_SHORTCUT_OPTIONS.switchToNextTab).toEqual({ + mac: { combo: 'Ctrl+Tab', enabled: true }, + windows: { combo: 'Ctrl+Tab', enabled: true }, + }); + expect(DEFAULT_SHORTCUT_OPTIONS.switchToPreviousTab).toEqual({ + mac: { combo: 'Ctrl+Shift+Tab', enabled: true }, + windows: { combo: 'Ctrl+Shift+Tab', enabled: true }, + }); expect(DEFAULT_SHORTCUT_OPTIONS.toggleLogPanel).toEqual({ mac: { combo: 'Meta+Shift+H', enabled: true }, windows: { combo: 'Ctrl+H', enabled: true }, diff --git a/frontend/src/utils/shortcuts.ts b/frontend/src/utils/shortcuts.ts index 7308469..937b609 100644 --- a/frontend/src/utils/shortcuts.ts +++ b/frontend/src/utils/shortcuts.ts @@ -7,6 +7,8 @@ export type ShortcutAction = | 'sendAIChatMessage' | 'focusSidebarSearch' | 'newQueryTab' + | 'switchToNextTab' + | 'switchToPreviousTab' | 'newConnection' | 'toggleAIPanel' | 'toggleLogPanel' @@ -92,6 +94,8 @@ export const SHORTCUT_ACTION_ORDER: ShortcutAction[] = [ 'sendAIChatMessage', 'focusSidebarSearch', 'newQueryTab', + 'switchToNextTab', + 'switchToPreviousTab', 'newConnection', 'toggleAIPanel', 'toggleLogPanel', @@ -135,6 +139,16 @@ export const SHORTCUT_ACTION_META: Record = label: '新建查询页', description: '创建一个新的 SQL 查询标签页', }, + switchToNextTab: { + label: '切换到下一个标签页', + description: '在打开的标签页中向右切换', + allowInEditable: true, + }, + switchToPreviousTab: { + label: '切换到上一个标签页', + description: '在打开的标签页中向左切换', + allowInEditable: true, + }, newConnection: { label: '新建数据源', description: '创建新的数据库、运行时或其他数据源连接', @@ -194,6 +208,14 @@ export const DEFAULT_SHORTCUT_OPTIONS: ShortcutOptions = { mac: { combo: 'Meta+N', enabled: true }, windows: { combo: 'Ctrl+N', enabled: true }, }, + switchToNextTab: { + mac: { combo: 'Ctrl+Tab', enabled: true }, + windows: { combo: 'Ctrl+Tab', enabled: true }, + }, + switchToPreviousTab: { + mac: { combo: 'Ctrl+Shift+Tab', enabled: true }, + windows: { combo: 'Ctrl+Shift+Tab', enabled: true }, + }, newConnection: { mac: { combo: 'Meta+Shift+N', enabled: true }, windows: { combo: 'Ctrl+Shift+N', enabled: true },