添加数据库管理工具,修改部分问题

This commit is contained in:
lijun 2025-09-06 21:42:15 +08:00
parent 37b2ad396f
commit 3c9a1651ea
12 changed files with 1888 additions and 308 deletions

6
components.d.ts vendored
View File

@ -14,6 +14,8 @@ declare module '@vue/runtime-core' {
ElCard: typeof import('element-plus/es')['ElCard']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCol: typeof import('element-plus/es')['ElCol']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
@ -35,15 +37,19 @@ declare module '@vue/runtime-core' {
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElStatistic: typeof import('element-plus/es')['ElStatistic']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTimeline: typeof import('element-plus/es')['ElTimeline']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTooltip: typeof import('element-plus/es')['ElTooltip']

View File

@ -5,7 +5,7 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>大学生学业作品AI生成工具</title>
<title>集成AI工具</title>
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_830376_92o68tc95je.css">
</head>

142
package-lock.json generated
View File

@ -9,6 +9,11 @@
"version": "5.5.0",
"dependencies": {
"@agoose77/markdown-it-mermaid": "^1.1.0",
"@codemirror/basic-setup": "^0.20.0",
"@codemirror/lang-sql": "^6.9.1",
"@codemirror/state": "^6.5.2",
"@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.38.1",
"@element-plus/icons-vue": "*",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
@ -264,6 +269,112 @@
"@lezer/common": "^1.0.0"
}
},
"node_modules/@codemirror/basic-setup": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@codemirror/basic-setup/-/basic-setup-0.20.0.tgz",
"integrity": "sha512-W/ERKMLErWkrVLyP5I8Yh8PXl4r+WFNkdYVSzkXYPQv2RMPSkWpr2BgggiSJ8AHF/q3GuApncDD8I4BZz65fyg==",
"deprecated": "In version 6.0, this package has been renamed to just 'codemirror'",
"dependencies": {
"@codemirror/autocomplete": "^0.20.0",
"@codemirror/commands": "^0.20.0",
"@codemirror/language": "^0.20.0",
"@codemirror/lint": "^0.20.0",
"@codemirror/search": "^0.20.0",
"@codemirror/state": "^0.20.0",
"@codemirror/view": "^0.20.0"
}
},
"node_modules/@codemirror/basic-setup/node_modules/@codemirror/autocomplete": {
"version": "0.20.3",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-0.20.3.tgz",
"integrity": "sha512-lYB+NPGP+LEzAudkWhLfMxhTrxtLILGl938w+RcFrGdrIc54A+UgmCoz+McE3IYRFp4xyQcL4uFJwo+93YdgHw==",
"dependencies": {
"@codemirror/language": "^0.20.0",
"@codemirror/state": "^0.20.0",
"@codemirror/view": "^0.20.0",
"@lezer/common": "^0.16.0"
}
},
"node_modules/@codemirror/basic-setup/node_modules/@codemirror/commands": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-0.20.0.tgz",
"integrity": "sha512-v9L5NNVA+A9R6zaFvaTbxs30kc69F6BkOoiEbeFw4m4I0exmDEKBILN6mK+GksJtvTzGBxvhAPlVFTdQW8GB7Q==",
"dependencies": {
"@codemirror/language": "^0.20.0",
"@codemirror/state": "^0.20.0",
"@codemirror/view": "^0.20.0",
"@lezer/common": "^0.16.0"
}
},
"node_modules/@codemirror/basic-setup/node_modules/@codemirror/language": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-0.20.2.tgz",
"integrity": "sha512-WB3Bnuusw0xhVvhBocieYKwJm04SOk5bPoOEYksVHKHcGHFOaYaw+eZVxR4gIqMMcGzOIUil0FsCmFk8yrhHpw==",
"dependencies": {
"@codemirror/state": "^0.20.0",
"@codemirror/view": "^0.20.0",
"@lezer/common": "^0.16.0",
"@lezer/highlight": "^0.16.0",
"@lezer/lr": "^0.16.0",
"style-mod": "^4.0.0"
}
},
"node_modules/@codemirror/basic-setup/node_modules/@codemirror/lint": {
"version": "0.20.3",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-0.20.3.tgz",
"integrity": "sha512-06xUScbbspZ8mKoODQCEx6hz1bjaq9m8W8DxdycWARMiiX1wMtfCh/MoHpaL7ws/KUMwlsFFfp2qhm32oaCvVA==",
"dependencies": {
"@codemirror/state": "^0.20.0",
"@codemirror/view": "^0.20.2",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/basic-setup/node_modules/@codemirror/search": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-0.20.1.tgz",
"integrity": "sha512-ROe6gRboQU5E4z6GAkNa2kxhXqsGNbeLEisbvzbOeB7nuDYXUZ70vGIgmqPu0tB+1M3F9yWk6W8k2vrFpJaD4Q==",
"dependencies": {
"@codemirror/state": "^0.20.0",
"@codemirror/view": "^0.20.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/basic-setup/node_modules/@codemirror/state": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.20.1.tgz",
"integrity": "sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ=="
},
"node_modules/@codemirror/basic-setup/node_modules/@codemirror/view": {
"version": "0.20.7",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.20.7.tgz",
"integrity": "sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==",
"dependencies": {
"@codemirror/state": "^0.20.0",
"style-mod": "^4.0.0",
"w3c-keyname": "^2.2.4"
}
},
"node_modules/@codemirror/basic-setup/node_modules/@lezer/common": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.16.1.tgz",
"integrity": "sha512-qPmG7YTZ6lATyTOAWf8vXE+iRrt1NJd4cm2nJHK+v7X9TsOF6+HtuU/ctaZy2RCrluxDb89hI6KWQ5LfQGQWuA=="
},
"node_modules/@codemirror/basic-setup/node_modules/@lezer/highlight": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-0.16.0.tgz",
"integrity": "sha512-iE5f4flHlJ1g1clOStvXNLbORJoiW4Kytso6ubfYzHnaNo/eo5SKhxs4wv/rtvwZQeZrK3we8S9SyA7OGOoRKQ==",
"dependencies": {
"@lezer/common": "^0.16.0"
}
},
"node_modules/@codemirror/basic-setup/node_modules/@lezer/lr": {
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-0.16.3.tgz",
"integrity": "sha512-pau7um4eAw94BEuuShUIeQDTf3k4Wt6oIUOYxMmkZgDHdqtIcxWND4LRxi8nI9KuT4I1bXQv67BCapkxt7Ywqw==",
"dependencies": {
"@lezer/common": "^0.16.0"
}
},
"node_modules/@codemirror/commands": {
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz",
@ -275,6 +386,19 @@
"@lezer/common": "^1.1.0"
}
},
"node_modules/@codemirror/lang-sql": {
"version": "6.9.1",
"resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.9.1.tgz",
"integrity": "sha512-ecSk3gm/mlINcURMcvkCZmXgdzPSq8r/yfCtTB4vgqGGIbBC2IJIAy7GqYTy5pgBEooTVmHP2GZK6Z7h63CDGg==",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@codemirror/language": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.0.tgz",
@ -316,12 +440,24 @@
"@marijn/find-cluster-break": "^1.0.0"
}
},
"node_modules/@codemirror/theme-one-dark": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz",
"integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@lezer/highlight": "^1.0.0"
}
},
"node_modules/@codemirror/view": {
"version": "6.36.8",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.8.tgz",
"integrity": "sha512-yoRo4f+FdnD01fFt4XpfpMCcCAo9QvZOtbrXExn4SqzH32YC6LgzqxfLZw/r6Ge65xyY03mK/UfUqrVw1gFiFg==",
"version": "6.38.1",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.1.tgz",
"integrity": "sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==",
"dependencies": {
"@codemirror/state": "^6.5.0",
"crelt": "^1.0.6",
"style-mod": "^4.1.0",
"w3c-keyname": "^2.2.4"
}

View File

@ -9,6 +9,11 @@
},
"dependencies": {
"@agoose77/markdown-it-mermaid": "^1.1.0",
"@codemirror/basic-setup": "^0.20.0",
"@codemirror/lang-sql": "^6.9.1",
"@codemirror/state": "^6.5.2",
"@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.38.1",
"@element-plus/icons-vue": "*",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",

View File

@ -39,7 +39,7 @@ export const menuData: Menus[] = [
'id': '71',
'title': '用户功能管理',
'index': '71',
'icon': 'HomeFilled',
'icon': 'UserFilled',
'children': [
{
id: '51',
@ -234,13 +234,13 @@ export const menuData: Menus[] = [
},
{
id: '59',
icon: 'ChatDotSquare',
icon: 'Select',
index: '/project-select',
title: '项目选择',
},
{
id: '72',
icon: 'ChatDotSquare',
icon: 'Edit',
index: '/db-manage',
title: '数据库管理工具',
},

View File

@ -2,7 +2,7 @@
<div class="container">
<div class="content-title">支持拖拽</div>
<el-upload class="upload-demo" drag
action="http://127.0.0.1:41000/file/upload" multiple
action="https://pm.ljsea.top/file/upload" multiple
:data="uploadData"
:headers="headers"
:on-success="handleSuccess"

View File

@ -3,7 +3,7 @@ export interface DatabaseConfig {
ID: number;
CreatedAt: string;
UpdatedAt: string;
DeletedAt: null;
DeletedAt: string | null;
UserID: number;
DB_IP: string;
DB_Port: number;
@ -15,3 +15,29 @@ export interface DatabaseConfig {
DB_STATUS: number;
Name: string;
}
export interface ISQLOperation {
/** 记录ID */
ID: number;
/** 创建时间 */
CreatedAt: string;
/** 更新时间 */
UpdatedAt: string;
/** 删除时间null表示未删除 */
DeletedAt: string | null;
/** 操作用户ID */
UserID: number;
/** 执行的SQL语句 */
SQL: string;
/** 数据库ID */
DB_ID: number;
/** 执行状态0可能表示成功或初始状态 */
Status: number;
}

View File

@ -0,0 +1,525 @@
<template>
<div class="db-manage-container">
<el-row :gutter="20">
<!-- 左侧数据库表列表 -->
<el-col :span="4">
<div class="table-list">
<h3>数据库表</h3>
<el-scrollbar height="400px">
<el-tree
:data="tables"
node-key="name"
:props="defaultProps"
@node-click="handleTableClick"
/>
</el-scrollbar>
</div>
</el-col>
<!-- 右侧主内容区 -->
<el-col :span="20">
<!-- 顶部下拉选择框 -->
<el-row :gutter="20" class="mb-20">
<el-col :span="10">
<el-select
v-model="selectedDatabase"
filterable
placeholder="请选择数据库"
class="w-100"
@change="getRunSQLHistory"
>
<template #prefix>
<el-icon><search /></el-icon>
</template>
<el-option
v-for="item in databases"
:key="item.ID"
:label="item.Name"
:value="item.ID"
>
<span>{{ item.Name }}</span>
<span class="option-actions">
<el-icon @click.stop="editDatabase(item)"><edit /></el-icon>
<el-icon @click.stop="deleteDatabase(item)"
><delete
/></el-icon>
</span>
</el-option>
<template #append>
<el-button icon="plus" @click="showAddDatabaseDialog" />
</template>
</el-select>
</el-col>
<el-col :span="1">
<el-icon @click="showAddDatabaseDialog"><FolderAdd /></el-icon>
</el-col>
<el-col :span="6">
<el-select
v-model="selectedConnection"
filterable
placeholder="请选择服务器"
class="w-100"
>
<template #prefix>
<el-icon><search /></el-icon>
</template>
<el-option
v-for="item in connections"
:key="item.domain"
:label="item.name"
:value="item.domain"
>
<span>{{ item.name }}</span>
</el-option>
<template #append>
<el-button circle @click="showAddDatabaseDialog" />
</template>
</el-select>
</el-col>
<el-col :span="3">
<el-button
type="primary"
@click="showSQLHistoryDialog"
class="w-100"
:loading="executing"
>执行历史SQL</el-button
>
</el-col>
</el-row>
<!-- SQL输入框和执行按钮 -->
<el-row class="mb-20">
<el-col :span="20">
<SqlEditor v-model="sqlQuery" />
</el-col>
<el-col :span="4">
<el-button
type="primary"
@click="executeSql"
class="w-100"
:loading="executing"
>
执行
</el-button>
</el-col>
</el-row>
<!-- 结果表格 -->
<el-table
:data="tableData"
border
style="width: 100%"
height="400px"
v-horizontal-scroll="'always'"
v-loading="loading"
>
<el-table-column
v-for="column in tableColumns"
:key="column.prop"
:prop="column.prop"
:label="column.label"
/>
</el-table>
</el-col>
</el-row>
<!-- 添加/编辑数据库对话框 -->
<el-dialog
v-model="databaseDialogVisible"
:title="isEditDatabase ? '编辑数据库' : '添加数据库'"
width="50%"
:close-on-click-modal="false"
>
<el-form :model="databaseForm" label-width="100px">
<el-form-item label="数据库名称" required>
<el-input
v-model="databaseForm.DB_NAME"
placeholder="请输入数据库名称"
/>
</el-form-item>
<el-form-item label="数据库密码" required>
<el-input
v-model="databaseForm.DB_Password"
type="password"
placeholder="请输入数据库密码"
show-password
/>
</el-form-item>
<el-form-item label="用户名" required>
<el-input
v-model="databaseForm.DB_User"
placeholder="请输入用户名称"
/>
</el-form-item>
<el-form-item label="IP或域名" required>
<el-input
v-model="databaseForm.DB_IP"
placeholder="请输入数据库ip或域名"
/>
</el-form-item>
<el-form-item label="数据库端口" required>
<el-input
v-model="databaseForm.DB_Port"
placeholder="请输入数据库端口"
/>
</el-form-item>
<el-form-item label="数据库类型" required>
<el-select
v-model="databaseForm.DB_Type"
placeholder="请选择数据库类型"
>
<el-option label="MySQL" value="0" />
<el-option label="PostgreSQL" value="1" />
</el-select>
</el-form-item>
<el-form-item label="描述信息">
<el-input
v-model="databaseForm.DB_Desc"
placeholder="请输入备注信息"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="databaseDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveDatabase">保存</el-button>
</template>
</el-dialog>
<el-dialog
v-model="sqlHistoryDialogVisible"
title="SQL执行历史"
width="50%"
:close-on-click-modal="false"
>
<el-table :data="sqlHistory" stripe width="100%" height="600px" fit>
<el-table-column prop="ID" label="SQLID" width="150"></el-table-column>
<el-table-column prop="SQL" label="SQL" width="500"> </el-table-column>
<el-table-column label="操作" width="270">
<template #default="scope">
<el-button
type="primary"
size="mini"
@click.prevent="useCurrSql(scope.$index)"
>引用</el-button
>
<el-button
type="primary"
size="mini"
@click.prevent="DelSqlHistory(scope.$index)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="sqlHistoryDialogVisible = false">取消</el-button>
</template>
</el-dialog>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive } from "vue";
import {
FindDBManageListService,
AddDBManageService,
UpdateDBManageService,
RunSQLService,
GetSQLRunHistoryService,
} from "@/api/dbm";
import { DatabaseConfig, ISQLOperation } from "@/types/dbm";
import horizontalScroll from "el-table-horizontal-scroll";
import { Search, Edit, Delete, Plus } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import SqlEditor from './sqlEditor.vue';
//
const databases = ref<DatabaseConfig[]>([]);
const selectedDatabase = ref("");
//
const connections = ref([
{ name: "腾讯服务器", domain: "tx.ljsea.top" },
{ name: "阿里云服务器", domain: "al.ljsea.top" },
{ name: "家里服务器", domain: "gep.ljsea.top" },
]);
const supportdedDBTypes = ref({ 0: "mysql", 1: "postgresql" });
const selectedConnection = ref("");
// SQL
const sqlQuery = ref("");
const executing = ref(false);
const tableData = ref([]);
const tableColumns = ref([]);
const loading = ref(false);
//
const tables = ref([
{
name: "users",
children: [{ name: "id" }, { name: "username" }, { name: "email" }],
},
{
name: "products",
children: [{ name: "id" }, { name: "name" }, { name: "price" }],
},
]);
const defaultProps = {
children: "children",
label: "name",
};
//
const databaseDialogVisible = ref(false);
const sqlHistoryDialogVisible = ref(false);
const isEditDatabase = ref(false);
const databaseForm = ref<DatabaseConfig>();
const sqlHistory = ref<ISQLOperation[]>([]);
//
const connectionDialogVisible = ref(false);
const isEditConnection = ref(false);
const connectionForm = reactive({
id: 0,
name: "",
host: "",
port: "",
username: "",
password: "",
});
const showSQLHistoryDialog = async () => {
sqlHistoryDialogVisible.value = true;
await getRunSQLHistory();
};
const showAddDatabaseDialog = () => {
isEditDatabase.value = false;
databaseForm.value = {
ID: 0,
DB_IP: "",
DB_Port: 3306,
DB_NAME: "",
DB_User: "",
DB_Password: "",
DB_Type: 0,
CreatedAt: "",
UpdatedAt: "",
};
databaseDialogVisible.value = true;
};
const editDatabase = (item) => {
isEditDatabase.value = true;
databaseForm.value = item;
databaseDialogVisible.value = true;
};
const GetDBManageList = async () => {
let req = {
get_type: 0,
token: localStorage.getItem("token"),
id: localStorage.getItem("userId"),
};
await FindDBManageListService(req)
.then((res: any) => {
if (res.code === 0) {
databases.value = res.data;
for (let i = 0; i < databases.value.length; i++) {
let type = supportdedDBTypes.value[databases.value[i].DB_Type];
databases.value[i].Name =
type +
" - " +
databases.value[i].DB_IP +
":" +
databases.value[i].DB_Port +
" - " +
databases.value[i].DB_NAME;
}
ElMessage.success("获取数据库列表成功");
} else {
console.error("获取数据库列表失败:", res.message);
}
})
.catch((error: any) => {
console.error("请求错误:", error);
});
};
GetDBManageList();
const useCurrSql = (index) => {
sqlQuery.value = sqlHistory.value[index].SQL;
sqlHistoryDialogVisible.value = false;
};
const DelSqlHistory = (index) => {
sqlHistory.value.splice(index, 1);
};
const deleteDatabase = (item) => {
databases.value = databases.value.filter((db) => db.ID !== item.id);
};
const saveDatabase = () => {
let req = {};
if (isEditDatabase.value) {
req = {
token: localStorage.getItem("token"),
db_id: databaseForm.value.ID,
db_name: databaseForm.value.DB_NAME,
db_type: databaseForm.value.DB_Type,
db_ip: databaseForm.value.DB_IP,
db_port: databaseForm.value.DB_Port,
db_user: databaseForm.value.DB_User,
db_password: databaseForm.value.DB_Password,
db_desc: databaseForm.value.DB_Desc,
};
UpdateDBManageService(req)
.then((res: any) => {
if (res.code === 0) {
ElMessage.success("更新成功");
GetDBManageList();
} else {
ElMessage.error("更新失败");
}
})
.catch((error: any) => {
console.error("请求错误:", error);
});
} else {
req = {
token: localStorage.getItem("token"),
db_id: databaseForm.value.ID,
db_name: databaseForm.value.DB_NAME,
db_type: databaseForm.value.DB_Type,
db_ip: databaseForm.value.DB_IP,
db_port: databaseForm.value.DB_Port,
db_user: databaseForm.value.DB_User,
db_password: databaseForm.value.DB_Password,
db_desc: databaseForm.value.DB_Desc,
};
AddDBManageService(req)
.then((res: any) => {
if (res.code === 0) {
ElMessage.success("添加成功");
GetDBManageList();
} else {
ElMessage.error("添加失败");
}
})
.catch((error: any) => {
console.error("请求错误:", error);
});
}
databaseDialogVisible.value = false;
};
const getRunSQLHistory = async () => {
if (!selectedDatabase.value) {
ElMessage({
type: "error",
message: "请选择数据库",
});
return;
}
let req = {
db_id: selectedDatabase.value,
token: localStorage.getItem("token"),
get_type: 0,
};
await GetSQLRunHistoryService(req)
.then((res: any) => {
if (res.code === 0) {
sqlHistory.value = res.data;
ElMessage({
type: "success",
message: "获取SQL执行历史成功",
});
} else {
console.error("获取SQL执行历史失败:", res.message);
}
})
.catch((error: any) => {
console.error("请求错误:", error);
});
};
const executeSql = () => {
if (!selectedDatabase.value) {
ElMessage({
type: "error",
message: "请选择数据库",
});
return;
}
executing.value = true;
loading.value = true;
let req = {
token: localStorage.getItem("token"),
id: localStorage.getItem("userId"),
sql: sqlQuery.value,
db_id: selectedDatabase.value,
};
try {
RunSQLService(req)
.then((res: any) => {
if (res.code === 0) {
tableData.value = res.data.Rows;
tableColumns.value = res.data.Columns;
} else {
console.error("执行SQL失败:", res.message);
ElMessage({
type: "error",
message: res.message,
});
}
})
.catch((error: any) => {
console.error("请求错误:", error);
});
} catch (e) {
console.log(e);
}
executing.value = false;
loading.value = false;
};
const handleTableClick = (data) => {
if (!data.children) {
sqlQuery.value = `SELECT * FROM ${data.name}`;
}
};
</script>
<style scoped>
.db-manage-container {
padding: 20px;
}
.table-list {
background: #fff;
padding: 10px;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.mb-20 {
margin-bottom: 20px;
}
.w-100 {
width: 100%;
}
.option-actions {
float: right;
}
.option-actions .el-icon {
margin-left: 10px;
cursor: pointer;
}
</style>

View File

@ -1,31 +1,19 @@
<template>
<div class="db-manage-container">
<el-row :gutter="20">
<!-- 左侧数据库表列表 -->
<el-col :span="4">
<div class="table-list">
<h3>数据库表</h3>
<el-scrollbar height="400px">
<el-tree
:data="tables"
node-key="name"
:props="defaultProps"
@node-click="handleTableClick"
/>
</el-scrollbar>
</div>
</el-col>
<!-- 右侧主内容区 -->
<el-col :span="20">
<el-col :span="24">
<!-- 顶部下拉选择框 -->
<el-row :gutter="20" class="mb-20">
<el-col :span="12">
<el-col :span="10">
<el-select
v-model="selectedDatabase"
filterable
placeholder="请选择数据库"
class="w-100"
@change="getRunSQLHistory"
>
<template #prefix>
<el-icon><search /></el-icon>
@ -39,7 +27,9 @@
<span>{{ item.Name }}</span>
<span class="option-actions">
<el-icon @click.stop="editDatabase(item)"><edit /></el-icon>
<el-icon @click.stop="deleteDatabase(item)"><delete /></el-icon>
<el-icon @click.stop="deleteDatabase(item)"
><delete
/></el-icon>
</span>
</el-option>
<template #append>
@ -47,11 +37,14 @@
</template>
</el-select>
</el-col>
<el-col :span="12">
<el-col :span="1">
<el-icon @click="showAddDatabaseDialog"><FolderAdd /></el-icon>
</el-col>
<el-col :span="6">
<el-select
v-model="selectedConnection"
filterable
placeholder="请选择连接"
placeholder="请选择服务器"
class="w-100"
>
<template #prefix>
@ -59,21 +52,26 @@
</template>
<el-option
v-for="item in connections"
:key="item.id"
:key="item.domain"
:label="item.name"
:value="item.id"
:value="item.domain"
>
<span>{{ item.name }}</span>
<span class="option-actions">
<el-icon @click.stop="editConnection(item)"><edit /></el-icon>
<el-icon @click.stop="deleteConnection(item)"><delete /></el-icon>
</span>
</el-option>
<template #append>
<el-button icon="plus" @click="showAddConnectionDialog" />
<el-button circle @click="showAddDatabaseDialog" />
</template>
</el-select>
</el-col>
<el-col :span="3">
<el-button
type="primary"
@click="showSQLHistoryDialog"
class="w-100"
:loading="executing"
>执行历史SQL</el-button
>
</el-col>
</el-row>
<!-- SQL输入框和执行按钮 -->
@ -85,9 +83,16 @@
:rows="3"
placeholder="请输入SQL语句"
/>
<!-- <SqlEditor v-model="sqlQuery" /> -->
</el-col>
<el-col :span="4">
<el-button type="primary" @click="executeSql" class="w-100" :loading="executing">
<el-button
type="primary"
@click="executeSql"
class="w-100"
:loading="executing"
>
执行
</el-button>
</el-col>
@ -96,7 +101,6 @@
<!-- 结果表格 -->
<el-table
:data="tableData"
border
style="width: 100%"
height="400px"
v-horizontal-scroll="'always'"
@ -121,7 +125,63 @@
>
<el-form :model="databaseForm" label-width="100px">
<el-form-item label="数据库名称" required>
<el-input v-model="databaseForm.name" placeholder="请输入数据库名称" />
<el-input
v-model="databaseForm.DB_NAME"
placeholder="请输入数据库名称"
/>
</el-form-item>
</el-form>
<el-form :model="databaseForm" label-width="100px">
<el-form-item label="数据库密码" required>
<el-input
v-model="databaseForm.DB_Password"
type="password"
placeholder="请输入数据库密码"
show-password
/>
</el-form-item>
</el-form>
<el-form :model="databaseForm" label-width="100px">
<el-form-item label="用户名" required>
<el-input
v-model="databaseForm.DB_User"
placeholder="请输入用户名称"
/>
</el-form-item>
</el-form>
<el-form :model="databaseForm" label-width="100px">
<el-form-item label="IP或域名" required>
<el-input
v-model="databaseForm.DB_IP"
placeholder="请输入数据库ip或域名"
/>
</el-form-item>
</el-form>
<el-form :model="databaseForm" label-width="100px">
<el-form-item label="数据库端口" required>
<el-input
v-model="databaseForm.DB_Port"
placeholder="请输入数据库端口"
/>
</el-form-item>
</el-form>
<el-form :model="databaseForm" label-width="100px">
<el-form-item label="数据库类型" required>
<el-select
v-model="databaseForm.DB_Type"
placeholder="请选择数据库类型"
>
<el-option label="MySQL" value="0" />
<el-option label="PostgreSQL" value="1" />
</el-select>
</el-form-item>
</el-form>
<el-form :model="databaseForm" label-width="100px">
<el-form-item label="描述信息">
<el-input
v-model="databaseForm.DB_Desc"
placeholder="请输入备注信息"
/>
</el-form-item>
</el-form>
<template #footer>
@ -130,236 +190,315 @@
</template>
</el-dialog>
<!-- 添加/编辑连接对话框 -->
<el-dialog
v-model="connectionDialogVisible"
:title="isEditConnection ? '编辑连接' : '添加连接'"
v-model="sqlHistoryDialogVisible"
title="SQL执行历史"
width="50%"
:close-on-click-modal="false"
>
<el-form :model="connectionForm" label-width="100px">
<el-form-item label="连接名称" required>
<el-input v-model="connectionForm.name" placeholder="请输入连接名称" />
</el-form-item>
<el-form-item label="连接地址" required>
<el-input v-model="connectionForm.host" placeholder="请输入连接地址" />
</el-form-item>
<el-form-item label="端口" required>
<el-input v-model="connectionForm.port" placeholder="请输入端口" />
</el-form-item>
<el-form-item label="用户名" required>
<el-input v-model="connectionForm.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="密码" required>
<el-input v-model="connectionForm.password" type="password" placeholder="请输入密码" />
</el-form-item>
</el-form>
<el-table :data="sqlHistory" stripe width="100%" height="600px" fit>
<el-table-column prop="ID" label="SQLID" width="150"></el-table-column>
<el-table-column prop="SQL" label="SQL" width="500"> </el-table-column>
<el-table-column label="操作" width="270">
<template #default="scope">
<el-button
type="primary"
size="mini"
@click.prevent="useCurrSql(scope.$index)"
>引用</el-button
>
<el-button
type="primary"
size="mini"
@click.prevent="DelSqlHistory(scope.$index)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="connectionDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveConnection">保存</el-button>
<el-button @click="sqlHistoryDialogVisible = false">取消</el-button>
</template>
</el-dialog>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive } from 'vue';
import {FindDBManageListService,AddDBManageService,UpdateDBManageService,RunSQLService,GetSQLRunHistoryService } from '@/api/dbm';
import {DatabaseConfig} from '@/types/dbm';
import horizontalScroll from 'el-table-horizontal-scroll'
<script setup lang="ts">
import { ref, reactive } from "vue";
import {
Search, Edit, Delete, Plus
} from '@element-plus/icons-vue';
FindDBManageListService,
AddDBManageService,
UpdateDBManageService,
RunSQLService,
GetSQLRunHistoryService,
} from "@/api/dbm";
import { DatabaseConfig, ISQLOperation } from "@/types/dbm";
import { Search, Edit, Delete, Plus } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import SqlEditor from './sqlEditor.vue';
//
const databases = ref<DatabaseConfig[]>([]);
const selectedDatabase = ref("");
//
const connections = ref([
{ name: "腾讯服务器", domain: "tx.ljsea.top" },
{ name: "阿里云服务器", domain: "al.ljsea.top" },
{ name: "家里服务器", domain: "gep.ljsea.top" },
]);
const supportdedDBTypes = ref({ 0: "mysql", 1: "postgresql" });
const selectedConnection = ref("");
// SQL
const sqlQuery = ref("");
const executing = ref(false);
const tableData = ref([]);
const tableColumns = ref([]);
const loading = ref(false);
//
export default defineComponent({
name: 'DbManage',
components: {
Search, Edit, Delete, Plus,horizontalScroll
},
setup() {
//
const databases = ref<DatabaseConfig[]>([]);
const selectedDatabase = ref('');
//
const databaseDialogVisible = ref(false);
const sqlHistoryDialogVisible = ref(false);
const isEditDatabase = ref(false);
const databaseForm = ref<DatabaseConfig>();
const sqlHistory = ref<ISQLOperation[]>([]);
//
const connections = ref([
{ id: 1, name: '本地开发环境' },
{ id: 2, name: '测试环境' },
{ id: 3, name: '生产环境' },
]);
const selectedConnection = ref('');
// SQL
const sqlQuery = ref('');
const executing = ref(false);
const tableData = ref([]);
const tableColumns = ref([]);
const loading = ref(false);
//
const tables = ref([
{ name: 'users', children: [{ name: 'id' }, { name: 'username' }, { name: 'email' }] },
{ name: 'products', children: [{ name: 'id' }, { name: 'name' }, { name: 'price' }] },
]);
const defaultProps = {
children: 'children',
label: 'name',
};
//
const databaseDialogVisible = ref(false);
const isEditDatabase = ref(false);
const databaseForm = reactive({
//
const connectionDialogVisible = ref(false);
const isEditConnection = ref(false);
const connectionForm = reactive({
id: 0,
name: '',
});
name: "",
host: "",
port: "",
username: "",
password: "",
});
const showSQLHistoryDialog = async () => {
sqlHistoryDialogVisible.value = true;
console.log('SQL history dialog opened:', sqlHistoryDialogVisible.value);
await getRunSQLHistory();
};
//
const connectionDialogVisible = ref(false);
const isEditConnection = ref(false);
const connectionForm = reactive({
id: 0,
name: '',
host: '',
port: '',
username: '',
password: '',
});
//
const showAddDatabaseDialog = () => {
//
const showAddDatabaseDialog = () => {
isEditDatabase.value = false;
databaseForm.id = 0;
databaseForm.name = '';
databaseDialogVisible.value = true;
databaseForm.value = {
ID: 0,
DB_IP: "",
DB_Port: 3306,
DB_NAME: "",
DB_User: "",
DB_Password: "",
DB_Type: 0,
CreatedAt: "",
UpdatedAt: "",
UserID: 0,
DB_Desc: "",
DB_STATUS: 0,
Name: "",
DeletedAt: null,
};
databaseDialogVisible.value = true;
};
const editDatabase = (item) => {
const editDatabase = (item) => {
isEditDatabase.value = true;
databaseForm.id = item.id;
databaseForm.name = item.name;
console.log("editDatabase:", item);
databaseForm.value = item;
databaseDialogVisible.value = true;
};
};
const GetDBManageList = async () => {
let req = {"get_type":0, "token":localStorage.getItem("token"), "id":localStorage.getItem("userId")}
await FindDBManageListService(req).then((res: any) => {
const GetDBManageList = async () => {
let req = {
get_type: 0,
token: localStorage.getItem("token"),
id: localStorage.getItem("userId"),
};
await FindDBManageListService(req)
.then((res: any) => {
if (res.code === 0) {
databases.value = res.data;
for (let i = 0; i < databases.value.length; i++) {
let type = "MySQL";
switch(databases.value[i].DB_Type) {
case 0:
type = "MySQL";
break;
case 1:
type = "PostgreSQL";
break;
}
databases.value[i].Name = type + " - "+ databases.value[i].DB_IP + ":" + databases.value[i].DB_Port + " - " + databases.value[i].DB_NAME;
let type = supportdedDBTypes.value[databases.value[i].DB_Type];
databases.value[i].Name =
type +
" - " +
databases.value[i].DB_IP +
":" +
databases.value[i].DB_Port +
" - " +
databases.value[i].DB_NAME;
}
ElMessage.success("获取数据库列表成功");
console.log("获取数据库列表成功:", databases.value);
// console.log(":", res.data);
// console.log(":", res.data);
// console.log(":", res.data);
} else {
console.error('获取数据库列表失败:', res.message);
console.error("获取数据库列表失败:", res.message);
}
}).catch((error: any) => {
console.error('请求错误:', error);
})
.catch((error: any) => {
console.error("请求错误:", error);
});
};
GetDBManageList();
};
GetDBManageList();
const deleteDatabase = (item) => {
const useCurrSql = (index) => {
sqlQuery.value = sqlHistory.value[index].SQL;
sqlHistoryDialogVisible.value = false;
};
const DelSqlHistory = (index) => {
sqlHistory.value.splice(index, 1);
};
const deleteDatabase = (item) => {
// API
databases.value = databases.value.filter(db => db.ID !== item.id);
};
databases.value = databases.value.filter((db) => db.ID !== item.id);
};
const saveDatabase = () => {
const saveDatabase = () => {
let req = {};
if (isEditDatabase.value) {
//
const index = databases.value.findIndex(db => db.ID === databaseForm.id);
if (index !== -1) {
databases.value[index].Name = databaseForm.name;
// const index = databases.value.findIndex(db => db.ID === databaseForm.id);
// if (index !== -1) {
// databases.value[index].Name = databaseForm.name;
// }]
req = {
token: localStorage.getItem("token"),
db_id: databaseForm.value.ID,
db_name: databaseForm.value.DB_NAME,
db_type: databaseForm.value.DB_Type,
db_ip: databaseForm.value.DB_IP,
db_port: databaseForm.value.DB_Port,
db_user: databaseForm.value.DB_User,
db_password: databaseForm.value.DB_Password,
db_desc: databaseForm.value.DB_Desc,
};
UpdateDBManageService(req)
.then((res: any) => {
if (res.code === 0) {
ElMessage.success("更新成功");
GetDBManageList();
} else {
ElMessage.error("更新失败");
}
})
.catch((error: any) => {
console.error("请求错误:", error);
});
} else {
//
// databases.value.push({
// id: databases.value.length + 1,
// name: databaseForm.name,
// });
req = {
token: localStorage.getItem("token"),
db_id: databaseForm.value.ID,
db_name: databaseForm.value.DB_NAME,
db_type: databaseForm.value.DB_Type,
db_ip: databaseForm.value.DB_IP,
db_port: databaseForm.value.DB_Port,
db_user: databaseForm.value.DB_User,
db_password: databaseForm.value.DB_Password,
db_desc: databaseForm.value.DB_Desc,
};
AddDBManageService(req)
.then((res: any) => {
if (res.code === 0) {
ElMessage.success("添加成功");
GetDBManageList();
} else {
ElMessage.error("添加失败");
}
})
.catch((error: any) => {
console.error("请求错误:", error);
});
}
databaseDialogVisible.value = false;
};
const showAddConnectionDialog = () => {
isEditConnection.value = false;
connectionForm.id = 0;
connectionForm.name = '';
connectionForm.host = '';
connectionForm.port = '';
connectionForm.username = '';
connectionForm.password = '';
connectionDialogVisible.value = true;
};
const editConnection = (item) => {
isEditConnection.value = true;
connectionForm.id = item.id;
connectionForm.name = item.name;
connectionForm.host = item.host || '';
connectionForm.port = item.port || '';
connectionForm.username = item.username || '';
connectionForm.password = item.password || '';
connectionDialogVisible.value = true;
};
const deleteConnection = (item) => {
// API
connections.value = connections.value.filter(conn => conn.id !== item.id);
};
const saveConnection = () => {
if (isEditConnection.value) {
//
const index = connections.value.findIndex(conn => conn.id === connectionForm.id);
if (index !== -1) {
connections.value[index] = { ...connectionForm };
}
} else {
//
connections.value.push({
id: connections.value.length + 1,
...connectionForm,
};
const getRunSQLHistory = async () => {
if (!selectedDatabase.value) {
ElMessage({
type: "error",
message: "请选择数据库",
});
return;
}
connectionDialogVisible.value = false;
let req = {
db_id: selectedDatabase.value,
token: localStorage.getItem("token"),
get_type: 0,
};
await GetSQLRunHistoryService(req)
.then((res: any) => {
if (res.code === 0) {
sqlHistory.value = res.data;
ElMessage({
type: "success",
message: "获取SQL执行历史成功",
});
} else {
console.error("获取SQL执行历史失败:", res.message);
}
})
.catch((error: any) => {
console.error("请求错误:", error);
});
};
const executeSql = () => {
const executeSql = () => {
if (!selectedDatabase.value) {
ElMessage({
type: "error",
message: "请选择数据库",
});
return;
}
executing.value = true;
loading.value = true;
let req = {
"token":localStorage.getItem("token"),
"id":localStorage.getItem("userId"),
"sql":sqlQuery.value,
"db_id":selectedDatabase.value,
token: localStorage.getItem("token"),
id: localStorage.getItem("userId"),
sql: sqlQuery.value,
db_id: selectedDatabase.value,
};
try{
RunSQLService(req).then((res: any) => {
try {
RunSQLService(req)
.then((res: any) => {
if (res.code === 0) {
tableData.value = res.data;
tableData.value = res.data.Rows;
tableColumns.value = res.data.Columns;
// let keys = Object.keys(res.data.Rows[0]);
//console.log( 'columns:',tableColumns.value);
// tableColumns.value = res.columns;
let first = res.data[0];
let keys = Object.keys(first);
tableColumns.value = keys.map((key) => {
return { prop: key, label: key };
});
console.log(res.data);
// tableColumns.value = keys.map((key) => {
// return { prop: key, label: key };
// });
// console.log(res.data);
} else {
console.error('执行SQL失败:', res.message);
console.error("执行SQL失败:", res.message);
ElMessage({
type: "error",
message: res.message,
});
}
}).catch((error: any) => {
console.error('请求错误:', error);
})
.catch((error: any) => {
console.error("请求错误:", error);
});
} catch (e) {
console.log(e);
@ -367,46 +506,9 @@ export default defineComponent({
executing.value = false;
loading.value = false;
};
};
const handleTableClick = (data) => {
//
if (!data.children) {
sqlQuery.value = `SELECT * FROM ${data.name}`;
}
};
return {
databases,
selectedDatabase,
connections,
selectedConnection,
sqlQuery,
executing,
tableData,
tableColumns,
loading,
tables,
defaultProps,
databaseDialogVisible,
isEditDatabase,
databaseForm,
connectionDialogVisible,
isEditConnection,
connectionForm,
showAddDatabaseDialog,
editDatabase,
deleteDatabase,
saveDatabase,
showAddConnectionDialog,
editConnection,
deleteConnection,
saveConnection,
executeSql,
handleTableClick,
};
},
});
</script>
<style scoped>

View File

@ -0,0 +1,573 @@
<template>
<div class="db-manage-container">
<el-row :gutter="20">
<!-- 左侧数据库表列表 -->
<el-col :span="4">
<div class="table-list">
<h3>数据库表</h3>
<el-scrollbar height="400px">
<el-tree
:data="tables"
node-key="name"
:props="defaultProps"
@node-click="handleTableClick"
/>
</el-scrollbar>
</div>
</el-col>
<!-- 右侧主内容区 -->
<el-col :span="20">
<!-- 顶部下拉选择框 -->
<el-row :gutter="20" class="mb-20">
<el-col :span="10">
<el-select
v-model="selectedDatabase"
filterable
placeholder="请选择数据库"
class="w-100"
@change="getRunSQLHistory"
>
<template #prefix>
<el-icon><search /></el-icon>
</template>
<el-option
v-for="item in databases"
:key="item.ID"
:label="item.Name"
:value="item.ID"
>
<span>{{ item.Name }}</span>
<span class="option-actions">
<el-icon @click.stop="editDatabase(item)"><edit /></el-icon>
<el-icon @click.stop="deleteDatabase(item)"
><delete
/></el-icon>
</span>
</el-option>
<template #append>
<el-button icon="plus" @click="showAddDatabaseDialog" />
</template>
</el-select>
</el-col>
<el-col :span="1">
<el-icon @click="showAddDatabaseDialog"><FolderAdd /></el-icon>
</el-col>
<el-col :span="6">
<el-select
v-model="selectedConnection"
filterable
placeholder="请选择服务器"
class="w-100"
>
<template #prefix>
<el-icon><search /></el-icon>
</template>
<el-option
v-for="item in connections"
:key="item.domain"
:label="item.name"
:value="item.domain"
>
<span>{{ item.name }}</span>
</el-option>
<template #append>
<el-button circle @click="showAddDatabaseDialog" />
</template>
</el-select>
</el-col>
<el-col :span="3">
<el-button
type="primary"
@click="showSQLHistoryDialog"
class="w-100"
:loading="executing"
>执行历史SQL</el-button
>
</el-col>
</el-row>
<!-- SQL输入框和执行按钮 -->
<el-row class="mb-20">
<el-col :span="20">
<el-input
v-model="sqlQuery"
type="textarea"
:rows="3"
placeholder="请输入SQL语句"
/>
<!-- <SqlEditor v-model="sqlQuery" /> -->
</el-col>
<el-col :span="4">
<el-button
type="primary"
@click="executeSql"
class="w-100"
:loading="executing"
>
执行
</el-button>
</el-col>
</el-row>
<!-- 结果表格 -->
<el-table
:data="tableData"
style="width: 100%"
height="400px"
v-horizontal-scroll="'always'"
v-loading="loading"
>
<el-table-column
v-for="column in tableColumns"
:key="column.prop"
:prop="column.prop"
:label="column.label"
/>
</el-table>
</el-col>
</el-row>
<!-- 添加/编辑数据库对话框 -->
<el-dialog
v-model="databaseDialogVisible"
:title="isEditDatabase ? '编辑数据库' : '添加数据库'"
width="50%"
:close-on-click-modal="false"
>
<el-form :model="databaseForm" label-width="100px">
<el-form-item label="数据库名称" required>
<el-input
v-model="databaseForm.DB_NAME"
placeholder="请输入数据库名称"
/>
</el-form-item>
</el-form>
<el-form :model="databaseForm" label-width="100px">
<el-form-item label="数据库密码" required>
<el-input
v-model="databaseForm.DB_Password"
type="password"
placeholder="请输入数据库密码"
show-password
/>
</el-form-item>
</el-form>
<el-form :model="databaseForm" label-width="100px">
<el-form-item label="用户名" required>
<el-input
v-model="databaseForm.DB_User"
placeholder="请输入用户名称"
/>
</el-form-item>
</el-form>
<el-form :model="databaseForm" label-width="100px">
<el-form-item label="IP或域名" required>
<el-input
v-model="databaseForm.DB_IP"
placeholder="请输入数据库ip或域名"
/>
</el-form-item>
</el-form>
<el-form :model="databaseForm" label-width="100px">
<el-form-item label="数据库端口" required>
<el-input
v-model="databaseForm.DB_Port"
placeholder="请输入数据库端口"
/>
</el-form-item>
</el-form>
<el-form :model="databaseForm" label-width="100px">
<el-form-item label="数据库类型" required>
<el-select
v-model="databaseForm.DB_Type"
placeholder="请选择数据库类型"
>
<el-option label="MySQL" value="0" />
<el-option label="PostgreSQL" value="1" />
</el-select>
</el-form-item>
</el-form>
<el-form :model="databaseForm" label-width="100px">
<el-form-item label="描述信息">
<el-input
v-model="databaseForm.DB_Desc"
placeholder="请输入备注信息"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="databaseDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveDatabase">保存</el-button>
</template>
</el-dialog>
<el-dialog
v-model="sqlHistoryDialogVisible"
title="SQL执行历史"
width="50%"
:close-on-click-modal="false"
>
<el-table :data="sqlHistory" stripe width="100%" height="600px" fit>
<el-table-column prop="ID" label="SQLID" width="150"></el-table-column>
<el-table-column prop="SQL" label="SQL" width="500"> </el-table-column>
<el-table-column label="操作" width="270">
<template #default="scope">
<el-button
type="primary"
size="mini"
@click.prevent="useCurrSql(scope.$index)"
>引用</el-button
>
<el-button
type="primary"
size="mini"
@click.prevent="DelSqlHistory(scope.$index)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="sqlHistoryDialogVisible = false">取消</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
import {
FindDBManageListService,
AddDBManageService,
UpdateDBManageService,
RunSQLService,
GetSQLRunHistoryService,
} from "@/api/dbm";
import { DatabaseConfig, ISQLOperation } from "@/types/dbm";
import { Search, Edit, Delete, Plus } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import SqlEditor from './sqlEditor.vue';
//
const databases = ref<DatabaseConfig[]>([]);
const selectedDatabase = ref("");
//
const connections = ref([
{ name: "腾讯服务器", domain: "tx.ljsea.top" },
{ name: "阿里云服务器", domain: "al.ljsea.top" },
{ name: "家里服务器", domain: "gep.ljsea.top" },
]);
const supportdedDBTypes = ref({ 0: "mysql", 1: "postgresql" });
const selectedConnection = ref("");
// SQL
const sqlQuery = ref("");
const executing = ref(false);
const tableData = ref([]);
const tableColumns = ref([]);
const loading = ref(false);
//
const tables = ref([
{
name: "users",
children: [{ name: "id" }, { name: "username" }, { name: "email" }],
},
{
name: "products",
children: [{ name: "id" }, { name: "name" }, { name: "price" }],
},
]);
const defaultProps = {
children: "children",
label: "name",
};
//
const databaseDialogVisible = ref(false);
const sqlHistoryDialogVisible = ref(false);
const isEditDatabase = ref(false);
const databaseForm = ref<DatabaseConfig>();
const sqlHistory = ref<ISQLOperation[]>([]);
//
const connectionDialogVisible = ref(false);
const isEditConnection = ref(false);
const connectionForm = reactive({
id: 0,
name: "",
host: "",
port: "",
username: "",
password: "",
});
const showSQLHistoryDialog = async () => {
sqlHistoryDialogVisible.value = true;
console.log('SQL history dialog opened:', sqlHistoryDialogVisible.value);
await getRunSQLHistory();
};
//
const showAddDatabaseDialog = () => {
isEditDatabase.value = false;
databaseForm.value = {
ID: 0,
DB_IP: "",
DB_Port: 3306,
DB_NAME: "",
DB_User: "",
DB_Password: "",
DB_Type: 0,
CreatedAt: "",
UpdatedAt: "",
UserID: 0,
DB_Desc: "",
DB_STATUS: 0,
Name: "",
DeletedAt: null,
};
databaseDialogVisible.value = true;
};
const editDatabase = (item) => {
isEditDatabase.value = true;
console.log("editDatabase:", item);
databaseForm.value = item;
databaseDialogVisible.value = true;
};
const GetDBManageList = async () => {
let req = {
get_type: 0,
token: localStorage.getItem("token"),
id: localStorage.getItem("userId"),
};
await FindDBManageListService(req)
.then((res: any) => {
if (res.code === 0) {
databases.value = res.data;
for (let i = 0; i < databases.value.length; i++) {
let type = supportdedDBTypes.value[databases.value[i].DB_Type];
databases.value[i].Name =
type +
" - " +
databases.value[i].DB_IP +
":" +
databases.value[i].DB_Port +
" - " +
databases.value[i].DB_NAME;
}
ElMessage.success("获取数据库列表成功");
console.log("获取数据库列表成功:", databases.value);
// console.log(":", res.data);
// console.log(":", res.data);
// console.log(":", res.data);
} else {
console.error("获取数据库列表失败:", res.message);
}
})
.catch((error: any) => {
console.error("请求错误:", error);
});
};
GetDBManageList();
const useCurrSql = (index) => {
sqlQuery.value = sqlHistory.value[index].SQL;
sqlHistoryDialogVisible.value = false;
};
const DelSqlHistory = (index) => {
sqlHistory.value.splice(index, 1);
};
const deleteDatabase = (item) => {
// API
databases.value = databases.value.filter((db) => db.ID !== item.id);
};
const saveDatabase = () => {
let req = {};
if (isEditDatabase.value) {
//
// const index = databases.value.findIndex(db => db.ID === databaseForm.id);
// if (index !== -1) {
// databases.value[index].Name = databaseForm.name;
// }]
req = {
token: localStorage.getItem("token"),
db_id: databaseForm.value.ID,
db_name: databaseForm.value.DB_NAME,
db_type: databaseForm.value.DB_Type,
db_ip: databaseForm.value.DB_IP,
db_port: databaseForm.value.DB_Port,
db_user: databaseForm.value.DB_User,
db_password: databaseForm.value.DB_Password,
db_desc: databaseForm.value.DB_Desc,
};
UpdateDBManageService(req)
.then((res: any) => {
if (res.code === 0) {
ElMessage.success("更新成功");
GetDBManageList();
} else {
ElMessage.error("更新失败");
}
})
.catch((error: any) => {
console.error("请求错误:", error);
});
} else {
//
// databases.value.push({
// id: databases.value.length + 1,
// name: databaseForm.name,
// });
req = {
token: localStorage.getItem("token"),
db_id: databaseForm.value.ID,
db_name: databaseForm.value.DB_NAME,
db_type: databaseForm.value.DB_Type,
db_ip: databaseForm.value.DB_IP,
db_port: databaseForm.value.DB_Port,
db_user: databaseForm.value.DB_User,
db_password: databaseForm.value.DB_Password,
db_desc: databaseForm.value.DB_Desc,
};
AddDBManageService(req)
.then((res: any) => {
if (res.code === 0) {
ElMessage.success("添加成功");
GetDBManageList();
} else {
ElMessage.error("添加失败");
}
})
.catch((error: any) => {
console.error("请求错误:", error);
});
}
databaseDialogVisible.value = false;
};
const getRunSQLHistory = async () => {
if (!selectedDatabase.value) {
ElMessage({
type: "error",
message: "请选择数据库",
});
return;
}
let req = {
db_id: selectedDatabase.value,
token: localStorage.getItem("token"),
get_type: 0,
};
await GetSQLRunHistoryService(req)
.then((res: any) => {
if (res.code === 0) {
sqlHistory.value = res.data;
ElMessage({
type: "success",
message: "获取SQL执行历史成功",
});
} else {
console.error("获取SQL执行历史失败:", res.message);
}
})
.catch((error: any) => {
console.error("请求错误:", error);
});
};
const executeSql = () => {
if (!selectedDatabase.value) {
ElMessage({
type: "error",
message: "请选择数据库",
});
return;
}
executing.value = true;
loading.value = true;
let req = {
token: localStorage.getItem("token"),
id: localStorage.getItem("userId"),
sql: sqlQuery.value,
db_id: selectedDatabase.value,
};
try {
RunSQLService(req)
.then((res: any) => {
if (res.code === 0) {
tableData.value = res.data.Rows;
tableColumns.value = res.data.Columns;
// let keys = Object.keys(res.data.Rows[0]);
//console.log( 'columns:',tableColumns.value);
// tableColumns.value = res.columns;
// tableColumns.value = keys.map((key) => {
// return { prop: key, label: key };
// });
// console.log(res.data);
} else {
console.error("执行SQL失败:", res.message);
ElMessage({
type: "error",
message: res.message,
});
}
})
.catch((error: any) => {
console.error("请求错误:", error);
});
} catch (e) {
console.log(e);
}
executing.value = false;
loading.value = false;
};
const handleTableClick = (data) => {
//
if (!data.children) {
sqlQuery.value = `SELECT * FROM ${data.name}`;
}
};
</script>
<style scoped>
.db-manage-container {
padding: 20px;
}
.table-list {
background: #fff;
padding: 10px;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.mb-20 {
margin-bottom: 20px;
}
.w-100 {
width: 100%;
}
.option-actions {
float: right;
}
.option-actions .el-icon {
margin-left: 10px;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,88 @@
<template>
<div ref="editorContainer" class="sql-editor-container" />
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, type Ref } from 'vue';
import { EditorState, EditorView, basicSetup } from '@codemirror/basic-setup';
import { sql } from '@codemirror/lang-sql';
import { oneDark } from '@codemirror/theme-one-dark'; //
// props v-model
const props = defineProps<{
modelValue: string;
}>();
// emits
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void;
}>();
//
const editorContainer: Ref<HTMLElement | null> = ref(null);
let editorView: EditorView | null = null;
//
const initEditor = () => {
if (!editorContainer.value) return;
//
const extensions = [
basicSetup, //
sql(), // SQL
oneDark, // 使
EditorView.lineWrapping, //
EditorView.updateListener.of((update) => {
// modelValue
if (update.docChanged) {
const newValue = update.state.doc.toString();
emit('update:modelValue', newValue);
}
}),
];
// 使 modelValue
const state = EditorState.create({
doc: props.modelValue,
extensions,
});
// EditorView
editorView = new EditorView({
state,
parent: editorContainer.value,
});
};
// modelValue
watch(
() => props.modelValue,
(newValue) => {
if (editorView && newValue !== editorView.state.doc.toString()) {
editorView.dispatch({
changes: { from: 0, to: editorView.state.doc.length, insert: newValue },
});
}
},
{ deep: true }
);
//
onMounted(() => {
initEditor();
});
//
onUnmounted(() => {
editorView?.destroy();
});
</script>
<style scoped>
.sql-editor-container {
height: 400px; /* 必须指定高度 */
border: 1px solid #e5e7eb;
border-radius: 4px;
overflow: hidden;
}
</style>

133
yarn.lock
View File

@ -87,6 +87,16 @@
resolved "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz"
integrity sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==
"@codemirror/autocomplete@^0.20.0":
version "0.20.3"
resolved "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-0.20.3.tgz"
integrity sha512-lYB+NPGP+LEzAudkWhLfMxhTrxtLILGl938w+RcFrGdrIc54A+UgmCoz+McE3IYRFp4xyQcL4uFJwo+93YdgHw==
dependencies:
"@codemirror/language" "^0.20.0"
"@codemirror/state" "^0.20.0"
"@codemirror/view" "^0.20.0"
"@lezer/common" "^0.16.0"
"@codemirror/autocomplete@^6.0.0":
version "6.18.6"
resolved "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz"
@ -97,6 +107,29 @@
"@codemirror/view" "^6.17.0"
"@lezer/common" "^1.0.0"
"@codemirror/basic-setup@^0.20.0":
version "0.20.0"
resolved "https://registry.npmjs.org/@codemirror/basic-setup/-/basic-setup-0.20.0.tgz"
integrity sha512-W/ERKMLErWkrVLyP5I8Yh8PXl4r+WFNkdYVSzkXYPQv2RMPSkWpr2BgggiSJ8AHF/q3GuApncDD8I4BZz65fyg==
dependencies:
"@codemirror/autocomplete" "^0.20.0"
"@codemirror/commands" "^0.20.0"
"@codemirror/language" "^0.20.0"
"@codemirror/lint" "^0.20.0"
"@codemirror/search" "^0.20.0"
"@codemirror/state" "^0.20.0"
"@codemirror/view" "^0.20.0"
"@codemirror/commands@^0.20.0":
version "0.20.0"
resolved "https://registry.npmjs.org/@codemirror/commands/-/commands-0.20.0.tgz"
integrity sha512-v9L5NNVA+A9R6zaFvaTbxs30kc69F6BkOoiEbeFw4m4I0exmDEKBILN6mK+GksJtvTzGBxvhAPlVFTdQW8GB7Q==
dependencies:
"@codemirror/language" "^0.20.0"
"@codemirror/state" "^0.20.0"
"@codemirror/view" "^0.20.0"
"@lezer/common" "^0.16.0"
"@codemirror/commands@^6.0.0", "@codemirror/commands@6.x":
version "6.8.1"
resolved "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz"
@ -107,6 +140,30 @@
"@codemirror/view" "^6.27.0"
"@lezer/common" "^1.1.0"
"@codemirror/lang-sql@^6.9.1":
version "6.9.1"
resolved "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.9.1.tgz"
integrity sha512-ecSk3gm/mlINcURMcvkCZmXgdzPSq8r/yfCtTB4vgqGGIbBC2IJIAy7GqYTy5pgBEooTVmHP2GZK6Z7h63CDGg==
dependencies:
"@codemirror/autocomplete" "^6.0.0"
"@codemirror/language" "^6.0.0"
"@codemirror/state" "^6.0.0"
"@lezer/common" "^1.2.0"
"@lezer/highlight" "^1.0.0"
"@lezer/lr" "^1.0.0"
"@codemirror/language@^0.20.0":
version "0.20.2"
resolved "https://registry.npmjs.org/@codemirror/language/-/language-0.20.2.tgz"
integrity sha512-WB3Bnuusw0xhVvhBocieYKwJm04SOk5bPoOEYksVHKHcGHFOaYaw+eZVxR4gIqMMcGzOIUil0FsCmFk8yrhHpw==
dependencies:
"@codemirror/state" "^0.20.0"
"@codemirror/view" "^0.20.0"
"@lezer/common" "^0.16.0"
"@lezer/highlight" "^0.16.0"
"@lezer/lr" "^0.16.0"
style-mod "^4.0.0"
"@codemirror/language@^6.0.0", "@codemirror/language@6.x":
version "6.11.0"
resolved "https://registry.npmjs.org/@codemirror/language/-/language-6.11.0.tgz"
@ -119,6 +176,15 @@
"@lezer/lr" "^1.0.0"
style-mod "^4.0.0"
"@codemirror/lint@^0.20.0":
version "0.20.3"
resolved "https://registry.npmjs.org/@codemirror/lint/-/lint-0.20.3.tgz"
integrity sha512-06xUScbbspZ8mKoODQCEx6hz1bjaq9m8W8DxdycWARMiiX1wMtfCh/MoHpaL7ws/KUMwlsFFfp2qhm32oaCvVA==
dependencies:
"@codemirror/state" "^0.20.0"
"@codemirror/view" "^0.20.2"
crelt "^1.0.5"
"@codemirror/lint@^6.0.0":
version "6.8.5"
resolved "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz"
@ -128,6 +194,15 @@
"@codemirror/view" "^6.35.0"
crelt "^1.0.5"
"@codemirror/search@^0.20.0":
version "0.20.1"
resolved "https://registry.npmjs.org/@codemirror/search/-/search-0.20.1.tgz"
integrity sha512-ROe6gRboQU5E4z6GAkNa2kxhXqsGNbeLEisbvzbOeB7nuDYXUZ70vGIgmqPu0tB+1M3F9yWk6W8k2vrFpJaD4Q==
dependencies:
"@codemirror/state" "^0.20.0"
"@codemirror/view" "^0.20.0"
crelt "^1.0.5"
"@codemirror/search@^6.0.0":
version "6.5.11"
resolved "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz"
@ -137,19 +212,44 @@
"@codemirror/view" "^6.0.0"
crelt "^1.0.5"
"@codemirror/state@^6.0.0", "@codemirror/state@^6.4.0", "@codemirror/state@^6.5.0", "@codemirror/state@6.x":
"@codemirror/state@^0.20.0":
version "0.20.1"
resolved "https://registry.npmjs.org/@codemirror/state/-/state-0.20.1.tgz"
integrity sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ==
"@codemirror/state@^6.0.0", "@codemirror/state@^6.4.0", "@codemirror/state@^6.5.0", "@codemirror/state@^6.5.2", "@codemirror/state@6.x":
version "6.5.2"
resolved "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz"
integrity sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==
dependencies:
"@marijn/find-cluster-break" "^1.0.0"
"@codemirror/view@^6.0.0", "@codemirror/view@^6.17.0", "@codemirror/view@^6.23.0", "@codemirror/view@^6.27.0", "@codemirror/view@^6.35.0", "@codemirror/view@6.x":
version "6.36.8"
resolved "https://registry.npmjs.org/@codemirror/view/-/view-6.36.8.tgz"
integrity sha512-yoRo4f+FdnD01fFt4XpfpMCcCAo9QvZOtbrXExn4SqzH32YC6LgzqxfLZw/r6Ge65xyY03mK/UfUqrVw1gFiFg==
"@codemirror/theme-one-dark@^6.1.3":
version "6.1.3"
resolved "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz"
integrity sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==
dependencies:
"@codemirror/language" "^6.0.0"
"@codemirror/state" "^6.0.0"
"@codemirror/view" "^6.0.0"
"@lezer/highlight" "^1.0.0"
"@codemirror/view@^0.20.0", "@codemirror/view@^0.20.2":
version "0.20.7"
resolved "https://registry.npmjs.org/@codemirror/view/-/view-0.20.7.tgz"
integrity sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==
dependencies:
"@codemirror/state" "^0.20.0"
style-mod "^4.0.0"
w3c-keyname "^2.2.4"
"@codemirror/view@^6.0.0", "@codemirror/view@^6.17.0", "@codemirror/view@^6.23.0", "@codemirror/view@^6.27.0", "@codemirror/view@^6.35.0", "@codemirror/view@^6.38.1", "@codemirror/view@6.x":
version "6.38.1"
resolved "https://registry.npmjs.org/@codemirror/view/-/view-6.38.1.tgz"
integrity sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==
dependencies:
"@codemirror/state" "^6.5.0"
crelt "^1.0.6"
style-mod "^4.1.0"
w3c-keyname "^2.2.4"
@ -353,11 +453,23 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@lezer/common@^1.0.0", "@lezer/common@^1.1.0":
"@lezer/common@^0.16.0":
version "0.16.1"
resolved "https://registry.npmjs.org/@lezer/common/-/common-0.16.1.tgz"
integrity sha512-qPmG7YTZ6lATyTOAWf8vXE+iRrt1NJd4cm2nJHK+v7X9TsOF6+HtuU/ctaZy2RCrluxDb89hI6KWQ5LfQGQWuA==
"@lezer/common@^1.0.0", "@lezer/common@^1.1.0", "@lezer/common@^1.2.0":
version "1.2.3"
resolved "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz"
integrity sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==
"@lezer/highlight@^0.16.0":
version "0.16.0"
resolved "https://registry.npmjs.org/@lezer/highlight/-/highlight-0.16.0.tgz"
integrity sha512-iE5f4flHlJ1g1clOStvXNLbORJoiW4Kytso6ubfYzHnaNo/eo5SKhxs4wv/rtvwZQeZrK3we8S9SyA7OGOoRKQ==
dependencies:
"@lezer/common" "^0.16.0"
"@lezer/highlight@^1.0.0":
version "1.2.1"
resolved "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz"
@ -365,6 +477,13 @@
dependencies:
"@lezer/common" "^1.0.0"
"@lezer/lr@^0.16.0":
version "0.16.3"
resolved "https://registry.npmjs.org/@lezer/lr/-/lr-0.16.3.tgz"
integrity sha512-pau7um4eAw94BEuuShUIeQDTf3k4Wt6oIUOYxMmkZgDHdqtIcxWND4LRxi8nI9KuT4I1bXQv67BCapkxt7Ywqw==
dependencies:
"@lezer/common" "^0.16.0"
"@lezer/lr@^1.0.0":
version "1.4.2"
resolved "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz"
@ -1289,7 +1408,7 @@ crc-32@~1.2.0, crc-32@~1.2.1:
resolved "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz"
integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==
crelt@^1.0.5:
crelt@^1.0.5, crelt@^1.0.6:
version "1.0.6"
resolved "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz"
integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==