mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-04 00:10:25 +08:00
refactor: 数据库虚拟table卡顿优化
This commit is contained in:
@@ -185,7 +185,7 @@ defineExpose({
|
||||
position: fixed;
|
||||
|
||||
.el-dropdown-menu__item {
|
||||
padding: 5px 10px;
|
||||
padding: 5px 12px;
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item {
|
||||
|
||||
37
mayfly_go_web/src/components/monaco/MonacoEditorDialog.ts
Normal file
37
mayfly_go_web/src/components/monaco/MonacoEditorDialog.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { VNode, h, render } from 'vue';
|
||||
import MonacoEditorDialogComp from './MonacoEditorDialogComp.vue';
|
||||
|
||||
export type MonacoEditorDialogProps = {
|
||||
content: string;
|
||||
title: string;
|
||||
language: string;
|
||||
height?: string;
|
||||
width?: string;
|
||||
confirmFn?: Function;
|
||||
cancelFn?: Function;
|
||||
};
|
||||
|
||||
const boxId = 'monaco-editor-dialog-id';
|
||||
|
||||
let boxInstance: VNode;
|
||||
|
||||
const MonacoEditorDialog = (props: MonacoEditorDialogProps): void => {
|
||||
if (!boxInstance) {
|
||||
const container = document.createElement('div');
|
||||
container.id = boxId;
|
||||
// 创建 虚拟dom
|
||||
boxInstance = h(MonacoEditorDialogComp);
|
||||
// 将虚拟dom渲染到 container dom 上
|
||||
render(boxInstance, container);
|
||||
// 最后将 container 追加到 body 上
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
const boxVue = boxInstance.component;
|
||||
if (boxVue) {
|
||||
// 调用open方法显示弹框,注意不能使用boxVue.ctx来调用组件函数(build打包后ctx会获取不到)
|
||||
boxVue.exposed?.open(props);
|
||||
}
|
||||
};
|
||||
|
||||
export default MonacoEditorDialog;
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog :destroy-on-close="true" :title="state.title" v-model="dialogVisible" :show-close="false" width="800px" @close="cancel">
|
||||
<monaco-editor height="600px" class="codesql" :language="state.language" v-model="contentValue" />
|
||||
<el-dialog :title="state.title" v-model="state.dialogVisible" :width="state.width" @close="cancel">
|
||||
<monaco-editor ref="editorRef" :height="state.height" class="editor" :language="state.language" v-model="contentValue" />
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="cancel">取消</el-button>
|
||||
<el-button @click="submit" type="primary">确定</el-button>
|
||||
<el-button @click="confirm" type="primary">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -13,26 +13,27 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, ref, nextTick, reactive } from 'vue';
|
||||
import { ElDialog, ElButton, InputInstance, ElMessage } from 'element-plus';
|
||||
import { toRefs, ref, reactive } from 'vue';
|
||||
import { ElDialog, ElButton, ElMessage } from 'element-plus';
|
||||
// import base style
|
||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
|
||||
import { MonacoEditorDialogProps } from './MonacoEditorDialog';
|
||||
|
||||
import { TableDataEditorProps } from './DbTableDataEditorDialog';
|
||||
const editorRef: any = ref(null);
|
||||
|
||||
const remarkInputRef = ref<InputInstance>();
|
||||
const state = reactive({
|
||||
dialogVisible: false,
|
||||
height: '450px',
|
||||
width: '800px',
|
||||
contentValue: '',
|
||||
title: '',
|
||||
language: '',
|
||||
});
|
||||
|
||||
const { dialogVisible, contentValue } = toRefs(state);
|
||||
let confirmFn: any;
|
||||
let cancelFn: any;
|
||||
|
||||
let cancelCallback: any;
|
||||
let runSuccessCallback: any;
|
||||
let runSuccess: boolean = false;
|
||||
const { contentValue } = toRefs(state);
|
||||
|
||||
function compressHTML(html: string) {
|
||||
return (
|
||||
@@ -45,11 +46,10 @@ function compressHTML(html: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行sql
|
||||
* 确认按钮
|
||||
*/
|
||||
const submit = async () => {
|
||||
runSuccess = true;
|
||||
if (runSuccessCallback) {
|
||||
const confirm = async () => {
|
||||
if (confirmFn) {
|
||||
if (state.language === 'json') {
|
||||
let val;
|
||||
try {
|
||||
@@ -64,34 +64,28 @@ const submit = async () => {
|
||||
}
|
||||
|
||||
// 压缩json字符串
|
||||
runSuccessCallback(JSON.stringify(val));
|
||||
confirmFn(JSON.stringify(val));
|
||||
} else if (state.language === 'html') {
|
||||
// 压缩html字符串
|
||||
runSuccessCallback(compressHTML(contentValue.value));
|
||||
confirmFn(compressHTML(contentValue.value));
|
||||
} else {
|
||||
runSuccessCallback(contentValue.value);
|
||||
confirmFn(contentValue.value);
|
||||
}
|
||||
}
|
||||
state.dialogVisible = false;
|
||||
setTimeout(() => {
|
||||
state.contentValue = '';
|
||||
state.title = '';
|
||||
runSuccessCallback = null;
|
||||
runSuccess = false;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
state.dialogVisible = false;
|
||||
// 没有执行成功,并且取消回调函数存在,则执行
|
||||
if (!runSuccess && cancelCallback) {
|
||||
cancelCallback();
|
||||
}
|
||||
cancelFn && cancelFn();
|
||||
setTimeout(() => {
|
||||
state.contentValue = '';
|
||||
state.title = '';
|
||||
cancelCallback = null;
|
||||
runSuccess = false;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
@@ -107,35 +101,34 @@ const formatXML = function (xml: string, tab?: string) {
|
||||
return formatted.substring(1, formatted.length - 3);
|
||||
};
|
||||
|
||||
const open = (props: TableDataEditorProps) => {
|
||||
cancelCallback = props.cancelCallback;
|
||||
runSuccessCallback = props.runSuccessCallback;
|
||||
// 格式化输出json
|
||||
if (props.language === 'json') {
|
||||
try {
|
||||
state.contentValue = JSON.stringify(JSON.parse(props.content), null, '\t');
|
||||
} catch (e) {
|
||||
state.contentValue = 'json格式字符串错误: ' + props.content;
|
||||
const open = (optionProps: MonacoEditorDialogProps) => {
|
||||
confirmFn = optionProps.confirmFn;
|
||||
cancelFn = optionProps.cancelFn;
|
||||
|
||||
const language = optionProps.language;
|
||||
state.language = language;
|
||||
state.title = optionProps.title;
|
||||
if (optionProps.height) {
|
||||
state.height = optionProps.height;
|
||||
}
|
||||
|
||||
state.contentValue = optionProps.content;
|
||||
// 格式化输出html;
|
||||
if (language === 'html' || language == 'xml') {
|
||||
state.contentValue = formatXML(optionProps.content);
|
||||
}
|
||||
// 格式化输出html
|
||||
if (props.language === 'html') {
|
||||
state.contentValue = formatXML(props.content);
|
||||
}
|
||||
state.title = props.title;
|
||||
state.language = props.language;
|
||||
state.dialogVisible = true;
|
||||
nextTick(() => {
|
||||
|
||||
setTimeout(() => {
|
||||
remarkInputRef.value?.focus();
|
||||
});
|
||||
});
|
||||
editorRef.value?.format();
|
||||
}, 300);
|
||||
|
||||
state.dialogVisible = true;
|
||||
};
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.codesql {
|
||||
<style lang="scss" scoped>
|
||||
.editor {
|
||||
font-size: 9pt;
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<slot v-else :node="node" :data="data" name="prefix"></slot>
|
||||
|
||||
<span class="ml3">
|
||||
<span class="ml3" :title="data.labelRemark">
|
||||
<slot name="label" :data="data"> {{ data.label }}</slot>
|
||||
</span>
|
||||
|
||||
|
||||
@@ -13,6 +13,11 @@ export class TagTreeNode {
|
||||
*/
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* 节点名称备注(用于元素title属性)
|
||||
*/
|
||||
labelRemark: string;
|
||||
|
||||
/**
|
||||
* 树节点类型
|
||||
*/
|
||||
@@ -38,6 +43,11 @@ export class TagTreeNode {
|
||||
this.type = type || new NodeType(TagTreeNode.TagPath);
|
||||
}
|
||||
|
||||
withLabelRemark(labelRemark: any) {
|
||||
this.labelRemark = labelRemark;
|
||||
return this;
|
||||
}
|
||||
|
||||
withIsLeaf(isLeaf: boolean) {
|
||||
this.isLeaf = isLeaf;
|
||||
return this;
|
||||
|
||||
@@ -41,12 +41,6 @@
|
||||
<SvgIcon v-if="data.icon" :name="data.icon.name" :color="data.icon.color" />
|
||||
</template>
|
||||
|
||||
<template #label="{ data }">
|
||||
<el-tooltip placement="left" :show-after="1000" v-if="data.type.value == SqlExecNodeType.Table" :content="data.params.tableComment">
|
||||
{{ data.label }}
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<template #suffix="{ data }">
|
||||
<span class="db-table-size" v-if="data.type.value == SqlExecNodeType.Table && data.params.size">{{ ` ${data.params.size}` }}</span>
|
||||
<span class="db-table-size" v-if="data.type.value == SqlExecNodeType.TableMenu && data.params.dbTableSize">{{
|
||||
@@ -323,7 +317,8 @@ const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
|
||||
tableComment: x.tableComment,
|
||||
size: formatByteSize(x.dataLength + x.indexLength, 1),
|
||||
})
|
||||
.withIcon(TableIcon);
|
||||
.withIcon(TableIcon)
|
||||
.withLabelRemark(`${x.tableName} ${x.tableComment ? '| ' + x.tableComment : ''}`);
|
||||
});
|
||||
// 设置父节点参数的表大小
|
||||
parentNode.params.dbTableSize = formatByteSize(dbTableSize);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { h, render, VNode } from 'vue';
|
||||
import SqlExecDialog from './SqlExecDialog.vue';
|
||||
import {SqlLanguage} from 'sql-formatter/lib/src/sqlFormatter'
|
||||
import { SqlLanguage } from 'sql-formatter/lib/src/sqlFormatter';
|
||||
|
||||
export type SqlExecProps = {
|
||||
sql: string;
|
||||
@@ -11,35 +11,26 @@ export type SqlExecProps = {
|
||||
cancelCallback?: Function;
|
||||
};
|
||||
|
||||
const boxId = 'sql-exec-id';
|
||||
const boxId = 'sql-exec-dialog-id';
|
||||
|
||||
const renderBox = (): VNode => {
|
||||
const props: SqlExecProps = {
|
||||
sql: '',
|
||||
dbId: 0,
|
||||
} as any;
|
||||
let boxInstance: VNode;
|
||||
|
||||
const SqlExecBox = (props: SqlExecProps): void => {
|
||||
if (!boxInstance) {
|
||||
const container = document.createElement('div');
|
||||
container.id = boxId;
|
||||
// 创建 虚拟dom
|
||||
const boxVNode = h(SqlExecDialog, props);
|
||||
boxInstance = h(SqlExecDialog);
|
||||
// 将虚拟dom渲染到 container dom 上
|
||||
render(boxVNode, container);
|
||||
render(boxInstance, container);
|
||||
// 最后将 container 追加到 body 上
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
return boxVNode;
|
||||
};
|
||||
|
||||
let boxInstance: any;
|
||||
|
||||
const SqlExecBox = (props: SqlExecProps): void => {
|
||||
if (boxInstance) {
|
||||
const boxVue = boxInstance.component;
|
||||
if (boxVue) {
|
||||
// 调用open方法显示弹框,注意不能使用boxVue.ctx来调用组件函数(build打包后ctx会获取不到)
|
||||
boxVue.exposed.open(props);
|
||||
} else {
|
||||
boxInstance = renderBox();
|
||||
SqlExecBox(props);
|
||||
boxVue.exposed?.open(props);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog :destroy-on-close="true" title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px" @close="cancel">
|
||||
<el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px" @close="cancel">
|
||||
<monaco-editor height="300px" class="codesql" language="sql" v-model="sqlValue" />
|
||||
<el-input @keyup.enter="runSql" ref="remarkInputRef" v-model="remark" placeholder="请输入执行备注" class="mt5" />
|
||||
<template #footer>
|
||||
|
||||
@@ -32,22 +32,19 @@
|
||||
</div>
|
||||
|
||||
<!-- 字段名列 -->
|
||||
<div v-else style="position: relative">
|
||||
<div v-else @contextmenu="headerContextmenuClick($event, column)" style="position: relative">
|
||||
<!-- 字段列的数据类型 -->
|
||||
<div class="column-type">
|
||||
<span v-if="ColumnTypeSubscript[dbDialect.getDataType(column.columnType)] === 'icon-clock'">
|
||||
<span v-if="column.dataTypeSubscript === 'icon-clock'">
|
||||
<SvgIcon :size="10" name="Clock" style="cursor: unset" />
|
||||
</span>
|
||||
<span class="font8" v-else>{{ ColumnTypeSubscript[dbDialect.getDataType(column.columnType)] }}</span>
|
||||
<span class="font8" v-else>{{ column.dataTypeSubscript }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="showColumnTip" @mouseover="column.showSetting = true" @mouseleave="column.showSetting = false">
|
||||
<el-tooltip :show-after="500" raw-content placement="top">
|
||||
<template #content> {{ getColumnTip(column) }} </template>
|
||||
<el-text tag="b" style="cursor: pointer">
|
||||
<div v-if="showColumnTip">
|
||||
<el-text tag="b" :title="column.remark" style="cursor: pointer">
|
||||
{{ column.title }}
|
||||
</el-text>
|
||||
</el-tooltip>
|
||||
|
||||
<span>
|
||||
<SvgIcon
|
||||
@@ -63,20 +60,6 @@
|
||||
{{ column.title }}
|
||||
</el-text>
|
||||
</div>
|
||||
|
||||
<el-dropdown trigger="click" class="column-header-op" size="small">
|
||||
<SvgIcon :size="16" name="CaretBottom" />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<template v-for="menu in tableHeadlerMenu">
|
||||
<el-dropdown-item :key="menu.clickId" v-if="!menu.isHide(column)" @click="menu?.onClickFunc(column)">
|
||||
<SvgIcon v-if="menu.icon" :name="menu.icon" />
|
||||
{{ menu.txt }}
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -161,7 +144,7 @@ import { dateStrFormat } from '@/common/utils/date';
|
||||
import { useIntervalFn } from '@vueuse/core';
|
||||
import { ColumnTypeSubscript, DataType, DbDialect, DbType, getDbDialect } from '../../dialect/index';
|
||||
import ColumnFormItem from './ColumnFormItem.vue';
|
||||
import TableDataEditorBox from '@/views/ops/db/component/table/DbTableDataEditorDialog';
|
||||
import MonacoEditorDialog from '@/components/monaco/MonacoEditorDialog';
|
||||
|
||||
const emits = defineEmits(['dataDelete', 'sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField']);
|
||||
|
||||
@@ -236,9 +219,6 @@ const cmHeaderCancenFixed = new ContextmenuItem('cancelFixed', '取消固定')
|
||||
.withOnClick((data: any) => (data.fixed = false))
|
||||
.withHideFunc((data: any) => !data.fixed);
|
||||
|
||||
// 标头菜单
|
||||
const tableHeadlerMenu = [cmHeaderAsc, cmHeaderDesc, cmHeaderFixed, cmHeaderCancenFixed];
|
||||
|
||||
/** 表数据 contextmenu items **/
|
||||
|
||||
const cmDataCopyCell = new ContextmenuItem('copyValue', '复制')
|
||||
@@ -465,6 +445,11 @@ const setTableData = (datas: any) => {
|
||||
const setTableColumns = (columns: any) => {
|
||||
state.columns = columns.map((x: any) => {
|
||||
const columnName = x.columnName;
|
||||
// 数据类型
|
||||
x.dataType = dbDialect.getDataType(x.columnType);
|
||||
x.dataTypeSubscript = ColumnTypeSubscript[x.dataType];
|
||||
x.remark = `${x.columnType} ${x.columnComment ? ' | ' + x.columnComment : ''}`;
|
||||
|
||||
return {
|
||||
...x,
|
||||
key: columnName,
|
||||
@@ -561,6 +546,16 @@ const rowEventHandlers = {
|
||||
},
|
||||
};
|
||||
|
||||
const headerContextmenuClick = (event: any, data: any) => {
|
||||
event.preventDefault(); // 阻止默认的右击菜单行为
|
||||
|
||||
const { clientX, clientY } = event;
|
||||
state.contextmenu.dropdown.x = clientX;
|
||||
state.contextmenu.dropdown.y = clientY;
|
||||
state.contextmenu.items = [cmHeaderAsc, cmHeaderDesc, cmHeaderFixed, cmHeaderCancenFixed];
|
||||
contextmenuRef.value.openContextmenu(data);
|
||||
};
|
||||
|
||||
const dataContextmenuClick = (event: any, rowIndex: number, column: any, data: any) => {
|
||||
event.preventDefault(); // 阻止默认的右击菜单行为
|
||||
|
||||
@@ -681,18 +676,18 @@ const onEnterEditMode = (rowData: any, column: any, rowIndex = 0, columnIndex =
|
||||
rowIndex: rowIndex,
|
||||
colIndex: columnIndex,
|
||||
oldValue: rowData[column.dataKey],
|
||||
dataType: dbDialect.getDataType(column.columnType),
|
||||
dataType: column.dataType,
|
||||
};
|
||||
|
||||
// 编辑器语言,如:json、html、string,目前支持json、html使用MonacoEditor编辑器
|
||||
let editorLang = getEditorLangByValue(rowData[column.dataKey]);
|
||||
state.editorLang = editorLang;
|
||||
if (editorLang === 'html' || editorLang === 'json') {
|
||||
TableDataEditorBox({
|
||||
MonacoEditorDialog({
|
||||
content: rowData[column.dataKey],
|
||||
title: '编辑字段' + column.dataKey,
|
||||
title: `编辑字段 [${column.dataKey}]`,
|
||||
language: editorLang,
|
||||
runSuccessCallback: (newVal: any) => {
|
||||
confirmFn: (newVal: any) => {
|
||||
rowData[column.dataKey] = newVal;
|
||||
onExitEditMode(rowData, column, rowIndex);
|
||||
},
|
||||
@@ -835,11 +830,6 @@ const getFormatTimeValue = (dataType: DataType, originValue: string): string =>
|
||||
}
|
||||
};
|
||||
|
||||
const getColumnTip = (column: any) => {
|
||||
const comment = column.columnComment;
|
||||
return `${column.columnType} ${comment ? ' | ' + comment : ''}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 触发响应式实时刷新,否则需要滑动或移动才能使样式实时生效
|
||||
*/
|
||||
@@ -902,12 +892,5 @@ defineExpose({
|
||||
padding: 2px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.column-header-op {
|
||||
color: var(--el-color-primary);
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import { h, render, VNode } from 'vue';
|
||||
import SqlExecDialog from './DbTableDataEditorDialog.vue';
|
||||
|
||||
export type TableDataEditorProps = {
|
||||
content: string;
|
||||
title: string;
|
||||
language: string;
|
||||
runSuccessCallback?: Function;
|
||||
cancelCallback?: Function;
|
||||
};
|
||||
|
||||
const boxId = 'table-data-editor-id';
|
||||
|
||||
const renderBox = (): VNode => {
|
||||
const props: TableDataEditorProps = {
|
||||
content: '',
|
||||
title: '',
|
||||
language: '',
|
||||
};
|
||||
const container = document.createElement('div');
|
||||
container.id = boxId;
|
||||
// 创建 虚拟dom
|
||||
const boxVNode = h(SqlExecDialog, props);
|
||||
// 将虚拟dom渲染到 container dom 上
|
||||
render(boxVNode, container);
|
||||
// 最后将 container 追加到 body 上
|
||||
document.body.appendChild(container);
|
||||
|
||||
return boxVNode;
|
||||
};
|
||||
|
||||
let boxInstance: any;
|
||||
|
||||
const TableDataEditorBox = (props: TableDataEditorProps): void => {
|
||||
if (boxInstance) {
|
||||
const boxVue = boxInstance.component;
|
||||
// 调用open方法显示弹框,注意不能使用boxVue.ctx来调用组件函数(build打包后ctx会获取不到)
|
||||
boxVue.exposed.open(props);
|
||||
} else {
|
||||
boxInstance = renderBox();
|
||||
TableDataEditorBox(props);
|
||||
}
|
||||
};
|
||||
|
||||
export default TableDataEditorBox;
|
||||
@@ -38,9 +38,10 @@
|
||||
|
||||
<el-tooltip :show-after="500" class="box-item" effect="dark" content="commit" placement="top">
|
||||
<template #content>
|
||||
1. 右击数据可显示操作菜单 <br />
|
||||
1. 右击数据/表头可显示操作菜单 <br />
|
||||
2. 按住Ctrl点击数据则为多选 <br />
|
||||
3. 双击单元格可编辑数据
|
||||
3. 双击单元格可编辑数据 <br />
|
||||
4. 鼠标悬停字段名或标签树的表名可提示相关备注
|
||||
</template>
|
||||
<el-link icon="QuestionFilled" :underline="false"> </el-link>
|
||||
</el-tooltip>
|
||||
|
||||
Reference in New Issue
Block a user