diff --git a/packages/designer/src/project/project.ts b/packages/designer/src/project/project.ts index 2c16c25b0..9a482d3ee 100644 --- a/packages/designer/src/project/project.ts +++ b/packages/designer/src/project/project.ts @@ -101,7 +101,9 @@ export class Project { if (this.documents.length < 1) { return; } - this.documents.forEach((doc) => doc.remove()); + for (let i = this.documents.length - 1; i >= 0; i--) { + this.documents[i].remove(); + } } removeDocument(doc: DocumentModel) { diff --git a/packages/editor-core/src/di/ioc-context.ts b/packages/editor-core/src/di/ioc-context.ts index 27b854cab..5520230bf 100644 --- a/packages/editor-core/src/di/ioc-context.ts +++ b/packages/editor-core/src/di/ioc-context.ts @@ -1,5 +1,9 @@ import { IocContext } from 'power-di'; +// 原本的 canBeKey 里判断函数的方法是 instanceof Function,在某些 babel 编译 class 后的场景不满足该判断条件,此处暴力破解 +IocContext.prototype.canBeKey = (obj: any) => + typeof obj === 'function' || ['string', 'symbol'].includes(typeof obj); + export * from 'power-di'; export const globalContext = IocContext.DefaultInstance; diff --git a/packages/editor-preset-vision/.eslintrc.js b/packages/editor-preset-vision/.eslintrc.js index 4888cbdf0..4c845e2de 100644 --- a/packages/editor-preset-vision/.eslintrc.js +++ b/packages/editor-preset-vision/.eslintrc.js @@ -2,7 +2,7 @@ module.exports = { extends: 'eslint-config-ali/typescript/react', rules: { 'react/no-multi-comp': 1, - 'no-unused-expressions': 1, + 'no-unused-expressions': 0, 'implicit-arrow-linebreak': 1, 'no-nested-ternary': 1, 'no-mixed-operators': 1, @@ -16,5 +16,6 @@ module.exports = { 'react/no-deprecated': 1, 'no-useless-escape': 1, 'brace-style': 1, + '@typescript-eslint/member-ordering': 0, } } \ No newline at end of file diff --git a/packages/editor-preset-vision/jest.config.js b/packages/editor-preset-vision/jest.config.js index 810547389..2cbe0c3fe 100644 --- a/packages/editor-preset-vision/jest.config.js +++ b/packages/editor-preset-vision/jest.config.js @@ -1,6 +1,6 @@ const esModules = [ '@recore/obx-react', - '@ali/lowcode-editor-core', + // '@ali/lowcode-editor-core', ].join('|'); module.exports = { diff --git a/packages/editor-preset-vision/src/bus.ts b/packages/editor-preset-vision/src/bus.ts index dd6b8386a..83dd0db71 100644 --- a/packages/editor-preset-vision/src/bus.ts +++ b/packages/editor-preset-vision/src/bus.ts @@ -64,15 +64,15 @@ export class Bus { const bus = new Bus(); -editor.on('hotkey.callback.call', (data) => { +editor?.on('hotkey.callback.call', (data) => { bus.emit('ve.hotkey.callback.call', data); }); -editor.on('history.back', (data) => { +editor?.on('history.back', (data) => { bus.emit('ve.history.back', data); }); -editor.on('history.forward', (data) => { +editor?.on('history.forward', (data) => { bus.emit('ve.history.forward', data); }); diff --git a/packages/editor-preset-vision/src/editor.ts b/packages/editor-preset-vision/src/editor.ts index 33c650dcf..3ff2575b3 100644 --- a/packages/editor-preset-vision/src/editor.ts +++ b/packages/editor-preset-vision/src/editor.ts @@ -3,8 +3,6 @@ import { isPlainObject, hasOwnProperty, cloneDeep, isI18NObject, isUseI18NSetter import { globalContext, Editor } from '@ali/lowcode-editor-core'; import { Designer, LiveEditing, TransformStage, Node, getConvertedExtraKey } from '@ali/lowcode-designer'; import Outline, { OutlineBackupPane, getTreeMaster } from '@ali/lowcode-plugin-outline-pane'; -import { toCss } from '@ali/vu-css-style'; -import logger from '@ali/vu-logger'; import bus from './bus'; import { VE_EVENTS } from './base/const'; @@ -13,7 +11,16 @@ import { Skeleton, SettingsPrimaryPane, registerDefaults } from '@ali/lowcode-ed import { deepValueParser } from './deep-value-parser'; import { liveEditingRule, liveEditingSaveHander } from './vc-live-editing'; -import { isVariable } from './utils'; +import { + compatibleReducer, + compatiblePageReducer, + stylePropsReducer, + upgradePropsReducer, + filterReducer, + removeEmptyPropsReducer, + initNodeReducer, + liveLifecycleReducer, +} from './props-reducers'; export const editor = new Editor(); globalContext.register(editor, Editor); @@ -31,313 +38,31 @@ designer.project.onCurrentDocumentChange((doc) => { bus.emit(VE_EVENTS.VE_PAGE_PAGE_READY); }); -interface Variable { - type: 'variable'; - variable: string; - value: any; -} - -function upgradePropsReducer(props: any) { - if (!props || !isPlainObject(props)) { - return props; - } - if (isJSBlock(props)) { - if (props.value.componentName === 'Slot') { - return { - type: 'JSSlot', - title: (props.value.props as any)?.slotTitle, - name: (props.value.props as any)?.slotName, - value: props.value.children, - }; - } else { - return props.value; - } - } - if (isVariable(props)) { - return { - type: 'JSExpression', - value: props.variable, - mock: props.value, - }; - } - const newProps: any = {}; - Object.keys(props).forEach((key) => { - if (/^__slot__/.test(key) && props[key] === true) { - return; - } - newProps[key] = upgradePropsReducer(props[key]); - }); - return newProps; -} - // 升级 Props designer.addPropsReducer(upgradePropsReducer, TransformStage.Upgrade); -function getCurrentFieldIds() { - const fieldIds: any = []; - const nodesMap = designer?.currentDocument?.nodesMap || new Map(); - nodesMap.forEach((curNode: any) => { - const fieldId = nodesMap?.get(curNode.id)?.getPropValue('fieldId'); - if (fieldId) { - fieldIds.push(fieldId); - } - }); - return fieldIds; -} - // 节点 props 初始化 -designer.addPropsReducer((props, node) => { - // debugger; - // run initials - const newProps: any = { - ...props, - }; - if (newProps.fieldId) { - const fieldIds = getCurrentFieldIds(); +designer.addPropsReducer(initNodeReducer, TransformStage.Init); - // 全局的关闭 uniqueIdChecker 信号,在 ve-utils 中实现 - if (fieldIds.indexOf(props.fieldId) >= 0 && !(window as any).__disable_unique_id_checker__) { - newProps.fieldId = undefined; - } - } - const initials = node.componentMeta.getMetadata().experimental?.initials; - if (initials) { - const getRealValue = (propValue: any) => { - if (isVariable(propValue)) { - return propValue.value; - } - if (isJSExpression(propValue)) { - return propValue.mock; - } - return propValue; - }; - initials.forEach((item) => { - // FIXME! this implements SettingTarget - try { - // FIXME! item.name could be 'xxx.xxx' - const ov = newProps[item.name]; - const v = item.initial(node as any, getRealValue(ov)); - if (ov === undefined && v !== undefined) { - newProps[item.name] = v; - } - // 兼容 props 中的属性为 i18n 类型,但是仅提供了一个字符串值,非变量绑定 - if (isUseI18NSetter(node.componentMeta.prototype, item.name) && - !isI18NObject(ov) && - !isJSExpression(ov) && - !isJSBlock(ov) && - !isJSSlot(ov) && - !isVariable(ov) && - (isString(v) || isI18NObject(v))) { - newProps[item.name] = convertToI18NObject(v); - } - } catch (e) { - if (hasOwnProperty(props, item.name)) { - newProps[item.name] = props[item.name]; - } - } - if (newProps[item.name] && !node.props.has(item.name)) { - node.props.add(newProps[item.name], item.name, false, { skipSetSlot: true }); - } - }); - } - return newProps; -}, TransformStage.Init); +designer.addPropsReducer(liveLifecycleReducer, TransformStage.Render); -designer.addPropsReducer((props: any, node: Node) => { - // live 模式下解析 lifeCycles - if (node.isRoot() && props && props.lifeCycles) { - if (editor.get('designMode') === 'live') { - const lifeCycleMap = { - didMount: 'componentDidMount', - willUnmount: 'componentWillUnMount', - }; - const lifeCycles = props.lifeCycles; - Object.keys(lifeCycleMap).forEach(key => { - if (lifeCycles[key]) { - lifeCycles[lifeCycleMap[key]] = lifeCycles[key]; - } - }); - return props; - } - return { - ...props, - lifeCycles: {}, - }; - } - return props; -}, TransformStage.Render); - -function filterReducer(props: any, node: Node): any { - const filters = node.componentMeta.getMetadata().experimental?.filters; - if (filters && filters.length) { - const newProps = { ...props }; - filters.forEach((item) => { - // FIXME! item.name could be 'xxx.xxx' - if (!hasOwnProperty(newProps, item.name)) { - return; - } - try { - if (item.filter(node.settingEntry.getProp(item.name), props[item.name]) === false) { - delete newProps[item.name]; - } - } catch (e) { - console.warn(e); - logger.trace(e); - } - }); - return newProps; - } - return props; -} designer.addPropsReducer(filterReducer, TransformStage.Save); designer.addPropsReducer(filterReducer, TransformStage.Render); -function compatiableReducer(props: any) { - if (!props || !isPlainObject(props)) { - return props; - } - // 为了能降级到老版本,建议在后期版本去掉以下代码 - if (isJSSlot(props)) { - return { - type: 'JSBlock', - value: { - componentName: 'Slot', - children: props.value, - props: { - slotTitle: props.title, - slotName: props.name, - }, - }, - }; - } - if (isJSExpression(props) && !props.events) { - return { - type: 'variable', - value: props.mock, - variable: props.value, - }; - } - const newProps: any = {}; - Object.entries(props).forEach(([key, val]) => { - newProps[key] = compatiableReducer(val); - }); - return newProps; -} // FIXME: Dirty fix, will remove this reducer -designer.addPropsReducer(compatiableReducer, TransformStage.Save); +designer.addPropsReducer(compatibleReducer, TransformStage.Save); // 兼容历史版本的 Page 组件 -designer.addPropsReducer((props: any, node: Node) => { - const lifeCycleNames = ['didMount', 'willUnmount']; - if (node.isRoot()) { - lifeCycleNames.forEach((key) => { - if (props[key]) { - const lifeCycles = node.props.getPropValue(getConvertedExtraKey('lifeCycles')) || {}; - lifeCycles[key] = props[key]; - node.props.setPropValue(getConvertedExtraKey('lifeCycles'), lifeCycles); - } - }); - } - return props; -}, TransformStage.Save); - -// designer.addPropsReducer((props: any, node: Node) => { -// const lifeCycleNames = ['didMount', 'willUnmount']; -// const lifeCycleMap = { -// didMount: 'componentDidMount', -// willUnmount: 'componentWillUnMount', -// }; -// if (node.componentName === 'Page') { -// debugger; -// lifeCycleNames.forEach(key => { -// if (props[key]) { -// const lifeCycles = node.props.getPropValue(getConvertedExtraKey('lifeCycles')) || {}; -// lifeCycles[lifeCycleMap[key]] = props[key]; -// node.props.setPropValue(getConvertedExtraKey('lifeCycles'), lifeCycles); -// } else if (node.props.getPropValue(getConvertedExtraKey('lifeCycles'))) { -// const lifeCycles = node.props.getPropValue(getConvertedExtraKey('lifeCycles')) || {}; -// lifeCycles[lifeCycleMap[key]] = lifeCycles[key]; -// node.props.setPropValue(getConvertedExtraKey('lifeCycles'), lifeCycles); -// } -// }); -// } -// return props; -// }, TransformStage.Init); +designer.addPropsReducer(compatiblePageReducer, TransformStage.Save); // 设计器组件样式处理 -function stylePropsReducer(props: any, node: any) { - if (props && typeof props === 'object' && props.__style__) { - const cssId = `_style_pesudo_${ node.id.replace(/\$/g, '_')}`; - const cssClass = `_css_pesudo_${ node.id.replace(/\$/g, '_')}`; - const styleProp = props.__style__; - appendStyleNode(props, styleProp, cssClass, cssId); - } - if (props && typeof props === 'object' && props.pageStyle) { - const cssId = '_style_pesudo_engine-document'; - const cssClass = 'engine-document'; - const styleProp = props.pageStyle; - appendStyleNode(props, styleProp, cssClass, cssId); - } - if (props && typeof props === 'object' && props.containerStyle) { - const cssId = `_style_pesudo_${ node.id}`; - const cssClass = `_css_pesudo_${ node.id.replace(/\$/g, '_')}`; - const styleProp = props.containerStyle; - appendStyleNode(props, styleProp, cssClass, cssId); - } - return props; -} - -function appendStyleNode(props: any, styleProp: any, cssClass: string, cssId: string) { - const doc = designer.currentDocument?.simulator?.contentDocument; - if (!doc) { - return; - } - const dom = doc.getElementById(cssId); - if (dom) { - dom.parentNode?.removeChild(dom); - } - if (typeof styleProp === 'object') { - styleProp = toCss(styleProp); - } - if (typeof styleProp === 'string') { - const s = doc.createElement('style'); - props.className = cssClass; - s.setAttribute('type', 'text/css'); - s.setAttribute('id', cssId); - doc.getElementsByTagName('head')[0].appendChild(s); - - s.appendChild(doc.createTextNode(styleProp.replace(/(\d+)rpx/g, (a, b) => { - return `${b / 2}px`; - }).replace(/:root/g, `.${ cssClass}`))); - } -} designer.addPropsReducer(stylePropsReducer, TransformStage.Render); // 国际化 & Expression 渲染时处理 designer.addPropsReducer(deepValueParser, TransformStage.Render); -// 清除空的 props value -function removeEmptyProps(props: any, node: Node) { - if (node.isRoot() && props.dataSource && Array.isArray(props.dataSource.online)) { - const online = cloneDeep(props.dataSource.online); - online.forEach((item: any) => { - const newParam: any = {}; - if (Array.isArray(item?.options?.params)) { - item.options.params.forEach((element: any) => { - if (element.name) { - newParam[element.name] = element.value; - } - }); - item.options.params = newParam; - } - }); - props.dataSource.list = online; - } - return props; -} - // Init 的时候没有拿到 dataSource, 只能在 Render 和 Save 的时候都调用一次,理论上执行时机在 Init // Render 和 Save 都要各调用一次,感觉也是有问题的,是不是应该在 Render 执行一次就行了?见上 filterReducer 也是一样的处理方式。 -designer.addPropsReducer(removeEmptyProps, TransformStage.Render); -designer.addPropsReducer(removeEmptyProps, TransformStage.Save); +designer.addPropsReducer(removeEmptyPropsReducer, TransformStage.Render); +designer.addPropsReducer(removeEmptyPropsReducer, TransformStage.Save); skeleton.add({ area: 'mainArea', diff --git a/packages/editor-preset-vision/src/index.ts b/packages/editor-preset-vision/src/index.ts index 06a04bb58..47a684711 100644 --- a/packages/editor-preset-vision/src/index.ts +++ b/packages/editor-preset-vision/src/index.ts @@ -162,7 +162,7 @@ export { Symbols, }; -const version = '6.0.0(LowcodeEngine 0.9.3)'; +const version = '6.0.0 (LowcodeEngine 0.9.32)'; console.log( `%c VisionEngine %c v${version} `, diff --git a/packages/editor-preset-vision/src/pages.ts b/packages/editor-preset-vision/src/pages.ts index 81ae1d672..b6d3dc0ef 100644 --- a/packages/editor-preset-vision/src/pages.ts +++ b/packages/editor-preset-vision/src/pages.ts @@ -64,7 +64,6 @@ const pages = Object.assign(project, { item.methods = {}; } }); - console.log(pages, componentsTree); project.load( { version: '1.0.0', diff --git a/packages/editor-preset-vision/src/props-reducers/downgrade-schema-reducer.ts b/packages/editor-preset-vision/src/props-reducers/downgrade-schema-reducer.ts new file mode 100644 index 000000000..709d4390c --- /dev/null +++ b/packages/editor-preset-vision/src/props-reducers/downgrade-schema-reducer.ts @@ -0,0 +1,51 @@ +import { getConvertedExtraKey } from '@ali/lowcode-designer'; +import { + isPlainObject, +} from '@ali/lowcode-utils'; +import { isJSExpression, isJSSlot } from '@ali/lowcode-types'; + +export function compatibleReducer(props: any) { + if (!props || !isPlainObject(props)) { + return props; + } + // 为了能降级到老版本,建议在后期版本去掉以下代码 + if (isJSSlot(props)) { + return { + type: 'JSBlock', + value: { + componentName: 'Slot', + children: props.value, + props: { + slotTitle: props.title, + slotName: props.name, + }, + }, + }; + } + if (isJSExpression(props) && !props.events) { + return { + type: 'variable', + value: props.mock, + variable: props.value, + }; + } + const newProps: any = {}; + Object.entries(props).forEach(([key, val]) => { + newProps[key] = compatibleReducer(val); + }); + return newProps; +} + +export function compatiblePageReducer(props: any, node: Node) { + const lifeCycleNames = ['didMount', 'willUnmount']; + if (node.isRoot()) { + lifeCycleNames.forEach(key => { + if (props[key]) { + const lifeCycles = node.props.getPropValue(getConvertedExtraKey('lifeCycles')) || {}; + lifeCycles[key] = props[key]; + node.props.setPropValue(getConvertedExtraKey('lifeCycles'), lifeCycles); + } + }); + } + return props; +} diff --git a/packages/editor-preset-vision/src/props-reducers/filter-reducer.ts b/packages/editor-preset-vision/src/props-reducers/filter-reducer.ts new file mode 100644 index 000000000..928e8dd82 --- /dev/null +++ b/packages/editor-preset-vision/src/props-reducers/filter-reducer.ts @@ -0,0 +1,25 @@ +import logger from '@ali/vu-logger'; +import { hasOwnProperty } from '@ali/lowcode-utils'; + +export function filterReducer(props: any, node: Node): any { + const filters = node.componentMeta.getMetadata().experimental?.filters; + if (filters && filters.length) { + const newProps = { ...props }; + filters.forEach((item) => { + // FIXME! item.name could be 'xxx.xxx' + if (!hasOwnProperty(newProps, item.name)) { + return; + } + try { + if (item.filter(node.settingEntry.getProp(item.name), props[item.name]) === false) { + delete newProps[item.name]; + } + } catch (e) { + console.warn(e); + logger.trace(e); + } + }); + return newProps; + } + return props; +} \ No newline at end of file diff --git a/packages/editor-preset-vision/src/props-reducers/index.ts b/packages/editor-preset-vision/src/props-reducers/index.ts new file mode 100644 index 000000000..347e5edf9 --- /dev/null +++ b/packages/editor-preset-vision/src/props-reducers/index.ts @@ -0,0 +1,7 @@ +export * from './downgrade-schema-reducer'; +export * from './filter-reducer'; +export * from './init-node-reducer'; +export * from './live-lifecycle-reducer'; +export * from './remove-empty-prop-reducer'; +export * from './style-reducer'; +export * from './upgrade-reducer'; diff --git a/packages/editor-preset-vision/src/props-reducers/init-node-reducer.ts b/packages/editor-preset-vision/src/props-reducers/init-node-reducer.ts new file mode 100644 index 000000000..72e7f4e5d --- /dev/null +++ b/packages/editor-preset-vision/src/props-reducers/init-node-reducer.ts @@ -0,0 +1,68 @@ +import { + hasOwnProperty, + isI18NObject, + isUseI18NSetter, + convertToI18NObject, + isString, +} from '@ali/lowcode-utils'; +import { isJSExpression, isJSBlock, isJSSlot } from '@ali/lowcode-types'; +import { isVariable, getCurrentFieldIds } from '../utils'; + +export function initNodeReducer(props, node) { + // debugger; + // run initials + const newProps: any = { + ...props, + }; + if (newProps.fieldId) { + const fieldIds = getCurrentFieldIds(); + + // 全局的关闭 uniqueIdChecker 信号,在 ve-utils 中实现 + if (fieldIds.indexOf(props.fieldId) >= 0 && !(window as any).__disable_unique_id_checker__) { + newProps.fieldId = undefined; + } + } + const initials = node.componentMeta.getMetadata().experimental?.initials; + if (initials) { + const getRealValue = (propValue: any) => { + if (isVariable(propValue)) { + return propValue.value; + } + if (isJSExpression(propValue)) { + return propValue.mock; + } + return propValue; + }; + initials.forEach(item => { + // FIXME! this implements SettingTarget + try { + // FIXME! item.name could be 'xxx.xxx' + const ov = newProps[item.name]; + const v = item.initial(node as any, getRealValue(ov)); + if (ov === undefined && v !== undefined) { + newProps[item.name] = v; + } + // 兼容 props 中的属性为 i18n 类型,但是仅提供了一个字符串值,非变量绑定 + if ( + isUseI18NSetter(node.componentMeta.prototype, item.name) && + !isI18NObject(ov) && + !isJSExpression(ov) && + !isJSBlock(ov) && + !isJSSlot(ov) && + !isVariable(ov) && + (isString(v) || isI18NObject(v)) + ) { + newProps[item.name] = convertToI18NObject(v); + } + } catch (e) { + if (hasOwnProperty(props, item.name)) { + newProps[item.name] = props[item.name]; + } + } + if (newProps[item.name] && !node.props.has(item.name)) { + node.props.add(newProps[item.name], item.name, false, { skipSetSlot: true }); + } + }); + } + return newProps; +} diff --git a/packages/editor-preset-vision/src/props-reducers/live-lifecycle-reducer.ts b/packages/editor-preset-vision/src/props-reducers/live-lifecycle-reducer.ts new file mode 100644 index 000000000..a626c3b30 --- /dev/null +++ b/packages/editor-preset-vision/src/props-reducers/live-lifecycle-reducer.ts @@ -0,0 +1,27 @@ +import { globalContext, Editor } from '@ali/lowcode-editor-core'; +import { Node } from '@ali/lowcode-designer'; + +export function liveLifecycleReducer(props: any, node: Node) { + const editor = globalContext.get(Editor); + // live 模式下解析 lifeCycles + if (node.isRoot() && props && props.lifeCycles) { + if (editor.get('designMode') === 'live') { + const lifeCycleMap = { + didMount: 'componentDidMount', + willUnmount: 'componentWillUnMount', + }; + const lifeCycles = props.lifeCycles; + Object.keys(lifeCycleMap).forEach(key => { + if (lifeCycles[key]) { + lifeCycles[lifeCycleMap[key]] = lifeCycles[key]; + } + }); + return props; + } + return { + ...props, + lifeCycles: {}, + }; + } + return props; +} \ No newline at end of file diff --git a/packages/editor-preset-vision/src/props-reducers/remove-empty-prop-reducer.ts b/packages/editor-preset-vision/src/props-reducers/remove-empty-prop-reducer.ts new file mode 100644 index 000000000..305f0bdb7 --- /dev/null +++ b/packages/editor-preset-vision/src/props-reducers/remove-empty-prop-reducer.ts @@ -0,0 +1,23 @@ +import { + cloneDeep, +} from '@ali/lowcode-utils'; + +// 清除空的 props value +export function removeEmptyPropsReducer(props: any, node: Node) { + if (node.isRoot() && props.dataSource && Array.isArray(props.dataSource.online)) { + const online = cloneDeep(props.dataSource.online); + online.forEach((item: any) => { + const newParam: any = {}; + if (Array.isArray(item?.options?.params)) { + item.options.params.forEach((element: any) => { + if (element.name) { + newParam[element.name] = element.value; + } + }); + item.options.params = newParam; + } + }); + props.dataSource.list = online; + } + return props; +} diff --git a/packages/editor-preset-vision/src/props-reducers/style-reducer.ts b/packages/editor-preset-vision/src/props-reducers/style-reducer.ts new file mode 100644 index 000000000..d5facb0a6 --- /dev/null +++ b/packages/editor-preset-vision/src/props-reducers/style-reducer.ts @@ -0,0 +1,51 @@ +import { globalContext, Editor } from '@ali/lowcode-editor-core'; +import { toCss } from '@ali/vu-css-style'; + +export function stylePropsReducer(props: any, node: any) { + if (props && typeof props === 'object' && props.__style__) { + const cssId = `_style_pesudo_${ node.id.replace(/\$/g, '_')}`; + const cssClass = `_css_pesudo_${ node.id.replace(/\$/g, '_')}`; + const styleProp = props.__style__; + appendStyleNode(props, styleProp, cssClass, cssId); + } + if (props && typeof props === 'object' && props.pageStyle) { + const cssId = '_style_pesudo_engine-document'; + const cssClass = 'engine-document'; + const styleProp = props.pageStyle; + appendStyleNode(props, styleProp, cssClass, cssId); + } + if (props && typeof props === 'object' && props.containerStyle) { + const cssId = `_style_pesudo_${ node.id}`; + const cssClass = `_css_pesudo_${ node.id.replace(/\$/g, '_')}`; + const styleProp = props.containerStyle; + appendStyleNode(props, styleProp, cssClass, cssId); + } + return props; +} + +function appendStyleNode(props: any, styleProp: any, cssClass: string, cssId: string) { + const editor = globalContext.get(Editor); + const designer = editor.get('designer'); + const doc = designer.currentDocument?.simulator?.contentDocument; + if (!doc) { + return; + } + const dom = doc.getElementById(cssId); + if (dom) { + dom.parentNode?.removeChild(dom); + } + if (typeof styleProp === 'object') { + styleProp = toCss(styleProp); + } + if (typeof styleProp === 'string') { + const s = doc.createElement('style'); + props.className = cssClass; + s.setAttribute('type', 'text/css'); + s.setAttribute('id', cssId); + doc.getElementsByTagName('head')[0].appendChild(s); + + s.appendChild(doc.createTextNode(styleProp.replace(/(\d+)rpx/g, (a, b) => { + return `${b / 2}px`; + }).replace(/:root/g, `.${ cssClass}`))); + } +} \ No newline at end of file diff --git a/packages/editor-preset-vision/src/props-reducers/upgrade-reducer.ts b/packages/editor-preset-vision/src/props-reducers/upgrade-reducer.ts new file mode 100644 index 000000000..38bbca7ff --- /dev/null +++ b/packages/editor-preset-vision/src/props-reducers/upgrade-reducer.ts @@ -0,0 +1,38 @@ +import { + isPlainObject, +} from '@ali/lowcode-utils'; +import { isJSBlock } from '@ali/lowcode-types'; +import { isVariable } from '../utils'; + +export function upgradePropsReducer(props: any) { + if (!props || !isPlainObject(props)) { + return props; + } + if (isJSBlock(props)) { + if (props.value.componentName === 'Slot') { + return { + type: 'JSSlot', + title: (props.value.props as any)?.slotTitle, + name: (props.value.props as any)?.slotName, + value: props.value.children, + }; + } else { + return props.value; + } + } + if (isVariable(props)) { + return { + type: 'JSExpression', + value: props.variable, + mock: props.value, + }; + } + const newProps: any = {}; + Object.keys(props).forEach((key) => { + if (/^__slot__/.test(key) && props[key] === true) { + return; + } + newProps[key] = upgradePropsReducer(props[key]); + }); + return newProps; +} \ No newline at end of file diff --git a/packages/editor-preset-vision/src/utils/index.ts b/packages/editor-preset-vision/src/utils/index.ts index c07d6ad04..ec3d7ea31 100644 --- a/packages/editor-preset-vision/src/utils/index.ts +++ b/packages/editor-preset-vision/src/utils/index.ts @@ -1,3 +1,25 @@ -export function isVariable(obj: any) { +import { globalContext, Editor } from '@ali/lowcode-editor-core'; + +interface Variable { + type: 'variable'; + variable: string; + value: any; +} + +export function isVariable(obj: any): obj is Variable { return obj && obj.type === 'variable'; } + +export function getCurrentFieldIds() { + const editor = globalContext.get(Editor); + const designer = editor.get('designer'); + const fieldIds: any = []; + const nodesMap = designer?.currentDocument?.nodesMap || new Map(); + nodesMap.forEach((curNode: any) => { + const fieldId = nodesMap?.get(curNode.id)?.getPropValue('fieldId'); + if (fieldId) { + fieldIds.push(fieldId); + } + }); + return fieldIds; +} diff --git a/packages/editor-preset-vision/tests/bundle/prototype.test.ts b/packages/editor-preset-vision/tests/bundle/prototype.test.ts new file mode 100644 index 000000000..a120522a1 --- /dev/null +++ b/packages/editor-preset-vision/tests/bundle/prototype.test.ts @@ -0,0 +1,71 @@ +import set from 'lodash/set'; +import cloneDeep from 'lodash/clonedeep'; +import '../fixtures/window'; +// import { Project } from '../../src/project/project'; +// import { Node } from '../../src/document/node/node'; +// import { Designer } from '../../src/designer/designer'; +import divPrototypeConfig from '../fixtures/prototype/div-vision'; +import divPrototypeMeta from '../fixtures/prototype/div-meta'; +// import VisualEngine from '../../src'; +import { designer } from '../../src/editor'; +import Prototype from '../../src/bundle/prototype'; +import { Editor } from '@ali/lowcode-editor-core'; +// import { getIdsFromSchema, getNodeFromSchemaById } from '../utils'; + + +describe('Prototype', () => { + it('构造函数 - OldPrototypeConfig', () => { + const proto = new Prototype(divPrototypeConfig); + expect(proto.getComponentName()).toBe('Div'); + expect(proto.getId()).toBe('Div'); + expect(proto.getCategory()).toBe('布局'); + expect(proto.getDocUrl()).toBe('http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md'); + expect(proto.getIcon()).toBeUndefined; + expect(proto.getTitle()).toBe('Div'); + expect(proto.isPrototype).toBeTruthy; + expect(proto.isContainer()).toBeTruthy; + expect(proto.isModal()).toBeFalsy; + }); + it('构造函数 - ComponentMetadata', () => { + const proto = new Prototype(divPrototypeMeta); + expect(proto.getComponentName()).toBe('Div'); + expect(proto.getId()).toBe('Div'); + expect(proto.getCategory()).toBe('布局'); + expect(proto.getDocUrl()).toBe('http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md'); + expect(proto.getIcon()).toBeUndefined; + expect(proto.getTitle()).toBe('Div'); + expect(proto.isPrototype).toBeTruthy; + expect(proto.isContainer()).toBeTruthy; + expect(proto.isModal()).toBeFalsy; + }); + it('构造函数 - ComponentMeta', () => { + const meta = designer.createComponentMeta(divPrototypeMeta); + const proto = new Prototype(meta); + expect(proto.getComponentName()).toBe('Div'); + expect(proto.getId()).toBe('Div'); + expect(proto.getCategory()).toBe('布局'); + expect(proto.getDocUrl()).toBe('http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md'); + expect(proto.getIcon()).toBeUndefined; + expect(proto.getTitle()).toBe('Div'); + expect(proto.isPrototype).toBeTruthy; + expect(proto.isContainer()).toBeTruthy; + expect(proto.isModal()).toBeFalsy; + }); + it('构造函数 - 静态函数 create', () => { + const proto = Prototype.create(divPrototypeConfig); + expect(proto.getComponentName()).toBe('Div'); + expect(proto.getId()).toBe('Div'); + expect(proto.getCategory()).toBe('布局'); + expect(proto.getDocUrl()).toBe('http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md'); + expect(proto.getIcon()).toBeUndefined; + expect(proto.getTitle()).toBe('Div'); + expect(proto.isPrototype).toBeTruthy; + expect(proto.isContainer()).toBeTruthy; + expect(proto.isModal()).toBeFalsy; + }); + it('构造函数 - lookup: true', () => { + const proto = Prototype.create(divPrototypeConfig); + const proto2 = Prototype.create(divPrototypeConfig, {}, true); + expect(proto).toBe(proto2); + }); +}); diff --git a/packages/editor-preset-vision/tests/fixtures/prototype/div-meta.ts b/packages/editor-preset-vision/tests/fixtures/prototype/div-meta.ts new file mode 100644 index 000000000..a2b410494 --- /dev/null +++ b/packages/editor-preset-vision/tests/fixtures/prototype/div-meta.ts @@ -0,0 +1,259 @@ +export default { + componentName: 'Div', + title: '容器', + docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md', + devMode: 'procode', + tags: ['布局'], + configure: { + props: [ + { + type: 'field', + name: 'behavior', + title: '默认状态', + extraProps: { + display: 'inline', + defaultValue: 'NORMAL', + }, + setter: { + componentName: 'MixedSetter', + props: { + setters: [ + { + key: null, + ref: null, + props: { + options: [ + { + title: '普通', + value: 'NORMAL', + }, + { + title: '隐藏', + value: 'HIDDEN', + }, + ], + loose: false, + cancelable: false, + }, + _owner: null, + }, + 'VariableSetter', + ], + }, + }, + }, + { + type: 'field', + name: '__style__', + title: { + label: '样式设置', + tip: '点击 ? 查看样式设置器用法指南', + docUrl: 'https://lark.alipay.com/legao/help/design-tool-style', + }, + extraProps: { + display: 'accordion', + defaultValue: {}, + }, + setter: { + key: null, + ref: null, + props: { + advanced: true, + }, + _owner: null, + }, + }, + { + type: 'group', + name: 'groupkh97h5kc', + title: '高级', + extraProps: { + display: 'accordion', + }, + items: [ + { + type: 'field', + name: 'fieldId', + title: { + label: '唯一标识', + }, + extraProps: { + display: 'block', + }, + setter: { + key: null, + ref: null, + props: { + placeholder: '请输入唯一标识', + multiline: false, + rows: 10, + required: false, + pattern: null, + maxLength: null, + }, + _owner: null, + }, + }, + { + type: 'field', + name: 'useFieldIdAsDomId', + title: { + label: '将唯一标识用作 DOM ID', + }, + extraProps: { + display: 'block', + defaultValue: false, + }, + setter: { + key: null, + ref: null, + props: {}, + _owner: null, + }, + }, + { + type: 'field', + name: 'customClassName', + title: '自定义样式类', + extraProps: { + display: 'block', + defaultValue: '', + }, + setter: { + componentName: 'MixedSetter', + props: { + setters: [ + { + key: null, + ref: null, + props: { + placeholder: null, + multiline: false, + rows: 10, + required: false, + pattern: null, + maxLength: null, + }, + _owner: null, + }, + 'VariableSetter', + ], + }, + }, + }, + { + type: 'field', + name: 'events', + title: { + label: '动作设置', + tip: '点击 ? 查看如何设置组件的事件响应动作', + docUrl: 'https://lark.alipay.com/legao/legao/events-call', + }, + extraProps: { + display: 'accordion', + defaultValue: { + ignored: true, + }, + }, + setter: { + key: null, + ref: null, + props: { + events: [ + { + name: 'onClick', + title: '当点击时', + initialValue: + "/**\n * 容器 当点击时\n */\nfunction onClick(event) {\n console.log('onClick', event);\n}", + }, + { + name: 'onMouseEnter', + title: '当鼠标进入时', + initialValue: + "/**\n * 容器 当鼠标进入时\n */\nfunction onMouseEnter(event) {\n console.log('onMouseEnter', event);\n}", + }, + { + name: 'onMouseLeave', + title: '当鼠标离开时', + initialValue: + "/**\n * 容器 当鼠标离开时\n */\nfunction onMouseLeave(event) {\n console.log('onMouseLeave', event);\n}", + }, + ], + }, + _owner: null, + }, + }, + { + type: 'field', + name: 'onClick', + extraProps: { + defaultValue: { + ignored: true, + }, + }, + setter: 'I18nSetter', + }, + { + type: 'field', + name: 'onMouseEnter', + extraProps: { + defaultValue: { + ignored: true, + }, + }, + setter: 'I18nSetter', + }, + { + type: 'field', + name: 'onMouseLeave', + extraProps: { + defaultValue: { + ignored: true, + }, + }, + setter: 'I18nSetter', + }, + ], + }, + ], + component: { + isContainer: true, + nestingRule: {}, + }, + supports: {}, + }, + experimental: { + callbacks: {}, + initials: [ + { + name: 'behavior', + }, + { + name: '__style__', + }, + { + name: 'fieldId', + }, + { + name: 'useFieldIdAsDomId', + }, + { + name: 'customClassName', + }, + { + name: 'events', + }, + { + name: 'onClick', + }, + { + name: 'onMouseEnter', + }, + { + name: 'onMouseLeave', + }, + ], + filters: [], + autoruns: [], + }, +}; diff --git a/packages/editor-preset-vision/tests/fixtures/prototype/div-vision.ts b/packages/editor-preset-vision/tests/fixtures/prototype/div-vision.ts new file mode 100644 index 000000000..c4ae4374f --- /dev/null +++ b/packages/editor-preset-vision/tests/fixtures/prototype/div-vision.ts @@ -0,0 +1,175 @@ +export default { + title: '容器', + componentName: 'Div', + docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md', + category: '布局', + isContainer: true, + configure: [ + { + name: 'behavior', + title: '默认状态', + display: 'inline', + initialValue: 'NORMAL', + supportVariable: true, + setter: { + key: null, + ref: null, + props: { + options: [ + { + title: '普通', + value: 'NORMAL', + }, + { + title: '隐藏', + value: 'HIDDEN', + }, + ], + loose: false, + cancelable: false, + }, + _owner: null, + }, + }, + { + name: '__style__', + title: '样式设置', + display: 'accordion', + collapsed: false, + initialValue: {}, + tip: { + url: 'https://lark.alipay.com/legao/help/design-tool-style', + content: '点击 ? 查看样式设置器用法指南', + }, + setter: { + key: null, + ref: null, + props: { + advanced: true, + }, + _owner: null, + }, + }, + { + type: 'group', + title: '高级', + display: 'accordion', + items: [ + { + name: 'fieldId', + title: '唯一标识', + display: 'block', + tip: + '组件的唯一标识符,不能够与其它组件重名,不能够为空,且只能够使用以字母开头的,下划线以及数字的组合。', + setter: { + key: null, + ref: null, + props: { + placeholder: '请输入唯一标识', + multiline: false, + rows: 10, + required: false, + pattern: null, + maxLength: null, + }, + _owner: null, + }, + }, + { + name: 'useFieldIdAsDomId', + title: '将唯一标识用作 DOM ID', + display: 'block', + tip: + '开启这个配置项后,会在当前组件的 HTML 元素上加入 id="当前组件的 fieldId",一般用于做 utils 的绑定,不常用', + initialValue: false, + setter: { + key: null, + ref: null, + props: {}, + _owner: null, + }, + }, + { + name: 'customClassName', + title: '自定义样式类', + display: 'block', + supportVariable: true, + initialValue: '', + setter: { + key: null, + ref: null, + props: { + placeholder: null, + multiline: false, + rows: 10, + required: false, + pattern: null, + maxLength: null, + }, + _owner: null, + }, + }, + { + name: 'events', + title: '动作设置', + tip: { + url: 'https://lark.alipay.com/legao/legao/events-call', + content: '点击 ? 查看如何设置组件的事件响应动作', + }, + display: 'accordion', + initialValue: { + ignored: true, + }, + setter: { + key: null, + ref: null, + props: { + events: [ + { + name: 'onClick', + title: '当点击时', + initialValue: + "/**\n * 容器 当点击时\n */\nfunction onClick(event) {\n console.log('onClick', event);\n}", + }, + { + name: 'onMouseEnter', + title: '当鼠标进入时', + initialValue: + "/**\n * 容器 当鼠标进入时\n */\nfunction onMouseEnter(event) {\n console.log('onMouseEnter', event);\n}", + }, + { + name: 'onMouseLeave', + title: '当鼠标离开时', + initialValue: + "/**\n * 容器 当鼠标离开时\n */\nfunction onMouseLeave(event) {\n console.log('onMouseLeave', event);\n}", + }, + ], + }, + _owner: null, + }, + }, + { + name: 'onClick', + display: 'none', + initialValue: { + ignored: true, + }, + }, + { + name: 'onMouseEnter', + display: 'none', + initialValue: { + ignored: true, + }, + }, + { + name: 'onMouseLeave', + display: 'none', + initialValue: { + ignored: true, + }, + }, + ], + }, + ], +}; diff --git a/packages/editor-preset-vision/tests/fixtures/schema/form.ts b/packages/editor-preset-vision/tests/fixtures/schema/form.ts index f9d9477fb..905b89561 100644 --- a/packages/editor-preset-vision/tests/fixtures/schema/form.ts +++ b/packages/editor-preset-vision/tests/fixtures/schema/form.ts @@ -1,750 +1,254 @@ export default { - schemaType: 'superform', - schemaVersion: '5.0', - pages: [ + componentName: 'Page', + id: 'node_k1ow3cb9', + props: { + extensions: { + 启用页头: true, + }, + pageStyle: { + backgroundColor: '#f2f3f5', + }, + containerStyle: {}, + className: 'page_kh05zf9c', + templateVersion: '1.0.0', + }, + lifeCycles: { + constructor: { + type: 'js', + compiled: + "function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}", + source: + "function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}", + }, + }, + condition: true, + css: + 'body{background-color:#f2f3f5}.card_kh05zf9d {\n margin-bottom: 12px;\n}.card_kh05zf9e {\n margin-bottom: 12px;\n}.button_kh05zf9f {\n margin-right: 16px;\n width: 80px\n}.button_kh05zf9g {\n width: 80px;\n}.div_kh05zf9h {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}', + methods: { + __initMethods__: { + type: 'js', + source: 'function (exports, module) { /*set actions code here*/ }', + compiled: 'function (exports, module) { /*set actions code here*/ }', + }, + }, + dataSource: { + offline: [], + globalConfig: { + fit: { + compiled: '', + source: '', + type: 'js', + error: {}, + }, + }, + online: [], + sync: true, + list: [], + }, + children: [ { - componentsMap: [ - { - componentName: 'Text', - package: '@ali/vc-text', - }, - { - componentName: 'Slot', - package: '@ali/vc-slot', - }, + componentName: 'RootHeader', + id: 'node_k1ow3cba', + props: {}, + condition: true, + children: [ { componentName: 'PageHeader', - package: '@ali/vc-deep', - }, - { - componentName: 'RootHeader', - package: '@ali/vc-page', - }, - { - componentName: 'TextField', - package: '@ali/vc-deep', - }, - { - componentName: 'Column', - package: '@ali/vc-deep', - }, - { - componentName: 'SelectField', - package: '@ali/vc-deep', - }, - { - componentName: 'ColumnsLayout', - package: '@ali/vc-deep', - }, - { - componentName: 'CardContent', - package: '@ali/vc-deep', - }, - { - componentName: 'Card', - package: '@ali/vc-deep', - }, - { - componentName: 'Button', - package: '@ali/vc-deep', - }, - { - componentName: 'Div', - package: '@ali/vc-div', - }, - { - componentName: 'Form', - package: '@ali/vc-deep', - }, - { - componentName: 'RootContent', - package: '@ali/vc-page', - }, - { - componentName: 'RootFooter', - package: '@ali/vc-page', - }, - { - componentName: 'Page', - package: '@ali/vc-page', - }, - ], - componentsTree: [ - { - componentName: 'Page', - id: 'node_k1ow3cb9', + id: 'node_k1ow3cbd', props: { - extensions: { - 启用页头: true, - }, - pageStyle: { - backgroundColor: '#f2f3f5', - }, - containerStyle: {}, - className: 'page_kh05zf9c', - templateVersion: '1.0.0', - }, - lifeCycles: { - constructor: { - type: 'js', - compiled: - "function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}", - source: - "function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}", - }, + extraContent: '', + __slot__extraContent: false, + __slot__action: false, + title: '', + content: '', + __slot__logo: false, + __slot__crumb: false, + crumb: '', + tab: '', + logo: '', + action: '', + __slot__tab: false, + __style__: {}, + __slot__content: false, + fieldId: 'pageHeader_k1ow3h1i', + subTitle: '', }, condition: true, - css: - 'body{background-color:#f2f3f5}.card_kh05zf9d {\n margin-bottom: 12px;\n}.card_kh05zf9e {\n margin-bottom: 12px;\n}.button_kh05zf9f {\n margin-right: 16px;\n width: 80px\n}.button_kh05zf9g {\n width: 80px;\n}.div_kh05zf9h {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}', - methods: { - __initMethods__: { - type: 'js', - source: 'function (exports, module) { /*set actions code here*/ }', - compiled: 'function (exports, module) { /*set actions code here*/ }', + }, + ], + }, + { + componentName: 'RootContent', + id: 'node_k1ow3cbb', + props: { + contentBgColor: 'transparent', + contentPadding: '0', + contentMargin: '20', + }, + condition: true, + children: [ + { + componentName: 'Form', + id: 'node_k1ow3cbq', + props: { + size: 'medium', + labelAlign: 'top', + autoValidate: true, + scrollToFirstError: true, + autoUnmount: true, + behavior: 'NORMAL', + dataSource: { + type: 'variable', + variable: 'state.formData', }, + __style__: {}, + fieldId: 'form', + fieldOptions: {}, }, - dataSource: { - offline: [], - globalConfig: { - fit: { - compiled: '', - source: '', - type: 'js', - error: {}, - }, - }, - online: [], - sync: true, - list: [], - }, + condition: true, children: [ { - componentName: 'RootHeader', - id: 'node_k1ow3cba', - props: {}, - condition: true, - children: [ - { - componentName: 'PageHeader', - id: 'node_k1ow3cbd', - props: { - extraContent: '', - __slot__extraContent: false, - __slot__action: false, - title: { - type: 'JSBlock', - value: { - componentName: 'Slot', - children: [ - { - componentName: 'Text', - id: 'node_k1ow3cbf', - props: { - showTitle: false, - behavior: 'NORMAL', - content: { - type: 'variable', - value: { - use: 'zh_CN', - en_US: 'Title', - zh_CN: '个人信息', - type: 'i18n', - }, - variable: 'state.title', - }, - __style__: {}, - fieldId: 'text_k1ow3h1j', - maxLine: 0, - }, - condition: true, - }, - ], - props: { - slotTitle: '标题区域', - slotName: 'title', - }, - }, - }, - content: '', - __slot__logo: false, - __slot__crumb: false, - crumb: '', - tab: '', - logo: '', - action: '', - __slot__tab: false, - __style__: {}, - __slot__content: false, - fieldId: 'pageHeader_k1ow3h1i', - subTitle: { - type: 'JSBlock', - value: { - componentName: 'Slot', - children: [ - { - componentName: 'Text', - id: 'node_ockh05zr7j3', - props: { - content: { - type: 'i18n', - en_US: 'Title', - zh_CN: '副标题', - }, - showTitle: false, - behavior: 'NORMAL', - maxLine: 0, - __style__: {}, - fieldId: 'text_kh06n1mc', - }, - }, - ], - props: { - slotTitle: '副标题', - slotName: 'subTitle', - }, - }, - }, - }, - condition: true, - }, - ], - }, - { - componentName: 'RootContent', - id: 'node_k1ow3cbb', + componentName: 'Card', + id: 'node_k1ow3cbj', props: { - contentBgColor: 'transparent', - contentPadding: '0', - contentMargin: '20', + __slot__title: false, + subTitle: { + use: 'zh_CN', + en_US: '', + zh_CN: '', + type: 'i18n', + }, + __slot__subTitle: false, + extra: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + className: 'card_kh05zf9d', + title: { + use: 'zh_CN', + en_US: 'Title', + zh_CN: '基本信息', + type: 'i18n', + }, + __slot__extra: false, + showHeadDivider: true, + __style__: ':root {\n margin-bottom: 12px;\n}', + showTitleBullet: true, + contentHeight: '', + fieldId: 'card_k1ow3h1l', + dividerNoInset: false, }, condition: true, children: [ { - componentName: 'Form', - id: 'node_k1ow3cbq', - props: { - size: 'medium', - labelAlign: 'top', - autoValidate: true, - scrollToFirstError: true, - autoUnmount: true, - behavior: 'NORMAL', - dataSource: { - type: 'variable', - variable: 'state.formData', - }, - __style__: {}, - fieldId: 'form', - fieldOptions: {}, - }, + componentName: 'CardContent', + id: 'node_k1ow3cbk', + props: {}, condition: true, children: [ { - componentName: 'Card', - id: 'node_k1ow3cbj', + componentName: 'ColumnsLayout', + id: 'node_k1ow3cbw', props: { - __slot__title: false, - subTitle: { - use: 'zh_CN', - en_US: '', - zh_CN: '', - type: 'i18n', - }, - __slot__subTitle: false, - extra: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - className: 'card_kh05zf9d', - title: { - use: 'zh_CN', - en_US: 'Title', - zh_CN: '基本信息', - type: 'i18n', - }, - __slot__extra: false, - showHeadDivider: true, - __style__: ':root {\n margin-bottom: 12px;\n}', - showTitleBullet: true, - contentHeight: '', - fieldId: 'card_k1ow3h1l', - dividerNoInset: false, + layout: '4:8', + columnGap: '20', + rowGap: 0, + __style__: {}, + fieldId: 'columns_k1ow3h1v', }, condition: true, children: [ { - componentName: 'CardContent', - id: 'node_k1ow3cbk', - props: {}, - condition: true, - children: [ - { - componentName: 'ColumnsLayout', - id: 'node_k1ow3cbw', - props: { - layout: '4:8', - columnGap: '20', - rowGap: 0, - __style__: {}, - fieldId: 'columns_k1ow3h1v', - }, - condition: true, - children: [ - { - componentName: 'Column', - id: 'node_k1ow3cbx', - props: { - colSpan: '', - __style__: {}, - fieldId: 'column_k1p1bnjm', - }, - condition: true, - children: [ - { - componentName: 'TextField', - id: 'node_k1ow3cbz', - props: { - fieldName: 'name', - hasClear: false, - autoFocus: false, - tips: { - en_US: '', - zh_CN: '', - type: 'i18n', - }, - trim: false, - labelTextAlign: 'right', - placeholder: { - use: 'zh_CN', - en_US: 'please input', - zh_CN: '请输入', - type: 'i18n', - }, - state: '', - behavior: 'NORMAL', - value: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - addonBefore: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - validation: [ - { - type: 'required', - }, - ], - hasLimitHint: false, - cutString: false, - __style__: {}, - fieldId: 'textField_k1ow3h1w', - htmlType: 'input', - autoHeight: false, - labelColOffset: 0, - label: { - use: 'zh_CN', - en_US: 'TextField', - zh_CN: '姓名', - type: 'i18n', - }, - __category__: 'form', - labelColSpan: 4, - wrapperColSpan: 0, - rows: 4, - addonAfter: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - wrapperColOffset: 0, - size: 'medium', - labelAlign: 'top', - __useMediator: 'value', - labelTipsTypes: 'none', - labelTipsIcon: '', - labelTipsText: { - type: 'i18n', - use: 'zh_CN', - en_US: '', - zh_CN: '', - }, - maxLength: 200, - }, - condition: true, - }, - { - componentName: 'TextField', - id: 'node_k1ow3cc1', - props: { - fieldName: 'englishName', - hasClear: false, - autoFocus: false, - tips: { - en_US: '', - zh_CN: '', - type: 'i18n', - }, - trim: false, - labelTextAlign: 'right', - placeholder: { - use: 'zh_CN', - en_US: 'please input', - zh_CN: '请输入', - type: 'i18n', - }, - state: '', - behavior: 'NORMAL', - value: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - addonBefore: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - validation: [], - hasLimitHint: false, - cutString: false, - __style__: {}, - fieldId: 'textField_k1ow3h1y', - htmlType: 'input', - autoHeight: false, - labelColOffset: 0, - label: { - use: 'zh_CN', - en_US: 'TextField', - zh_CN: '英文名', - type: 'i18n', - }, - __category__: 'form', - labelColSpan: 4, - wrapperColSpan: 0, - rows: 4, - addonAfter: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - wrapperColOffset: 0, - size: 'medium', - labelAlign: 'top', - __useMediator: 'value', - labelTipsTypes: 'none', - labelTipsIcon: '', - labelTipsText: { - type: 'i18n', - use: 'zh_CN', - en_US: '', - zh_CN: '', - }, - maxLength: 200, - }, - condition: true, - }, - { - componentName: 'TextField', - id: 'node_k1ow3cc3', - props: { - fieldName: 'jobTitle', - hasClear: false, - autoFocus: false, - tips: { - en_US: '', - zh_CN: '', - type: 'i18n', - }, - trim: false, - labelTextAlign: 'right', - placeholder: { - use: 'zh_CN', - en_US: 'please input', - zh_CN: '请输入', - type: 'i18n', - }, - state: '', - behavior: 'NORMAL', - value: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - addonBefore: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - validation: [], - hasLimitHint: false, - cutString: false, - __style__: {}, - fieldId: 'textField_k1ow3h20', - htmlType: 'input', - autoHeight: false, - labelColOffset: 0, - label: { - use: 'zh_CN', - en_US: 'TextField', - zh_CN: '职位', - type: 'i18n', - }, - __category__: 'form', - labelColSpan: 4, - wrapperColSpan: 0, - rows: 4, - addonAfter: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - wrapperColOffset: 0, - size: 'medium', - labelAlign: 'top', - __useMediator: 'value', - labelTipsTypes: 'none', - labelTipsIcon: '', - labelTipsText: { - type: 'i18n', - use: 'zh_CN', - en_US: '', - zh_CN: '', - }, - maxLength: 200, - }, - condition: true, - }, - ], - }, - { - componentName: 'Column', - id: 'node_k1ow3cby', - props: { - colSpan: '', - __style__: {}, - fieldId: 'column_k1p1bnjn', - }, - condition: true, - children: [ - { - componentName: 'TextField', - id: 'node_k1ow3cc2', - props: { - fieldName: 'nickName', - hasClear: false, - autoFocus: false, - tips: { - en_US: '', - zh_CN: '', - type: 'i18n', - }, - trim: false, - labelTextAlign: 'right', - placeholder: { - use: 'zh_CN', - en_US: 'please input', - zh_CN: '请输入', - type: 'i18n', - }, - state: '', - behavior: 'NORMAL', - value: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - addonBefore: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - validation: [], - hasLimitHint: false, - cutString: false, - __style__: {}, - fieldId: 'textField_k1ow3h1z', - htmlType: 'input', - autoHeight: false, - labelColOffset: 0, - label: { - use: 'zh_CN', - en_US: 'TextField', - zh_CN: '花名', - type: 'i18n', - }, - __category__: 'form', - labelColSpan: 4, - wrapperColSpan: 0, - rows: 4, - addonAfter: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - wrapperColOffset: 0, - size: 'medium', - labelAlign: 'top', - __useMediator: 'value', - labelTipsTypes: 'none', - labelTipsIcon: '', - labelTipsText: { - type: 'i18n', - use: 'zh_CN', - en_US: '', - zh_CN: '', - }, - maxLength: 200, - }, - condition: true, - }, - { - componentName: 'SelectField', - id: 'node_k1ow3cc0', - props: { - fieldName: 'gender', - hasClear: false, - tips: { - en_US: '', - zh_CN: '', - type: 'i18n', - }, - mode: 'single', - showSearch: false, - autoWidth: true, - labelTextAlign: 'right', - placeholder: { - use: 'zh_CN', - en_US: 'please select', - zh_CN: '请选择', - type: 'i18n', - }, - hasBorder: true, - behavior: 'NORMAL', - value: '', - validation: [ - { - type: 'required', - }, - ], - __style__: {}, - fieldId: 'select_k1ow3h1x', - notFoundContent: { - use: 'zh_CN', - type: 'i18n', - }, - labelColOffset: 0, - label: { - use: 'zh_CN', - en_US: 'SelectField', - zh_CN: '性别', - type: 'i18n', - }, - __category__: 'form', - labelColSpan: 4, - wrapperColSpan: 0, - wrapperColOffset: 0, - hasSelectAll: false, - hasArrow: true, - size: 'medium', - labelAlign: 'top', - filterLocal: true, - dataSource: [ - { - defaultChecked: false, - text: { - en_US: 'Option 1', - zh_CN: '男', - type: 'i18n', - __sid__: 'param_k1owc4tb', - }, - __sid__: 'serial_k1owc4t1', - value: 'M', - sid: 'opt_k1owc4t2', - }, - { - defaultChecked: false, - text: { - en_US: 'Option 2', - zh_CN: '女', - type: 'i18n', - __sid__: 'param_k1owc4tf', - }, - __sid__: 'serial_k1owc4t2', - value: 'F', - sid: 'opt_k1owc4t3', - }, - ], - __useMediator: 'value', - labelTipsTypes: 'none', - labelTipsIcon: '', - labelTipsText: { - type: 'i18n', - use: 'zh_CN', - en_US: '', - zh_CN: '', - }, - useDetailValue: false, - searchDelay: 300, - }, - condition: true, - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - componentName: 'Card', - id: 'node_k1ow3cbl', - props: { - __slot__title: false, - subTitle: { - use: 'zh_CN', - en_US: '', - zh_CN: '', - type: 'i18n', - }, - __slot__subTitle: false, - extra: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - className: 'card_kh05zf9e', - title: { - use: 'zh_CN', - en_US: 'Title', - zh_CN: '部门信息', - type: 'i18n', - }, - __slot__extra: false, - showHeadDivider: true, - __style__: ':root {\n margin-bottom: 12px;\n}', - showTitleBullet: true, - contentHeight: '', - fieldId: 'card_k1ow3h1m', - dividerNoInset: false, - }, - condition: true, - children: [ - { - componentName: 'CardContent', - id: 'node_k1ow3cbm', - props: {}, + componentName: 'Column', + id: 'node_k1ow3cbx', + props: { + colSpan: '', + __style__: {}, + fieldId: 'column_k1p1bnjm', + }, condition: true, children: [ { componentName: 'TextField', - id: 'node_k1ow3cc4', + id: 'node_k1ow3cbz', props: { - fieldName: 'department', + fieldName: 'name', + hasClear: false, + autoFocus: false, + tips: { + en_US: '', + zh_CN: '', + type: 'i18n', + }, + trim: false, + labelTextAlign: 'right', + placeholder: { + use: 'zh_CN', + en_US: 'please input', + zh_CN: '请输入', + type: 'i18n', + }, + state: '', + behavior: 'NORMAL', + value: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + addonBefore: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + validation: [ + { + type: 'required', + }, + ], + hasLimitHint: false, + cutString: false, + __style__: {}, + fieldId: 'textField_k1ow3h1w', + htmlType: 'input', + autoHeight: false, + labelColOffset: 0, + label: { + use: 'zh_CN', + en_US: 'TextField', + zh_CN: '姓名', + type: 'i18n', + }, + __category__: 'form', + labelColSpan: 4, + wrapperColSpan: 0, + rows: 4, + addonAfter: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + wrapperColOffset: 0, + size: 'medium', + labelAlign: 'top', + __useMediator: 'value', + labelTipsTypes: 'none', + labelTipsIcon: '', + labelTipsText: { + type: 'i18n', + use: 'zh_CN', + en_US: '', + zh_CN: '', + }, + maxLength: 200, + }, + condition: true, + }, + { + componentName: 'TextField', + id: 'node_k1ow3cc1', + props: { + fieldName: 'englishName', hasClear: false, autoFocus: false, tips: { @@ -776,14 +280,14 @@ export default { hasLimitHint: false, cutString: false, __style__: {}, - fieldId: 'textField_k1ow3h21', + fieldId: 'textField_k1ow3h1y', htmlType: 'input', autoHeight: false, labelColOffset: 0, label: { use: 'zh_CN', en_US: 'TextField', - zh_CN: '所属部门', + zh_CN: '英文名', type: 'i18n', }, __category__: 'form', @@ -812,264 +316,251 @@ export default { condition: true, }, { - componentName: 'ColumnsLayout', - id: 'node_k1ow3cc5', + componentName: 'TextField', + id: 'node_k1ow3cc3', props: { - layout: '6:6', - columnGap: '20', - rowGap: 0, + fieldName: 'jobTitle', + hasClear: false, + autoFocus: false, + tips: { + en_US: '', + zh_CN: '', + type: 'i18n', + }, + trim: false, + labelTextAlign: 'right', + placeholder: { + use: 'zh_CN', + en_US: 'please input', + zh_CN: '请输入', + type: 'i18n', + }, + state: '', + behavior: 'NORMAL', + value: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + addonBefore: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + validation: [], + hasLimitHint: false, + cutString: false, __style__: {}, - fieldId: 'columns_k1ow3h22', + fieldId: 'textField_k1ow3h20', + htmlType: 'input', + autoHeight: false, + labelColOffset: 0, + label: { + use: 'zh_CN', + en_US: 'TextField', + zh_CN: '职位', + type: 'i18n', + }, + __category__: 'form', + labelColSpan: 4, + wrapperColSpan: 0, + rows: 4, + addonAfter: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + wrapperColOffset: 0, + size: 'medium', + labelAlign: 'top', + __useMediator: 'value', + labelTipsTypes: 'none', + labelTipsIcon: '', + labelTipsText: { + type: 'i18n', + use: 'zh_CN', + en_US: '', + zh_CN: '', + }, + maxLength: 200, }, condition: true, - children: [ - { - componentName: 'Column', - id: 'node_k1ow3cc6', - props: { - colSpan: '', - __style__: {}, - fieldId: 'column_k1p1bnjo', - }, - condition: true, - children: [ - { - componentName: 'TextField', - id: 'node_k1ow3cc8', - props: { - fieldName: 'leader', - hasClear: false, - autoFocus: false, - tips: { - en_US: '', - zh_CN: '', - type: 'i18n', - }, - trim: false, - labelTextAlign: 'right', - placeholder: { - use: 'zh_CN', - en_US: 'please input', - zh_CN: '请输入', - type: 'i18n', - }, - state: '', - behavior: 'NORMAL', - value: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - addonBefore: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - validation: [], - hasLimitHint: false, - cutString: false, - __style__: {}, - fieldId: 'textField_k1ow3h23', - htmlType: 'input', - autoHeight: false, - labelColOffset: 0, - label: { - use: 'zh_CN', - en_US: 'TextField', - zh_CN: '主管', - type: 'i18n', - }, - __category__: 'form', - labelColSpan: 4, - wrapperColSpan: 0, - rows: 4, - addonAfter: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - wrapperColOffset: 0, - size: 'medium', - labelAlign: 'top', - __useMediator: 'value', - labelTipsTypes: 'none', - labelTipsIcon: '', - labelTipsText: { - type: 'i18n', - use: 'zh_CN', - en_US: '', - zh_CN: '', - }, - maxLength: 200, - }, - condition: true, - }, - ], - }, - { - componentName: 'Column', - id: 'node_k1ow3cc7', - props: { - colSpan: '', - __style__: {}, - fieldId: 'column_k1p1bnjp', - }, - condition: true, - children: [ - { - componentName: 'TextField', - id: 'node_k1ow3cc9', - props: { - fieldName: 'hrg', - hasClear: false, - autoFocus: false, - tips: { - en_US: '', - zh_CN: '', - type: 'i18n', - }, - trim: false, - labelTextAlign: 'right', - placeholder: { - use: 'zh_CN', - en_US: 'please input', - zh_CN: '请输入', - type: 'i18n', - }, - state: '', - behavior: 'NORMAL', - value: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - addonBefore: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - validation: [], - hasLimitHint: false, - cutString: false, - __style__: {}, - fieldId: 'textField_k1ow3h24', - htmlType: 'input', - autoHeight: false, - labelColOffset: 0, - label: { - use: 'zh_CN', - en_US: 'TextField', - zh_CN: 'HRG', - type: 'i18n', - }, - __category__: 'form', - labelColSpan: 4, - wrapperColSpan: 0, - rows: 4, - addonAfter: { - use: 'zh_CN', - zh_CN: '', - type: 'i18n', - }, - wrapperColOffset: 0, - size: 'medium', - labelAlign: 'top', - __useMediator: 'value', - labelTipsTypes: 'none', - labelTipsIcon: '', - labelTipsText: { - type: 'i18n', - use: 'zh_CN', - en_US: '', - zh_CN: '', - }, - maxLength: 200, - }, - condition: true, - }, - ], - }, - ], }, ], }, - ], - }, - { - componentName: 'Div', - id: 'node_k1ow3cbo', - props: { - className: 'div_kh05zf9h', - behavior: 'NORMAL', - __style__: - ':root {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}', - events: {}, - fieldId: 'div_k1ow3h1o', - useFieldIdAsDomId: false, - customClassName: '', - }, - condition: true, - children: [ { - componentName: 'Button', - id: 'node_k1ow3cbn', + componentName: 'Column', + id: 'node_k1ow3cby', props: { - triggerEventsWhenLoading: false, - onClick: { - rawType: 'events', - type: 'JSExpression', - value: - 'this.utils.legaoBuiltin.execEventFlow.bind(this, [this.submit])', - events: [ - { - name: 'submit', - id: 'submit', - params: {}, - type: 'actionRef', - uuid: '1570966253282_0', + colSpan: '', + __style__: {}, + fieldId: 'column_k1p1bnjn', + }, + condition: true, + children: [ + { + componentName: 'TextField', + id: 'node_k1ow3cc2', + props: { + fieldName: 'nickName', + hasClear: false, + autoFocus: false, + tips: { + en_US: '', + zh_CN: '', + type: 'i18n', }, - ], + trim: false, + labelTextAlign: 'right', + placeholder: { + use: 'zh_CN', + en_US: 'please input', + zh_CN: '请输入', + type: 'i18n', + }, + state: '', + behavior: 'NORMAL', + value: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + addonBefore: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + validation: [], + hasLimitHint: false, + cutString: false, + __style__: {}, + fieldId: 'textField_k1ow3h1z', + htmlType: 'input', + autoHeight: false, + labelColOffset: 0, + label: { + use: 'zh_CN', + en_US: 'TextField', + zh_CN: '花名', + type: 'i18n', + }, + __category__: 'form', + labelColSpan: 4, + wrapperColSpan: 0, + rows: 4, + addonAfter: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + wrapperColOffset: 0, + size: 'medium', + labelAlign: 'top', + __useMediator: 'value', + labelTipsTypes: 'none', + labelTipsIcon: '', + labelTipsText: { + type: 'i18n', + use: 'zh_CN', + en_US: '', + zh_CN: '', + }, + maxLength: 200, + }, + condition: true, }, - size: 'medium', - baseIcon: '', - otherIcon: '', - className: 'button_kh05zf9f', - type: 'primary', - behavior: 'NORMAL', - loading: false, - content: { - use: 'zh_CN', - en_US: 'Button', - zh_CN: '提交', - type: 'i18n', + { + componentName: 'SelectField', + id: 'node_k1ow3cc0', + props: { + fieldName: 'gender', + hasClear: false, + tips: { + en_US: '', + zh_CN: '', + type: 'i18n', + }, + mode: 'single', + showSearch: false, + autoWidth: true, + labelTextAlign: 'right', + placeholder: { + use: 'zh_CN', + en_US: 'please select', + zh_CN: '请选择', + type: 'i18n', + }, + hasBorder: true, + behavior: 'NORMAL', + value: '', + validation: [ + { + type: 'required', + }, + ], + __style__: {}, + fieldId: 'select_k1ow3h1x', + notFoundContent: { + use: 'zh_CN', + type: 'i18n', + }, + labelColOffset: 0, + label: { + use: 'zh_CN', + en_US: 'SelectField', + zh_CN: '性别', + type: 'i18n', + }, + __category__: 'form', + labelColSpan: 4, + wrapperColSpan: 0, + wrapperColOffset: 0, + hasSelectAll: false, + hasArrow: true, + size: 'medium', + labelAlign: 'top', + filterLocal: true, + dataSource: [ + { + defaultChecked: false, + text: { + en_US: 'Option 1', + zh_CN: '男', + type: 'i18n', + __sid__: 'param_k1owc4tb', + }, + __sid__: 'serial_k1owc4t1', + value: 'M', + sid: 'opt_k1owc4t2', + }, + { + defaultChecked: false, + text: { + en_US: 'Option 2', + zh_CN: '女', + type: 'i18n', + __sid__: 'param_k1owc4tf', + }, + __sid__: 'serial_k1owc4t2', + value: 'F', + sid: 'opt_k1owc4t3', + }, + ], + __useMediator: 'value', + labelTipsTypes: 'none', + labelTipsIcon: '', + labelTipsText: { + type: 'i18n', + use: 'zh_CN', + en_US: '', + zh_CN: '', + }, + useDetailValue: false, + searchDelay: 300, + }, + condition: true, }, - __style__: ':root {\n margin-right: 16px;\n width: 80px\n}', - fieldId: 'button_k1ow3h1n', - }, - condition: true, - }, - { - componentName: 'Button', - id: 'node_k1ow3cbp', - props: { - triggerEventsWhenLoading: false, - size: 'medium', - baseIcon: '', - otherIcon: '', - className: 'button_kh05zf9g', - type: 'normal', - behavior: 'NORMAL', - loading: false, - content: { - use: 'zh_CN', - en_US: 'Button', - zh_CN: '取消', - type: 'i18n', - }, - __style__: ':root {\n width: 80px;\n}', - fieldId: 'button_k1ow3h1p', - }, - condition: true, + ], }, ], }, @@ -1078,34 +569,387 @@ export default { ], }, { - componentName: 'RootFooter', - id: 'node_k1ow3cbc', - props: {}, + componentName: 'Card', + id: 'node_k1ow3cbl', + props: { + __slot__title: false, + subTitle: { + use: 'zh_CN', + en_US: '', + zh_CN: '', + type: 'i18n', + }, + __slot__subTitle: false, + extra: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + className: 'card_kh05zf9e', + title: { + use: 'zh_CN', + en_US: 'Title', + zh_CN: '部门信息', + type: 'i18n', + }, + __slot__extra: false, + showHeadDivider: true, + __style__: ':root {\n margin-bottom: 12px;\n}', + showTitleBullet: true, + contentHeight: '', + fieldId: 'card_k1ow3h1m', + dividerNoInset: false, + }, condition: true, + children: [ + { + componentName: 'CardContent', + id: 'node_k1ow3cbm', + props: {}, + condition: true, + children: [ + { + componentName: 'TextField', + id: 'node_k1ow3cc4', + props: { + fieldName: 'department', + hasClear: false, + autoFocus: false, + tips: { + en_US: '', + zh_CN: '', + type: 'i18n', + }, + trim: false, + labelTextAlign: 'right', + placeholder: { + use: 'zh_CN', + en_US: 'please input', + zh_CN: '请输入', + type: 'i18n', + }, + state: '', + behavior: 'NORMAL', + value: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + addonBefore: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + validation: [], + hasLimitHint: false, + cutString: false, + __style__: {}, + fieldId: 'textField_k1ow3h21', + htmlType: 'input', + autoHeight: false, + labelColOffset: 0, + label: { + use: 'zh_CN', + en_US: 'TextField', + zh_CN: '所属部门', + type: 'i18n', + }, + __category__: 'form', + labelColSpan: 4, + wrapperColSpan: 0, + rows: 4, + addonAfter: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + wrapperColOffset: 0, + size: 'medium', + labelAlign: 'top', + __useMediator: 'value', + labelTipsTypes: 'none', + labelTipsIcon: '', + labelTipsText: { + type: 'i18n', + use: 'zh_CN', + en_US: '', + zh_CN: '', + }, + maxLength: 200, + }, + condition: true, + }, + { + componentName: 'ColumnsLayout', + id: 'node_k1ow3cc5', + props: { + layout: '6:6', + columnGap: '20', + rowGap: 0, + __style__: {}, + fieldId: 'columns_k1ow3h22', + }, + condition: true, + children: [ + { + componentName: 'Column', + id: 'node_k1ow3cc6', + props: { + colSpan: '', + __style__: {}, + fieldId: 'column_k1p1bnjo', + }, + condition: true, + children: [ + { + componentName: 'TextField', + id: 'node_k1ow3cc8', + props: { + fieldName: 'leader', + hasClear: false, + autoFocus: false, + tips: { + en_US: '', + zh_CN: '', + type: 'i18n', + }, + trim: false, + labelTextAlign: 'right', + placeholder: { + use: 'zh_CN', + en_US: 'please input', + zh_CN: '请输入', + type: 'i18n', + }, + state: '', + behavior: 'NORMAL', + value: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + addonBefore: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + validation: [], + hasLimitHint: false, + cutString: false, + __style__: {}, + fieldId: 'textField_k1ow3h23', + htmlType: 'input', + autoHeight: false, + labelColOffset: 0, + label: { + use: 'zh_CN', + en_US: 'TextField', + zh_CN: '主管', + type: 'i18n', + }, + __category__: 'form', + labelColSpan: 4, + wrapperColSpan: 0, + rows: 4, + addonAfter: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + wrapperColOffset: 0, + size: 'medium', + labelAlign: 'top', + __useMediator: 'value', + labelTipsTypes: 'none', + labelTipsIcon: '', + labelTipsText: { + type: 'i18n', + use: 'zh_CN', + en_US: '', + zh_CN: '', + }, + maxLength: 200, + }, + condition: true, + }, + ], + }, + { + componentName: 'Column', + id: 'node_k1ow3cc7', + props: { + colSpan: '', + __style__: {}, + fieldId: 'column_k1p1bnjp', + }, + condition: true, + children: [ + { + componentName: 'TextField', + id: 'node_k1ow3cc9', + props: { + fieldName: 'hrg', + hasClear: false, + autoFocus: false, + tips: { + en_US: '', + zh_CN: '', + type: 'i18n', + }, + trim: false, + labelTextAlign: 'right', + placeholder: { + use: 'zh_CN', + en_US: 'please input', + zh_CN: '请输入', + type: 'i18n', + }, + state: '', + behavior: 'NORMAL', + value: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + addonBefore: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + validation: [], + hasLimitHint: false, + cutString: false, + __style__: {}, + fieldId: 'textField_k1ow3h24', + htmlType: 'input', + autoHeight: false, + labelColOffset: 0, + label: { + use: 'zh_CN', + en_US: 'TextField', + zh_CN: 'HRG', + type: 'i18n', + }, + __category__: 'form', + labelColSpan: 4, + wrapperColSpan: 0, + rows: 4, + addonAfter: { + use: 'zh_CN', + zh_CN: '', + type: 'i18n', + }, + wrapperColOffset: 0, + size: 'medium', + labelAlign: 'top', + __useMediator: 'value', + labelTipsTypes: 'none', + labelTipsIcon: '', + labelTipsText: { + type: 'i18n', + use: 'zh_CN', + en_US: '', + zh_CN: '', + }, + maxLength: 200, + }, + condition: true, + }, + ], + }, + ], + }, + ], + }, + ], + }, + { + componentName: 'Div', + id: 'node_k1ow3cbo', + props: { + className: 'div_kh05zf9h', + behavior: 'NORMAL', + __style__: + ':root {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}', + events: {}, + fieldId: 'div_k1ow3h1o', + useFieldIdAsDomId: false, + customClassName: '', + }, + condition: true, + children: [ + { + componentName: 'Button', + id: 'node_k1ow3cbn', + props: { + triggerEventsWhenLoading: false, + onClick: { + rawType: 'events', + type: 'JSExpression', + value: + 'this.utils.legaoBuiltin.execEventFlow.bind(this, [this.submit])', + events: [ + { + name: 'submit', + id: 'submit', + params: {}, + type: 'actionRef', + uuid: '1570966253282_0', + }, + ], + }, + size: 'medium', + baseIcon: '', + otherIcon: '', + className: 'button_kh05zf9f', + type: 'primary', + behavior: 'NORMAL', + loading: false, + content: { + use: 'zh_CN', + en_US: 'Button', + zh_CN: '提交', + type: 'i18n', + }, + __style__: ':root {\n margin-right: 16px;\n width: 80px\n}', + fieldId: 'button_k1ow3h1n', + }, + condition: true, + }, + { + componentName: 'Button', + id: 'node_k1ow3cbp', + props: { + triggerEventsWhenLoading: false, + size: 'medium', + baseIcon: '', + otherIcon: '', + className: 'button_kh05zf9g', + type: 'normal', + behavior: 'NORMAL', + loading: false, + content: { + use: 'zh_CN', + en_US: 'Button', + zh_CN: '取消', + type: 'i18n', + }, + __style__: ':root {\n width: 80px;\n}', + fieldId: 'button_k1ow3h1p', + }, + condition: true, + }, + ], }, ], }, ], - id: 'FORM-MV766X71QI5KCL6T4CXKF9DJUME73BD0FQAGKE', + }, + { + componentName: 'RootFooter', + id: 'node_k1ow3cbc', + props: {}, + condition: true, }, ], - actions: { - module: { - compiled: - "'use strict';\n\nexports.__esModule = true;\nexports.helloPage = helloPage;\nexports.submit = submit;\n/**\n * 私有函数,只能JS面板内部调用\n * 动作面板帮助文档:\n * @see https://lark.alipay.com/legao/help/design-tool-logic\n */\nfunction printLog(obj) {\n console.info(obj);\n}\n\n/**\n * 页面内的函数,可以被页面内任何位置调用\n */\nfunction helloPage() {\n console.log('hello page');\n}\n\n/**\n* button onClick\n*/\nfunction submit() {\n var _this = this;\n\n this.$('form').submit(function (data, error) {\n if (data) {\n console.log(data); // 这是表单提交的数据\n _this.utils.toast({\n type: 'success',\n title: '提交成功'\n });\n }\n });\n}", - source: - "/**\n * 私有函数,只能JS面板内部调用\n * 动作面板帮助文档:\n * @see https://lark.alipay.com/legao/help/design-tool-logic\n */\nfunction printLog(obj) {\n console.info(obj);\n}\n\n/**\n * 页面内的函数,可以被页面内任何位置调用\n */\nexport function helloPage() {\n console.log('hello page');\n}\n\n\n/**\n* button onClick\n*/\nexport function submit(){\n this.$('form').submit((data, error) => {\n if (data) {\n console.log(data); // 这是表单提交的数据\n this.utils.toast({\n type: 'success',\n title: '提交成功'\n });\n }\n });\n}", - }, - type: 'FUNCTION', - list: [ - { - id: 'helloPage', - title: 'helloPage', - }, - { - id: 'submit', - title: 'submit', - }, - ], - }, }; diff --git a/packages/editor-preset-vision/tests/utils/index.ts b/packages/editor-preset-vision/tests/utils/index.ts new file mode 100644 index 000000000..70fce0af2 --- /dev/null +++ b/packages/editor-preset-vision/tests/utils/index.ts @@ -0,0 +1 @@ +export { getIdsFromSchema, getNodeFromSchemaById } from '@ali/lowcode-test-mate/es/utils'; diff --git a/packages/editor-preset-vision/tests/vision-api/api-export.test.ts b/packages/editor-preset-vision/tests/vision-api/api-export.test.ts new file mode 100644 index 000000000..50278dc72 --- /dev/null +++ b/packages/editor-preset-vision/tests/vision-api/api-export.test.ts @@ -0,0 +1,89 @@ +import set from 'lodash/set'; +import cloneDeep from 'lodash/clonedeep'; +import '../fixtures/window'; +// import { Project } from '../../src/project/project'; +// import { Node } from '../../src/document/node/node'; +// import { Designer } from '../../src/designer/designer'; +import formSchema from '../fixtures/schema/form'; +import VisualEngine, { + designer, + editor, + skeleton, + /** + * VE.Popup + */ + Popup, + /** + * VE Utils + */ + utils, + I18nUtil, + Hotkey, + Env, + monitor, + /* pub/sub 集线器 */ + Bus, + /* 事件 */ + EVENTS, + /* 修饰方法 */ + HOOKS, + Exchange, + context, + /** + * VE.init + * + * Initialized the whole VisualEngine UI + */ + init, + ui, + Panes, + modules, + Trunk, + Prototype, + Bundle, + Pages, + DragEngine, + Viewport, + Version, + Project, + logger, + Symbols, +} from '../../src'; +import { Editor } from '@ali/lowcode-editor-core'; + +describe('API 多种导出场景测试', () => { + it('window.VisualEngine 和 npm 导出 API 测试', () => { + expect(VisualEngine).toBe(window.VisualEngine); + }); + + it('npm 导出 API 对比测试', () => { + expect(VisualEngine.designer).toBe(designer); + expect(VisualEngine.editor).toBe(editor); + expect(VisualEngine.skeleton).toBe(skeleton); + expect(VisualEngine.Popup).toBe(Popup); + expect(VisualEngine.utils).toBe(utils); + expect(VisualEngine.I18nUtil).toBe(I18nUtil); + expect(VisualEngine.Hotkey).toBe(Hotkey); + expect(VisualEngine.Env).toBe(Env); + expect(VisualEngine.monitor).toBe(monitor); + expect(VisualEngine.Bus).toBe(Bus); + expect(VisualEngine.EVENTS).toBe(EVENTS); + expect(VisualEngine.HOOKS).toBe(HOOKS); + expect(VisualEngine.Exchange).toBe(Exchange); + expect(VisualEngine.context).toBe(context); + expect(VisualEngine.init).toBe(init); + expect(VisualEngine.ui).toBe(ui); + expect(VisualEngine.Panes).toBe(Panes); + expect(VisualEngine.modules).toBe(modules); + expect(VisualEngine.Trunk).toBe(Trunk); + expect(VisualEngine.Prototype).toBe(Prototype); + expect(VisualEngine.Bundle).toBe(Bundle); + expect(VisualEngine.DragEngine).toBe(DragEngine); + expect(VisualEngine.Pages).toBe(Pages); + expect(VisualEngine.Viewport).toBe(Viewport); + expect(VisualEngine.Version).toBe(Version); + expect(VisualEngine.Project).toBe(Project); + expect(VisualEngine.logger).toBe(logger); + expect(VisualEngine.Symbols).toBe(Symbols); + }); +}); \ No newline at end of file diff --git a/packages/editor-preset-vision/tests/vision-api/pages.test.ts b/packages/editor-preset-vision/tests/vision-api/pages.test.ts index d020b6df1..259cbef63 100644 --- a/packages/editor-preset-vision/tests/vision-api/pages.test.ts +++ b/packages/editor-preset-vision/tests/vision-api/pages.test.ts @@ -1,46 +1,161 @@ import set from 'lodash/set'; import cloneDeep from 'lodash/clonedeep'; import '../fixtures/window'; -// import { Project } from '../../src/project/project'; -// import { Node } from '../../src/document/node/node'; -// import { Designer } from '../../src/designer/designer'; import formSchema from '../fixtures/schema/form'; import VisualEngine from '../../src'; -// import { getIdsFromSchema, getNodeFromSchemaById } from '../utils'; +import { Editor } from '@ali/lowcode-editor-core'; +import { getIdsFromSchema, getNodeFromSchemaById } from '../utils'; -// const mockCreateSettingEntry = jest.fn(); -// jest.mock('../../src/designer/designer', () => { -// return { -// Designer: jest.fn().mockImplementation(() => { -// return { -// getComponentMeta() { -// return { -// getMetadata() { -// return { experimental: null }; -// }, -// }; -// }, -// transformProps(props) { return props; }, -// createSettingEntry: mockCreateSettingEntry, -// postEvent() {}, -// }; -// }), -// }; -// }); +const pageSchema = { componentsTree: [formSchema] }; - - -// let designer = null; -// beforeAll(() => { -// designer = new Designer({}); -// }); - -describe('schema 生成节点模型测试', () => { - describe('block ❌ | component ❌ | slot ❌', () => { - it('基本的节点模型初始化,模型导出,初始化传入 schema', () => { - console.log(VisualEngine); - - console.log(VisualEngine.Pages.addPage(formSchema)); +describe('VisualEngine.Pages 相关 API 测试', () => { + afterEach(() => { + VisualEngine.Pages.unload(); + }); + describe('addPage 系列', () => { + it('基本的节点模型初始化,初始化传入 schema', () => { + const doc = VisualEngine.Pages.addPage(pageSchema)!; + expect(doc).toBeTruthy(); + const ids = getIdsFromSchema(formSchema); + const expectedNodeCnt = ids.length; + expect(doc.nodesMap.size).toBe(expectedNodeCnt); + }); + it('基本的节点模型初始化,初始化传入 schema,带有 slot', () => { + const formSchemaWithSlot = set(cloneDeep(formSchema), 'children[0].children[0].props.title', { + type: 'JSBlock', + value: { + componentName: 'Slot', + children: [ + { + componentName: 'Text', + id: 'node_k1ow3cbf', + props: { + showTitle: false, + behavior: 'NORMAL', + content: { + type: 'variable', + value: { + use: 'zh_CN', + en_US: 'Title', + zh_CN: '个人信息', + type: 'i18n', + }, + variable: 'state.title', + }, + __style__: {}, + fieldId: 'text_k1ow3h1j', + maxLine: 0, + }, + condition: true, + }, + ], + props: { + slotTitle: '标题区域', + slotName: 'title', + }, + }, + }); + const doc = VisualEngine.Pages.addPage({ componentsTree: [formSchemaWithSlot] })!; + expect(doc).toBeTruthy(); + const ids = getIdsFromSchema(formSchema); + const expectedNodeCnt = ids.length; + // slot 会多出(1 + N)个节点 + expect(doc.nodesMap.size).toBe(expectedNodeCnt + 2); + }); + it('导出 schema', () => { + const doc = VisualEngine.Pages.addPage(pageSchema)!; + expect(doc).toBeTruthy(); + const ids = getIdsFromSchema(formSchema); + const expectedNodeCnt = ids.length; + const exportedData = doc.toData(); + expect(exportedData).toHaveProperty('componentsMap'); + expect(exportedData).toHaveProperty('componentsTree'); + expect(exportedData.componentsTree).toHaveLength(1); + const exportedSchema = exportedData.componentsTree[0]; + expect(getIdsFromSchema(exportedSchema).length).toBe(expectedNodeCnt); }); }); -}); \ No newline at end of file + describe('removePage 系列', () => { + it('removePage', () => { + const doc = VisualEngine.Pages.addPage(pageSchema)!; + expect(doc).toBeTruthy(); + expect(VisualEngine.Pages.documents).toHaveLength(1); + VisualEngine.Pages.removePage(doc); + expect(VisualEngine.Pages.documents).toHaveLength(0); + }); + }); + describe('getPage 系列', () => { + it('getPage', () => { + const doc = VisualEngine.Pages.addPage(pageSchema); + const anotherFormSchema = set(cloneDeep(formSchema), 'id', 'page'); + const doc2 = VisualEngine.Pages.addPage({ componentsTree: [anotherFormSchema] }); + expect(VisualEngine.Pages.getPage(0)).toBe(doc); + expect(VisualEngine.Pages.getPage((_doc) => _doc.rootNode.id === 'page')).toBe(doc2); + }); + }); + describe('setPages 系列', () => { + it('setPages componentsTree 只有一个元素', () => { + VisualEngine.Pages.setPages([pageSchema]); + const { currentDocument } = VisualEngine.Pages; + const ids = getIdsFromSchema(formSchema); + const expectedNodeCnt = ids.length; + const exportedData = currentDocument.toData(); + expect(exportedData).toHaveProperty('componentsMap'); + expect(exportedData).toHaveProperty('componentsTree'); + expect(exportedData.componentsTree).toHaveLength(1); + const exportedSchema = exportedData.componentsTree[0]; + expect(getIdsFromSchema(exportedSchema).length).toBe(expectedNodeCnt); + }); + }); + describe('setCurrentPage / getCurrentPage / currentPage / currentDocument 系列', () => { + it('getCurrentPage', () => { + const doc = VisualEngine.Pages.addPage(pageSchema)!; + expect(doc).toBeTruthy(); + expect(doc).toBe(VisualEngine.Pages.getCurrentPage()); + expect(doc).toBe(VisualEngine.Pages.currentDocument); + expect(doc).toBe(VisualEngine.Pages.currentPage); + }); + it('setCurrentPage', () => { + const doc = VisualEngine.Pages.addPage(pageSchema); + expect(doc).toBe(VisualEngine.Pages.currentDocument); + const anotherFormSchema = set(cloneDeep(formSchema), 'id', 'page'); + const doc2 = VisualEngine.Pages.addPage({ componentsTree: [anotherFormSchema] }); + expect(doc2).toBe(VisualEngine.Pages.currentDocument); + VisualEngine.Pages.setCurrentPage(doc); + expect(doc).toBe(VisualEngine.Pages.currentDocument); + }); + }); + describe('onCurrentPageChange 系列', () => { + it('多次切换', () => { + const doc = VisualEngine.Pages.addPage(pageSchema); + const anotherFormSchema = set(cloneDeep(formSchema), 'id', 'page'); + const doc2 = VisualEngine.Pages.addPage({ componentsTree: [anotherFormSchema] }); + const docChangeHandler = jest.fn(); + VisualEngine.Pages.onCurrentDocumentChange(docChangeHandler); + VisualEngine.Pages.setCurrentPage(doc); + expect(docChangeHandler).toHaveBeenCalledTimes(1); + expect(docChangeHandler).toHaveBeenLastCalledWith(doc); + + VisualEngine.Pages.setCurrentPage(doc2); + expect(docChangeHandler).toHaveBeenCalledTimes(2); + expect(docChangeHandler).toHaveBeenLastCalledWith(doc2); + }); + }); + describe('toData 系列', () => { + it('基本的节点模型初始化,模型导出,初始化传入 schema', () => { + const doc = VisualEngine.Pages.addPage(pageSchema); + const anotherFormSchema = set(cloneDeep(formSchema), 'id', 'page'); + const doc2 = VisualEngine.Pages.addPage({ componentsTree: [anotherFormSchema] }); + const dataList = VisualEngine.Pages.toData(); + expect(dataList.length).toBe(2); + expect(dataList[0]).toHaveProperty('componentsMap'); + expect(dataList[0]).toHaveProperty('componentsTree'); + expect(dataList[0].componentsTree).toHaveLength(1); + expect(dataList[0].componentsTree[0].id).toBe('node_k1ow3cb9'); + expect(dataList[1]).toHaveProperty('componentsMap'); + expect(dataList[1]).toHaveProperty('componentsTree'); + expect(dataList[1].componentsTree).toHaveLength(1); + expect(dataList[1].componentsTree[0].id).toBe('page'); + }); + }); +}); diff --git a/packages/editor-preset-vision/tests/vision-api/project.test.ts b/packages/editor-preset-vision/tests/vision-api/project.test.ts new file mode 100644 index 000000000..6e07cfb02 --- /dev/null +++ b/packages/editor-preset-vision/tests/vision-api/project.test.ts @@ -0,0 +1,38 @@ +import set from 'lodash/set'; +import cloneDeep from 'lodash/clonedeep'; +import '../fixtures/window'; +// import { Project } from '../../src/project/project'; +// import { Node } from '../../src/document/node/node'; +// import { Designer } from '../../src/designer/designer'; +import formSchema from '../fixtures/schema/form'; +import VisualEngine from '../../src'; +import { Editor } from '@ali/lowcode-editor-core'; + +// import { getIdsFromSchema, getNodeFromSchemaById } from '../utils'; + + +describe.skip('VisualEngine.Project 相关 API 测试', () => { + describe('getSchema / setSchema 系列', () => { + it('基本的节点模型初始化,模型导出,初始化传入 schema', () => { + // console.log(VisualEngine); + // console.log(Editor instanceof Function); + // console.log(Editor.toString()) + // console.log(new Editor()); + // console.log(Editor2 instanceof Function); + + console.log(VisualEngine.Pages.addPage(formSchema)); + }); + }); + + describe('setConfig 系列', () => { + it('基本的节点模型初始化,模型导出,初始化传入 schema', () => { + // console.log(VisualEngine); + // console.log(Editor instanceof Function); + // console.log(Editor.toString()) + // console.log(new Editor()); + // console.log(Editor2 instanceof Function); + + console.log(VisualEngine.Pages.addPage(formSchema)); + }); + }); +}); \ No newline at end of file