From 540dbc2a2811de2c6df9d4eff48519c81b36e43d Mon Sep 17 00:00:00 2001 From: Syngnat Date: Fri, 19 Jun 2026 14:55:21 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(sidebar):=20?= =?UTF-8?q?=E6=8A=BD=E5=87=BA=20Command=20Search=20=E9=9D=A2=E6=9D=BF?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新建 SidebarSearchPanel,承接 V2 命令搜索弹层的行、分组和空状态渲染 - Sidebar.tsx 保留搜索状态、过滤和执行逻辑,仅通过 typed props 传入子组件 - 保持 @ 对象搜索、? AI 提问、同步过滤与键盘导航行为不变 --- frontend/src/components/Sidebar.tsx | 127 ++++--------- .../components/sidebar/SidebarSearchPanel.tsx | 167 ++++++++++++++++++ 2 files changed, 198 insertions(+), 96 deletions(-) create mode 100644 frontend/src/components/sidebar/SidebarSearchPanel.tsx diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index d6100da..8745404 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -1,5 +1,6 @@ import Modal from './common/ResizableDraggableModal'; import SidebarConnectionRail from './sidebar/SidebarConnectionRail'; +import SidebarSearchPanel, { type SidebarSearchPanelProps } from './sidebar/SidebarSearchPanel'; import { V2_RAIL_UNGROUPED_CONNECTION_GROUP_ID, formatSidebarRowCount, @@ -7527,101 +7528,6 @@ const Sidebar: React.FC<{ } }; - const renderV2CommandSearchRow = (item: V2CommandSearchItem, active: boolean) => ( - - ); - - const renderV2CommandSearchSection = (title: string, items: V2CommandSearchItem[]) => { - if (items.length === 0) return null; - return ( -
-
{title}
- {items.map((item) => renderV2CommandSearchRow( - item, - commandSearchFlatItems[v2CommandActiveIndex]?.key === item.key, - ))} -
- ); - }; - - const renderV2CommandSearchOverlay = () => { - if (!isV2CommandSearchOpen) return null; - const emptyCopy = v2CommandSearchAiMode - ? '输入「?」后加问题,按 Enter 发送到 AI 面板。' - : (v2CommandSearchObjectMode - ? '未找到匹配的表、视图或物化视图。' - : '未找到匹配项。可输入 @表名 只搜表对象,或输入 ?问题 让 AI 回答。'); - return ( -
-
event.stopPropagation()}> -
- - handleV2CommandSearchValueChange(event.target.value)} - onKeyDown={handleV2CommandSearchKeyDown} - placeholder={v2CommandSearchPlaceholder} - /> - - - - - - -
-
- {renderV2CommandSearchSection('跳转 · GO TO', filteredCommandSearchTreeItems)} - {renderV2CommandSearchSection('AI · ASK', commandSearchAiItem)} - {renderV2CommandSearchSection('动作 · ACTIONS', filteredCommandSearchActionItems)} - {renderV2CommandSearchSection('近期查询 · RECENT', filteredCommandSearchRecentItems)} - {commandSearchFlatItems.length === 0 ? ( -
- {emptyCopy} -
- ) : null} -
-
- 导航 - 选择 - @只搜表对象 - ?发送给 AI -
-
-
- ); - }; - expandConnectionFromRailRef.current = (connectionId: string) => { const conn = connections.find((item) => item.id === connectionId); if (conn) { @@ -9163,6 +9069,35 @@ const Sidebar: React.FC<{ const v2CommandSearchLabel = t('sidebar.command_search.label'); const v2CommandSearchPlaceholder = t('sidebar.command_search.placeholder'); + const v2CommandSearchPanelProps: SidebarSearchPanelProps = { + isOpen: isV2CommandSearchOpen, + searchValue: v2CommandSearchValue, + activeIndex: v2CommandActiveIndex, + label: v2CommandSearchLabel, + placeholder: v2CommandSearchPlaceholder, + persistedFilter: v2PersistedSidebarFilter, + persistentFilterEnabled: v2CommandSearchPersistentFilterEnabled, + aiMode: v2CommandSearchAiMode, + objectMode: v2CommandSearchObjectMode, + flatItems: commandSearchFlatItems, + sections: { + goTo: filteredCommandSearchTreeItems, + ai: commandSearchAiItem, + actions: filteredCommandSearchActionItems, + recent: filteredCommandSearchRecentItems, + }, + inputRef: commandSearchInputRef, + handlers: { + onSearchValueChange: handleV2CommandSearchValueChange, + onKeyDown: handleV2CommandSearchKeyDown, + onClose: closeV2CommandSearch, + onItemSelect: (item: V2CommandSearchItem) => runCommandSearchItem(item), + onItemHover: (key: string) => setV2CommandActiveIndex(commandSearchFlatItems.findIndex((entry) => entry.key === key)), + onTogglePersistentFilter: toggleV2CommandSearchPersistentFilter, + onResetFilter: resetV2SidebarFilter, + }, + }; + // V2 Connection Rail 子组件 props(从原 renderV2ConnectionRail 抽出,保留所有原行为) const v2ConnectionRailProps = { labels: { @@ -9511,7 +9446,7 @@ const Sidebar: React.FC<{ )} - {renderV2CommandSearchOverlay()} + {contextMenu?.kind && typeof document !== 'undefined' && createPortal(
{ + isOpen: boolean; + searchValue: string; + activeIndex: number; + label: string; + placeholder: string; + persistedFilter: string; + persistentFilterEnabled: boolean; + aiMode: boolean; + objectMode: boolean; + flatItems: TItem[]; + sections: { + goTo: TItem[]; + ai: TItem[]; + actions: TItem[]; + recent: TItem[]; + }; + inputRef: React.Ref; + handlers: { + onSearchValueChange: (value: string) => void; + onKeyDown: (event: React.KeyboardEvent) => void; + onClose: () => void; + onItemSelect: (item: TItem) => void; + onItemHover: (key: string) => void; + onTogglePersistentFilter: (enabled: boolean) => void; + onResetFilter: () => void; + }; +} + +const SidebarSearchPanel = ({ + isOpen, + searchValue, + activeIndex, + label, + placeholder, + persistedFilter, + persistentFilterEnabled, + aiMode, + objectMode, + flatItems, + sections, + inputRef, + handlers, +}: SidebarSearchPanelProps) => { + if (!isOpen) return null; + + const emptyCopy = aiMode + ? '输入「?」后加问题,按 Enter 发送到 AI 面板。' + : objectMode + ? '未找到匹配的表、视图或物化视图。' + : '未找到匹配项。可输入 @表名 只搜表对象,或输入 ?问题 让 AI 回答。'; + + const renderRow = (item: TItem, active: boolean) => ( + + ); + + const renderSection = (title: string, items: TItem[]) => { + if (items.length === 0) return null; + return ( +
+
{title}
+ {items.map((item) => + renderRow(item, flatItems[activeIndex]?.key === item.key), + )} +
+ ); + }; + + return ( +
+
event.stopPropagation()} + > +
+ + handlers.onSearchValueChange(event.target.value)} + onKeyDown={handlers.onKeyDown} + placeholder={placeholder} + /> + + + + + + +
+
+ {renderSection('跳转 · GO TO', sections.goTo)} + {renderSection('AI · ASK', sections.ai)} + {renderSection('动作 · ACTIONS', sections.actions)} + {renderSection('近期查询 · RECENT', sections.recent)} + {flatItems.length === 0 ? ( +
{emptyCopy}
+ ) : null} +
+
+ 导航 + 选择 + @只搜表对象 + ?发送给 AI +
+
+
+ ); +}; + +export default SidebarSearchPanel;