-
- {{ themeConfig.globalViceTitle }}
-
-
{
- themeConfig.globalI18n = lang;
- }
- "
- >
+
+
+
+
+
+
{
+ themeConfig.globalI18n = lang;
+ }
+ "
+ >
+
+
+
+
+
+
+ {{ item.extra.flag }}
+ {{ item.label }}
+
+
+
+
+
+
+
+
+
-
-
-
- {{ $t('login.thirdPartyLogin') }}:
-
-
-
-
-
-
-
-
+
+
+
+
{{ $t('login.thirdPartyLogin') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ © {{ new Date().getFullYear() }} {{ themeConfig.globalViceTitle }}. All rights reserved.
@@ -83,14 +105,13 @@
-
diff --git a/frontend/src/views/msg/channel/ChannelList.vue b/frontend/src/views/msg/channel/ChannelList.vue
index c15282d4..ccd5bc47 100755
--- a/frontend/src/views/msg/channel/ChannelList.vue
+++ b/frontend/src/views/msg/channel/ChannelList.vue
@@ -30,7 +30,7 @@ import { ref, toRefs, reactive, onMounted, Ref } from 'vue';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth';
-import { SearchItem } from '@/components/SearchForm';
+import { SearchItem } from '@/components/pagetable/SearchForm';
import { useI18nCreateTitle, useI18nDeleteConfirm, useI18nDeleteSuccessMsg, useI18nEditTitle } from '@/hooks/useI18n';
import { channelApi } from '../api';
import { ChannelStatusEnum, ChannelTypeEnum } from '../enums';
diff --git a/frontend/src/views/msg/tmpl/TmplList.vue b/frontend/src/views/msg/tmpl/TmplList.vue
index 87034bb0..9847a0e8 100755
--- a/frontend/src/views/msg/tmpl/TmplList.vue
+++ b/frontend/src/views/msg/tmpl/TmplList.vue
@@ -61,7 +61,7 @@ import { ref, toRefs, reactive, onMounted, Ref } from 'vue';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth';
-import { SearchItem } from '@/components/SearchForm';
+import { SearchItem } from '@/components/pagetable/SearchForm';
import { useI18nCreateTitle, useI18nDeleteConfirm, useI18nDeleteSuccessMsg, useI18nEditTitle, useI18nOperateSuccessMsg } from '@/hooks/useI18n';
import { tmplApi } from '../api';
import { TmplStatusEnum, TmplTypeEnum, ChannelTypeEnum } from '../enums';
diff --git a/frontend/src/views/oauth/Oauth2Callback.vue b/frontend/src/views/oauth/Oauth2Callback.vue
index 058dd79d..518071f7 100644
--- a/frontend/src/views/oauth/Oauth2Callback.vue
+++ b/frontend/src/views/oauth/Oauth2Callback.vue
@@ -3,7 +3,7 @@
diff --git a/frontend/src/views/ops/component/tag.ts b/frontend/src/views/ops/component/tag.ts
index 8ff34937..83eaae95 100644
--- a/frontend/src/views/ops/component/tag.ts
+++ b/frontend/src/views/ops/component/tag.ts
@@ -1,4 +1,4 @@
-import { OptionsApi, SearchItem } from '@/components/SearchForm';
+import { OptionsApi, SearchItem } from '@/components/pagetable/SearchForm';
import { ContextmenuItem } from '@/components/contextmenu';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import { tagApi } from '../tag/api';
diff --git a/frontend/src/views/ops/db/DbBackupHistoryList.vue b/frontend/src/views/ops/db/DbBackupHistoryList.vue
index aba70460..d97d37d7 100644
--- a/frontend/src/views/ops/db/DbBackupHistoryList.vue
+++ b/frontend/src/views/ops/db/DbBackupHistoryList.vue
@@ -37,7 +37,7 @@ import { toRefs, reactive, Ref, ref } from 'vue';
import { dbApi } from './api';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
-import { SearchItem } from '@/components/SearchForm';
+import { SearchItem } from '@/components/pagetable/SearchForm';
import { ElMessage, ElMessageBox } from 'element-plus';
const pageTableRef: Ref
= ref(null);
diff --git a/frontend/src/views/ops/db/DbBackupList.vue b/frontend/src/views/ops/db/DbBackupList.vue
index 63b79cfc..7a90cc0a 100644
--- a/frontend/src/views/ops/db/DbBackupList.vue
+++ b/frontend/src/views/ops/db/DbBackupList.vue
@@ -50,7 +50,7 @@ import { toRefs, reactive, defineAsyncComponent, Ref, ref } from 'vue';
import { dbApi } from './api';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
-import { SearchItem } from '@/components/SearchForm';
+import { SearchItem } from '@/components/pagetable/SearchForm';
import { ElMessage, ElMessageBox } from 'element-plus';
const DbBackupEdit = defineAsyncComponent(() => import('./DbBackupEdit.vue'));
diff --git a/frontend/src/views/ops/db/DbRestoreList.vue b/frontend/src/views/ops/db/DbRestoreList.vue
index 86b02652..5c264413 100644
--- a/frontend/src/views/ops/db/DbRestoreList.vue
+++ b/frontend/src/views/ops/db/DbRestoreList.vue
@@ -64,7 +64,7 @@ import { toRefs, reactive, defineAsyncComponent, Ref, ref } from 'vue';
import { dbApi } from './api';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
-import { SearchItem } from '@/components/SearchForm';
+import { SearchItem } from '@/components/pagetable/SearchForm';
import { ElMessage, ElMessageBox } from 'element-plus';
import { formatDate } from '@/common/utils/format';
const DbRestoreEdit = defineAsyncComponent(() => import('./DbRestoreEdit.vue'));
diff --git a/frontend/src/views/ops/db/DbSqlExecLog.vue b/frontend/src/views/ops/db/DbSqlExecLog.vue
index 2048f5bf..633357db 100644
--- a/frontend/src/views/ops/db/DbSqlExecLog.vue
+++ b/frontend/src/views/ops/db/DbSqlExecLog.vue
@@ -45,7 +45,7 @@ import { dbApi } from './api';
import { DbSqlExecTypeEnum, DbSqlExecStatusEnum } from './enums';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
-import { SearchItem } from '@/components/SearchForm';
+import { SearchItem } from '@/components/pagetable/SearchForm';
import { formatDate } from '@/common/utils/format';
const props = defineProps({
diff --git a/frontend/src/views/ops/db/DbTransferList.vue b/frontend/src/views/ops/db/DbTransferList.vue
index 718acbf5..9491d85b 100644
--- a/frontend/src/views/ops/db/DbTransferList.vue
+++ b/frontend/src/views/ops/db/DbTransferList.vue
@@ -88,7 +88,7 @@ import { dbApi } from './api';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth';
-import { SearchItem } from '@/components/SearchForm';
+import { SearchItem } from '@/components/pagetable/SearchForm';
import { getDbDialect } from '@/views/ops/db/dialect';
import { DbTransferRunningStateEnum } from './enums';
import TerminalLog from '@/components/terminal/TerminalLog.vue';
diff --git a/frontend/src/views/ops/db/InstanceList.vue b/frontend/src/views/ops/db/InstanceList.vue
index f9158898..25859649 100644
--- a/frontend/src/views/ops/db/InstanceList.vue
+++ b/frontend/src/views/ops/db/InstanceList.vue
@@ -83,7 +83,7 @@ import { TableColumn } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth';
import SvgIcon from '@/components/svgIcon/index.vue';
import { getDbDialect } from './dialect';
-import { SearchItem } from '@/components/SearchForm';
+import { SearchItem } from '@/components/pagetable/SearchForm';
import ResourceAuthCert from '../component/ResourceAuthCert.vue';
import ResourceTags from '../component/ResourceTags.vue';
import { getTagPathSearchItem } from '../component/tag';
diff --git a/frontend/src/views/ops/db/SyncTaskList.vue b/frontend/src/views/ops/db/SyncTaskList.vue
index d7607a2e..23c2f688 100644
--- a/frontend/src/views/ops/db/SyncTaskList.vue
+++ b/frontend/src/views/ops/db/SyncTaskList.vue
@@ -54,7 +54,7 @@ import { dbApi } from './api';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth';
-import { SearchItem } from '@/components/SearchForm';
+import { SearchItem } from '@/components/pagetable/SearchForm';
import { DbDataSyncRecentStateEnum, DbDataSyncRunningStateEnum } from './enums';
import { useI18nConfirm, useI18nCreateTitle, useI18nDeleteConfirm, useI18nDeleteSuccessMsg, useI18nEditTitle, useI18nOperateSuccessMsg } from '@/hooks/useI18n';
diff --git a/frontend/src/views/ops/db/component/DbDetail.vue b/frontend/src/views/ops/db/component/DbDetail.vue
new file mode 100644
index 00000000..3e41b070
--- /dev/null
+++ b/frontend/src/views/ops/db/component/DbDetail.vue
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+ {{ state.detail.id }}
+ {{ state.detail.code }}
+ {{ state.detail.name }}
+
+
+
+
+
+ {{ state.detail.host }}:{{ state.detail.port }}
+
+
+ {{ state.detail.authCertName }}
+
+ {{ state.detail.remark }}
+
+ {{ formatDate(state.detail.createTime) }}
+ {{ state.detail.creator }}
+
+ {{ formatDate(state.detail.updateTime) }}
+ {{ state.detail.modifier }}
+
+
+
+
+
+
+
diff --git a/frontend/src/views/ops/db/component/sqleditor/DbSqlEditor.vue b/frontend/src/views/ops/db/component/sqleditor/DbSqlEditor.vue
index a8e8d6a2..2715b361 100644
--- a/frontend/src/views/ops/db/component/sqleditor/DbSqlEditor.vue
+++ b/frontend/src/views/ops/db/component/sqleditor/DbSqlEditor.vue
@@ -301,13 +301,13 @@ const getKey = () => {
return props.dbId + ':' + props.dbName;
};
-/**
+/*
* 执行sql
*/
const onRunSql = async (newTab = false) => {
// 没有选中的文本,则为全部文本
let sql = getSql() as string;
- notBlank(sql && sql.trim(), t('db.noSelctRunSqlMsg'));
+ notBlank(sql && sql.trim(), t('db.noSelctRunSqlTips'));
// 去除字符串前的空格、换行等
sql = sql.replace(/(^\s*)/g, '');
@@ -315,17 +315,8 @@ const onRunSql = async (newTab = false) => {
if (sqls.length == 1) {
const oneSql = sqls[0];
- // 简单截取前十个字符
- const sqlPrefix = oneSql.slice(0, 10).toLowerCase();
- const nonQuery =
- sqlPrefix.startsWith('update') ||
- sqlPrefix.startsWith('insert') ||
- sqlPrefix.startsWith('delete') ||
- sqlPrefix.startsWith('alter') ||
- sqlPrefix.startsWith('drop') ||
- sqlPrefix.startsWith('create');
let execRemark;
- if (nonQuery) {
+ if (!getNowDbInst().isQuerySql(oneSql)) {
const res: any = await ElMessageBox.prompt(t('db.enterExecRemarkTips'), 'Tip', {
confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'),
@@ -334,17 +325,115 @@ const onRunSql = async (newTab = false) => {
execRemark = res.value;
}
runSql(oneSql, execRemark, newTab);
+ return;
+ }
+
+ // 处理多条SQL - 合并相同类型的结果
+ await runMultipleSqls(sqls, newTab);
+};
+
+/**
+ * 执行多条SQL并合并结果
+ */
+const runMultipleSqls = async (sqls: string[], newTab: boolean) => {
+ // 分类SQL语句
+ const nonQuerySqls: string[] = []; // 影响行数类SQL (UPDATE, INSERT, DELETE等)
+ const querySqls: string[] = []; // 查询类SQL (SELECT等)
+
+ const dbInst = getNowDbInst();
+ // 分类SQL
+ sqls.forEach((sql) => {
+ if (!dbInst.isQuerySql(sql)) {
+ nonQuerySqls.push(sql);
+ } else {
+ querySqls.push(sql);
+ }
+ });
+
+ // 先执行非查询类SQL(可以合并结果)
+ if (nonQuerySqls.length > 0) {
+ await runNonQuerySqls(nonQuerySqls, newTab);
+ newTab = true; // 后续查询需要新标签页
+ }
+
+ // 再执行查询类SQL(每条需要独立标签页)
+ for (let i = 0; i < querySqls.length; i++) {
+ const sql = querySqls[i];
+ await runSql(sql, '', newTab || i > 0);
+ }
+};
+
+/**
+ * 执行非查询类SQL并合并结果
+ */
+const runNonQuerySqls = async (sqls: string[], newTab: boolean) => {
+ let execRes: ExecResTab;
+ let i = 0;
+ let id;
+
+ // 获取或创建结果标签页
+ if (newTab || state.execResTabs.length == 0) {
+ id = state.execResTabs.length == 0 ? 1 : state.execResTabs[state.execResTabs.length - 1].id + 1;
+ execRes = new ExecResTab(id);
+ state.execResTabs.push(execRes);
+ i = state.execResTabs.length - 1;
} else {
- let isFirst = true;
- for (let s of sqls) {
- if (isFirst) {
- isFirst = false;
- runSql(s, '', newTab);
- } else {
- runSql(s, '', true);
+ i = state.execResTabs.findIndex((x) => x.id == state.activeTab);
+ execRes = state.execResTabs[i];
+ if (unref(execRes.loading)) {
+ ElMessage.error(t('db.currentSqlTabIsRunning'));
+ return;
+ }
+ id = execRes.id;
+ }
+
+ state.activeTab = id;
+ const startTime = new Date().getTime();
+
+ try {
+ execRes.errorMsg = '';
+ execRes.sql = sqls.join('\n\n---\n\n'); // 显示所有SQL
+
+ // 执行所有非查询SQL
+ const results: any[] = [];
+ for (const sql of sqls) {
+ try {
+ const { data, execute } = getNowDbInst().execSql(props.dbName, sql, '');
+ await execute();
+ const result: any = (data.value as any)[0];
+ results.push({
+ sql: result.sql,
+ rowsAffected: result.res?.[0]?.rowsAffected,
+ error: result.errorMsg || '-',
+ });
+ } catch (error: any) {
+ results.push({
+ sql: sql,
+ error: error.message || error.msg,
+ });
}
}
+
+ // 设置表格列
+ state.execResTabs[i].tableColumn = [
+ { columnName: 'sql', columnType: 'string', show: true },
+ { columnName: 'rowsAffected', columnType: 'number', show: true },
+ { columnName: 'error', columnType: 'string', show: true },
+ ];
+
+ state.execResTabs[i].data = results;
+ cancelUpdateFields(execRes);
+ } catch (e: any) {
+ execRes.data = [];
+ execRes.tableColumn = [];
+ execRes.table = '';
+ state.execResTabs[i].errorMsg = e.message || e.msg || 'Execution failed';
+ return;
+ } finally {
+ execRes.execTime = new Date().getTime() - startTime;
}
+
+ execRes.table = '';
};
/**
diff --git a/frontend/src/views/ops/db/db.ts b/frontend/src/views/ops/db/db.ts
index bec1468f..21d75648 100644
--- a/frontend/src/views/ops/db/db.ts
+++ b/frontend/src/views/ops/db/db.ts
@@ -381,6 +381,24 @@ export class DbInst {
return this.getDialect().quoteIdentifier(name);
};
+ /**
+ * 判断sql是否为查询类sql
+ * @param sql sql
+ * @returns
+ */
+ isQuerySql(sql: string) {
+ // 简单截取前十个字符
+ const sqlPrefix = sql.slice(0, 10).toLowerCase();
+ const nonQuery =
+ sqlPrefix.startsWith('update') ||
+ sqlPrefix.startsWith('insert') ||
+ sqlPrefix.startsWith('delete') ||
+ sqlPrefix.startsWith('alter') ||
+ sqlPrefix.startsWith('drop') ||
+ sqlPrefix.startsWith('create');
+ return !nonQuery;
+ }
+
/**
* 获取或新建dbInst,如果缓存中不存在则新建,否则直接返回
* @param inst 数据库实例,后端返回的列表接口中的信息
diff --git a/frontend/src/views/ops/es/EsInstanceList.vue b/frontend/src/views/ops/es/EsInstanceList.vue
index da515e8d..0b79c656 100644
--- a/frontend/src/views/ops/es/EsInstanceList.vue
+++ b/frontend/src/views/ops/es/EsInstanceList.vue
@@ -69,7 +69,7 @@ import { formatDate } from '@/common/utils/format';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth';
-import { SearchItem } from '@/components/SearchForm';
+import { SearchItem } from '@/components/pagetable/SearchForm';
import ResourceAuthCert from '../component/ResourceAuthCert.vue';
import ResourceTags from '../component/ResourceTags.vue';
import { getTagPathSearchItem } from '../component/tag';
diff --git a/frontend/src/views/ops/machine/MachineList.vue b/frontend/src/views/ops/machine/MachineList.vue
index cb3a7531..8e871579 100644
--- a/frontend/src/views/ops/machine/MachineList.vue
+++ b/frontend/src/views/ops/machine/MachineList.vue
@@ -270,7 +270,7 @@ import { TableColumn } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth';
import { formatByteSize, formatDate } from '@/common/utils/format';
import { TagResourceTypePath } from '@/common/commonEnum';
-import { SearchItem } from '@/components/SearchForm';
+import { SearchItem } from '@/components/pagetable/SearchForm';
import { getTagPathSearchItem } from '../component/tag';
import MachineFile from '@/views/ops/machine/file/MachineFile.vue';
import ResourceAuthCert from '../component/ResourceAuthCert.vue';
diff --git a/frontend/src/views/ops/machine/ScriptManage.vue b/frontend/src/views/ops/machine/ScriptManage.vue
index d09f6c20..18e5fec4 100644
--- a/frontend/src/views/ops/machine/ScriptManage.vue
+++ b/frontend/src/views/ops/machine/ScriptManage.vue
@@ -96,10 +96,10 @@ import ScriptEdit from './ScriptEdit.vue';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { DynamicFormDialog } from '@/components/dynamic-form';
-import { SearchItem } from '@/components/SearchForm';
+import { SearchItem } from '@/components/pagetable/SearchForm';
import { useI18n } from 'vue-i18n';
import { useI18nCreateTitle, useI18nDeleteConfirm, useI18nDeleteSuccessMsg, useI18nEditTitle } from '@/hooks/useI18n';
-import { OptionsApi } from '@/components/SearchForm/index';
+import { OptionsApi } from '@/components/pagetable/SearchForm/index';
const { t } = useI18n();
diff --git a/frontend/src/views/ops/machine/component/MachineDetail.vue b/frontend/src/views/ops/machine/component/MachineDetail.vue
index 65debe5e..07f12d50 100644
--- a/frontend/src/views/ops/machine/component/MachineDetail.vue
+++ b/frontend/src/views/ops/machine/component/MachineDetail.vue
@@ -1,37 +1,37 @@
-
-
-
- {{ props.code }}
-
+
+
+
+ {{ props.code }}
+
+
-
- {{ state.machineDetail.id }}
- {{ state.machineDetail.code }}
- {{ state.machineDetail.name }}
+
+ {{ state.machineDetail.id }}
+ {{ state.machineDetail.code }}
+ {{ state.machineDetail.name }}
-
+
- {{ state.machineDetail.ip }}
- {{ state.machineDetail.port }}
+ {{ state.machineDetail.ip }}
+ {{ state.machineDetail.port }}
- {{ state.machineDetail.remark }}
+ {{ state.machineDetail.remark }}
-
- {{ state.machineDetail.sshTunnelMachineId > 0 ? $t('common.yes') : $t('common.no') }}
-
-
- {{ state.machineDetail.enableRecorder == 1 ? $t('common.yes') : $t('common.no') }}
-
+
+ {{ state.machineDetail.sshTunnelMachineId > 0 ? $t('common.yes') : $t('common.no') }}
+
+
+ {{ state.machineDetail.enableRecorder == 1 ? $t('common.yes') : $t('common.no') }}
+
- {{ formatDate(state.machineDetail.createTime) }}
- {{ state.machineDetail.creator }}
+ {{ formatDate(state.machineDetail.createTime) }}
+ {{ state.machineDetail.creator }}
- {{ formatDate(state.machineDetail.updateTime) }}
- {{ state.machineDetail.modifier }}
-
-
-
+ {{ formatDate(state.machineDetail.updateTime) }}
+ {{ state.machineDetail.modifier }}
+
+