mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-06 20:03:05 +08:00
@@ -107,6 +107,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
const tableSortPreference = useStore(state => state.tableSortPreference);
|
||||
const recordTableAccess = useStore(state => state.recordTableAccess);
|
||||
const setTableSortPreference = useStore(state => state.setTableSortPreference);
|
||||
const addSqlLog = useStore(state => state.addSqlLog);
|
||||
const darkMode = theme === 'dark';
|
||||
const resolvedAppearance = resolveAppearanceValues(appearance);
|
||||
const opacity = normalizeOpacityForPlatform(resolvedAppearance.opacity);
|
||||
@@ -1809,6 +1810,95 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
}
|
||||
};
|
||||
|
||||
const handleBatchClear = async () => {
|
||||
const selectedObjects = batchTables.filter(t => checkedTableKeys.includes(t.key));
|
||||
if (selectedObjects.length === 0) {
|
||||
message.warning('请至少选择一个对象');
|
||||
return;
|
||||
}
|
||||
|
||||
const { conn, dbName } = batchDbContext;
|
||||
const objectNames = selectedObjects.map(t => t.objectName);
|
||||
|
||||
const ok = await new Promise<boolean>((resolve) => {
|
||||
Modal.confirm({
|
||||
title: '确认清空选中表',
|
||||
content: `清空选中表会永久删除表中所有数据,操作不可逆,是否继续?\r\n\r\n连接: ${conn.name}\n数据库: ${dbName}`,
|
||||
okText: '继续',
|
||||
cancelText: '取消',
|
||||
onOk: () => resolve(true),
|
||||
onCancel: () => resolve(false),
|
||||
});
|
||||
});
|
||||
if (!ok) return;
|
||||
|
||||
setIsBatchModalOpen(false);
|
||||
const hide = message.loading(`正在清空选中表 (${objectNames.length})...`, 0);
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
const app = (window as any).go.app.App;
|
||||
const res = await app.TruncateTables(normalizeConnConfig(conn.config), dbName, objectNames);
|
||||
const duration = Date.now() - startTime;
|
||||
hide();
|
||||
if (res.success) {
|
||||
message.success('清空成功');
|
||||
// 构造 SQL 日志
|
||||
let logSql = `/* Truncate Tables (${objectNames.length} tables) */\n`;
|
||||
if (res.data && res.data.executedSQLs && Array.isArray(res.data.executedSQLs)) {
|
||||
logSql += res.data.executedSQLs.join(';\n') + ';';
|
||||
} else {
|
||||
logSql += objectNames.map(name => name).join('; ');
|
||||
}
|
||||
addSqlLog({
|
||||
id: Date.now().toString(),
|
||||
timestamp: Date.now(),
|
||||
sql: logSql,
|
||||
status: 'success',
|
||||
duration,
|
||||
message: res.message,
|
||||
dbName,
|
||||
affectedRows: res.data?.count || 0
|
||||
});
|
||||
} else if (res.message !== 'Cancelled') {
|
||||
message.error('清空失败: ' + res.message);
|
||||
// 记录失败的日志
|
||||
const duration = Date.now() - startTime;
|
||||
let logSql = `/* Truncate Tables (${objectNames.length} tables) - FAILED */\n`;
|
||||
if (res.data && res.data.executedSQLs && Array.isArray(res.data.executedSQLs)) {
|
||||
logSql += res.data.executedSQLs.join(';\n') + ';';
|
||||
} else {
|
||||
logSql += objectNames.map(name => name).join('; ');
|
||||
}
|
||||
addSqlLog({
|
||||
id: Date.now().toString(),
|
||||
timestamp: Date.now(),
|
||||
sql: logSql,
|
||||
status: 'error',
|
||||
duration,
|
||||
message: res.message,
|
||||
dbName
|
||||
});
|
||||
}
|
||||
} catch (e: any) {
|
||||
const duration = Date.now() - startTime;
|
||||
hide();
|
||||
const errMsg = e?.message || String(e);
|
||||
message.error('清空失败: ' + errMsg);
|
||||
// 记录异常的日志
|
||||
let logSql = `/* Truncate Tables (${objectNames.length} tables) - ERROR */\n`;
|
||||
logSql += objectNames.map(name => name).join('; ');
|
||||
addSqlLog({
|
||||
id: Date.now().toString(),
|
||||
timestamp: Date.now(),
|
||||
sql: logSql,
|
||||
status: 'error',
|
||||
duration,
|
||||
message: errMsg,
|
||||
dbName
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleCheckAll = (checked: boolean) => {
|
||||
if (batchSelectionScope === 'all') {
|
||||
setCheckedTableKeys(checked ? allBatchObjectKeys : []);
|
||||
@@ -3717,6 +3807,15 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
取消
|
||||
</Button>
|
||||
<Space size={8} wrap style={{ marginLeft: 'auto' }}>
|
||||
<Button
|
||||
key="clear"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => handleBatchClear()}
|
||||
disabled={checkedTableKeys.length === 0}
|
||||
>
|
||||
清空表
|
||||
</Button>
|
||||
<Button
|
||||
key="export-schema"
|
||||
icon={<ExportOutlined />}
|
||||
|
||||
2
frontend/wailsjs/go/app/App.d.ts
vendored
2
frontend/wailsjs/go/app/App.d.ts
vendored
@@ -192,3 +192,5 @@ export function SelectSSHKeyFile(arg1:string):Promise<connection.QueryResult>;
|
||||
export function SetWindowTranslucency(arg1:number,arg2:number):Promise<void>;
|
||||
|
||||
export function TestConnection(arg1:connection.ConnectionConfig):Promise<connection.QueryResult>;
|
||||
|
||||
export function TruncateTables(arg1:connection.ConnectionConfig,arg2:string,arg3:Array<string>):Promise<connection.QueryResult>;
|
||||
|
||||
@@ -377,3 +377,7 @@ export function SetWindowTranslucency(arg1, arg2) {
|
||||
export function TestConnection(arg1) {
|
||||
return window['go']['app']['App']['TestConnection'](arg1);
|
||||
}
|
||||
|
||||
export function TruncateTables(arg1, arg2, arg3) {
|
||||
return window['go']['app']['App']['TruncateTables'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
@@ -775,6 +775,59 @@ func (a *App) ExportDatabaseSQL(config connection.ConnectionConfig, dbName strin
|
||||
return connection.QueryResult{Success: true, Message: "导出完成"}
|
||||
}
|
||||
|
||||
// TruncateTables 清空指定表的数据(针对 MySQL 使用 TRUNCATE,MongoDB 使用 delete,否则使用 DELETE)
|
||||
func (a *App) TruncateTables(config connection.ConnectionConfig, dbName string, tableNames []string) connection.QueryResult {
|
||||
runConfig := normalizeRunConfig(config, dbName)
|
||||
|
||||
dbInst, err := a.getDatabase(runConfig)
|
||||
if err != nil {
|
||||
return connection.QueryResult{Success: false, Message: err.Error()}
|
||||
}
|
||||
|
||||
objects := make([]string, 0, len(tableNames))
|
||||
seen := make(map[string]struct{}, len(tableNames))
|
||||
for _, t := range tableNames {
|
||||
tt := strings.TrimSpace(t)
|
||||
if tt == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[tt]; ok {
|
||||
continue
|
||||
}
|
||||
seen[tt] = struct{}{}
|
||||
objects = append(objects, tt)
|
||||
}
|
||||
|
||||
dbType := strings.ToLower(strings.TrimSpace(runConfig.Type))
|
||||
var executedSQLs []string
|
||||
for _, objectName := range objects {
|
||||
var sql string
|
||||
if dbType == "mysql" || dbType == "mariadb" {
|
||||
sql = fmt.Sprintf("TRUNCATE TABLE %s", quoteQualifiedIdentByType(runConfig.Type, objectName))
|
||||
} else if dbType == "mongodb" {
|
||||
// MongoDB 使用 delete 命令清空集合中的所有文档
|
||||
// deletes 的 limit 为 0 表示删除所有匹配的文档
|
||||
sql = fmt.Sprintf(`{"delete":"%s","deletes":[{"q":{},"limit":0}]}`, objectName)
|
||||
} else {
|
||||
sql = fmt.Sprintf("DELETE FROM %s", quoteQualifiedIdentByType(runConfig.Type, objectName))
|
||||
}
|
||||
|
||||
if _, err := dbInst.Exec(sql); err != nil {
|
||||
return connection.QueryResult{Success: false, Message: fmt.Sprintf("清空 %s 失败: %v", objectName, err)}
|
||||
}
|
||||
executedSQLs = append(executedSQLs, sql)
|
||||
}
|
||||
|
||||
return connection.QueryResult{
|
||||
Success: true,
|
||||
Message: "清空成功",
|
||||
Data: map[string]interface{}{
|
||||
"executedSQLs": executedSQLs,
|
||||
"count": len(executedSQLs),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func quoteIdentByType(dbType string, ident string) string {
|
||||
if ident == "" {
|
||||
return ident
|
||||
|
||||
Reference in New Issue
Block a user