feat(playground): 完善form editor

This commit is contained in:
roymondchen 2023-12-15 20:05:29 +08:00
parent b72e487b58
commit 538f96c082
12 changed files with 174 additions and 31 deletions

View File

@ -2,6 +2,7 @@
<div <div
v-if="config" v-if="config"
:id="config.id" :id="config.id"
:data-magic-id="config.id"
:style="config.tip ? 'display: flex;align-items: baseline;' : ''" :style="config.tip ? 'display: flex;align-items: baseline;' : ''"
:class="`m-form-container m-container-${type || ''} ${config.className || ''}`" :class="`m-form-container m-container-${type || ''} ${config.className || ''}`"
> >

View File

@ -181,7 +181,7 @@ export default class ActionManager extends EventEmitter {
return el.id === this.selectedEl?.id; return el.id === this.selectedEl?.id;
} }
public setSelectedEl(el: HTMLElement): void { public setSelectedEl(el?: HTMLElement): void {
this.selectedEl = el; this.selectedEl = el;
} }
@ -258,7 +258,7 @@ export default class ActionManager extends EventEmitter {
} }
public select(el: HTMLElement, event: MouseEvent | undefined): void { public select(el: HTMLElement, event: MouseEvent | undefined): void {
this.selectedEl = el; this.setSelectedEl(el);
this.clearSelectStatus(SelectStatus.MULTI_SELECT); this.clearSelectStatus(SelectStatus.MULTI_SELECT);
this.dr.select(el, event); this.dr.select(el, event);
} }
@ -399,7 +399,7 @@ export default class ActionManager extends EventEmitter {
// 如果已有单选选中元素不是magic-ui-page就可以加入多选列表 // 如果已有单选选中元素不是magic-ui-page就可以加入多选列表
if (this.selectedEl && !this.selectedEl.className.includes(PAGE_CLASS)) { if (this.selectedEl && !this.selectedEl.className.includes(PAGE_CLASS)) {
this.selectedElList.push(this.selectedEl as HTMLElement); this.selectedElList.push(this.selectedEl as HTMLElement);
this.selectedEl = undefined; this.setSelectedEl(undefined);
} }
// 判断元素是否已在多选列表 // 判断元素是否已在多选列表
const existIndex = this.selectedElList.findIndex((selectedDom) => selectedDom.id === el.id); const existIndex = this.selectedElList.findIndex((selectedDom) => selectedDom.id === el.id);

View File

@ -156,12 +156,6 @@ export default class StageRender extends EventEmitter {
x = x - rect.left; x = x - rect.left;
y = y - rect.top; y = y - rect.top;
} }
} else if (this.nativeContainer) {
const rect = this.nativeContainer.getClientRects()[0];
if (rect) {
x = x - rect.left;
y = y - rect.top;
}
} }
return this.getDocument()?.elementsFromPoint(x / this.zoom, y / this.zoom) as HTMLElement[]; return this.getDocument()?.elementsFromPoint(x / this.zoom, y / this.zoom) as HTMLElement[];

View File

@ -0,0 +1,3 @@
import type { FormConfig } from '@tmagic/form';
export default [] as FormConfig;

View File

@ -0,0 +1,27 @@
import type { FormConfig } from '@tmagic/form';
export default [
{
name: 'id',
type: 'hidden',
},
{
name: 'type',
type: 'hidden',
},
{
name: 'name',
text: '表单key',
extra: '字段名',
},
{
name: 'text',
text: '标签文本',
extra: 'label 标签的文本',
},
{
name: 'labelWidth',
text: '标签宽度',
extra: '表单域标签的的宽度,例如 "50px"。支持 auto。',
},
] as FormConfig;

View File

@ -0,0 +1,3 @@
import type { FormConfig } from '@tmagic/form';
export default [] as FormConfig;

View File

@ -0,0 +1,13 @@
import checkbox from './checkbox';
import display from './display';
import number from './number';
import switchConfig from './switch';
import text from './text';
export default {
text,
checkbox,
display,
number,
switch: switchConfig,
};

View File

@ -0,0 +1,23 @@
import type { FormConfig } from '@tmagic/form';
export default [
{
type: 'number',
name: 'min',
text: '最小值',
},
{
type: 'number',
name: 'max',
text: '最大值',
},
{
type: 'number',
name: 'step',
text: '步数',
},
{
name: 'placeholder',
text: 'placeholder',
},
] as FormConfig;

View File

@ -0,0 +1,3 @@
import type { FormConfig } from '@tmagic/form';
export default [] as FormConfig;

View File

@ -0,0 +1,26 @@
import type { FormConfig } from '@tmagic/form';
export default [
{
name: 'placeholder',
text: 'placeholder',
},
{
name: 'append',
legend: '后置按钮',
type: 'fieldset',
labelWidth: '80px',
items: [
{
name: 'text',
text: '按钮文案',
},
{
name: 'handler',
type: 'vs-code',
height: '400px',
text: '点击',
},
],
},
] as FormConfig;

View File

