1
0
mirror of synced 2026-04-03 06:28:35 +08:00

feat: 新增tmagic-form-runtime

This commit is contained in:
roymondchen
2024-01-09 14:58:13 +08:00
parent a10ae0ddd1
commit f8443ed316
24 changed files with 694 additions and 147 deletions

View File

@@ -0,0 +1,25 @@
<template>
<MForm ref="mForm" :id="config?.id" :data-magic-id="config?.id" :config="formConfig" :init-values="values"></MForm>
</template>
<script setup lang="ts">
import { watch } from 'vue';
import { MForm } from '@tmagic/form';
import type StageCore from '@tmagic/stage';
import { useFormConfig } from './useFormConfig';
const props = defineProps<{
stage: StageCore;
}>();
const { mForm, formConfig, config, values } = useFormConfig(props.stage.renderer.contentWindow);
watch(formConfig, async () => {
setTimeout(() => {
const page = props.stage.renderer.getDocument()?.querySelector<HTMLElement>('.m-form');
page && props.stage.renderer.contentWindow?.magic.onPageElUpdate(page);
});
});
</script>

View File

@@ -0,0 +1,161 @@
import type { ComponentGroup } from '@tmagic/editor';
export const COMPONENT_GROUP_LIST: ComponentGroup[] = [
{
title: '容器',
items: [
{
text: '普通容器',
type: 'container',
data: {
items: [],
},
},
{
text: '表格',
type: 'table',
data: {
items: [],
},
},
{
text: '组列表',
type: 'group-list',
data: {
items: [],
},
},
{
text: '面板',
type: 'panel',
data: {
items: [],
},
},
{
text: '行',
type: 'row',
data: {
items: [],
},
},
],
},
{
title: '表单组件',
items: [
{
text: '输入框',
type: 'text',
data: {
text: '输入框',
name: 'text',
},
},
{
text: '数字输入框',
type: 'number',
data: {
text: '数字输入框',
name: 'number',
},
},
{
text: '文本域',
type: 'textarea',
data: {
text: '文本域',
name: 'textarea',
},
},
{
text: '链接',
type: 'link',
data: {
text: '链接',
name: 'link',
},
},
{
text: '日期',
type: 'datetime',
data: {
text: '日期',
name: 'datetime',
},
},
{
text: '时间',
type: 'time',
data: {
text: '时间',
name: 'time',
},
},
{
text: '选中器',
type: 'select',
data: {
text: '选中器',
name: 'select',
},
},
{
text: '级联选择器',
type: 'cascader',
data: {
text: '级联选择器',
name: 'cascader',
},
},
{
text: '开关',
type: 'switch',
data: {
text: '开关',
name: 'switch',
},
},
{
text: '多选框',
type: 'checkbox',
data: {
text: '多选框',
name: 'checkbox',
},
},
{
text: '多选组',
type: 'checkboxGroup',
data: {
text: '多选组',
name: 'checkboxGroup',
},
},
{
text: '单选框',
type: 'radio',
data: {
text: '单选框',
name: 'radio',
},
},
{
text: '单选组',
type: 'radioGroup',
data: {
text: '单选组',
name: 'radioGroup',
},
},
{
text: '取色器',
type: 'colorPicker',
data: {
text: '取色器',
name: 'colorPicker',
},
},
],
},
];

View File

@@ -0,0 +1,14 @@
import { createForm } from '@tmagic/form';
export default createForm([
{
name: 'activeValue',
text: '选中时的值',
defaultValue: true,
},
{
name: 'inactiveValue',
text: '没有选中时的值',
defaultValue: false,
},
]);

View File

@@ -0,0 +1,33 @@
import { createForm } from '@tmagic/form';
export default createForm([
{
name: 'id',
type: 'hidden',
},
{
name: 'type',
type: 'hidden',
},
{
name: 'name',
text: '表单key',
extra: '字段名',
},
{
name: 'text',
text: '标签文本',
extra: 'label 标签的文本',
},
{
name: 'labelWidth',
text: '标签宽度',
extra: '表单域标签的的宽度,例如 "50px"。支持 auto。',
},
{
name: 'disabled',
text: '是否禁用',
type: 'switch',
defaultValue: false,
},
]);

View File

@@ -0,0 +1,3 @@
import { createForm } from '@tmagic/form';
export default createForm([]);

View File

@@ -0,0 +1,17 @@
import type { FormConfig } from '@tmagic/form';
import checkbox from './checkbox';
import display from './display';
import number from './number';
import switchConfig from './switch';
import text from './text';
const configs: Record<string, FormConfig> = {
text,
checkbox,
display,
number,
switch: switchConfig,
};
export default configs;

View File

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

View File

@@ -0,0 +1,3 @@
import { createForm } from '@tmagic/form';
export default createForm([]);

View File

@@ -0,0 +1,33 @@
import { createForm } from '@tmagic/form';
export default createForm([
{
name: 'placeholder',
text: 'placeholder',
},
{
name: 'append',
legend: '后置按钮',
type: 'fieldset',
labelWidth: '80px',
checkbox: true,
expand: true,
items: [
{
name: 'type',
type: 'hidden',
defaultValue: 'button',
},
{
name: 'text',
text: '按钮文案',
},
{
name: 'handler',
type: 'vs-code',
height: '400px',
text: '点击',
},
],
},
]);

