mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-03 07:50:25 +08:00
feat: 数据库表格重构
This commit is contained in:
@@ -116,7 +116,7 @@ const loadTableData = async (inst: any, schema: string, tableName: string) => {
|
||||
tab.dbType = inst.type;
|
||||
tab.db = schema;
|
||||
tab.type = TabType.TableData;
|
||||
tab.other = {
|
||||
tab.params = {
|
||||
table: tableName
|
||||
}
|
||||
state.tabs.set(label, tab)
|
||||
@@ -136,7 +136,7 @@ const addQueryTab = async (inst: any, db: string, sqlName: string = '') => {
|
||||
} else {
|
||||
let count = 1;
|
||||
state.tabs.forEach((v) => {
|
||||
if (v.type == TabType.Query && !v.other.sqlName) {
|
||||
if (v.type == TabType.Query && !v.params.sqlName) {
|
||||
count++;
|
||||
}
|
||||
})
|
||||
@@ -153,7 +153,7 @@ const addQueryTab = async (inst: any, db: string, sqlName: string = '') => {
|
||||
tab.dbType = inst.type;
|
||||
tab.db = db;
|
||||
tab.type = TabType.Query;
|
||||
tab.other = {
|
||||
tab.params = {
|
||||
sqlName: sqlName,
|
||||
dbs: instanceTreeRef.value.getSchemas(dbId)
|
||||
}
|
||||
@@ -258,7 +258,7 @@ const registerSqlCompletionItemProvider = () => {
|
||||
const tokens = textBeforePointer.trim().split(/\s+/)
|
||||
const lastToken = tokens[tokens.length - 1].toLowerCase()
|
||||
|
||||
const dbs = nowTab.other && nowTab.other.dbs;
|
||||
const dbs = nowTab.params && nowTab.params.dbs;
|
||||
// console.log("光标前文本:=>" + textBeforePointerMulti)
|
||||
|
||||
// console.log("最后输入的:=>" + lastToken)
|
||||
@@ -290,14 +290,6 @@ const registerSqlCompletionItemProvider = () => {
|
||||
if (tableInfo.tableName) {
|
||||
let table = tableInfo.tableName
|
||||
let db = tableInfo.dbName;
|
||||
// // 取出表名并提示
|
||||
// let dbs = state.monacoOptions.dbTables[dbId + db]
|
||||
// let columns = dbs ? (dbs[table] || []) : [];
|
||||
// if ((!columns || columns.length === 0) && db) {
|
||||
// state.monacoOptions.dbTables[dbId + db] = await loadHintTables(dbId, db)
|
||||
// dbs = state.monacoOptions.dbTables[dbId + db]
|
||||
// columns = dbs ? (dbs[table] || []) : [];
|
||||
// }
|
||||
// 取出表名并提示
|
||||
let dbHits = await dbInst.loadDbHints(db)
|
||||
let columns = dbHits[table]
|
||||
|
||||
323
mayfly_go_web/src/views/ops/db/component/DbTable.vue
Normal file
323
mayfly_go_web/src/views/ops/db/component/DbTable.vue
Normal file
@@ -0,0 +1,323 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-table @cell-dblclick="(row: any, column: any, cell: any, event: any) => cellClick(row, column, cell)"
|
||||
@sort-change="(sort: any) => onTableSortChange(sort)" @selection-change="onDataSelectionChange"
|
||||
:data="datas" size="small" :max-height="tableHeight" v-loading="loading" element-loading-text="查询中..."
|
||||
:empty-text="emptyText" stripe border class="mt5">
|
||||
<el-table-column v-if="datas.length > 0 && table" type="selection" width="35" />
|
||||
<el-table-column min-width="100" :width="DbInst.flexColumnWidth(item, datas)" align="center"
|
||||
v-for="item in columnNames" :key="item" :prop="item" :label="item" show-overflow-tooltip
|
||||
:sortable="sortable">
|
||||
<template #header v-if="showColumnTip">
|
||||
<el-tooltip raw-content placement="top" effect="customized">
|
||||
<template #content> {{ getColumnTip(item) }} </template>
|
||||
{{ item }}
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, watch, reactive, toRefs } from 'vue';
|
||||
import { DbInst, UpdateFieldsMeta, FieldsMeta } from '../db';
|
||||
|
||||
const emits = defineEmits(['sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField'])
|
||||
|
||||
const props = defineProps({
|
||||
dbId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
dbType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
db: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
table: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
},
|
||||
columnNames: {
|
||||
type: Array,
|
||||
},
|
||||
sortable: {
|
||||
type: [String, Boolean],
|
||||
default: false,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
emptyText: {
|
||||
type: String,
|
||||
default: '暂无数据',
|
||||
},
|
||||
showColumnTip: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '600'
|
||||
}
|
||||
})
|
||||
|
||||
const state = reactive({
|
||||
dbId: 0, // 当前选中操作的数据库实例
|
||||
dbType: '',
|
||||
db: '', // 数据库名
|
||||
table: '', // 当前的表名
|
||||
datas: [],
|
||||
columnNames: [],
|
||||
columns: [],
|
||||
sortable: false,
|
||||
loading: false,
|
||||
selectionDatas: [] as any,
|
||||
showColumnTip: false,
|
||||
tableHeight: '600',
|
||||
emptyText: '',
|
||||
updatedFields: [] as UpdateFieldsMeta[],// 各个tab表被修改的字段信息
|
||||
});
|
||||
|
||||
const {
|
||||
tableHeight,
|
||||
datas,
|
||||
sortable,
|
||||
loading,
|
||||
columnNames,
|
||||
showColumnTip,
|
||||
} = toRefs(state);
|
||||
|
||||
watch(props, (newValue: any) => {
|
||||
setState(newValue);
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
console.log('in DbTable mounted');
|
||||
setState(props);
|
||||
})
|
||||
|
||||
const setState = (props: any) => {
|
||||
state.dbId = props.dbId;
|
||||
state.dbType = props.dbType;
|
||||
state.db = props.db;
|
||||
state.table = props.table;
|
||||
state.datas = props.data;
|
||||
state.tableHeight = props.height;
|
||||
state.sortable = props.sortable;
|
||||
state.loading = props.loading;
|
||||
state.columnNames = props.columnNames;
|
||||
state.showColumnTip = props.showColumnTip;
|
||||
state.emptyText = props.emptyText;
|
||||
}
|
||||
|
||||
const getColumnTip = (columnName: string) => {
|
||||
// 优先从 table map中获取
|
||||
let columns = getNowDb().getColumns(state.table);
|
||||
if (!columns) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const column = columns.find((c: any) => c.columnName == columnName);
|
||||
const comment = column.columnComment;
|
||||
return `${column.columnType} ${comment ? ' | ' + comment : ''}`;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 表排序字段变更
|
||||
*/
|
||||
const onTableSortChange = async (sort: any) => {
|
||||
if (!sort.prop) {
|
||||
return;
|
||||
}
|
||||
cancelUpdateFields();
|
||||
emits('sortChange', sort);
|
||||
};
|
||||
|
||||
const onDataSelectionChange = (datas: []) => {
|
||||
state.selectionDatas = datas;
|
||||
emits('selectionChange', datas);
|
||||
};
|
||||
|
||||
// 监听单元格点击事件
|
||||
const cellClick = (row: any, column: any, cell: any) => {
|
||||
const property = column.property;
|
||||
// 如果当前操作的表名不存在 或者 当前列的property不存在(如多选框),则不允许修改当前单元格内容
|
||||
if (!state.table || !property) {
|
||||
return;
|
||||
}
|
||||
let div: HTMLElement = cell.children[0];
|
||||
if (div && div.tagName === 'DIV') {
|
||||
// 转为字符串比较,可能存在数字等
|
||||
let text = (row[property] || row[property] == 0 ? row[property] : '') + '';
|
||||
let input = document.createElement('input');
|
||||
input.setAttribute('value', text);
|
||||
// 将表格width也赋值于输入框,避免输入框长度超过表格长度
|
||||
input.setAttribute('style', 'height:23px;text-align:center;border:none;' + div.getAttribute('style'));
|
||||
cell.replaceChildren(input);
|
||||
input.focus();
|
||||
input.addEventListener('blur', async () => {
|
||||
row[property] = input.value;
|
||||
cell.replaceChildren(div);
|
||||
if (input.value !== text) {
|
||||
let currentUpdatedFields = state.updatedFields
|
||||
const dbInst = getNowDbInst();
|
||||
// 主键
|
||||
const primaryKey = await dbInst.loadTableColumn(state.db, state.table);
|
||||
const primaryKeyValue = row[primaryKey.columnName];
|
||||
// 更新字段列信息
|
||||
const updateColumn = await dbInst.loadTableColumn(state.db, state.table, property);
|
||||
const newField = {
|
||||
div, row,
|
||||
fieldName: column.rawColumnKey,
|
||||
fieldType: updateColumn.columnType,
|
||||
oldValue: text,
|
||||
newValue: input.value
|
||||
} as FieldsMeta;
|
||||
|
||||
// 被修改的字段
|
||||
const primaryKeyFields = currentUpdatedFields.filter((meta) => meta.primaryKey === primaryKeyValue)
|
||||
let hasKey = false;
|
||||
if (primaryKeyFields.length <= 0) {
|
||||
primaryKeyFields[0] = {
|
||||
primaryKey: primaryKeyValue,
|
||||
primaryKeyName: primaryKey.columnName,
|
||||
primaryKeyType: primaryKey.columnType,
|
||||
fields: [newField]
|
||||
}
|
||||
} else {
|
||||
hasKey = true
|
||||
let hasField = primaryKeyFields[0].fields.some(a => {
|
||||
if (a.fieldName === newField.fieldName) {
|
||||
a.newValue = newField.newValue
|
||||
}
|
||||
return a.fieldName === newField.fieldName
|
||||
})
|
||||
if (!hasField) {
|
||||
primaryKeyFields[0].fields.push(newField)
|
||||
}
|
||||
}
|
||||
let fields = primaryKeyFields[0].fields
|
||||
|
||||
const fieldsParam = fields.filter((a) => {
|
||||
if (a.fieldName === column.rawColumnKey) {
|
||||
a.newValue = input.value
|
||||
}
|
||||
return a.fieldName === column.rawColumnKey
|
||||
})
|
||||
|
||||
const field = fieldsParam.length > 0 && fieldsParam[0] || {} as FieldsMeta
|
||||
if (field.oldValue === input.value) { // 新值=旧值
|
||||
// 删除数据
|
||||
div.classList.remove('update_field_active')
|
||||
let delIndex: number[] = [];
|
||||
currentUpdatedFields.forEach((a, i) => {
|
||||
if (a.primaryKey === primaryKeyValue) {
|
||||
a.fields = a.fields && a.fields.length > 0 ? a.fields.filter(f => f.fieldName !== column.rawColumnKey) : [];
|
||||
a.fields.length <= 0 && delIndex.push(i)
|
||||
}
|
||||
});
|
||||
delIndex.forEach(i => delete currentUpdatedFields[i])
|
||||
currentUpdatedFields = currentUpdatedFields.filter(a => a)
|
||||
} else {
|
||||
// 新增数据
|
||||
div.classList.add('update_field_active')
|
||||
if (hasKey) {
|
||||
currentUpdatedFields.forEach((value, index, array) => {
|
||||
if (value.primaryKey === primaryKeyValue) {
|
||||
array[index].fields = fields
|
||||
}
|
||||
})
|
||||
} else {
|
||||
currentUpdatedFields.push({
|
||||
primaryKey: primaryKeyValue,
|
||||
primaryKeyName: primaryKey.columnName,
|
||||
primaryKeyType: primaryKey.columnType,
|
||||
fields
|
||||
})
|
||||
}
|
||||
}
|
||||
state.updatedFields = currentUpdatedFields;
|
||||
changeUpdatedField();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const submitUpdateFields = () => {
|
||||
let currentUpdatedFields = state.updatedFields;
|
||||
if (currentUpdatedFields.length <= 0) {
|
||||
return;
|
||||
}
|
||||
const db = state.db;
|
||||
let res = '';
|
||||
let divs: HTMLElement[] = [];
|
||||
currentUpdatedFields.forEach(a => {
|
||||
let sql = `UPDATE ${state.table} SET `;
|
||||
let primaryKey = a.primaryKey;
|
||||
let primaryKeyType = a.primaryKeyType;
|
||||
let primaryKeyName = a.primaryKeyName;
|
||||
a.fields.forEach(f => {
|
||||
sql += ` ${f.fieldName} = ${DbInst.wrapColumnValue(f.fieldType, f.newValue)},`
|
||||
divs.push(f.div)
|
||||
})
|
||||
sql = sql.substring(0, sql.length - 1)
|
||||
sql += ` WHERE ${primaryKeyName} = ${DbInst.wrapColumnValue(primaryKeyType, primaryKey)} ;`
|
||||
res += sql;
|
||||
})
|
||||
|
||||
DbInst.getInst(state.dbId).promptExeSql(db, res, () => { }, () => {
|
||||
currentUpdatedFields = [];
|
||||
divs.forEach(a => {
|
||||
a.classList.remove('update_field_active');
|
||||
})
|
||||
state.updatedFields = [];
|
||||
changeUpdatedField();
|
||||
});
|
||||
}
|
||||
|
||||
const cancelUpdateFields = () => {
|
||||
state.updatedFields.forEach((a: any) => {
|
||||
a.fields.forEach((b: any) => {
|
||||
b.div.classList.remove('update_field_active')
|
||||
b.row[b.fieldName] = b.oldValue
|
||||
})
|
||||
})
|
||||
state.updatedFields = [];
|
||||
changeUpdatedField();
|
||||
}
|
||||
|
||||
|
||||
const changeUpdatedField = () => {
|
||||
emits('changeUpdatedField', state.updatedFields);
|
||||
}
|
||||
|
||||
const getNowDb = () => {
|
||||
return DbInst.getInst(state.dbId).getDb(state.db);
|
||||
}
|
||||
|
||||
const getNowDbInst = () => {
|
||||
return DbInst.getInst(state.dbId, state.dbType);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
submitUpdateFields,
|
||||
cancelUpdateFields
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.update_field_active {
|
||||
background-color: var(--el-color-success)
|
||||
}
|
||||
</style>
|
||||
@@ -51,33 +51,28 @@
|
||||
<el-link type="success" :underline="false" @click="exportData"><span
|
||||
style="font-size: 12px">导出</span></el-link>
|
||||
</span>
|
||||
<span v-if="updatedFields.length > 0">
|
||||
<span v-if="hasUpdatedFileds">
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
<el-link type="success" :underline="false" @click="submitUpdateFields()"><span
|
||||
style="font-size: 12px">提交</span></el-link>
|
||||
</span>
|
||||
<span v-if="updatedFields.length > 0">
|
||||
<span v-if="hasUpdatedFileds">
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
<el-link type="warning" :underline="false" @click="cancelUpdateFields"><span
|
||||
style="font-size: 12px">取消</span></el-link>
|
||||
</span>
|
||||
</el-row>
|
||||
<el-table @cell-dblclick="(row: any, column: any, cell: any, event: any) => cellClick(row, column, cell)"
|
||||
@selection-change="onDataSelectionChange" size="small" :data="execRes.data" :max-height="250"
|
||||
v-loading="loading" element-loading-text="查询中..."
|
||||
empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改" stripe border class="mt5">
|
||||
<el-table-column v-if="execRes.tableColumn.length > 0 && table" type="selection" witih="35" />
|
||||
<el-table-column min-witih="100" :witih="DbInst.flexColumnWidth(item, execRes.data)" align="center"
|
||||
v-for="item in execRes.tableColumn" :key="item" :prop="item" :label="item" show-overflow-tooltip>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<db-table ref="dbTableRef" :db-id="state.ti.dbId" :db-type="state.ti.dbType" :db="state.ti.db"
|
||||
:data="execRes.data" :table="state.table" :column-names="execRes.tableColumn" :loading="loading"
|
||||
height="250" empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改"
|
||||
@selection-change="onDataSelectionChange" @change-updated-field="changeUpdatedField"></db-table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, watch, onMounted, computed, reactive, toRefs } from 'vue';
|
||||
import { nextTick, watch, onMounted, computed, reactive, toRefs, ref, Ref } from 'vue';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
import { getSession } from '@/common/utils/storage';
|
||||
import { isTrue, notBlank } from '@/common/assert';
|
||||
@@ -91,8 +86,8 @@ import { editor } from 'monaco-editor';
|
||||
// 主题仓库 https://github.com/brijeshb42/monaco-themes
|
||||
// 主题例子 https://editor.bitwiser.in/
|
||||
import SolarizedLight from 'monaco-themes/themes/Solarized-light.json';
|
||||
|
||||
import { DbInst, UpdateFieldsMeta, FieldsMeta, TabInfo } from '../../db';
|
||||
import DbTable from '../DbTable.vue'
|
||||
import { TabInfo } from '../../db';
|
||||
import { exportCsv } from '@/common/utils/export';
|
||||
import { dateStrFormat } from '@/common/utils/date';
|
||||
import { dbApi } from '../../api';
|
||||
@@ -119,6 +114,7 @@ const props = defineProps({
|
||||
const store = useStore();
|
||||
const token = getSession('token');
|
||||
let monacoEditor = {} as editor.IStandaloneCodeEditor;
|
||||
const dbTableRef = ref(null) as Ref;
|
||||
|
||||
const state = reactive({
|
||||
token,
|
||||
@@ -135,7 +131,7 @@ const state = reactive({
|
||||
},
|
||||
selectionDatas: [] as any,
|
||||
editorHeight: '500',
|
||||
updatedFields: [] as UpdateFieldsMeta[],// 各个tab表被修改的字段信息
|
||||
hasUpdatedFileds: false,
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -145,7 +141,7 @@ const {
|
||||
table,
|
||||
sqlName,
|
||||
loading,
|
||||
updatedFields,
|
||||
hasUpdatedFileds,
|
||||
} = toRefs(state);
|
||||
|
||||
watch(() => props.editorHeight, (newValue: any) => {
|
||||
@@ -156,11 +152,11 @@ onMounted(async () => {
|
||||
console.log('in query mounted');
|
||||
state.ti = props.data;
|
||||
state.editorHeight = props.editorHeight;
|
||||
const other = state.ti.other;
|
||||
state.dbs = other && other.dbs;
|
||||
const params = state.ti.params;
|
||||
state.dbs = params && params.dbs;
|
||||
|
||||
if (other && other.sqlName) {
|
||||
state.sqlName = other.sqlName;
|
||||
if (params && params.sqlName) {
|
||||
state.sqlName = params.sqlName;
|
||||
const res = await dbApi.getSql.request({ id: state.ti.dbId, type: 1, name: state.sqlName, db: state.ti.db });
|
||||
state.sql = res.sql;
|
||||
}
|
||||
@@ -495,6 +491,11 @@ const onDataSelectionChange = (datas: []) => {
|
||||
state.selectionDatas = datas;
|
||||
};
|
||||
|
||||
const changeUpdatedField = (updatedFields: []) => {
|
||||
// 如果存在要更新字段,则显示提交和取消按钮
|
||||
state.hasUpdatedFileds = updatedFields && updatedFields.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行删除数据事件
|
||||
*/
|
||||
@@ -513,153 +514,12 @@ const onDeleteData = async () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 监听单元格点击事件
|
||||
const cellClick = (row: any, column: any, cell: any) => {
|
||||
const property = column.property;
|
||||
const table = state.table;
|
||||
// 如果当前操作的表名不存在 或者 当前列的property不存在(如多选框),则不允许修改当前单元格内容
|
||||
if (!table || !property) {
|
||||
return;
|
||||
}
|
||||
let div: HTMLElement = cell.children[0];
|
||||
if (div && div.tagName === 'DIV') {
|
||||
// 转为字符串比较,可能存在数字等
|
||||
let text = (row[property] || row[property] == 0 ? row[property] : '') + '';
|
||||
let input = document.createElement('input');
|
||||
input.setAttribute('value', text);
|
||||
// 将表格witih也赋值于输入框,避免输入框长度超过表格长度
|
||||
input.setAttribute('style', 'height:23px;text-align:center;border:none;' + div.getAttribute('style'));
|
||||
cell.replaceChildren(input);
|
||||
input.focus();
|
||||
input.addEventListener('blur', async () => {
|
||||
row[property] = input.value;
|
||||
cell.replaceChildren(div);
|
||||
if (input.value !== text) {
|
||||
let currentUpdatedFields = state.updatedFields
|
||||
const dbInst = state.ti.getNowDbInst()
|
||||
const db = state.ti.getNowDb();
|
||||
// 主键
|
||||
const primaryKey = await dbInst.loadTableColumn(state.ti.db, table);
|
||||
const primaryKeyValue = row[primaryKey.columnName];
|
||||
// 更新字段列信息
|
||||
const updateColumn = db.getColumn(table, property);
|
||||
const newField = {
|
||||
div, row,
|
||||
fieldName: column.rawColumnKey,
|
||||
fieldType: updateColumn.columnType,
|
||||
oldValue: text,
|
||||
newValue: input.value
|
||||
} as FieldsMeta;
|
||||
|
||||
// 被修改的字段
|
||||
const primaryKeyFields = currentUpdatedFields.filter((meta) => meta.primaryKey === primaryKeyValue)
|
||||
let hasKey = false;
|
||||
if (primaryKeyFields.length <= 0) {
|
||||
primaryKeyFields[0] = {
|
||||
primaryKey: primaryKeyValue,
|
||||
primaryKeyName: primaryKey.columnName,
|
||||
primaryKeyType: primaryKey.columnType,
|
||||
fields: [newField]
|
||||
}
|
||||
} else {
|
||||
hasKey = true
|
||||
let hasField = primaryKeyFields[0].fields.some(a => {
|
||||
if (a.fieldName === newField.fieldName) {
|
||||
a.newValue = newField.newValue
|
||||
}
|
||||
return a.fieldName === newField.fieldName
|
||||
})
|
||||
if (!hasField) {
|
||||
primaryKeyFields[0].fields.push(newField)
|
||||
}
|
||||
}
|
||||
let fields = primaryKeyFields[0].fields
|
||||
|
||||
const fieldsParam = fields.filter((a) => {
|
||||
if (a.fieldName === column.rawColumnKey) {
|
||||
a.newValue = input.value
|
||||
}
|
||||
return a.fieldName === column.rawColumnKey
|
||||
})
|
||||
|
||||
const field = fieldsParam.length > 0 && fieldsParam[0] || {} as FieldsMeta
|
||||
if (field.oldValue === input.value) { // 新值=旧值
|
||||
// 删除数据
|
||||
div.classList.remove('update_field_active')
|
||||
let delIndex: number[] = [];
|
||||
currentUpdatedFields.forEach((a, i) => {
|
||||
if (a.primaryKey === primaryKeyValue) {
|
||||
a.fields = a.fields && a.fields.length > 0 ? a.fields.filter(f => f.fieldName !== column.rawColumnKey) : [];
|
||||
a.fields.length <= 0 && delIndex.push(i)
|
||||
}
|
||||
});
|
||||
delIndex.forEach(i => delete currentUpdatedFields[i])
|
||||
currentUpdatedFields = currentUpdatedFields.filter(a => a)
|
||||
} else {
|
||||
// 新增数据
|
||||
div.classList.add('update_field_active')
|
||||
if (hasKey) {
|
||||
currentUpdatedFields.forEach((value, index, array) => {
|
||||
if (value.primaryKey === primaryKeyValue) {
|
||||
array[index].fields = fields
|
||||
}
|
||||
})
|
||||
} else {
|
||||
currentUpdatedFields.push({
|
||||
primaryKey: primaryKeyValue,
|
||||
primaryKeyName: primaryKey.columnName,
|
||||
primaryKeyType: primaryKey.columnType,
|
||||
fields
|
||||
})
|
||||
}
|
||||
}
|
||||
state.updatedFields = currentUpdatedFields;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const submitUpdateFields = () => {
|
||||
let currentUpdatedFields = state.updatedFields;
|
||||
if (currentUpdatedFields.length <= 0) {
|
||||
return;
|
||||
}
|
||||
const { db } = state.ti;
|
||||
const table = state.table;
|
||||
let res = '';
|
||||
let divs: HTMLElement[] = [];
|
||||
currentUpdatedFields.forEach(a => {
|
||||
let sql = `UPDATE ${table} SET `;
|
||||
let primaryKey = a.primaryKey;
|
||||
let primaryKeyType = a.primaryKeyType;
|
||||
let primaryKeyName = a.primaryKeyName;
|
||||
a.fields.forEach(f => {
|
||||
sql += ` ${f.fieldName} = ${DbInst.wrapColumnValue(f.fieldType, f.newValue)},`
|
||||
divs.push(f.div)
|
||||
})
|
||||
sql = sql.substring(0, sql.length - 1)
|
||||
sql += ` WHERE ${primaryKeyName} = ${DbInst.wrapColumnValue(primaryKeyType, primaryKey)} ;`
|
||||
res += sql;
|
||||
})
|
||||
|
||||
state.ti.getNowDbInst().promptExeSql(db, res, () => { }, () => {
|
||||
currentUpdatedFields = [];
|
||||
divs.forEach(a => {
|
||||
a.classList.remove('update_field_active');
|
||||
})
|
||||
state.updatedFields = [];
|
||||
});
|
||||
|
||||
dbTableRef.value.submitUpdateFields();
|
||||
}
|
||||
|
||||
const cancelUpdateFields = () => {
|
||||
state.updatedFields.forEach((a: any) => {
|
||||
a.fields.forEach((b: any) => {
|
||||
b.div.classList.remove('update_field_active')
|
||||
b.row[b.fieldName] = b.oldValue
|
||||
})
|
||||
})
|
||||
state.updatedFields = [];
|
||||
dbTableRef.value.cancelUpdateFields();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -28,13 +28,11 @@
|
||||
</el-tooltip>
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
|
||||
<el-tooltip v-if="state.updatedFields.length > 0" class="box-item" effect="dark" content="提交修改"
|
||||
placement="top">
|
||||
<el-tooltip v-if="hasUpdatedFileds" class="box-item" effect="dark" content="提交修改" placement="top">
|
||||
<el-link @click="submitUpdateFields()" type="success" :underline="false" class="f12">提交</el-link>
|
||||
</el-tooltip>
|
||||
<el-divider v-if="state.updatedFields.length > 0" direction="vertical" border-style="dashed" />
|
||||
<el-tooltip v-if="state.updatedFields.length > 0" class="box-item" effect="dark" content="取消修改"
|
||||
placement="top">
|
||||
<el-divider v-if="hasUpdatedFileds" direction="vertical" border-style="dashed" />
|
||||
<el-tooltip v-if="hasUpdatedFileds" class="box-item" effect="dark" content="取消修改" placement="top">
|
||||
<el-link @click="cancelUpdateFields" type="warning" :underline="false" class="f12">取消</el-link>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
@@ -65,22 +63,12 @@
|
||||
</el-input>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-table @cell-dblclick="(row: any, column: any, cell: any, event: any) => cellClick(row, column, cell)"
|
||||
@sort-change="(sort: any) => onTableSortChange(sort)" @selection-change="onDataSelectionChange"
|
||||
:data="datas" size="small" :max-height="tableHeight" v-loading="loading" element-loading-text="查询中..."
|
||||
empty-text="暂无数据" stripe border class="mt5">
|
||||
<el-table-column v-if="datas.length > 0" type="selection" width="35" />
|
||||
<el-table-column min-width="100" :width="DbInst.flexColumnWidth(item, datas)" align="center"
|
||||
v-for="item in columnNames" :key="item" :prop="item" :label="item" show-overflow-tooltip
|
||||
:sortable="'custom'">
|
||||
<template #header>
|
||||
<el-tooltip raw-content placement="top" effect="customized">
|
||||
<template #content> {{ getColumnTip(item) }} </template>
|
||||
{{ item }}
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<db-table ref="dbTableRef" :db-id="state.ti.dbId" :db-type="state.ti.dbType" :db="state.ti.db" :data="datas"
|
||||
:table="state.table" :column-names="columnNames" :loading="loading" :height="tableHeight"
|
||||
:show-column-tip="true" :sortable="true" @sort-change="(sort: any) => onTableSortChange(sort)"
|
||||
@selection-change="onDataSelectionChange" @change-updated-field="changeUpdatedField"></db-table>
|
||||
|
||||
<el-row type="flex" class="mt5" justify="center">
|
||||
<el-pagination small :total="count" @current-change="pageChange()" layout="prev, pager, next, total, jumper"
|
||||
v-model:current-page="pageNum" :page-size="DbInst.DefaultLimit"></el-pagination>
|
||||
@@ -115,16 +103,16 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, watch, reactive, toRefs } from 'vue';
|
||||
import { isTrue, notEmpty } from '@/common/assert';
|
||||
import { onMounted, watch, reactive, toRefs, ref, Ref } from 'vue';
|
||||
import { isTrue, notEmpty, notBlank } from '@/common/assert';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { DbInst, UpdateFieldsMeta, FieldsMeta, TabInfo } from '../../db';
|
||||
import { DbInst, TabInfo } from '../../db';
|
||||
import { exportCsv } from '@/common/utils/export';
|
||||
import { dateStrFormat } from '@/common/utils/date';
|
||||
import { notBlank } from '../../../../../common/assert';
|
||||
import DbTable from '../DbTable.vue'
|
||||
|
||||
const emits = defineEmits(['genInsertSql', 'clickSqlName', 'clickSchemaTable', 'changeSchema', 'loadSqlNames'])
|
||||
const emits = defineEmits(['genInsertSql'])
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
@@ -137,9 +125,10 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const dbTableRef = ref(null) as Ref;
|
||||
|
||||
const state = reactive({
|
||||
ti: {} as TabInfo,
|
||||
dbId: null, // 当前选中操作的数据库实例
|
||||
table: '', // 当前的表名
|
||||
datas: [],
|
||||
sql: '', // 当前数据tab执行的sql
|
||||
@@ -161,7 +150,7 @@ const state = reactive({
|
||||
value: null
|
||||
},
|
||||
tableHeight: '600',
|
||||
updatedFields: [] as UpdateFieldsMeta[],// 各个tab表被修改的字段信息
|
||||
hasUpdatedFileds: false,
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -172,6 +161,7 @@ const {
|
||||
columnNames,
|
||||
pageNum,
|
||||
count,
|
||||
hasUpdatedFileds,
|
||||
conditionDialog,
|
||||
} = toRefs(state);
|
||||
|
||||
@@ -183,8 +173,8 @@ onMounted(async () => {
|
||||
console.log('in table data mounted');
|
||||
state.ti = props.data;
|
||||
state.tableHeight = props.tableHeight;
|
||||
state.table = state.ti.other.table;
|
||||
notBlank(state.table, "TableData组件other.table信息不能为空")
|
||||
state.table = state.ti.params.table;
|
||||
notBlank(state.table, "TableData组件params.table信息不能为空")
|
||||
|
||||
const columns = await state.ti.getNowDbInst().loadColumns(state.ti.db, state.table);
|
||||
state.columns = columns;
|
||||
@@ -239,23 +229,6 @@ const exportData = () => {
|
||||
};
|
||||
|
||||
|
||||
const getColumnTip = (columnName: string) => {
|
||||
// 优先从 table map中获取
|
||||
let columns = getColumns();
|
||||
if (!columns) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const column = columns.find((c: any) => c.columnName == columnName);
|
||||
const comment = column.columnComment;
|
||||
return `${column.columnType} ${comment ? ' | ' + comment : ''}`;
|
||||
};
|
||||
|
||||
const getColumns = () => {
|
||||
return state.ti.getNowDb().getColumns(state.table);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 条件查询,点击列信息后显示输入对应的值
|
||||
*/
|
||||
@@ -319,6 +292,11 @@ const onDataSelectionChange = (datas: []) => {
|
||||
state.selectionDatas = datas;
|
||||
};
|
||||
|
||||
const changeUpdatedField = (updatedFields: []) => {
|
||||
// 如果存在要更新字段,则显示提交和取消按钮
|
||||
state.hasUpdatedFileds = updatedFields && updatedFields.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行删除数据事件
|
||||
*/
|
||||
@@ -336,151 +314,12 @@ const onGenerateInsertSql = async () => {
|
||||
emits('genInsertSql', state.ti.getNowDbInst().genInsertSql(state.ti.db, state.table, state.selectionDatas));
|
||||
};
|
||||
|
||||
|
||||
// 监听单元格点击事件
|
||||
const cellClick = (row: any, column: any, cell: any) => {
|
||||
const property = column.property;
|
||||
// 如果当前操作的表名不存在 或者 当前列的property不存在(如多选框),则不允许修改当前单元格内容
|
||||
if (!property) {
|
||||
return;
|
||||
}
|
||||
let div: HTMLElement = cell.children[0];
|
||||
if (div && div.tagName === 'DIV') {
|
||||
// 转为字符串比较,可能存在数字等
|
||||
let text = (row[property] || row[property] == 0 ? row[property] : '') + '';
|
||||
let input = document.createElement('input');
|
||||
input.setAttribute('value', text);
|
||||
// 将表格width也赋值于输入框,避免输入框长度超过表格长度
|
||||
input.setAttribute('style', 'height:23px;text-align:center;border:none;' + div.getAttribute('style'));
|
||||
cell.replaceChildren(input);
|
||||
input.focus();
|
||||
input.addEventListener('blur', async () => {
|
||||
row[property] = input.value;
|
||||
cell.replaceChildren(div);
|
||||
if (input.value !== text) {
|
||||
let currentUpdatedFields = state.updatedFields
|
||||
const db = state.ti.getNowDb();
|
||||
// 主键
|
||||
const primaryKey = db.getColumn(state.table);
|
||||
const primaryKeyValue = row[primaryKey.columnName];
|
||||
// 更新字段列信息
|
||||
const updateColumn = db.getColumn(state.table, property);
|
||||
const newField = {
|
||||
div, row,
|
||||
fieldName: column.rawColumnKey,
|
||||
fieldType: updateColumn.columnType,
|
||||
oldValue: text,
|
||||
newValue: input.value
|
||||
} as FieldsMeta;
|
||||
|
||||
// 被修改的字段
|
||||
const primaryKeyFields = currentUpdatedFields.filter((meta) => meta.primaryKey === primaryKeyValue)
|
||||
let hasKey = false;
|
||||
if (primaryKeyFields.length <= 0) {
|
||||
primaryKeyFields[0] = {
|
||||
primaryKey: primaryKeyValue,
|
||||
primaryKeyName: primaryKey.columnName,
|
||||
primaryKeyType: primaryKey.columnType,
|
||||
fields: [newField]
|
||||
}
|
||||
} else {
|
||||
hasKey = true
|
||||
let hasField = primaryKeyFields[0].fields.some(a => {
|
||||
if (a.fieldName === newField.fieldName) {
|
||||
a.newValue = newField.newValue
|
||||
}
|
||||
return a.fieldName === newField.fieldName
|
||||
})
|
||||
if (!hasField) {
|
||||
primaryKeyFields[0].fields.push(newField)
|
||||
}
|
||||
}
|
||||
let fields = primaryKeyFields[0].fields
|
||||
|
||||
const fieldsParam = fields.filter((a) => {
|
||||
if (a.fieldName === column.rawColumnKey) {
|
||||
a.newValue = input.value
|
||||
}
|
||||
return a.fieldName === column.rawColumnKey
|
||||
})
|
||||
|
||||
const field = fieldsParam.length > 0 && fieldsParam[0] || {} as FieldsMeta
|
||||
if (field.oldValue === input.value) { // 新值=旧值
|
||||
// 删除数据
|
||||
div.classList.remove('update_field_active')
|
||||
let delIndex: number[] = [];
|
||||
currentUpdatedFields.forEach((a, i) => {
|
||||
if (a.primaryKey === primaryKeyValue) {
|
||||
a.fields = a.fields && a.fields.length > 0 ? a.fields.filter(f => f.fieldName !== column.rawColumnKey) : [];
|
||||
a.fields.length <= 0 && delIndex.push(i)
|
||||
}
|
||||
});
|
||||
delIndex.forEach(i => delete currentUpdatedFields[i])
|
||||
currentUpdatedFields = currentUpdatedFields.filter(a => a)
|
||||
} else {
|
||||
// 新增数据
|
||||
div.classList.add('update_field_active')
|
||||
if (hasKey) {
|
||||
currentUpdatedFields.forEach((value, index, array) => {
|
||||
if (value.primaryKey === primaryKeyValue) {
|
||||
array[index].fields = fields
|
||||
}
|
||||
})
|
||||
} else {
|
||||
currentUpdatedFields.push({
|
||||
primaryKey: primaryKeyValue,
|
||||
primaryKeyName: primaryKey.columnName,
|
||||
primaryKeyType: primaryKey.columnType,
|
||||
fields
|
||||
})
|
||||
}
|
||||
}
|
||||
state.updatedFields = currentUpdatedFields;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const submitUpdateFields = () => {
|
||||
let currentUpdatedFields = state.updatedFields;
|
||||
if (currentUpdatedFields.length <= 0) {
|
||||
return;
|
||||
}
|
||||
const { db } = state.ti;
|
||||
let res = '';
|
||||
let divs: HTMLElement[] = [];
|
||||
currentUpdatedFields.forEach(a => {
|
||||
let sql = `UPDATE ${state.table} SET `;
|
||||
let primaryKey = a.primaryKey;
|
||||
let primaryKeyType = a.primaryKeyType;
|
||||
let primaryKeyName = a.primaryKeyName;
|
||||
a.fields.forEach(f => {
|
||||
sql += ` ${f.fieldName} = ${DbInst.wrapColumnValue(f.fieldType, f.newValue)},`
|
||||
divs.push(f.div)
|
||||
})
|
||||
sql = sql.substring(0, sql.length - 1)
|
||||
sql += ` WHERE ${primaryKeyName} = ${DbInst.wrapColumnValue(primaryKeyType, primaryKey)} ;`
|
||||
res += sql;
|
||||
})
|
||||
|
||||
state.ti.getNowDbInst().promptExeSql(db, res, () => { }, () => {
|
||||
currentUpdatedFields = [];
|
||||
divs.forEach(a => {
|
||||
a.classList.remove('update_field_active');
|
||||
})
|
||||
state.updatedFields = [];
|
||||
});
|
||||
|
||||
dbTableRef.value.submitUpdateFields();
|
||||
}
|
||||
|
||||
const cancelUpdateFields = () => {
|
||||
state.updatedFields.forEach((a: any) => {
|
||||
a.fields.forEach((b: any) => {
|
||||
b.div.classList.remove('update_field_active')
|
||||
b.row[b.fieldName] = b.oldValue
|
||||
})
|
||||
})
|
||||
state.updatedFields = [];
|
||||
dbTableRef.value.cancelUpdateFields();
|
||||
}
|
||||
|
||||
// 添加新数据行
|
||||
|
||||
@@ -405,7 +405,7 @@ export class TabInfo {
|
||||
/**
|
||||
* tab需要的其他信息
|
||||
*/
|
||||
other: any
|
||||
params: any
|
||||
|
||||
getNowDbInst() {
|
||||
if (!this.dbType) {
|
||||
|
||||
Reference in New Issue
Block a user