@ -43,7 +43,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, nextTick, ref, toRaw } from 'vue'; import { computed, nextTick, onBeforeUnmount, ref, toRaw } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { Coin, Connection, Document } from '@element-plus/icons-vue'; import { Coin, Connection, Document } from '@element-plus/icons-vue';
import serialize from 'serialize-javascript'; import serialize from 'serialize-javascript';
@ -233,6 +233,10 @@ editorService.usePlugin({
return [config, parent]; return [config, parent];
}, },
}); });
onBeforeUnmount(() => {
editorService.removeAllPlugins();
});
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -4,11 +4,11 @@
:menu="menu" :menu="menu"
:sidebar="sidebar" :sidebar="sidebar"
:component-group-list="componentGroupList" :component-group-list="componentGroupList"
:render-type="RenderType.NATIVE" :props-configs="propsConfigs"
:render="render" :render="render"
:can-select="canSelect" :can-select="canSelect"
:update-drag-el="updateDragEl"
:stage-rect="{ width: 'calc(100% - 70px)', height: '100%' }" :stage-rect="{ width: 'calc(100% - 70px)', height: '100%' }"
:moveable-options="{ resizable: false }"
> >
<template #layer-node-label="{ data }"> <template #layer-node-label="{ data }">
{{ data.text || data.name || 'container' }} {{ data.text || data.name || 'container' }}
@ -17,21 +17,33 @@
</template> </template>
<script setup lang="tsx"> <script setup lang="tsx">
import { createApp, nextTick, ref } from 'vue'; import { createApp, onBeforeUnmount, ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { Document } from '@element-plus/icons-vue'; import { Document } from '@element-plus/icons-vue';
import cssStyle from 'element-plus/dist/index.css?raw';
import { ComponentGroup, MenuBarData, SideBarData, TMagicEditor, traverseNode, uiService } from '@tmagic/editor'; import {
import MagicForm, { MForm } from '@tmagic/form'; ComponentGroup,
import { MApp, MNode, NodeType } from '@tmagic/schema'; MenuBarData,
import { getOffset, RenderType, RuntimeWindow, TargetElement } from '@tmagic/stage'; propsService,
import { guid } from '@tmagic/utils'; SideBarData,
TMagicEditor,
traverseNode,
uiService,
} from '@tmagic/editor';
import MagicForm, { type FormConfig, MForm } from '@tmagic/form';
import { type MApp, type MNode, NodeType } from '@tmagic/schema';
import type StageCore from '@tmagic/stage';
import { guid, injectStyle } from '@tmagic/utils';
import propsConfigs from '../configs/form-config';
import commonConfig from '../configs/form-config/common';
import formDsl from '../configs/formDsl'; import formDsl from '../configs/formDsl';
formDsl.forEach((item) => { formDsl.forEach((item) => {
traverseNode<any>(item, (item) => { traverseNode<any>(item, (item) => {
item.id = `${item.type}_${guid()}`; item.id = `${item.type}_${guid()}`;
item.type = item.type || (item.items ? 'container' : 'text');
item.style = { item.style = {
left: 0, left: 0,
top: 0, top: 0,
@ -53,30 +65,43 @@ const config = ref<MApp>({
], ],
}); });
const render = () => { const render = (stage: StageCore) => {
const el = globalThis.document.createElement('div'); injectStyle(stage.renderer.getDocument()!, cssStyle);
el.style.position = 'relative'; injectStyle(
stage.renderer.getDocument()!,
`
html,
body,
#app {
width: 100%;
height: 100%;
margin: 0;
}
::-webkit-scrollbar {
width: 0;
}
`,
);
const el: HTMLDivElement = globalThis.document.createElement('div');
el.id = 'app';
el.style.overflow = 'auto';
createApp(MForm, { createApp(MForm, {
config: formDsl, config: config.value.items[0].items,
initValues: {}, initValues: {},
}) })
.use(MagicForm) .use(MagicForm)
.mount(el); .mount(el);
nextTick(() => { stage.renderer.contentWindow?.magic?.onRuntimeReady({});
(globalThis as unknown as RuntimeWindow).magic.onPageElUpdate(el); setTimeout(() => {
stage.renderer.contentWindow?.magic.onPageElUpdate(el.children[0] as HTMLElement);
uiService.set('showRule', false); uiService.set('showRule', false);
}); });
return el; return el;
}; };
const updateDragEl = (el: TargetElement, target: TargetElement, container: HTMLElement) => {
const { left, top } = getOffset(container);
el.style.left = `${globalThis.parseFloat(el.style.left) - left}px`;
el.style.top = `${globalThis.parseFloat(el.style.top) - top}px`;
};
const componentGroupList: ComponentGroup[] = [ const componentGroupList: ComponentGroup[] = [
{ {
title: '容器', title: '容器',
@ -162,5 +187,26 @@ const sidebar: SideBarData = {
items: ['component-list', 'layer'], items: ['component-list', 'layer'],
}; };
const canSelect = (el: HTMLElement) => el.classList.contains('m-form-container'); const canSelect = (el: HTMLElement) => Boolean(el.dataset.magicId);
propsService.usePlugin({
afterFillConfig(config: FormConfig, itemConfig: FormConfig) {
return [
{
type: 'tab',
items: [
{
title: '属性',
labelWidth: '80px',
items: [...commonConfig, ...itemConfig],
},
],
},
];
},
});
onBeforeUnmount(() => {
propsService.removeAllPlugins();
});
</script> </script>