View File

@@ -0,0 +1,86 @@
import { createApp, onBeforeUnmount } from 'vue';
import cssStyle from 'element-plus/dist/index.css?raw';
import { editorService, Layout, propsService, uiService } from '@tmagic/editor';
import MagicForm, { type FormConfig } from '@tmagic/form';
import type StageCore from '@tmagic/stage';
import { injectStyle } from '@tmagic/utils';
import commonConfig from './form-config/common';
import App from './App.vue';
import formConfigs from './form-config';
export * from './component-group-list';
export const propsConfigs = formConfigs;
export const canSelect = (el: HTMLElement) => Boolean(el.dataset.magicId);
export const useRuntime = () => {
const render = (stage: StageCore) => {
injectStyle(stage.renderer.getDocument()!, cssStyle);
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(App, {
stage,
})
.use(MagicForm)
.mount(el);
setTimeout(() => {
uiService.set('showRule', false);
});
return el;
};
propsService.usePlugin({
afterFillConfig(config: FormConfig, itemConfig: FormConfig) {
return [
{
type: 'tab',
items: [
{
title: '属性',
labelWidth: '80px',
items: [...commonConfig, ...itemConfig],
},
],
},
];
},
});
editorService.usePlugin({
afterGetLayout() {
return Layout.RELATIVE;
},
});
onBeforeUnmount(() => {
propsService.removeAllPlugins();
editorService.removeAllPlugins();
});
return {
render,
};
};

View File

@@ -0,0 +1,143 @@
import { computed, nextTick, onBeforeUnmount, reactive, ref } from 'vue';
import Core from '@tmagic/core';
import { type FormConfig, initValue, MForm } from '@tmagic/form';
import type { Id, MApp, MNode } from '@tmagic/schema';
import type { RemoveData, RuntimeWindow, UpdateData } from '@tmagic/stage';
import { getNodePath, replaceChildNode } from '@tmagic/utils';
export const useFormConfig = (contentWindow: RuntimeWindow | null) => {
const mForm = ref<InstanceType<typeof MForm>>();
const root = ref<MApp>();
const values = ref({});
const curPageId = ref<Id>();
const selectedId = ref<Id>();
const config = computed(
() => root.value?.items?.find((item: MNode) => item.id === curPageId.value) || root.value?.items?.[0],
);
const formConfig = computed(() => (config.value?.items || []) as FormConfig);
const app = new Core({
ua: contentWindow?.navigator.userAgent,
platform: 'editor',
});
const resetValues = () => {
initValue(mForm.value?.formState, {
initValues: {},
config: formConfig.value,
}).then((value) => {
values.value = value;
});
};
const runtimeReadyHandler = ({ data }: any) => {
if (!data.tmagicRuntimeReady) {
return;
}
contentWindow?.magic?.onRuntimeReady({
getApp() {
return app;
},
updateRootConfig(config: MApp) {
root.value = config;
app?.setConfig(config, curPageId.value);
},
updatePageId(id: Id) {
curPageId.value = id;
app?.setPage(id);
},
select(id: Id) {
selectedId.value = id;
if (app?.getPage(id)) {
this.updatePageId?.(id);
}
const el = document.getElementById(`${id}`);
if (el) return el;
// 未在当前文档下找到目标元素,可能是还未渲染,等待渲染完成后再尝试获取
return nextTick().then(() => document.getElementById(`${id}`) as HTMLElement);
},
add({ config, parentId }: UpdateData) {
if (!root.value) throw new Error('error');
if (!selectedId.value) throw new Error('error');
if (!parentId) throw new Error('error');
const parent = getNodePath(parentId, [root.value]).pop();
if (!parent) throw new Error('未找到父节点');
if (config.type !== 'page') {
const parentNode = app?.page?.getNode(parent.id);
parentNode && app?.page?.initNode(config, parentNode);
}
if (parent.id !== selectedId.value) {
const index = parent.items?.findIndex((child: MNode) => child.id === selectedId.value);
parent.items?.splice(index + 1, 0, config);
} else {
// 新增节点添加到配置中
parent.items?.push(config);
}
resetValues();
},
update({ config, parentId }: UpdateData) {
if (!root.value || !app) throw new Error('error');
const newNode = app.dataSourceManager?.compiledNode(config) || config;
replaceChildNode(reactive(newNode), [root.value], parentId);
const nodeInstance = app.page?.getNode(config.id);
if (nodeInstance) {
nodeInstance.setData(config);
}
resetValues();
},
remove({ id, parentId }: RemoveData) {
if (!root.value) throw new Error('error');
const node = getNodePath(id, [root.value]).pop();
if (!node) throw new Error('未找到目标元素');
const parent = getNodePath(parentId, [root.value]).pop();
if (!parent) throw new Error('未找到父元素');
if (node.type === 'page') {
app?.deletePage();
} else {
app?.page?.deleteNode(node.id);
}
const index = parent.items?.findIndex((child: MNode) => child.id === node.id);
parent.items.splice(index, 1);
resetValues();
},
});
};
contentWindow?.addEventListener('message', runtimeReadyHandler);
onBeforeUnmount(() => {
contentWindow?.removeEventListener('message', runtimeReadyHandler);
});
return {
mForm,
config,
formConfig,
values,
};
};