refactor: 数据库表支持editor编辑调整

This commit is contained in:
meilin.huang
2023-12-22 12:29:46 +08:00
parent 54a0f0b3c7
commit 2b419bca11
7 changed files with 252 additions and 70 deletions

View File

@@ -1,7 +1,7 @@
<template>
<div class="monaco-editor" style="border: 1px solid var(--el-border-color-light, #ebeef5); height: 100%">
<div class="monaco-editor-content" ref="monacoTextarea" :style="{ height: height }"></div>
<el-select v-if="canChangeMode" class="code-mode-select" v-model="languageMode" @change="changeLanguage">
<el-select v-if="canChangeMode" class="code-mode-select" v-model="languageMode" @change="changeLanguage" filterable>
<el-option v-for="mode in languageArr" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
</el-select>
</div>
@@ -294,17 +294,19 @@ const registeShell = () => {
};
const format = () => {
/*
触发自动格式化;
*/
// 触发自动格式化;
monacoEditorIns.trigger('', 'editor.action.formatDocument', '');
};
const focus = () => {
monacoEditorIns.focus();
};
const getEditor = () => {
return monacoEditorIns;
};
defineExpose({ getEditor, format });
defineExpose({ getEditor, format, focus });
</script>
<style lang="scss">

View File

@@ -7,8 +7,8 @@ export type MonacoEditorDialogProps = {
language: string;
height?: string;
width?: string;
confirmFn?: Function;
cancelFn?: Function;
confirmFn?: Function; // 点击确认的回调函数入参editor value
cancelFn?: Function; // 点击取消 或 关闭弹窗的回调函数
};
const boxId = 'monaco-editor-dialog-id';

View File

