feat: 新增数据库sql执行记录&执行前原值等信息

This commit is contained in:
meilin.huang
2022-06-16 15:55:18 +08:00
parent f58331c1c1
commit 9b9173dea7
81 changed files with 559 additions and 1467 deletions

View File

@@ -292,10 +292,7 @@ export default defineComponent({
emit('cancel');
setTimeout(() => {
resetInputDb();
dbForm.value.resetFields();
// 重置对象属性为null
state.form = {} as any;
}, 200);
}, 500);
};
return {

View File

@@ -59,8 +59,11 @@
</template>
</el-table-column>
<!-- <el-table-column fixed="right" label="更多信息" min-width="100">
</el-table-column> -->
<el-table-column label="操作" min-width="120" fixed="right">
<template #default="scope">
<el-link type="primary" plain size="small" :underline="false" @click="onShowSqlExec(scope.row)">SQL执行记录</el-link>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination
@@ -124,6 +127,54 @@
</el-table>
</el-dialog>
<el-dialog
width="90%"
:title="`${sqlExecLogDialog.title} - SQL执行记录`"
:before-close="onBeforeCloseSqlExecDialog"
v-model="sqlExecLogDialog.visible"
>
<div class="toolbar">
<el-input v-model="sqlExecLogDialog.query.db" placeholder="请输入数据库名" clearable style="width: 150px" />
<el-input v-model="sqlExecLogDialog.query.table" placeholder="请输入表名" clearable class="ml5" style="width: 150px" />
<el-select v-model="sqlExecLogDialog.query.type" placeholder="请选择操作类型" clearable class="ml5">
<el-option label="UPDATE" :value="1"> </el-option>
<el-option label="DELETE" :value="2"> </el-option>
<el-option label="INSERT" :value="3"> </el-option>
</el-select>
<el-button class="ml5" @click="searchSqlExecLog" type="success" icon="search"></el-button>
</div>
<el-table border stripe :data="sqlExecLogDialog.data" size="small">
<el-table-column prop="db" label="数据库" min-width="60" show-overflow-tooltip> </el-table-column>
<el-table-column prop="table" label="" min-width="60" show-overflow-tooltip> </el-table-column>
<el-table-column prop="type" label="类型" width="85" show-overflow-tooltip>
<template #default="scope">
<el-tag v-if="scope.row.type == 1" color="#E4F5EB" size="small">UPDATE</el-tag>
<el-tag v-if="scope.row.type == 2" color="#F9E2AE" size="small">DELETE</el-tag>
<el-tag v-if="scope.row.type == 3" color="#A8DEE0" size="small">INSERT</el-tag>
</template>
</el-table-column>
<el-table-column prop="sql" label="SQL" min-width="230" show-overflow-tooltip> </el-table-column>
<el-table-column prop="oldValue" label="原值" min-width="150" show-overflow-tooltip> </el-table-column>
<el-table-column prop="creator" label="执行人" min-width="60" show-overflow-tooltip> </el-table-column>
<el-table-column prop="createTime" label="执行时间" show-overflow-tooltip>
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="60" show-overflow-tooltip> </el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination
style="text-align: right"
@current-change="handleSqlExecPageChange"
:total="sqlExecLogDialog.total"
layout="prev, pager, next, total, jumper"
v-model:current-page="sqlExecLogDialog.query.pageNum"
:page-size="sqlExecLogDialog.query.pageSize"
></el-pagination>
</el-row>
</el-dialog>
<el-dialog width="40%" :title="`${chooseTableName} 字段信息`" v-model="columnDialog.visible">
<el-table border stripe :data="columnDialog.columns" size="small">
<el-table-column prop="columnName" label="名称" show-overflow-tooltip> </el-table-column>
@@ -195,7 +246,18 @@ export default defineComponent({
},
datas: [],
total: 0,
// sql执行记录弹框
sqlExecLogDialog: {
title: '',
visible: false,
data: [],
total: 0,
query: {
dbId: 0,
pageNum: 1,
pageSize: 12,
},
},
chooseTableName: '',
tableInfoDialog: {
visible: false,
@@ -284,6 +346,32 @@ export default defineComponent({
} catch (err) {}
};
const onShowSqlExec = async (row: any) => {
state.sqlExecLogDialog.title = `${row.name}[${row.host}:${row.port}]`;
state.sqlExecLogDialog.query.dbId = row.id;
searchSqlExecLog();
state.sqlExecLogDialog.visible = true;
};
const onBeforeCloseSqlExecDialog = () => {
state.sqlExecLogDialog.visible = false;
state.sqlExecLogDialog.data = [];
state.sqlExecLogDialog.total = 0;
state.sqlExecLogDialog.query.dbId = 0;
state.sqlExecLogDialog.query.pageNum = 1;
};
const searchSqlExecLog = async () => {
const res = await dbApi.getSqlExecs.request(state.sqlExecLogDialog.query);
state.sqlExecLogDialog.data = res.list;
state.sqlExecLogDialog.total = res.total;
};
const handleSqlExecPageChange = (curPage: number) => {
state.sqlExecLogDialog.query.pageNum = curPage;
searchSqlExecLog();
};
const showTableInfo = async (row: any, db: string) => {
state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: row.id, db });
state.dbId = row.id;
@@ -360,6 +448,10 @@ export default defineComponent({
editDb,
valChange,
deleteDb,
onShowSqlExec,
onBeforeCloseSqlExecDialog,
handleSqlExecPageChange,
searchSqlExecLog,
showTableInfo,
closeTableInfo,
showColumns,

View File

@@ -239,7 +239,6 @@ import 'codemirror/theme/base16-light.css';
import 'codemirror/addon/selection/active-line';
import _CodeMirror from 'codemirror';
// import 'codemirror/mode/sql/sql.js';
import 'codemirror/addon/hint/show-hint.js';
import 'codemirror/addon/hint/sql-hint.js';
@@ -390,7 +389,44 @@ export default defineComponent({
let sql = getSql();
isTrue(sql && sql.trim(), '请选中需要执行的sql');
state.queryTab.loading = true;
// 去除字符串前的空格、换行等
sql = sql.replace(/(^\s*)/g, '');
let execRemark = '';
let canRun = true;
if (
sql.startsWith('update') ||
sql.startsWith('UPDATE') ||
sql.startsWith('INSERT') ||
sql.startsWith('insert') ||
sql.startsWith('DELETE') ||
sql.startsWith('delete')
) {
const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /^[\s\S]*.*[^\s][\s\S]*$/,
inputErrorMessage: '请输入执行该sql的备注信息',
});
execRemark = res.value;
if (!execRemark) {
canRun = false;
}
}
if (!canRun) {
return;
}
try {
state.queryTab.loading = true;
const colAndData: any = await runSql(sql, execRemark);
state.queryTab.execRes.data = colAndData.res;
state.queryTab.execRes.tableColumn = colAndData.colNames;
state.queryTab.loading = false;
} catch (e: any) {
state.queryTab.loading = false;
}
closeExecBtns();
// 即只有以该字符串开头的sql才可修改表数据内容
if (sql.startsWith('SELECT *') || sql.startsWith('select *') || sql.startsWith('SELECT\n *')) {
state.queryTab.selectionDatas = [];
@@ -407,16 +443,6 @@ export default defineComponent({
state.queryTab.nowTableName = '';
state.nowTableName = '';
}
try {
const colAndData: any = await runSql(sql);
state.queryTab.execRes.data = colAndData.res;
state.queryTab.execRes.tableColumn = colAndData.colNames;
state.queryTab.loading = false;
} catch (e: any) {
state.queryTab.loading = false;
}
closeExecBtns();
};
/**
@@ -424,12 +450,40 @@ export default defineComponent({
*
* @param sql 执行的sql
*/
const runSql = (sql: string) => {
return dbApi.sqlExec.request({
const runSql = async (sql: string, remark: string = '') => {
return await dbApi.sqlExec.request({
id: state.dbId,
db: state.db,
sql: sql.trim(),
remark,
});
// const sqlTrim = sql.trim();
// let remark = '';
// let canRun = true;
// const needRemark = ['update', 'UPDATE', 'delete', 'DELETE', 'INSERT', 'insert'].indexOf(sqlTrim.split(' ')[0]);
// if (needRemark) {
// const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
// confirmButtonText: '确定',
// cancelButtonText: '取消',
// });
// remark = res.value;
// if (!remark) {
// canRun = false;
// }
// }
// if (!canRun) {
// return;
// }
// try {
// state.queryTab.loading = true;
// return await dbApi.sqlExec.request({
// id: state.dbId,
// db: state.db,
// sql: sqlTrim,
// remark,
// });
// } catch (e: any) {}
};
const removeDataTab = (targetName: string) => {
@@ -497,8 +551,9 @@ export default defineComponent({
if (flag === 'equal') {
// 获取该列中第一个不为空的数据(内容)
for (let i = 0; i < tableData.length; i++) {
if (tableData[i][str].length > 0) {
columnContent = tableData[i][str];
// 转为字符串后比较
if ((tableData[i][str] + '').length > 0) {
columnContent = tableData[i][str] + '';
break;
}
}
@@ -515,7 +570,7 @@ export default defineComponent({
index = i;
}
}
columnContent = tableData[index][str];
columnContent = tableData[index][str] + '';
}
const contentWidth: number = getContentWidth(columnContent);
// 获取列名称的长度 加上排序图标长度
@@ -899,7 +954,8 @@ export default defineComponent({
if (!state.nowTableName || !property) {
return;
}
let text = row[property];
// 转为字符串比较,可能存在数字等
let text = row[property] + '';
let div = cell.children[0];
if (div) {
let input = document.createElement('input');

View File

@@ -12,7 +12,7 @@ export const dbApi = {
columnMetadata: Api.create("/dbs/{id}/c-metadata", 'get'),
// 获取表即列提示
hintTables: Api.create("/dbs/{id}/hint-tables", 'get'),
sqlExec: Api.create("/dbs/{id}/exec-sql", 'get'),
sqlExec: Api.create("/dbs/{id}/exec-sql", 'post'),
// 保存sql
saveSql: Api.create("/dbs/{id}/sql", 'post'),
// 获取保存的sql
@@ -20,4 +20,6 @@ export const dbApi = {
// 获取保存的sql names
getSqlNames: Api.create("/dbs/{id}/sql-names", 'get'),
deleteDbSql: Api.create("/dbs/{id}/sql", 'delete'),
// 获取数据库sql执行记录
getSqlExecs: Api.create("/dbs/{id}/sql-execs", 'get'),
}

View File

@@ -1,6 +1,7 @@
<template>
<div>
<el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px">
<el-input v-model="remark" placeholder="请输入执行备注" class="mb5" />
<codemirror height="350px" class="codesql" ref="cmEditor" language="sql" v-model="sqlValue" :options="cmOptions" />
<template #footer>
<span class="dialog-footer">
@@ -15,7 +16,7 @@
<script lang="ts">
import { toRefs, reactive, defineComponent } from 'vue';
import { dbApi } from '../api';
import { ElDialog, ElButton } from 'element-plus';
import { ElDialog, ElButton, ElInput, ElMessage } from 'element-plus';
// import base style
import 'codemirror/lib/codemirror.css';
// 引入主题后还需要在 options 中指定主题才会生效
@@ -32,6 +33,7 @@ export default defineComponent({
codemirror,
ElButton,
ElDialog,
ElInput,
},
props: {
visible: {
@@ -53,6 +55,7 @@ export default defineComponent({
sqlValue: '',
dbId: 0,
db: '',
remark: '',
btnLoading: false,
cmOptions: {
tabSize: 4,
@@ -77,11 +80,17 @@ export default defineComponent({
* 执行sql
*/
const runSql = async () => {
if (!state.remark) {
ElMessage.error('请输入执行的备注信息');
return;
}
try {
state.btnLoading = true;
await dbApi.sqlExec.request({
id: state.dbId,
db: state.db,
remark: state.remark,
sql: state.sqlValue.trim(),
});
runSuccess = true;
@@ -104,6 +113,7 @@ export default defineComponent({
setTimeout(() => {
state.dbId = 0;
state.sqlValue = '';
state.remark = '';
runSuccessCallback = null;
cancelCallback = null;
runSuccess = false;
@@ -133,7 +143,4 @@ export default defineComponent({
font-size: 9pt;
font-weight: 600;
}
.footer {
float: right;
}
</style>

View File

@@ -171,11 +171,6 @@ export default defineComponent({
const cancel = () => {
emit('update:visible', false);
emit('cancel');
setTimeout(() => {
machineForm.value.resetFields();
// 重置对象属性为null
state.form = {} as any;
}, 200);
};
return {

View File

@@ -166,11 +166,6 @@ export default defineComponent({
const cancel = () => {
emit('update:visible', false);
emit('cancel');
setTimeout(() => {
mongoForm.value.resetFields();
// 重置对象属性为null
state.form = {} as any;
}, 200);
};
return {

View File

@@ -170,11 +170,6 @@ export default defineComponent({
const cancel = () => {
emit('update:visible', false);
emit('cancel');
setTimeout(() => {
redisForm.value.resetFields();
// 重置对象属性为null
state.form = {} as any;
}, 200);
};
return {

View File

@@ -1,6 +1,6 @@
<template>
<div class="account-dialog">
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :show-close="false" width="35%">
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :show-close="false" width="35%" :destroy-on-close="true">
<el-form :model="form" ref="accountForm" :rules="rules" label-width="85px">
<el-form-item prop="username" label="用户名:" required>
<el-input :disabled="edit" v-model.trim="form.username" placeholder="请输入账号用户名" auto-complete="off"></el-input>
@@ -105,15 +105,7 @@ export default defineComponent({
const cancel = () => {
emit('update:visible', false);
setTimeout(() => {
emit('update:account', null);
}, 800);
emit('cancel');
setTimeout(() => {
accountForm.value.resetFields();
// 重置对象属性为null
state.form = {} as any;
}, 200);
};
return {

View File

@@ -2,7 +2,7 @@
<div class="account-dialog">
<el-dialog
:title="account == null ? '' : '分配“' + account.username + '”的角色'"
v-model="visible"
v-model="dialogVisible"
:before-close="cancel"
:show-close="false"
>
@@ -15,7 +15,7 @@
<el-table :data="allRole" border ref="roleTable" @select="select" style="width: 100%">
<el-table-column :selectable="selectable" type="selection" width="40"></el-table-column>
<el-table-column prop="name" label="角色名称"></el-table-column>
<el-table-column prop="code" label="角色code"></el-table-column>
<el-table-column prop="code" label="角色code"></el-table-column>
<el-table-column prop="remark" label="角色描述">
<template #default="scope">
{{ scope.row.remark ? scope.row.remark : '暂无描述' }}
@@ -59,7 +59,7 @@ export default defineComponent({
setup(props: any, { emit }) {
const roleTable: any = ref(null);
const state = reactive({
visible: false,
dialogVisible: false,
btnLoading: false,
// 所有角色
allRole: [] as any,
@@ -73,8 +73,8 @@ export default defineComponent({
total: 0,
});
watch(props, (newValue, oldValue) => {
state.visible = newValue.visible;
watch(props, (newValue) => {
state.dialogVisible = newValue.visible;
if (newValue.account && newValue.account.id != 0) {
accountApi.roleIds
.request({

View File

@@ -284,11 +284,6 @@ export default defineComponent({
const cancel = () => {
emit('update:visible', false);
emit('cancel');
setTimeout(() => {
menuForm.value.resetFields();
// 重置对象属性为null
state.form = {} as any;
}, 200);
};
return {

View File

@@ -109,9 +109,7 @@ export default defineComponent({
const cancel = () => {
// 更新父组件visible prop对应的值为false
emit('update:visible', false);
setTimeout(() => {
emit('cancel');
}, 700);
emit('cancel');
};
return {

View File

@@ -1,12 +1,17 @@
<template>
<div class="role-dialog">
<el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="500px">
<el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="500px" :destroy-on-close="true">
<el-form :model="form" label-width="90px">
<el-form-item prop="name" label="角色名称:" required>
<el-input v-model="form.name" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="code" label="角色code:" required>
<el-input :disabled="form.id != null" v-model="form.code" placeholder="COMMON开头则为所有账号共有角色" auto-complete="off"></el-input>
<el-input
:disabled="form.id != null"
v-model="form.code"
placeholder="COMMON开头则为所有账号共有角色"
auto-complete="off"
></el-input>
</el-form-item>
<el-form-item label="角色描述:">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入角色描述"></el-input>