diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 3dc871b5..9b5cf284 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -47,6 +47,8 @@ interface TreeNode { type BatchTableExportMode = 'schema' | 'backup' | 'dataOnly'; type BatchObjectType = 'table' | 'view'; +type BatchObjectFilterType = 'all' | BatchObjectType; +type BatchSelectionScope = 'filtered' | 'all'; interface BatchObjectItem { title: string; @@ -133,11 +135,47 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> const [selectedConnection, setSelectedConnection] = useState(''); const [selectedDatabase, setSelectedDatabase] = useState(''); const [availableDatabases, setAvailableDatabases] = useState([]); + const [batchFilterKeyword, setBatchFilterKeyword] = useState(''); + const [batchFilterType, setBatchFilterType] = useState('all'); + const [batchSelectionScope, setBatchSelectionScope] = useState('filtered'); + const filteredBatchObjects = useMemo(() => { + const keyword = batchFilterKeyword.trim().toLowerCase(); + return batchTables.filter((item) => { + if (batchFilterType !== 'all' && item.objectType !== batchFilterType) { + return false; + } + if (!keyword) { + return true; + } + return item.title.toLowerCase().includes(keyword) || item.objectName.toLowerCase().includes(keyword); + }); + }, [batchFilterKeyword, batchFilterType, batchTables]); const groupedBatchObjects = useMemo(() => { - const tables = batchTables.filter(item => item.objectType === 'table'); - const views = batchTables.filter(item => item.objectType === 'view'); + const tables = filteredBatchObjects.filter(item => item.objectType === 'table'); + const views = filteredBatchObjects.filter(item => item.objectType === 'view'); return { tables, views }; - }, [batchTables]); + }, [filteredBatchObjects]); + const allBatchObjectKeys = useMemo(() => batchTables.map(item => item.key), [batchTables]); + const allBatchObjectKeysByType = useMemo(() => { + if (batchFilterType === 'all') { + return allBatchObjectKeys; + } + return batchTables + .filter((item) => item.objectType === batchFilterType) + .map((item) => item.key); + }, [allBatchObjectKeys, batchFilterType, batchTables]); + const filteredBatchObjectKeys = useMemo(() => filteredBatchObjects.map(item => item.key), [filteredBatchObjects]); + const selectionScopeTargetKeys = useMemo( + () => (batchSelectionScope === 'filtered' ? filteredBatchObjectKeys : allBatchObjectKeysByType), + [allBatchObjectKeysByType, batchSelectionScope, filteredBatchObjectKeys] + ); + useEffect(() => { + if (batchFilterType === 'all') { + return; + } + const allowed = new Set(allBatchObjectKeysByType); + setCheckedTableKeys((prev) => prev.filter((key) => allowed.has(key))); + }, [allBatchObjectKeysByType, batchFilterType]); // Batch Database Operations Modal const [isBatchDbModalOpen, setIsBatchDbModalOpen] = useState(false); @@ -1313,6 +1351,9 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> setBatchTables([]); setCheckedTableKeys([]); setAvailableDatabases([]); + setBatchFilterKeyword(''); + setBatchFilterType('all'); + setBatchSelectionScope('filtered'); if (connId) { const conn = connections.find(c => c.id === connId); @@ -1413,6 +1454,9 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> setSelectedDatabase(''); setBatchTables([]); setCheckedTableKeys([]); + setBatchFilterKeyword(''); + setBatchFilterType('all'); + setBatchSelectionScope('filtered'); const conn = connections.find(c => c.id === connId); if (conn) { @@ -1422,6 +1466,9 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> const handleDatabaseChange = async (dbName: string) => { setSelectedDatabase(dbName); + setBatchFilterKeyword(''); + setBatchFilterType('all'); + setBatchSelectionScope('filtered'); const conn = connections.find(c => c.id === selectedConnection); if (conn && dbName) { @@ -1470,17 +1517,44 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> }; const handleCheckAll = (checked: boolean) => { - if (checked) { - setCheckedTableKeys(batchTables.map(t => t.key)); - } else { - setCheckedTableKeys([]); + if (batchSelectionScope === 'all') { + setCheckedTableKeys(checked ? allBatchObjectKeys : []); + return; } + if (filteredBatchObjectKeys.length === 0) { + return; + } + if (checked) { + setCheckedTableKeys(prev => { + const nextSet = new Set(prev); + filteredBatchObjectKeys.forEach((key) => nextSet.add(key)); + return allBatchObjectKeys.filter((key) => nextSet.has(key)); + }); + return; + } + const filteredKeySet = new Set(filteredBatchObjectKeys); + setCheckedTableKeys(prev => prev.filter((key) => !filteredKeySet.has(key))); }; const handleInvertSelection = () => { - const allKeys = batchTables.map(t => t.key); - const newChecked = allKeys.filter(k => !checkedTableKeys.includes(k)); - setCheckedTableKeys(newChecked); + if (batchSelectionScope === 'all') { + setCheckedTableKeys(prev => allBatchObjectKeys.filter((key) => !prev.includes(key))); + return; + } + if (filteredBatchObjectKeys.length === 0) { + return; + } + setCheckedTableKeys(prev => { + const nextSet = new Set(prev); + filteredBatchObjectKeys.forEach((key) => { + if (nextSet.has(key)) { + nextSet.delete(key); + } else { + nextSet.add(key); + } + }); + return allBatchObjectKeys.filter((key) => nextSet.has(key)); + }); }; const openBatchDatabaseModal = async () => { @@ -2874,6 +2948,43 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> + {batchTables.length > 0 && ( +
+ + setBatchFilterKeyword(e.target.value)} + placeholder="筛选表/视图名称" + prefix={} + style={{ width: 260 }} + /> + setBatchSelectionScope(value as BatchSelectionScope)} + style={{ width: 220 }} + options={[ + { label: '勾选作用于:当前筛选结果', value: 'filtered' }, + { label: '勾选作用于:全部对象', value: 'all' }, + ]} + /> + +
+ 当前筛选命中 {filteredBatchObjects.length} / {batchTables.length} 个对象 +
+
+ )} + {batchTables.length > 0 && ( <>
@@ -2881,18 +2992,21 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> @@ -2938,6 +3052,11 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
)} + {groupedBatchObjects.tables.length === 0 && groupedBatchObjects.views.length === 0 && ( +
+ 无匹配对象 +
+ )}