feat(editor,form,schema): 优化代码

Squash merge branch 'feature/parisma_881986193' into 'master'
1、扩展参数配置能力,支持参数类型定义,支持参数注释
2、修复代码块嵌套多层时绑定关系展示不正确的问题
3、支持在组件绑定位置编辑查看代码块
This commit is contained in:
parisma 2023-02-09 14:19:18 +08:00 committed by roymondchen
parent e42aee84bc
commit 0b537f5bff
11 changed files with 125 additions and 109 deletions

View File

@ -12,7 +12,7 @@
<TMagicButton type="primary" class="button" @click="toggleFullScreen">
{{ isFullScreen ? '退出全屏' : '全屏' }}</TMagicButton
>
<TMagicButton type="primary" class="button" @click="saveCode">保存</TMagicButton>
<TMagicButton type="primary" class="button" @click="saveAndClose">确认</TMagicButton>
<TMagicButton type="primary" class="button" @click="close">关闭</TMagicButton>
</div>
<div class="m-editor-content-bottom" v-else>
@ -54,7 +54,7 @@ const props = withDefaults(
autoSaveDraft: true,
},
);
const emit = defineEmits(['save', 'close', 'saveAndClose']);
const emit = defineEmits(['close', 'saveAndClose']);
const services = inject<Services>('services');
@ -95,14 +95,6 @@ const saveCodeDraft = async (codeValue: string) => {
tMagicMessage.success(`代码草稿保存成功 ${datetimeFormatter(new Date())}`);
};
//
const saveCode = (): void => {
if (!codeEditor.value || !props.editable) return;
//
editorContent.value = (codeEditor.value.getEditor() as monaco.editor.IStandaloneCodeEditor)?.getValue();
emit('save', editorContent.value);
};
//
const saveAndClose = (): void => {
if (!codeEditor.value || !props.editable) return;

View File

@ -6,9 +6,9 @@
<TMagicInput class="code-name-input" v-model="codeName" :disabled="!editable" />
</div>
<div class="code-name-wrapper">
<div class="code-name-label">参数定义</div>
<div class="code-name-label">参数</div>
<m-form-table
style="width: 320px"
style="width: 800px"
:config="tableConfig"
:model="tableModel"
:enableToggleMode="false"
@ -26,7 +26,6 @@
:autoSaveDraft="autoSaveDraft"
:codeOptions="codeOptions"
language="javascript"
@save="saveCode"
@saveAndClose="saveAndClose"
@close="close"
></CodeDraftEditor>
@ -34,36 +33,56 @@
</template>
<script lang="ts" setup name="MEditorFunctionEditor">
import { inject, provide, ref, watchEffect } from 'vue';
import { cloneDeep } from 'lodash-es';
import { TMagicCard, TMagicInput, tMagicMessage } from '@tmagic/design';
import { ColumnConfig, TableConfig } from '@tmagic/form';
import { CodeParam, Id } from '@tmagic/schema';
import type { Services } from '../type';
import CodeDraftEditor from './CodeDraftEditor.vue';
const tableConfig = {
border: true,
enableFullscreen: false,
name: 'params',
maxHeight: '150px',
const defaultParamColConfig: ColumnConfig = {
type: 'row',
label: '参数类型',
items: [
{
type: 'text',
label: '参数名',
name: 'name',
text: '参数类型',
labelWidth: '70px',
type: 'select',
name: 'type',
options: [
{
text: '数字',
label: '数字',
value: 'number',
},
{
text: '字符串',
label: '字符串',
value: 'text',
},
],
},
],
};
const props = withDefaults(
defineProps<{
/** 代码块id */
id: Id;
/** 代码块名称 */
name: string;
/** 代码内容 */
content: string;
/** 是否可编辑 */
editable?: boolean;
/** 是否自动保存草稿 */
autoSaveDraft?: boolean;
/** 编辑器扩展参数 */
codeOptions?: object;
/** 代码参数扩展配置 */
paramsColConfig?: ColumnConfig;
}>(),
{
editable: true,
@ -71,6 +90,32 @@ const props = withDefaults(
},
);
const paramsColConfig = props.paramsColConfig || defaultParamColConfig;
const tableConfig: TableConfig = {
type: 'table',
border: true,
enableFullscreen: false,
name: 'params',
maxHeight: '300px',
dropSort: false,
items: [
{
type: 'text',
label: '参数名',
name: 'name',
width: 200,
},
{
type: 'text',
label: '参数注释',
name: 'tip',
width: 200,
},
paramsColConfig,
],
};
const emit = defineEmits(['change', 'field-input']);
const services = inject<Services>('services');
@ -91,7 +136,7 @@ watchEffect(() => {
});
const initTableModel = (): void => {
const codeDsl = services?.codeBlockService.getCodeDslSync();
const codeDsl = cloneDeep(services?.codeBlockService.getCodeDslSync());
if (!codeDsl) return;
tableModel.value = {
params: codeDsl[props.id]?.params || [],

View File

@ -9,17 +9,27 @@
:size="size"
@change="changeHandler"
>
<template #operateCol="{ scope }">
<Icon
v-if="scope.row.codeId"
:icon="editable ? Edit : View"
class="edit-icon"
@click="editCode(scope.row.codeId)"
></Icon>
</template>
</m-form-table>
</div>
</template>
<script lang="ts" setup name="MEditorCodeSelect">
import { computed, defineEmits, defineProps, inject, watch } from 'vue';
import { Edit, View } from '@element-plus/icons-vue';
import { isEmpty, map } from 'lodash-es';
import { createValues, FormItem, FormState, TableConfig } from '@tmagic/form';
import { HookType, Id } from '@tmagic/schema';
import Icon from '../components/Icon.vue';
import { CodeParamStatement, HookData, Services } from '../type';
const services = inject<Services>('services');
const mForm = inject<FormState>('mForm');
@ -38,9 +48,10 @@ const codeDsl = computed(() => services?.codeBlockService.getCodeDslSync());
const tableConfig = computed<FormItem>(() => {
const defaultConfig = {
dropSort: true,
dropSort: false,
enableFullscreen: false,
border: true,
operateColWidth: 60,
items: [
{
type: 'select',
@ -68,8 +79,8 @@ const tableConfig = computed<FormItem>(() => {
defaultValue: {},
itemsFunction: (row: HookData) => {
const paramsConfig = getParamsConfig(row.codeId);
if (!row.params) row.params = {};
if (isEmpty(row.params)) {
// 使createValues
if (isEmpty(row.params) || !row.params) {
createValues(mForm, paramsConfig, {}, row.params);
}
return paramsConfig;
@ -83,6 +94,8 @@ const tableConfig = computed<FormItem>(() => {
};
});
const editable = computed(() => services?.codeBlockService.getEditStatus());
watch(
() => props.model[props.name],
(value) => {
@ -104,15 +117,18 @@ const changeHandler = async () => {
emit('change', props.model[props.name]);
};
const getParamsConfig = (codeId: Id) => {
const getParamsConfig = (codeId: Id): CodeParamStatement[] => {
if (!codeDsl.value) return [];
const paramStatements = codeDsl.value[codeId]?.params;
if (isEmpty(paramStatements)) return [];
return paramStatements.map((paramState: CodeParamStatement) => ({
name: paramState.name,
text: paramState.name,
labelWidth: '100px',
type: 'text',
text: paramState.name,
...paramState,
}));
};
const editCode = (codeId: Id) => {
services?.codeBlockService.setCodeEditorContent(true, codeId);
};
</script>

View File

@ -9,45 +9,18 @@
:show-close="false"
>
<Layout v-model:left="left" :min-left="45" class="code-editor-layout">
<!-- 左侧列表 -->
<template #left v-if="mode === CodeEditorMode.LIST">
<TMagicTree
v-if="!isEmpty(state.codeList)"
class="side-tree"
node-key="id"
empty-text="暂无代码块"
:data="state.codeList"
:highlight-current="true"
:current-node-key="state.codeList[0].id"
@node-click="selectHandler"
>
<template #default="{ data }">
<div :id="data.id" class="list-container">
<div class="list-item">
<div class="code-name">{{ data.name }}{{ data.id }}</div>
</div>
</div>
</template>
</TMagicTree>
</template>
<!-- 右侧区域 -->
<template #center>
<div
v-if="!isEmpty(codeConfig)"
:class="[
mode === CodeEditorMode.LIST
? 'm-editor-code-block-editor-panel-list-mode'
: 'm-editor-code-block-editor-panel',
]"
>
<div v-if="!isEmpty(codeConfig)" class="m-editor-code-block-editor-panel">
<slot name="code-block-edit-panel-header" :id="id"></slot>
<FunctionEditor
v-if="codeConfig"
:id="id"
:name="codeConfig.name"
:content="codeConfig.content"
:paramsColConfig="paramsColConfig"
:editable="!!editable"
:autoSaveDraft="mode === CodeEditorMode.EDITOR"
:autoSaveDraft="true"
:codeOptions="codeOptions"
></FunctionEditor>
</div>
@ -60,18 +33,22 @@
import { computed, inject, reactive, ref, watchEffect } from 'vue';
import { cloneDeep, forIn, isEmpty } from 'lodash-es';
import { TMagicDialog, TMagicTree } from '@tmagic/design';
import { TMagicDialog } from '@tmagic/design';
import { ColumnConfig } from '@tmagic/form';
import { CodeBlockContent } from '@tmagic/schema';
import FunctionEditor from '../../../components/FunctionEditor.vue';
import Layout from '../../../components/Layout.vue';
import type { CodeDslItem, ListState, Services } from '../../../type';
import { CodeEditorMode } from '../../../type';
import type { ListState, Services } from '../../../type';
import { serializeConfig } from '../../../utils/editor';
const services = inject<Services>('services');
const codeOptions = inject('codeOptions', {});
defineProps<{
paramsColConfig?: ColumnConfig;
}>();
const left = ref(200);
const currentTitle = ref('');
//
@ -81,7 +58,6 @@ const state = reactive<ListState>({
codeList: [],
});
const mode = computed(() => services?.codeBlockService.getMode());
const id = computed(() => services?.codeBlockService.getId() || '');
const editable = computed(() => services?.codeBlockService.getEditStatus());
// id
@ -105,9 +81,4 @@ watchEffect(async () => {
});
currentTitle.value = state.codeList[0]?.name || '';
});
const selectHandler = (data: CodeDslItem) => {
services?.codeBlockService.setId(data.id);
currentTitle.value = data.name;
};
</script>

View File

@ -78,7 +78,7 @@
</TMagicScrollbar>
<!-- 代码块编辑区 -->
<code-block-editor v-if="isShowCodeBlockEditor">
<code-block-editor v-if="isShowCodeBlockEditor" :paramsColConfig="paramsColConfig">
<template #code-block-edit-panel-header="{ id }">
<slot name="code-block-edit-panel-header" :id="id"></slot>
</template>
@ -92,16 +92,18 @@ import { Close, Edit, Link, View } from '@element-plus/icons-vue';
import { cloneDeep, forIn, isEmpty } from 'lodash-es';
import { TMagicButton, TMagicInput, tMagicMessage, TMagicScrollbar, TMagicTooltip, TMagicTree } from '@tmagic/design';
import { ColumnConfig } from '@tmagic/form';
import { CodeBlockContent, Id } from '@tmagic/schema';
import Icon from '../../../components/Icon.vue';
import type { CodeRelation, Services } from '../../../type';
import { CodeDeleteErrorType, CodeDslItem, CodeEditorMode, ListState } from '../../../type';
import { CodeDeleteErrorType, CodeDslItem, ListState } from '../../../type';
import codeBlockEditor from './CodeBlockEditor.vue';
const props = defineProps<{
customError?: (id: Id, errorType: CodeDeleteErrorType) => any;
paramsColConfig?: ColumnConfig;
}>();
const services = inject<Services>('services');
@ -168,7 +170,6 @@ const createCodeBlock = async () => {
content: `({app, params}) => {\n // place your code here\n}`,
params: [],
};
await codeBlockService.setMode(CodeEditorMode.EDITOR);
const id = await codeBlockService.getUniqueId();
await codeBlockService.setCodeDslById(id, codeConfig);
codeBlockService.setCodeEditorContent(true, id);
@ -176,7 +177,6 @@ const createCodeBlock = async () => {
//
const editCode = async (key: Id) => {
await services?.codeBlockService.setMode(CodeEditorMode.EDITOR);
services?.codeBlockService.setCodeEditorContent(true, key);
};

View File

@ -23,7 +23,7 @@ import { CodeBlockContent, CodeBlockDSL, HookType, Id, MNode } from '@tmagic/sch
import editorService from '../services/editor';
import type { CodeRelation, CodeState, HookData } from '../type';
import { CODE_DRAFT_STORAGE_KEY, CodeEditorMode } from '../type';
import { CODE_DRAFT_STORAGE_KEY } from '../type';
import { info } from '../utils/logger';
import BaseService from './BaseService';
@ -34,7 +34,6 @@ class CodeBlock extends BaseService {
codeDsl: null,
id: '',
editable: true,
mode: CodeEditorMode.EDITOR,
combineIds: [],
undeletableList: [],
relations: {},
@ -50,7 +49,6 @@ class CodeBlock extends BaseService {
'setCodeDslById',
'setCodeEditorShowStatus',
'setEditStatus',
'setMode',
'setCombineIds',
'setUndeleteableList',
'deleteCodeDslByIds',
@ -214,23 +212,6 @@ class CodeBlock extends BaseService {
return this.state.id;
}
/**
*
* @returns {CodeEditorMode}
*/
public getMode(): CodeEditorMode {
return this.state.mode;
}
/**
*
* @param {CodeEditorMode} mode
* @returns {void}
*/
public async setMode(mode: CodeEditorMode): Promise<void> {
this.state.mode = mode;
}
/**
* id数组
* @param {string[]} ids id数组
@ -349,7 +330,6 @@ class CodeBlock extends BaseService {
this.state.codeDsl = null;
this.state.id = '';
this.state.editable = true;
this.state.mode = CodeEditorMode.EDITOR;
this.state.combineIds = [];
this.state.undeletableList = [];
}
@ -386,6 +366,7 @@ class CodeBlock extends BaseService {
*/
private recurseMNode(node: MNode, relations: CodeRelation): void {
forIn(node, (value, key) => {
let unConfirmedValue: MNode = { id: node.id };
if (value?.hookType === HookType.CODE && !isEmpty(value.hookData)) {
value.hookData.forEach((relationItem: HookData) => {
// continue
@ -399,6 +380,16 @@ class CodeBlock extends BaseService {
}
codeItem[node.id].push(key);
});
// continue
return;
}
if (typeof value === 'object') {
// 检查value内部是否有嵌套
unConfirmedValue = {
...unConfirmedValue,
...value,
};
this.recurseMNode(unConfirmedValue, relations);
}
});
if (!isEmpty(node.items)) {

View File

@ -78,6 +78,10 @@
.m-fields-code-select {
width: 100%;
.edit-icon {
cursor: pointer;
margin-right: 5px;
}
}
.el-dialog.is-fullscreen.code-editor-dialog {

View File

@ -335,8 +335,6 @@ export type CodeState = {
id: Id;
/** 代码块是否可编辑 */
editable: boolean;
/** 代码编辑面板的展示模式 */
mode: CodeEditorMode;
/** list模式下左侧展示的代码列表 */
combineIds: string[];
/** 为业务逻辑预留的不可删除的代码块列表,由业务逻辑维护(如代码块上线后不可删除) */
@ -359,13 +357,6 @@ export type CodeRelation = {
};
};
export enum CodeEditorMode {
/** 左侧菜单,右侧代码 */
LIST = 'list',
/** 全屏代码 */
EDITOR = 'editor',
}
export interface CodeDslItem {
/** 代码块id */
id: Id;
@ -406,6 +397,7 @@ export interface CodeParamStatement {
name: string;
/** 参数类型 */
type?: string;
[key: string]: any;
}
export interface StepValue {

View File

@ -24,11 +24,12 @@
<TMagicTableColumn
label="操作"
width="55"
:width="config.operateColWidth || 55"
align="center"
:fixed="config.fixed === false ? undefined : 'left'"
>
<template v-slot="scope">
<slot name="operateCol" :scope="scope"></slot>
<TMagicIcon
v-show="showDelete(scope.$index + 1 + pagecontext * pagesize - 1)"
class="m-table-delete-icon"

View File

@ -598,11 +598,11 @@ export interface PanelConfig extends FormItem, ContainerCommonConfig {
schematic?: string;
}
export interface ColumnConfig extends FormItem, ContainerCommonConfig {
name: string;
export interface ColumnConfig extends FormItem {
name?: string;
label: string;
width: string | number;
sortable: boolean;
width?: string | number;
sortable?: boolean;
[key: string]: any;
}
@ -622,6 +622,8 @@ export interface TableConfig extends FormItem {
border?: boolean;
/** 显示行号 */
showIndex?: boolean;
/** 操作栏宽度 */
operateColWidth?: number | string;
pagination?: boolean;
enum?: any[];
/** 是否显示添加按钮 */
@ -642,7 +644,7 @@ export interface TableConfig extends FormItem {
enableFullscreen?: boolean;
fixed?: boolean;
itemExtra?: string | FilterFunction;
rowKey: string;
rowKey?: string;
}
export interface GroupListConfig extends FormItem {

View File

@ -90,6 +90,8 @@ export interface CodeBlockContent {
export interface CodeParam {
/** 参数名 */
name: string;
/** 扩展字段 */
[propName: string]: any;
}
export interface PastePosition {
left?: number;