diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx
index dcadb91..81548a7 100644
--- a/frontend/src/components/Sidebar.tsx
+++ b/frontend/src/components/Sidebar.tsx
@@ -28,9 +28,8 @@ import {
} from './sidebar/sidebarMetadataLoaders';
import {
useSidebarBatchExport,
- type BatchObjectFilterType,
- type BatchSelectionScope,
} from './sidebar/useSidebarBatchExport';
+import { SidebarBatchExportModals } from './sidebar/SidebarBatchExportModals';
import {
ExternalSQLFileModal,
SQLFileExecutionModal,
@@ -6179,301 +6178,49 @@ const Sidebar: React.FC<{
- , "批量操作表", "按对象批量导出结构、数据或完整备份。")}
- open={isBatchModalOpen}
- onCancel={() => setIsBatchModalOpen(false)}
- width={720}
- centered
- styles={{ content: modalPanelStyle, header: { background: 'transparent', borderBottom: 'none', paddingBottom: 10 }, body: { paddingTop: 8 }, footer: { background: 'transparent', borderTop: 'none', paddingTop: 12 } }}
- footer={
-
-
-
- }
- onClick={() => handleBatchClear()}
- disabled={checkedTableKeys.length === 0}
- >
- 清空表
-
- }
- onClick={() => handleBatchExport('schema')}
- disabled={checkedTableKeys.length === 0}
- >
- 导出结构
-
- }
- onClick={() => handleBatchExport('dataOnly')}
- disabled={checkedTableKeys.length === 0}
- >
- 仅数据(INSERT)
-
- }
- onClick={() => handleBatchExport('backup')}
- disabled={checkedTableKeys.length === 0}
- >
- 备份(结构+数据)
-
-
-
- }
- >
-
-
-
-
-
-
-
-
-
-
先选择连接与数据库,再决定导出范围和目标对象。
-
-
- {batchTables.length > 0 && (
-
-
- setBatchFilterKeyword(e.target.value)}
- placeholder="筛选表/视图名称"
- prefix={}
- style={{ width: 260 }}
- />
-
-
- 当前筛选命中 {filteredBatchObjects.length} / {batchTables.length} 个对象
-
-
- )}
-
- {batchTables.length > 0 && (
- <>
-
-
-
-
-
-
- 已选择 {checkedTableKeys.length} / {batchTables.length} 个对象
-
-
-
-
-
setCheckedTableKeys(values as string[])}
- style={{ width: '100%' }}
- >
-
- {groupedBatchObjects.tables.length > 0 && (
-
-
- 表 ({groupedBatchObjects.tables.length})
-
-
- {groupedBatchObjects.tables.map(table => (
-
-
- {table.title}
-
- ))}
-
-
- )}
- {groupedBatchObjects.views.length > 0 && (
-
-
- 视图 ({groupedBatchObjects.views.length})
-
-
- {groupedBatchObjects.views.map(view => (
-
-
- {view.title}
-
- ))}
-
-
- )}
- {groupedBatchObjects.tables.length === 0 && groupedBatchObjects.views.length === 0 && (
-
- 无匹配对象
-
- )}
-
-
-
- >
- )}
-
-
- , "批量操作库", "按数据库批量导出结构,或生成结构加数据的备份。")}
- open={isBatchDbModalOpen}
- onCancel={() => setIsBatchDbModalOpen(false)}
- width={640}
- centered
- styles={{ content: modalPanelStyle, header: { background: 'transparent', borderBottom: 'none', paddingBottom: 10 }, body: { paddingTop: 8 }, footer: { background: 'transparent', borderTop: 'none', paddingTop: 12 } }}
- footer={[
- ,
- }
- onClick={() => handleBatchDbExport(false)}
- disabled={checkedDbKeys.length === 0}
- >
- 导出库结构 ({checkedDbKeys.length})
- ,
- }
- onClick={() => handleBatchDbExport(true)}
- disabled={checkedDbKeys.length === 0}
- >
- 备份库 ({checkedDbKeys.length})
-
- ]}
- >
-
-
-
- {connections.filter(c => c.config.type !== 'redis').map(conn => (
-
- {conn.name}
-
- ))}
-
-
连接选定后会加载当前连接下可批量导出的数据库列表。
-
-
- {batchDatabases.length > 0 && (
- <>
-
-
-
-
-
-
- 已选择 {checkedDbKeys.length} / {batchDatabases.length} 个库
-
-
-
-
- setCheckedDbKeys(values as string[])}
- style={{ width: '100%' }}
- >
-
- {batchDatabases.map(db => (
-
-
- {db.title}
-
- ))}
-
-
-
- >
- )}
-
+ , "批量操作表", "按对象批量导出结构、数据或完整备份。")}
+ databaseModalTitle={renderSidebarModalTitle(, "批量操作库", "按数据库批量导出结构,或生成结构加数据的备份。")}
+ isBatchModalOpen={isBatchModalOpen}
+ setIsBatchModalOpen={setIsBatchModalOpen}
+ selectedConnection={selectedConnection}
+ selectedDatabase={selectedDatabase}
+ availableDatabases={availableDatabases}
+ batchTables={batchTables}
+ checkedTableKeys={checkedTableKeys}
+ setCheckedTableKeys={setCheckedTableKeys}
+ batchFilterKeyword={batchFilterKeyword}
+ setBatchFilterKeyword={setBatchFilterKeyword}
+ batchFilterType={batchFilterType}
+ setBatchFilterType={setBatchFilterType}
+ batchSelectionScope={batchSelectionScope}
+ setBatchSelectionScope={setBatchSelectionScope}
+ filteredBatchObjects={filteredBatchObjects}
+ groupedBatchObjects={groupedBatchObjects}
+ selectionScopeTargetKeys={selectionScopeTargetKeys}
+ handleConnectionChange={handleConnectionChange}
+ handleDatabaseChange={handleDatabaseChange}
+ handleBatchClear={handleBatchClear}
+ handleBatchExport={handleBatchExport}
+ handleCheckAll={handleCheckAll}
+ handleInvertSelection={handleInvertSelection}
+ isBatchDbModalOpen={isBatchDbModalOpen}
+ setIsBatchDbModalOpen={setIsBatchDbModalOpen}
+ selectedDbConnection={selectedDbConnection}
+ batchDatabases={batchDatabases}
+ checkedDbKeys={checkedDbKeys}
+ setCheckedDbKeys={setCheckedDbKeys}
+ handleDbConnectionChange={handleDbConnectionChange}
+ handleBatchDbExport={handleBatchDbExport}
+ handleCheckAllDb={handleCheckAllDb}
+ handleInvertSelectionDb={handleInvertSelectionDb}
+ />
void;
+ selectedConnection: string;
+ selectedDatabase: string;
+ availableDatabases: BatchObjectItem[];
+ batchTables: BatchObjectItem[];
+ checkedTableKeys: string[];
+ setCheckedTableKeys: (keys: string[]) => void;
+ batchFilterKeyword: string;
+ setBatchFilterKeyword: (value: string) => void;
+ batchFilterType: BatchObjectFilterType;
+ setBatchFilterType: (value: BatchObjectFilterType) => void;
+ batchSelectionScope: BatchSelectionScope;
+ setBatchSelectionScope: (value: BatchSelectionScope) => void;
+ filteredBatchObjects: BatchObjectItem[];
+ groupedBatchObjects: {
+ tables: BatchObjectItem[];
+ views: BatchObjectItem[];
+ };
+ selectionScopeTargetKeys: string[];
+ handleConnectionChange: (connectionId: string) => void;
+ handleDatabaseChange: (databaseName: string) => void;
+ handleBatchClear: () => void;
+ handleBatchExport: (mode: 'schema' | 'dataOnly' | 'backup') => void;
+ handleCheckAll: (checked: boolean) => void;
+ handleInvertSelection: () => void;
+ isBatchDbModalOpen: boolean;
+ setIsBatchDbModalOpen: (open: boolean) => void;
+ selectedDbConnection: string;
+ batchDatabases: BatchObjectItem[];
+ checkedDbKeys: string[];
+ setCheckedDbKeys: (keys: string[]) => void;
+ handleDbConnectionChange: (connectionId: string) => void;
+ handleBatchDbExport: (includeData: boolean) => void;
+ handleCheckAllDb: (checked: boolean) => void;
+ handleInvertSelectionDb: () => void;
+};
+
+const nonRedisConnections = (connections: SavedConnection[]) =>
+ connections.filter((connection) => connection.config.type !== 'redis');
+
+export const SidebarBatchExportModals: React.FC = ({
+ connections,
+ modalPanelStyle,
+ modalSectionStyle,
+ modalScrollSectionStyle,
+ modalHintTextStyle,
+ darkMode,
+ tableModalTitle,
+ databaseModalTitle,
+ isBatchModalOpen,
+ setIsBatchModalOpen,
+ selectedConnection,
+ selectedDatabase,
+ availableDatabases,
+ batchTables,
+ checkedTableKeys,
+ setCheckedTableKeys,
+ batchFilterKeyword,
+ setBatchFilterKeyword,
+ batchFilterType,
+ setBatchFilterType,
+ batchSelectionScope,
+ setBatchSelectionScope,
+ filteredBatchObjects,
+ groupedBatchObjects,
+ selectionScopeTargetKeys,
+ handleConnectionChange,
+ handleDatabaseChange,
+ handleBatchClear,
+ handleBatchExport,
+ handleCheckAll,
+ handleInvertSelection,
+ isBatchDbModalOpen,
+ setIsBatchDbModalOpen,
+ selectedDbConnection,
+ batchDatabases,
+ checkedDbKeys,
+ setCheckedDbKeys,
+ handleDbConnectionChange,
+ handleBatchDbExport,
+ handleCheckAllDb,
+ handleInvertSelectionDb,
+}) => (
+ <>
+ setIsBatchModalOpen(false)}
+ width={720}
+ centered
+ styles={{ content: modalPanelStyle, header: { background: 'transparent', borderBottom: 'none', paddingBottom: 10 }, body: { paddingTop: 8 }, footer: { background: 'transparent', borderTop: 'none', paddingTop: 12 } }}
+ footer={
+
+
+
+ }
+ onClick={() => handleBatchClear()}
+ disabled={checkedTableKeys.length === 0}
+ >
+ 清空表
+
+ }
+ onClick={() => handleBatchExport('schema')}
+ disabled={checkedTableKeys.length === 0}
+ >
+ 导出结构
+
+ }
+ onClick={() => handleBatchExport('dataOnly')}
+ disabled={checkedTableKeys.length === 0}
+ >
+ 仅数据(INSERT)
+
+ }
+ onClick={() => handleBatchExport('backup')}
+ disabled={checkedTableKeys.length === 0}
+ >
+ 备份(结构+数据)
+
+
+
+ }
+ >
+
+
+
+
+ {nonRedisConnections(connections).map(conn => (
+
+ {conn.name}
+
+ ))}
+
+
+
+
+
+ {availableDatabases.map(db => (
+
+ {db.title}
+
+ ))}
+
+
+
先选择连接与数据库,再决定导出范围和目标对象。
+
+
+ {batchTables.length > 0 && (
+
+
+ setBatchFilterKeyword(e.target.value)}
+ placeholder="筛选表/视图名称"
+ prefix={}
+ style={{ width: 260 }}
+ />
+ setBatchFilterType(value as BatchObjectFilterType)}
+ style={{ width: 140 }}
+ options={[
+ { label: '全部对象', value: 'all' },
+ { label: '仅表', value: 'table' },
+ { label: '仅视图', value: 'view' },
+ ]}
+ />
+ setBatchSelectionScope(value as BatchSelectionScope)}
+ style={{ width: 220 }}
+ options={[
+ { label: '勾选作用于:当前筛选结果', value: 'filtered' },
+ { label: '勾选作用于:全部对象', value: 'all' },
+ ]}
+ />
+
+
+ 当前筛选命中 {filteredBatchObjects.length} / {batchTables.length} 个对象
+
+
+ )}
+
+ {batchTables.length > 0 && (
+ <>
+
+
+
+
+
+
+ 已选择 {checkedTableKeys.length} / {batchTables.length} 个对象
+
+
+
+
+
setCheckedTableKeys(values as string[])}
+ style={{ width: '100%' }}
+ >
+
+ {groupedBatchObjects.tables.length > 0 && (
+
+
+ 表 ({groupedBatchObjects.tables.length})
+
+
+ {groupedBatchObjects.tables.map(table => (
+
+
+ {table.title}
+
+ ))}
+
+
+ )}
+ {groupedBatchObjects.views.length > 0 && (
+
+
+ 视图 ({groupedBatchObjects.views.length})
+
+
+ {groupedBatchObjects.views.map(view => (
+
+
+ {view.title}
+
+ ))}
+
+
+ )}
+ {groupedBatchObjects.tables.length === 0 && groupedBatchObjects.views.length === 0 && (
+
+ 无匹配对象
+
+ )}
+
+
+
+ >
+ )}
+
+
+ setIsBatchDbModalOpen(false)}
+ width={640}
+ centered
+ styles={{ content: modalPanelStyle, header: { background: 'transparent', borderBottom: 'none', paddingBottom: 10 }, body: { paddingTop: 8 }, footer: { background: 'transparent', borderTop: 'none', paddingTop: 12 } }}
+ footer={[
+ ,
+ }
+ onClick={() => handleBatchDbExport(false)}
+ disabled={checkedDbKeys.length === 0}
+ >
+ 导出库结构 ({checkedDbKeys.length})
+ ,
+ }
+ onClick={() => handleBatchDbExport(true)}
+ disabled={checkedDbKeys.length === 0}
+ >
+ 备份库 ({checkedDbKeys.length})
+ ,
+ ]}
+ >
+
+
+
+ {nonRedisConnections(connections).map(conn => (
+
+ {conn.name}
+
+ ))}
+
+
连接选定后会加载当前连接下可批量导出的数据库列表。
+
+
+ {batchDatabases.length > 0 && (
+ <>
+
+
+
+
+
+
+ 已选择 {checkedDbKeys.length} / {batchDatabases.length} 个库
+
+
+
+
+ setCheckedDbKeys(values as string[])}
+ style={{ width: '100%' }}
+ >
+
+ {batchDatabases.map(db => (
+
+
+ {db.title}
+
+ ))}
+
+
+
+ >
+ )}
+
+ >
+);