@@ -1,7 +1,7 @@
<template>
<div>
<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" />
<monaco-editor ref="editorRef" :height="state.height" class="editor" :language="state.language" v-model="contentValue" can-change-mode />
<template #footer>
<span class="dialog-footer">
<el-button @click="cancel">取消</el-button>
@@ -120,6 +120,7 @@ const open = (optionProps: MonacoEditorDialogProps) => {
setTimeout(() => {
editorRef.value?.format();
editorRef.value?.focus();
}, 300);
state.dialogVisible = true;

View File

@@ -237,7 +237,7 @@ body,
}
.color-success {
color: var(--el-color-success);
color: var(--el-color-success) !important;
}
.color-warning {

View File

@@ -1,19 +1,22 @@
<template>
<el-input
v-if="dataType == DataType.String"
:ref="(el: any) => focus && el?.focus()"
@blur="emit('blur')"
class="w100 mb4"
input-style="text-align: center; height: 26px;"
size="small"
v-model="itemValue"
:placeholder="placeholder"
/>
<div class="string-input-container w100" v-if="dataType == DataType.String">
<el-input
v-if="dataType == DataType.String"
:ref="(el: any) => focus && el?.focus()"
@blur="handlerBlur"
class="w100 mb4"
input-style="text-align: center; height: 26px;"
size="small"
v-model="itemValue"
:placeholder="placeholder"
/>
<SvgIcon v-if="showEditorIcon" @mousedown="openEditor" class="string-input-container-icon color-success" name="FullScreen" :size="10" />
</div>
<el-input
v-else-if="dataType == DataType.Number"
:ref="(el: any) => focus && el?.focus()"
@blur="emit('blur')"
@blur="handlerBlur"
class="w100 mb4"
input-style="text-align: center; height: 26px;"
size="small"
@@ -26,7 +29,7 @@
v-else-if="dataType == DataType.Date"
:ref="(el: any) => focus && el?.focus()"
@change="emit('blur')"
@blur="emit('blur')"
@blur="handlerBlur"
class="edit-time-picker mb4"
popper-class="edit-time-picker-popper"
size="small"
@@ -41,7 +44,7 @@
v-else-if="dataType == DataType.DateTime"
:ref="(el: any) => focus && el?.focus()"
@change="emit('blur')"
@blur="emit('blur')"
@blur="handlerBlur"
class="edit-time-picker mb4"
popper-class="edit-time-picker-popper"
size="small"
@@ -56,7 +59,7 @@
v-else-if="dataType == DataType.Time"
:ref="(el: any) => focus && el?.focus()"
@change="emit('blur')"
@blur="emit('blur')"
@blur="handlerBlur"
class="edit-time-picker mb4"
popper-class="edit-time-picker-popper"
size="small"
@@ -68,16 +71,19 @@
</template>
<script lang="ts" setup>
import { Ref } from 'vue';
import { Ref, ref, computed } from 'vue';
import { ElInput } from 'element-plus';
import { DataType } from '../../dialect/index';
import { useVModel } from '@vueuse/core';
import SvgIcon from '@/components/svgIcon/index.vue';
import MonacoEditorDialog from '@/components/monaco/MonacoEditorDialog';
export interface ColumnFormItemProps {
modelValue: string | number; // 绑定的值
dataType: DataType; // 数据类型
focus?: boolean; // 是否获取焦点
placeholder?: string;
columnName?: string;
}
const props = withDefaults(defineProps<ColumnFormItemProps>(), {
@@ -88,9 +94,75 @@ const props = withDefaults(defineProps<ColumnFormItemProps>(), {
const emit = defineEmits(['update:modelValue', 'blur']);
const itemValue: Ref<any> = useVModel(props, 'modelValue', emit);
const showEditorIcon = computed(() => {
return typeof itemValue.value === 'string' && itemValue.value.length > 50;
});
const editorOpening = ref(false);
const openEditor = () => {
editorOpening.value = true;
// 编辑器语言json、html、text
let editorLang = getEditorLangByValue(itemValue.value);
MonacoEditorDialog({
content: itemValue.value,
title: `编辑字段 [${props.columnName}]`,
language: editorLang,
confirmFn: (newVal: any) => {
itemValue.value = newVal;
closeEditorDialog();
},
cancelFn: closeEditorDialog,
});
};
const closeEditorDialog = () => {
editorOpening.value = false;
handlerBlur();
};
const handlerBlur = () => {
if (editorOpening.value) {
return;
}
emit('blur');
};
const getEditorLangByValue = (value: any) => {
// 判断是否是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 'text';
};
</script>
<style lang="scss">
.string-input-container {
position: relative;
}
.string-input-container-icon {
position: absolute;
top: 5px; /* 调整图标的垂直位置 */
right: 5px; /* 调整图标的水平位置 */
}
.edit-time-picker {
height: 26px;
width: 100% !important;

View File

@@ -79,8 +79,9 @@
<div v-if="canEdit(rowIndex, columnIndex)">
<ColumnFormItem
v-model="rowData[column.dataKey!]"
:data-type="nowUpdateCell.dataType"
:data-type="column.dataType"
@blur="onExitEditMode(rowData, column, rowIndex)"
:column-name="column.columnName"
focus
/>
</div>
@@ -144,7 +145,6 @@ 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 MonacoEditorDialog from '@/components/monaco/MonacoEditorDialog';
const emits = defineEmits(['dataDelete', 'sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField']);
@@ -315,7 +315,6 @@ const state = reactive({
loading: false,
tableHeight: '600px',
emptyText: '',
editorLang: 'string',
execTime: 0,
contextmenu: {
@@ -488,7 +487,7 @@ const cancelLoading = async () => {
* @param colIndex ci
*/
const canEdit = (rowIndex: number, colIndex: number) => {
return state.table && nowUpdateCell?.rowIndex == rowIndex && nowUpdateCell?.colIndex == colIndex && state.editorLang === 'string';
return state.table && nowUpdateCell?.rowIndex == rowIndex && nowUpdateCell?.colIndex == colIndex;
};
/**
@@ -643,29 +642,6 @@ 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;
@@ -678,21 +654,6 @@ const onEnterEditMode = (rowData: any, column: any, rowIndex = 0, columnIndex =
oldValue: rowData[column.dataKey],
dataType: column.dataType,
};
// 编辑器语言json、html、string目前支持json、html使用MonacoEditor编辑器
let editorLang = getEditorLangByValue(rowData[column.dataKey]);
state.editorLang = editorLang;
if (editorLang === 'html' || editorLang === 'json') {
MonacoEditorDialog({
content: rowData[column.dataKey],
title: `编辑字段 [${column.dataKey}]`,
language: editorLang,
confirmFn: (newVal: any) => {
rowData[column.dataKey] = newVal;
onExitEditMode(rowData, column, rowIndex);
},
});
}
};
const onExitEditMode = (rowData: any, column: any, rowIndex = 0) => {

View File

@@ -63,7 +63,7 @@
@keyup.enter.native="onSelectByCondition"
@select="handlerColumnSelect"
popper-class="my-autocomplete"
placeholder="输入SQL条件表达式后回车或点击查询图标过滤结果, 可根据备注或字段名提示"
placeholder="选择列 或 输入SQL条件表达式后回车或点击查询图标过滤结果, 输入时可根据字段名提示"
@clear="selectData"
size="small"
clearable
@@ -90,6 +90,38 @@
</template>
</span>
</template>
<template #prepend>
<el-popover :visible="state.condPopVisible" trigger="click" :width="320" placement="right">
<template #reference>
<el-button @click.stop="chooseCondColumnName" class="color-success" text size="small">选择列</el-button>
</template>
<el-table
:data="filterCondColumns"
max-height="500"
size="small"
@row-click="
(...event: any) => {
onConditionRowClick(event);
}
"
style="cursor: pointer"
>
<el-table-column property="columnName" label="列名" show-overflow-tooltip>
<template #header>
<el-input
ref="columnNameSearchInputRef"
v-model="state.columnNameSearch"
size="small"
placeholder="输入列名或备注过滤"
@click.stop="(e: any) => e.preventDefault()"
/>
</template>
</el-table-column>
<el-table-column property="columnComment" label="备注" show-overflow-tooltip> </el-table-column>
</el-table>
</el-popover>
</template>
</el-autocomplete>
</el-col>
</el-row>
@@ -126,6 +158,35 @@
<span>{{ state.sql }}</span>
</div>
<el-dialog v-model="conditionDialog.visible" :title="conditionDialog.title" width="420px">
<el-row>
<el-col :span="5">
<el-select v-model="conditionDialog.condition">
<el-option label="=" value="="> </el-option>
<el-option label="LIKE" value="LIKE"> </el-option>
<el-option label=">" value=">"> </el-option>
<el-option label=">=" value=">="> </el-option>
<el-option label="<" value="<"> </el-option>
<el-option label="<=" value="<="> </el-option>
</el-select>
</el-col>
<el-col :span="19">
<el-input
@keyup.enter.native="onConfirmCondition"
ref="condDialogInputRef"
v-model="conditionDialog.value"
:placeholder="conditionDialog.placeholder"
/>
</el-col>
</el-row>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancelCondition">取消</el-button>
<el-button type="primary" @click="onConfirmCondition">确定</el-button>
</span>
</template>
</el-dialog>
<el-dialog v-model="addDataDialog.visible" :title="addDataDialog.title" :destroy-on-close="true" width="600px">
<el-form ref="dataForm" :model="addDataDialog.data" label-width="auto" size="small">
<el-form-item
@@ -140,6 +201,7 @@
v-model="addDataDialog.data[`${column.columnName}`]"
:data-type="dbDialect.getDataType(column.columnType)"
:placeholder="`${column.columnType} ${column.columnComment}`"
:column-name="column.columnName"
/>
</el-form-item>
</el-form>
@@ -154,7 +216,7 @@
</template>
<script lang="ts" setup>
import { onMounted, reactive, Ref, ref, toRefs, watch } from 'vue';
import { computed, onMounted, reactive, Ref, ref, toRefs, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { DbInst } from '@/views/ops/db/db';
@@ -162,6 +224,7 @@ import DbTableData from './DbTableData.vue';
import { DbDialect, getDbDialect } from '@/views/ops/db/dialect';
import SvgIcon from '@/components/svgIcon/index.vue';
import ColumnFormItem from './ColumnFormItem.vue';
import { useEventListener } from '@vueuse/core';
const props = defineProps({
dbId: {
@@ -185,6 +248,8 @@ const props = defineProps({
const dataForm: any = ref(null);
const dbTableRef: Ref = ref(null);
const condInputRef: Ref = ref(null);
const columnNameSearchInputRef: Ref = ref(null);
const condDialogInputRef: Ref = ref(null);
const defaultPageSize = DbInst.DefaultLimit;
@@ -208,6 +273,17 @@ const state = reactive({
],
count: 0,
selectionDatas: [] as any,
condPopVisible: false,
columnNameSearch: '',
conditionDialog: {
title: '',
placeholder: '',
columnRow: null,
dataTab: null,
visible: false,
condition: '=',
value: null,
},
addDataDialog: {
data: {},
title: '',
@@ -219,7 +295,7 @@ const state = reactive({
dbDialect: {} as DbDialect,
});
const { datas, condition, loading, columns, pageNum, pageSize, pageSizes, count, hasUpdatedFileds, addDataDialog, dbDialect } = toRefs(state);
const { datas, condition, loading, columns, pageNum, pageSize, pageSizes, count, hasUpdatedFileds, conditionDialog, addDataDialog, dbDialect } = toRefs(state);
watch(
() => props.tableHeight,
@@ -243,8 +319,15 @@ onMounted(async () => {
await onRefresh();
state.dbDialect = getDbDialect(getNowDbInst().type);
useEventListener('click', handlerWindowClick);
});
const handlerWindowClick = () => {
if (state.condPopVisible) {
state.condPopVisible = false;
}
};
const onRefresh = async () => {
state.pageNum = 1;
await selectData();
@@ -302,7 +385,7 @@ const getColumnTips = (queryString: string, callback: any) => {
if (columnNameSearch) {
columnNameSearch = columnNameSearch.toLowerCase();
res = columns.filter((data: any) => {
return data.columnName.toLowerCase().includes(columnNameSearch) || data.columnComment.includes(columnNameSearch);
return data.columnName.toLowerCase().includes(columnNameSearch);
});
}
@@ -331,6 +414,69 @@ const handlerColumnSelect = (column: any) => {
}
};
/**
* 选择条件列
*/
const chooseCondColumnName = () => {
state.condPopVisible = !state.condPopVisible;
if (state.condPopVisible) {
columnNameSearchInputRef.value.clear();
columnNameSearchInputRef.value.focus();
}
};
/**
* 过滤条件列名
*/
const filterCondColumns = computed(() => {
const columns = state.columns;
let columnNameSearch = state.columnNameSearch;
if (!columnNameSearch) {
return columns;
}
columnNameSearch = columnNameSearch.toLowerCase();
return columns.filter((data: any) => {
return data.columnName.toLowerCase().includes(columnNameSearch) || data.columnComment.toLowerCase().includes(columnNameSearch);
});
});
/**
* 条件查询,点击列信息后显示输入对应的值
*/
const onConditionRowClick = (event: any) => {
const row = event[0];
state.conditionDialog.title = `请输入 [${row.columnName}] 的值`;
state.conditionDialog.placeholder = `${row.columnType} ${row.columnComment}`;
state.conditionDialog.columnRow = row;
state.conditionDialog.visible = true;
setTimeout(() => {
condDialogInputRef.value.focus();
}, 100);
};
// 确认条件
const onConfirmCondition = () => {
const conditionDialog = state.conditionDialog;
let condition = state.condition;
if (condition) {
condition += ` AND `;
}
const row = conditionDialog.columnRow as any;
condition += `${row.columnName} ${conditionDialog.condition} `;
state.condition = condition + DbInst.wrapColumnValue(row.columnType, conditionDialog.value);
onCancelCondition();
condInputRef.value.focus();
};
const onCancelCondition = () => {
state.conditionDialog.visible = false;
state.conditionDialog.title = ``;
state.conditionDialog.placeholder = ``;
state.conditionDialog.value = null;
state.conditionDialog.columnRow = null;
state.conditionDialog.dataTab = null;
};
/**
* 提交事务,用于没有开启自动提交事务
*/