mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 07:20:24 +08:00
!78 feat: 表格支持编辑json、xml数据
Merge pull request !78 from zongyangleo/dev_1221
This commit is contained in:
@@ -263,10 +263,20 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((p
|
||||
const NodeTypeDb = new NodeType(SqlExecNodeType.Db)
|
||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
const params = parentNode.params;
|
||||
// pg类数据库会多一层schema
|
||||
if (params.type == 'postgres' || params.type === 'dm') {
|
||||
return [new TagTreeNode(`${params.id}.${params.db}.schema-menu`, 'schema', NodeTypePostgresScheamMenu).withParams(params).withIcon(SchemaIcon)];
|
||||
const params = parentNode.params;
|
||||
const { id, db } = params;
|
||||
const schemaNames = await dbApi.pgSchemas.request({ id, db });
|
||||
return schemaNames.map((sn: any) => {
|
||||
// 将db变更为 db/schema;
|
||||
const nParams = { ...params };
|
||||
nParams.schema = sn;
|
||||
nParams.db = nParams.db + '/' + sn;
|
||||
nParams.dbs = schemaNames;
|
||||
return new TagTreeNode(`${params.id}.${params.db}.schema.${sn}`, sn, NodeTypePostgresScheam).withParams(nParams).withIcon(SchemaIcon);
|
||||
});
|
||||
}
|
||||
|
||||
return [
|
||||
new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu).withParams(params).withIcon(TableIcon),
|
||||
new TagTreeNode(getSqlMenuNodeKey(params.id, params.db), 'SQL', NodeTypeSqlMenu).withParams(params).withIcon(SqlIcon),
|
||||
@@ -274,23 +284,6 @@ const NodeTypeDb = new NodeType(SqlExecNodeType.Db)
|
||||
})
|
||||
.withNodeClickFunc(nodeClickChangeDb);
|
||||
|
||||
// postgres schema模式菜单
|
||||
const NodeTypePostgresScheamMenu = new NodeType(SqlExecNodeType.PgSchemaMenu)
|
||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
const params = parentNode.params;
|
||||
const { id, db } = params;
|
||||
const schemaNames = await dbApi.pgSchemas.request({ id, db });
|
||||
return schemaNames.map((sn: any) => {
|
||||
// 将db变更为 db/schema;
|
||||
const nParams = { ...params };
|
||||
nParams.schema = sn;
|
||||
nParams.db = nParams.db + '/' + sn;
|
||||
nParams.dbs = schemaNames;
|
||||
return new TagTreeNode(`${params.id}.${params.db}.schema.${sn}`, sn, NodeTypePostgresScheam).withParams(nParams).withIcon(SchemaIcon);
|
||||
});
|
||||
})
|
||||
.withNodeClickFunc(nodeClickChangeDb);
|
||||
|
||||
// postgres schema模式
|
||||
const NodeTypePostgresScheam = new NodeType(SqlExecNodeType.PgSchema)
|
||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
/>
|
||||
|
||||
<el-input
|
||||
v-if="dataType == DataType.Number"
|
||||
v-else-if="dataType == DataType.Number"
|
||||
:ref="(el: any) => focus && el?.focus()"
|
||||
@blur="emit('blur')"
|
||||
class="w100 mb4"
|
||||
@@ -23,7 +23,7 @@
|
||||
/>
|
||||
|
||||
<el-date-picker
|
||||
v-if="dataType == DataType.Date"
|
||||
v-else-if="dataType == DataType.Date"
|
||||
:ref="(el: any) => focus && el?.focus()"
|
||||
@change="emit('blur')"
|
||||
@blur="emit('blur')"
|
||||
@@ -38,7 +38,7 @@
|
||||
/>
|
||||
|
||||
<el-date-picker
|
||||
v-if="dataType == DataType.DateTime"
|
||||
v-else-if="dataType == DataType.DateTime"
|
||||
:ref="(el: any) => focus && el?.focus()"
|
||||
@change="emit('blur')"
|
||||
@blur="emit('blur')"
|
||||
@@ -53,7 +53,7 @@
|
||||
/>
|
||||
|
||||
<el-time-picker
|
||||
v-if="dataType == DataType.Time"
|
||||
v-else-if="dataType == DataType.Time"
|
||||
:ref="(el: any) => focus && el?.focus()"
|
||||
@change="emit('blur')"
|
||||
@blur="emit('blur')"
|
||||
|
||||
@@ -161,6 +161,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';
|
||||
|
||||
const emits = defineEmits(['dataDelete', 'sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField']);
|
||||
|
||||
@@ -334,6 +335,7 @@ const state = reactive({
|
||||
loading: false,
|
||||
tableHeight: '600px',
|
||||
emptyText: '',
|
||||
editorLang: 'string',
|
||||
|
||||
execTime: 0,
|
||||
contextmenu: {
|
||||
@@ -501,7 +503,7 @@ const cancelLoading = async () => {
|
||||
* @param colIndex ci
|
||||
*/
|
||||
const canEdit = (rowIndex: number, colIndex: number) => {
|
||||
return state.table && nowUpdateCell && nowUpdateCell.rowIndex == rowIndex && nowUpdateCell.colIndex == colIndex;
|
||||
return state.table && nowUpdateCell?.rowIndex == rowIndex && nowUpdateCell?.colIndex == colIndex && state.editorLang === 'string';
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -646,6 +648,29 @@ const onExportSql = async () => {
|
||||
);
|
||||
};
|
||||
|
||||
const getEditorLangByValue = (value: any) => {
|
||||
if (typeof value === 'string') {
|
||||
// 判断是否是json
|
||||
try {
|
||||
if (typeof JSON.parse(value) === 'object') {
|
||||
return 'json';
|
||||
}
|
||||
} catch (e) {
|
||||
/* empty */
|
||||
}
|
||||
// 判断是否是html
|
||||
try {
|
||||
const doc = new DOMParser().parseFromString(value, 'text/html');
|
||||
if (Array.from(doc.body.childNodes).some((node) => node.nodeType === 1)) {
|
||||
return 'html';
|
||||
}
|
||||
} catch (e) {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
return 'string';
|
||||
};
|
||||
|
||||
const onEnterEditMode = (rowData: any, column: any, rowIndex = 0, columnIndex = 0) => {
|
||||
if (!state.table) {
|
||||
return;
|
||||
@@ -658,10 +683,24 @@ const onEnterEditMode = (rowData: any, column: any, rowIndex = 0, columnIndex =
|
||||
oldValue: rowData[column.dataKey],
|
||||
dataType: dbDialect.getDataType(column.columnType),
|
||||
};
|
||||
|
||||
// 编辑器语言,如:json、html、string,目前支持json、html使用MonacoEditor编辑器
|
||||
let editorLang = getEditorLangByValue(rowData[column.dataKey]);
|
||||
state.editorLang = editorLang;
|
||||
if (editorLang === 'html' || editorLang === 'json') {
|
||||
TableDataEditorBox({
|
||||
content: rowData[column.dataKey],
|
||||
title: '编辑字段' + column.dataKey,
|
||||
language: editorLang,
|
||||
runSuccessCallback: (newVal: any) => {
|
||||
rowData[column.dataKey] = newVal;
|
||||
onExitEditMode(rowData, column, rowIndex);
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onExitEditMode = (rowData: any, column: any, rowIndex = 0) => {
|
||||
console.trace('exit');
|
||||
const oldValue = nowUpdateCell.oldValue;
|
||||
const newValue = rowData[column.dataKey];
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
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;
|
||||
@@ -0,0 +1,142 @@
|
||||
<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" />
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="cancel">取消</el-button>
|
||||
<el-button @click="submit" type="primary">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, ref, nextTick, reactive } from 'vue';
|
||||
import { ElDialog, ElButton, InputInstance, ElMessage } from 'element-plus';
|
||||
// import base style
|
||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
|
||||
|
||||
import { TableDataEditorProps } from './DbTableDataEditorDialog';
|
||||
|
||||
const remarkInputRef = ref<InputInstance>();
|
||||
const state = reactive({
|
||||
dialogVisible: false,
|
||||
contentValue: '',
|
||||
title: '',
|
||||
language: '',
|
||||
});
|
||||
|
||||
const { dialogVisible, contentValue } = toRefs(state);
|
||||
|
||||
let cancelCallback: any;
|
||||
let runSuccessCallback: any;
|
||||
let runSuccess: boolean = false;
|
||||
|
||||
function compressHTML(html: string) {
|
||||
return (
|
||||
html
|
||||
.replace(/[\r\n\t]+/g, ' ') // 移除换行符和制表符
|
||||
// .replace(/<!--[\s\S]*?-->/g, '') // 移除注释
|
||||
.replace(/\s{2,}/g, ' ') // 合并多个空格为一个空格
|
||||
.replace(/>\s+</g, '><')
|
||||
); // 移除标签之间的空格
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行sql
|
||||
*/
|
||||
const submit = async () => {
|
||||
runSuccess = true;
|
||||
if (runSuccessCallback) {
|
||||
if (state.language === 'json') {
|
||||
let val;
|
||||
try {
|
||||
val = JSON.parse(contentValue.value);
|
||||
if (typeof val !== 'object') {
|
||||
ElMessage.error('请输入正确的json');
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('请输入正确的json');
|
||||
return;
|
||||
}
|
||||
|
||||
// 压缩json字符串
|
||||
runSuccessCallback(JSON.stringify(val));
|
||||
} else if (state.language === 'html') {
|
||||
// 压缩html字符串
|
||||
runSuccessCallback(compressHTML(contentValue.value));
|
||||
} else {
|
||||
runSuccessCallback(contentValue.value);
|
||||
}
|
||||
}
|
||||
state.dialogVisible = false;
|
||||
setTimeout(() => {
|
||||
state.contentValue = '';
|
||||
state.title = '';
|
||||
runSuccessCallback = null;
|
||||
runSuccess = false;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
state.dialogVisible = false;
|
||||
// 没有执行成功,并且取消回调函数存在,则执行
|
||||
if (!runSuccess && cancelCallback) {
|
||||
cancelCallback();
|
||||
}
|
||||
setTimeout(() => {
|
||||
state.contentValue = '';
|
||||
state.title = '';
|
||||
cancelCallback = null;
|
||||
runSuccess = false;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const formatXML = function (xml: string, tab?: string) {
|
||||
let formatted = '',
|
||||
indent = '';
|
||||
tab = tab || ' ';
|
||||
xml.split(/>\s*</).forEach(function (node) {
|
||||
if (node.match(/^\/\w/)) indent = indent.substring(tab!.length);
|
||||
formatted += indent + '<' + node + '>\r\n';
|
||||
if (node.match(/^<?\w[^>]*[^\/]$/)) indent += tab;
|
||||
});
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 格式化输出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();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.codesql {
|
||||
font-size: 9pt;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user