feat: 完善数据库数据在线删除,编辑,新增等

This commit is contained in:
meilin.huang
2022-01-20 18:01:11 +08:00
parent a898edd5a1
commit 4e4bc79e7c

View File

@@ -45,7 +45,7 @@
</el-container> </el-container>
<el-tabs @tab-remove="removeDataTab" @tab-click="onDataTabClick" style="width: 70%; margin-left: 10px" v-model="activeName"> <el-tabs @tab-remove="removeDataTab" @tab-click="onDataTabClick" style="width: 70%; margin-left: 10px" v-model="activeName">
<el-tab-pane label="查询" :name="queryTabName"> <el-tab-pane :label="queryTab.label" :name="queryTab.name">
<div> <div>
<div> <div>
<div class="toolbar"> <div class="toolbar">
@@ -66,6 +66,7 @@
> >
<el-button type="success" icon="video-play" plain size="small">sql脚本执行</el-button> <el-button type="success" icon="video-play" plain size="small">sql脚本执行</el-button>
</el-upload> </el-upload>
<el-button @click="onCommit" class="ml5" type="success" icon="CircleCheck" plain size="small">commit</el-button>
</div> </div>
<div style="float: right" class="fl"> <div style="float: right" class="fl">
@@ -107,21 +108,30 @@
</div> </div>
<div class="mt10"> <div class="mt10">
<el-row v-if="queryTab.nowTableName">
<el-link @click="onDeleteData" class="ml5" type="danger" icon="delete" :underline="false"></el-link>
</el-row>
<el-table <el-table
@cell-dblclick="cellClick" @cell-dblclick="cellClick"
@selection-change="onDataSelectionChange"
style="margin-top: 1px" style="margin-top: 1px"
:data="execRes.data" :data="queryTab.execRes.data"
size="small" size="small"
max-height="220" max-height="220"
empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改" empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改"
stripe stripe
border border
> >
<el-table-column
v-if="queryTab.execRes.tableColumn.length > 0 && queryTab.nowTableName"
type="selection"
width="35"
/>
<el-table-column <el-table-column
min-width="100" min-width="100"
:width="flexColumnWidth(item, execRes.data)" :width="flexColumnWidth(item, queryTab.execRes.data)"
align="center" align="center"
v-for="item in execRes.tableColumn" v-for="item in queryTab.execRes.tableColumn"
:key="item" :key="item"
:prop="item" :prop="item"
:label="item" :label="item"
@@ -134,10 +144,19 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane closable v-for="dt in dataTabs" :key="dt.name" :label="dt.label" :name="dt.name"> <el-tab-pane closable v-for="dt in dataTabs" :key="dt.name" :label="dt.label" :name="dt.name">
<el-row v-if="dbId">
<el-link @click="onRefresh(dt.name)" icon="refresh" :underline="false"></el-link>
<el-link @click="addRow" class="ml5" type="primary" icon="plus" :underline="false"></el-link>
<el-link @click="onDeleteData" class="ml5" type="danger" icon="delete" :underline="false"></el-link>
<el-tooltip class="box-item" effect="dark" content="commit" placement="top">
<el-link @click="onCommit" class="ml5" type="success" icon="check" :underline="false"></el-link>
</el-tooltip>
</el-row>
<el-table <el-table
@cell-dblclick="cellClick" @cell-dblclick="cellClick"
@row-contextmenu="contextmenu"
@sort-change="onTableSortChange" @sort-change="onTableSortChange"
@selection-change="onDataSelectionChange"
style="margin-top: 1px" style="margin-top: 1px"
:data="dt.execRes.data" :data="dt.execRes.data"
size="small" size="small"
@@ -146,6 +165,7 @@
stripe stripe
border border
> >
<el-table-column v-if="dt.execRes.tableColumn.length > 0" type="selection" width="35" />
<el-table-column <el-table-column
min-width="100" min-width="100"
:width="flexColumnWidth(item, dt.execRes.data)" :width="flexColumnWidth(item, dt.execRes.data)"
@@ -159,9 +179,6 @@
> >
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-row v-if="dbId">
<el-button @click="addRow" type="text" icon="plus"></el-button>
</el-row>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</el-container> </el-container>
@@ -169,7 +186,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { h, toRefs, reactive, computed, defineComponent, ref, createApp } from 'vue'; import { h, toRefs, reactive, computed, defineComponent, ref } from 'vue';
import { dbApi } from './api'; import { dbApi } from './api';
import _ from 'lodash'; import _ from 'lodash';
@@ -186,11 +203,12 @@ import 'codemirror/addon/hint/show-hint.js';
import 'codemirror/addon/hint/sql-hint.js'; import 'codemirror/addon/hint/sql-hint.js';
import { format as sqlFormatter } from 'sql-formatter'; import { format as sqlFormatter } from 'sql-formatter';
import { notNull, notEmpty } from '@/common/assert'; import { notNull, notEmpty, isTrue } from '@/common/assert';
import { ElMessage, ElMessageBox, ElMenu, ElMenuItem } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import ProjectEnvSelect from '../component/ProjectEnvSelect.vue'; import ProjectEnvSelect from '../component/ProjectEnvSelect.vue';
import config from '@/common/config'; import config from '@/common/config';
import { getSession } from '@/common/utils/storage'; import { getSession } from '@/common/utils/storage';
import { key } from '../../../store/index';
export default defineComponent({ export default defineComponent({
name: 'SqlExec', name: 'SqlExec',
@@ -209,7 +227,6 @@ export default defineComponent({
tables: [], tables: [],
dbId: null, dbId: null,
tableName: '', tableName: '',
nowTableName: '', // 当前表格数据操作的数据库表名,用于双击编辑表内容使用
tableMetadata: [], tableMetadata: [],
columnMetadata: [], columnMetadata: [],
sqlName: '', // 当前sql模板名 sqlName: '', // 当前sql模板名
@@ -217,16 +234,19 @@ export default defineComponent({
sql: '', sql: '',
activeName: 'Query', activeName: 'Query',
queryTabName: 'Query', queryTabName: 'Query',
sqlTabs: { nowTableName: '', // 当前表格数据操作的数据库表名,用于双击编辑表内容使用
tabs: [] as any,
active: '',
index: 1,
},
dataTabs: {}, // 点击表信息后执行结果数据展示tabs dataTabs: {}, // 点击表信息后执行结果数据展示tabs
// 点击执行按钮执行结果信息 // 查询tab
execRes: { queryTab: {
data: [], label: '查询',
tableColumn: [], name: 'Query',
// 点击执行按钮执行结果信息
execRes: {
data: [],
tableColumn: [],
},
nowTableName: '', //当前表格数据操作的数据库表名,用于双击编辑表内容使用
selectionDatas: [],
}, },
params: { params: {
pageNum: 1, pageNum: 1,
@@ -260,6 +280,8 @@ export default defineComponent({
}, },
}); });
const tableMap = new Map();
const codemirror: any = computed(() => { const codemirror: any = computed(() => {
return cmEditor.value.coder; return cmEditor.value.coder;
}); });
@@ -303,19 +325,24 @@ export default defineComponent({
// 即只有以该字符串开头的sql才可修改表数据内容 // 即只有以该字符串开头的sql才可修改表数据内容
if (sql.startsWith('SELECT *') || sql.startsWith('select *') || sql.startsWith('SELECT\n *')) { if (sql.startsWith('SELECT *') || sql.startsWith('select *') || sql.startsWith('SELECT\n *')) {
state.queryTab.selectionDatas = [];
const tableName = sql.split(/from/i)[1]; const tableName = sql.split(/from/i)[1];
if (tableName) { if (tableName) {
state.nowTableName = tableName.trim().split(' ')[0]; const tn = tableName.trim().split(' ')[0];
state.queryTab.nowTableName = tn;
state.nowTableName = tn;
} else { } else {
state.queryTab.nowTableName = '';
state.nowTableName = ''; state.nowTableName = '';
} }
} else { } else {
state.queryTab.nowTableName = '';
state.nowTableName = ''; state.nowTableName = '';
} }
const colAndData: any = await runSql(sql); const colAndData: any = await runSql(sql);
state.execRes.data = colAndData.res; state.queryTab.execRes.data = colAndData.res;
state.execRes.tableColumn = colAndData.colNames; state.queryTab.execRes.tableColumn = colAndData.colNames;
}; };
/** /**
@@ -335,7 +362,7 @@ export default defineComponent({
let activeName = state.activeName; let activeName = state.activeName;
tabNames.forEach((name, index) => { tabNames.forEach((name, index) => {
if (name === targetName) { if (name === targetName) {
const nextTab = tabNames[index + 1] || tabNames[index - 1] || state.queryTabName; const nextTab = tabNames[index + 1] || tabNames[index - 1] || state.queryTab.name;
if (nextTab) { if (nextTab) {
activeName = nextTab; activeName = nextTab;
} }
@@ -351,10 +378,10 @@ export default defineComponent({
const onDataTabClick = (tab: any) => { const onDataTabClick = (tab: any) => {
const name = tab.props.name; const name = tab.props.name;
// 不是查询tab则为表数据tab同时赋值当前表名用于在线修改表数据等 // 不是查询tab则为表数据tab同时赋值当前表名用于在线修改表数据等
if (name != state.queryTabName) { if (name != state.queryTab.name) {
state.nowTableName = name; state.nowTableName = name;
} else { } else {
state.nowTableName = ''; state.nowTableName = state.queryTab.nowTableName;
} }
}; };
@@ -483,14 +510,7 @@ export default defineComponent({
if (tableName == '') { if (tableName == '') {
return; return;
} }
dbApi.columnMetadata state.columnMetadata = (await getColumns(tableName)) as any;
.request({
id: state.dbId,
tableName: tableName,
})
.then((res) => {
state.columnMetadata = res;
});
if (!execSelectSql) { if (!execSelectSql) {
return; return;
@@ -499,31 +519,60 @@ export default defineComponent({
// 执行sql并新增tab // 执行sql并新增tab
state.nowTableName = tableName; state.nowTableName = tableName;
state.activeName = tableName; state.activeName = tableName;
let tab = state.dataTabs[tableName]; let tab = state.dataTabs[tableName];
if (!tab) { // 如果存在该表tab则直接返回
tab = { if (tab) {
label: tableName, return;
name: tableName,
execRes: {
tableColumn: [],
data: [],
emptyResText: '执行中...',
},
};
} }
tab = {
label: tableName,
name: tableName,
execRes: {
tableColumn: [],
data: [],
emptyResText: '执行中...',
},
querySql: `SELECT * FROM ${tableName} LIMIT ${state.defalutLimit}`,
};
state.dataTabs[tableName] = tab; state.dataTabs[tableName] = tab;
state.dataTabs[tableName].execRes.tableColumn = []; state.dataTabs[tableName].execRes.tableColumn = [];
state.dataTabs[tableName].execRes.data = []; state.dataTabs[tableName].execRes.data = [];
const colAndData: any = await runSql(`SELECT * FROM ${tableName} LIMIT ${state.defalutLimit}`); onRefresh(tableName);
};
/**
* 获取表的所有列信息
*/
const getColumns = async (tableName: string) => {
// 优先从 table map中获取
let columns = tableMap.get(tableName);
if (columns) {
return columns;
}
columns = await dbApi.columnMetadata.request({
id: state.dbId,
tableName: tableName,
});
tableMap.set(tableName, columns);
return columns;
};
const onRefresh = async (tableName: string) => {
const colAndData: any = await runSql(state.dataTabs[tableName].querySql);
state.dataTabs[tableName].execRes.emptyResText = '没有数据'; state.dataTabs[tableName].execRes.emptyResText = '没有数据';
state.dataTabs[tableName].execRes.tableColumn = colAndData.colNames; state.dataTabs[tableName].execRes.tableColumn = colAndData.colNames;
state.dataTabs[tableName].execRes.data = colAndData.res; state.dataTabs[tableName].execRes.data = colAndData.res;
}; };
const changeSqlTemplate = () => { /**
getUserSql(); * 提交事务,用于没有开启自动提交事务
*/
const onCommit = () => {
runSql('COMMIT;');
ElMessage.success('COMMIT success');
}; };
/** /**
@@ -533,9 +582,15 @@ export default defineComponent({
if (!state.nowTableName) { if (!state.nowTableName) {
return; return;
} }
const tableName = state.activeName;
const sortType = sort.order == 'descending' ? 'DESC' : 'ASC'; const sortType = sort.order == 'descending' ? 'DESC' : 'ASC';
const colAndData: any = await runSql(`SELECT * FROM ${state.nowTableName} ORDER BY ${sort.prop} ${sortType} LIMIT ${state.defalutLimit}`);
state.dataTabs[state.activeName].execRes.data = colAndData.res; state.dataTabs[state.activeName].querySql = `SELECT * FROM ${tableName} ORDER BY ${sort.prop} ${sortType} LIMIT ${state.defalutLimit}`;
onRefresh(tableName);
};
const changeSqlTemplate = () => {
getUserSql();
}; };
/** /**
@@ -606,91 +661,64 @@ export default defineComponent({
// 清空数据库事件 // 清空数据库事件
const clearDb = () => { const clearDb = () => {
state.tableName = ''; state.tableName = '';
state.nowTableName = '';
state.tableMetadata = []; state.tableMetadata = [];
state.columnMetadata = []; state.columnMetadata = [];
state.dataTabs = {}; state.dataTabs = {};
state.sql = ''; state.sql = '';
state.sqlNames = []; state.sqlNames = [];
state.activeName = state.queryTabName; state.activeName = state.queryTab.name;
state.execRes.data = []; state.queryTab.execRes.data = [];
state.execRes.tableColumn = []; state.queryTab.execRes.tableColumn = [];
state.cmOptions.hintOptions.tables = []; state.cmOptions.hintOptions.tables = [];
tableMap.clear();
}; };
// 某一行鼠标右击
const contextmenu = (row: any, column: any, event: any) => { const onDataSelectionChange = (datas: []) => {
event.preventDefault(); if (isQueryTab()) {
let pagex = event.pageX; state.queryTab.selectionDatas = datas;
let pagey = event.pageY; } else {
let child = document.getElementById('contextmenu'); state.dataTabs[state.activeName].selectionDatas = datas;
if (child) {
document.body.removeChild(child);
} }
let div = document.createElement('div'); };
div.setAttribute('id', 'contextmenu');
div.setAttribute( /**
'style', * 执行删除数据事件
`overflow:hidden;border-radius:10px;border:1px solid #bababa;width:100px;position:absolute;left:${pagex}px;top:${pagey}px;z-index:1000` */
); const onDeleteData = async () => {
document.body.appendChild(div); const queryTab = isQueryTab();
document.body.addEventListener('click', (e: any) => { const deleteDatas = queryTab ? state.queryTab.selectionDatas : state.dataTabs[state.activeName].selectionDatas;
if (!e.target.className.includes('el-menu-item')) { isTrue(deleteDatas && deleteDatas.length > 0, '请先选择要删除的数据');
let child = document.getElementById('contextmenu'); const primaryKey = await getTablePrimaryKeyColume(state.nowTableName);
if (child) { const ids = deleteDatas.map((d: any) => `'${d[primaryKey]}'`).join(',');
document.body.removeChild(child); const sql = `DELETE FROM ${state.nowTableName} WHERE ${primaryKey} IN (${ids})`;
}
promptExeSql(sql, null, () => {
if (!queryTab) {
state.dataTabs[state.activeName].execRes.data = state.dataTabs[state.activeName].execRes.data.filter(
(d: any) => !(deleteDatas.findIndex((x: any) => x[primaryKey] == d[primaryKey]) != -1)
);
state.dataTabs[state.activeName].selectionDatas = [];
} else {
state.queryTab.execRes.data = state.queryTab.execRes.data.filter(
(d: any) => !(deleteDatas.findIndex((x: any) => x[primaryKey] == d[primaryKey]) != -1)
);
state.queryTab.selectionDatas = [];
} }
}); });
const menu = {
render() {
return h(
ElMenu,
{
activeTextColor: '#413F41',
textColor: '#413F41',
backgroundColor: '#eae4e9',
style: {
width: '100%',
},
},
{
default: () => [
h(
ElMenuItem,
{
onClick: () => {
ElMessageBox({
title: '删除记录',
message: '确定删除这条记录?',
showCancelButton: true,
confirmButtonText: '确定',
cancelButtonText: '取消',
})
.then(async (action) => {
let sql = `DELETE FROM ${state.tableName} WHERE id=${row.id};`;
await dbApi.sqlExec.request({
id: state.dbId,
sql: sql,
});
changeTable(state.tableName, true);
})
.catch(() => {});
},
},
{
default: () => ['删除记录'],
}
),
],
}
);
},
};
createApp(menu).mount('#contextmenu');
}; };
/**
* 是否为查询tab
*/
const isQueryTab = () => {
return state.activeName == state.queryTab.name;
};
// 监听单元格点击事件 // 监听单元格点击事件
const cellClick = (row: any, column: any, cell: any, event: any) => { const cellClick = (row: any, column: any, cell: any, event: any) => {
// 如果当前操作的表名不存在,则不允许修改格内容 // 如果当前操作的表名不存在 或者 当前列的property不存在(如多选框),则不允许修改当前单元格内容
if (!state.nowTableName) { if (!state.nowTableName || !column.property) {
return; return;
} }
let isDiv = cell.children[0].tagName === 'DIV'; let isDiv = cell.children[0].tagName === 'DIV';
@@ -703,11 +731,11 @@ export default defineComponent({
input.setAttribute('style', 'height:30px;' + div.getAttribute('style')); input.setAttribute('style', 'height:30px;' + div.getAttribute('style'));
cell.replaceChildren(input); cell.replaceChildren(input);
input.focus(); input.focus();
input.addEventListener('blur', () => { input.addEventListener('blur', async () => {
div.innerText = input.value; div.innerText = input.value;
cell.replaceChildren(div); cell.replaceChildren(div);
if (input.value !== text) { if (input.value !== text) {
const primaryKey = getTablePrimaryKeyColume(state.nowTableName); const primaryKey = await getTablePrimaryKeyColume(state.nowTableName);
const sql = `UPDATE ${state.nowTableName} SET ${column.rawColumnKey} = '${input.value}' WHERE ${primaryKey} = '${row[primaryKey]}'`; const sql = `UPDATE ${state.nowTableName} SET ${column.rawColumnKey} = '${input.value}' WHERE ${primaryKey} = '${row[primaryKey]}'`;
promptExeSql(sql, () => { promptExeSql(sql, () => {
div.innerText = text; div.innerText = text;
@@ -720,9 +748,9 @@ export default defineComponent({
/** /**
* 获取表主键列名,目前先以默认表字段第一个字段 * 获取表主键列名,目前先以默认表字段第一个字段
*/ */
const getTablePrimaryKeyColume = (tableName: string) => { const getTablePrimaryKeyColume = async (tableName: string) => {
// 'id [bigint(20) unsigned]' const cols = await getColumns(tableName);
return state.cmOptions.hintOptions.tables[tableName][0].split(' ')[0]; return cols[0].columnName;
}; };
/** /**
@@ -730,7 +758,7 @@ export default defineComponent({
*/ */
const promptExeSql = (sql: string, cancelFunc: any = null, successFunc: any = null) => { const promptExeSql = (sql: string, cancelFunc: any = null, successFunc: any = null) => {
ElMessageBox({ ElMessageBox({
title: '执行SQL', title: '执行SQL',
message: h( message: h(
'div', 'div',
{ {
@@ -742,7 +770,7 @@ export default defineComponent({
autocomplete: 'off', autocomplete: 'off',
rows: 8, rows: 8,
style: { style: {
height: '150px', height: '300px',
width: '100%', width: '100%',
fontWeight: '600', fontWeight: '600',
}, },
@@ -769,9 +797,11 @@ export default defineComponent({
} }
}, },
}) })
.then((action) => { .then(async (action) => {
runSql(sql); await runSql(sql);
successFunc(); if (successFunc) {
successFunc();
}
}) })
.catch(() => { .catch(() => {
if (cancelFunc) { if (cancelFunc) {
@@ -781,16 +811,20 @@ export default defineComponent({
}; };
// 添加新数据行 // 添加新数据行
const addRow = () => { const addRow = async () => {
const tableNmae = state.nowTableName;
const columns = await getColumns(tableNmae);
// key: 字段名value: 字段名提示
let obj: any = {}; let obj: any = {};
(state.execRes.tableColumn as any) = state.columnMetadata.map((i) => (i as any).columnName); columns.forEach((item: any) => {
state.execRes.tableColumn.forEach((item) => { obj[item.columnName] = `'${item.columnName}[${item.columnType}]${item.nullable == 'YES' ? '' : '[not null]'}'`;
obj[item] = 'NULL';
}); });
let columnNames = Object.keys(obj).join(',');
let values = Object.values(obj).join(','); let values = Object.values(obj).join(',');
let sql = `INSERT INTO ${state.tableName} VALUES (${values});`; let sql = `INSERT INTO ${state.nowTableName} (${columnNames}) VALUES (${values});`;
promptExeSql(sql, null, () => { promptExeSql(sql, null, () => {
changeTable(state.tableName, true); onRefresh(tableNmae);
}); });
}; };
@@ -864,8 +898,11 @@ export default defineComponent({
formatSql, formatSql,
onBeforeChange, onBeforeChange,
listenMouse, listenMouse,
onRefresh,
onCommit,
addRow, addRow,
contextmenu, onDataSelectionChange,
onDeleteData,
onTableSortChange, onTableSortChange,
}; };
}, },