diff --git a/packages/canvas/src/components/render/render.js b/packages/canvas/src/components/render/render.js index 54a2c8c4cd..dacd2a9f66 100644 --- a/packages/canvas/src/components/render/render.js +++ b/packages/canvas/src/components/render/render.js @@ -33,6 +33,7 @@ import { NODE_UID as DESIGN_UIDKEY, NODE_TAG as DESIGN_TAGKEY, NODE_LOOP as DESI const { BROADCAST_CHANNEL } = constants const { hyphenateRE } = utils +const customElements = {} const transformJSX = (code) => transformSync(code, { @@ -41,7 +42,7 @@ const transformJSX = (code) => babelPluginJSX, { pragma: 'h', - isCustomElement: (name) => custElements[name] + isCustomElement: (name) => customElements[name] } ] ] @@ -131,15 +132,10 @@ const isObject = (data) => { export const isStateAccessor = (stateData) => stateData?.accessor?.getter?.type === 'JSFunction' || stateData?.accessor?.setter?.type === 'JSFunction' -const parseI18n = (i18n, scope, ctx) => { - return parseExpression( - { - type: 'JSExpression', - value: `this.i18n('${i18n.key}')` - }, - scope, - { i18n: i18nHost.global.t, ...ctx } - ) +// 规避创建function eslint报错 +export const newFn = (...argv) => { + const Fn = Function + return new Fn(...argv) } const parseExpression = (data, scope, ctx) => { @@ -160,6 +156,26 @@ const parseExpression = (data, scope, ctx) => { } } +const parseI18n = (i18n, scope, ctx) => { + return parseExpression( + { + type: 'JSExpression', + value: `this.i18n('${i18n.key}')` + }, + scope, + { i18n: i18nHost.global.t, ...ctx } + ) +} + +const renderDefault = (children, scope, parent) => + children.map?.((child) => + h(renderer, { + schema: child, + scope, + parent + }) + ) + const parseJSSlot = (data, scope) => { return ($scope) => renderDefault(data.value, { ...scope, ...$scope }, data) } @@ -225,10 +241,105 @@ const parseFunctionString = (fnStr) => { return null } -// 规避创建function eslint报错 -export const newFn = (...argv) => { - const Fn = Function - return new Fn(...argv) +const getPlainProps = (object = {}) => { + const { slot, ...rest } = object + const props = {} + + if (slot) { + rest.slot = slot.name || slot + } + + Object.entries(rest).forEach(([key, value]) => { + let renderKey = key + + // html 标签属性会忽略大小写,所以传递包含大写的 props 需要转换为 kebab 形式的 props + if (!/on[A-Z]/.test(renderKey) && hyphenateRE.test(renderKey)) { + renderKey = hyphenate(renderKey) + } + + if (['boolean', 'string', 'number'].includes(typeof value)) { + props[renderKey] = value + } else { + // 如果传给webcomponent标签的是对象或者数组需要使用.prop修饰符,转化成h函数就是如下写法 + props[`.${renderKey}`] = value + } + }) + return props +} + +const generateCollection = (schema) => { + if (schema.componentName === 'Collection' && schema.props?.dataSource && schema.children) { + schema.children.forEach((item) => { + const fetchData = item.props?.fetchData + const methodMatch = fetchData?.value?.match(/this\.(.+?)}/) + if (fetchData && methodMatch?.[1]) { + const methodName = methodMatch[1].trim() + // 缓存表格fetchData对应的数据源信息 + collectionMethodsMap[methodName] = schema.props.dataSource + } + }) + } +} + +const generateBlockContent = (schema) => { + if (schema?.componentName === 'Collection') { + generateCollection(schema) + } + if (Array.isArray(schema?.children)) { + schema.children.forEach((item) => { + generateBlockContent(item) + }) + } +} + +const registerBlock = (componentName) => { + getController() + .registerBlock?.(componentName) + .then((res) => { + const blockSchema = res.content + + // 拿到区块数据,建立区块中数据源的映射关系 + generateBlockContent(blockSchema) + + // 如果区块的根节点有百分比高度,则需要特殊处理,把高度百分比传递下去,适配大屏应用 + if (/height:\s*?[\d|.]+?%/.test(blockSchema?.props?.style)) { + const blockDoms = document.querySelectorAll(hyphenate(componentName)) + blockDoms.forEach((item) => { + item.style.height = '100%' + }) + } + }) +} + +export const wrapCustomElement = (componentName) => { + const material = getController().getMaterial(componentName) + + if (!Object.keys(material).length) { + registerBlock(componentName) + } + + customElements[componentName] = { + name: componentName + '.ce', + render() { + return h( + hyphenate(componentName), + window.parent.TinyGlobalConfig.dslMode === 'Vue' ? getPlainProps(this.$attrs) : this.$attrs, + this.$slots.default?.() + ) + } + } + + return customElements[componentName] +} + +export const getComponent = (name) => { + return ( + Mapper[name] || + getNative(name) || + getBlock(name) || + customElements[name] || + (isHTMLTag(name) ? name : wrapCustomElement(name)) + ) } // 解析JSX字符串为可执行函数 @@ -297,6 +408,8 @@ const parseLoopArgs = (_loop) => { return undefined } +export const getIcon = (name) => window.TinyVueIcon?.[name]?.() || '' + const parseObjectData = (data, scope, ctx) => { if (!data) { return data @@ -382,124 +495,33 @@ const stopEvent = (event) => { return false } -const getPlainProps = (object = {}) => { - const { slot, ...rest } = object - const props = {} - - if (slot) { - rest.slot = slot.name || slot - } - - Object.entries(rest).forEach(([key, value]) => { - let renderKey = key - - // html 标签属性会忽略大小写,所以传递包含大写的 props 需要转换为 kebab 形式的 props - if (!/on[A-Z]/.test(renderKey) && hyphenateRE.test(renderKey)) { - renderKey = hyphenate(renderKey) - } - - if (['boolean', 'string', 'number'].includes(typeof value)) { - props[renderKey] = value - } else { - // 如果传给webcomponent标签的是对象或者数组需要使用.prop修饰符,转化成h函数就是如下写法 - props[`.${renderKey}`] = value - } - }) - return props -} - -const custElements = {} - -const generateCollection = (schema) => { - if (schema.componentName === 'Collection' && schema.props?.dataSource && schema.children) { - schema.children.forEach((item) => { - const fetchData = item.props?.fetchData - const methodMatch = fetchData?.value?.match(/this\.(.+?)}/) - if (fetchData && methodMatch?.[1]) { - const methodName = methodMatch[1].trim() - // 缓存表格fetchData对应的数据源信息 - collectionMethodsMap[methodName] = schema.props.dataSource - } - }) - } -} - -const generateBlockContent = (schema) => { - if (schema?.componentName === 'Collection') { - generateCollection(schema) - } - if (Array.isArray(schema?.children)) { - schema.children.forEach((item) => { - generateBlockContent(item) - }) - } -} - -const registerBlock = (componentName) => { - getController() - .registerBlock?.(componentName) - .then((res) => { - const blockSchema = res.content - - // 拿到区块数据,建立区块中数据源的映射关系 - generateBlockContent(blockSchema) - - // 如果区块的根节点有百分比高度,则需要特殊处理,把高度百分比传递下去,适配大屏应用 - if (/height:\s*?[\d|.]+?%/.test(blockSchema?.props?.style)) { - const blockDoms = document.querySelectorAll(hyphenate(componentName)) - blockDoms.forEach((item) => { - item.style.height = '100%' - }) - } - }) -} - -export const wrapCustomElement = (componentName) => { - const material = getController().getMaterial(componentName) - if (!Object.keys(material).length) { - registerBlock(componentName) - } - - custElements[componentName] = { - name: componentName + '.ce', - render() { - return h( - hyphenate(componentName), - window.parent.TinyGlobalConfig.dslMode === 'Vue' ? getPlainProps(this.$attrs) : this.$attrs, - this.$slots.default?.() - ) - } - } - - return custElements[componentName] -} - -const goupSlot = (children, isCustElm) => { - const slotGrup = {} +const generateSlotGroup = (children, isCustomElm) => { + const slotGroup = {} children.forEach((child) => { const { componentName, children, params = [], props } = child - const slot = child.slot || props?.slot || 'default' + const slot = child.slot || props?.slot?.name || props?.slot || 'default' - isCustElm && (child.props.slot = 'slot') // CE下需要给子节点加上slot标识 - slotGrup[slot] = slotGrup[slot] || { + isCustomElm && (child.props.slot = 'slot') // CE下需要给子节点加上slot标识 + slotGroup[slot] = slotGroup[slot] || { value: [], params } - slotGrup[slot].value.push(...(componentName === 'Template' ? children : [child])) // template 标签直接过滤掉 + + slotGroup[slot].value.push(...(componentName === 'Template' && children.length ? children : [child])) // template 标签直接过滤掉 }) - return slotGrup + return slotGroup } -const renderSlot = (children, scope, schema, isCustElm) => { +const renderSlot = (children, scope, schema, isCustomElm) => { if (children.some((a) => a.componentName === 'Template')) { - const slotGrup = goupSlot(children, isCustElm) + const slotGroup = generateSlotGroup(children, isCustomElm) const slots = {} - Object.keys(slotGrup).forEach((slotName) => { - slots[slotName] = ($scope) => renderDefault(slotGrup[slotName].value, { ...scope, ...$scope }, schema) + Object.keys(slotGroup).forEach((slotName) => { + slots[slotName] = ($scope) => renderDefault(slotGroup[slotName].value, { ...scope, ...$scope }, schema) }) return slots @@ -508,18 +530,6 @@ const renderSlot = (children, scope, schema, isCustElm) => { return { default: () => renderDefault(children, scope, schema) } } -export const getComponent = (name) => { - return ( - Mapper[name] || - getNative(name) || - getBlock(name) || - custElements[name] || - (isHTMLTag(name) ? name : wrapCustomElement(name)) - ) -} - -export const getIcon = (name) => window.TinyVueIcon?.[name]?.() || '' - const checkGroup = (componentName) => configure[componentName]?.nestingRule?.childWhitelist?.length const clickCapture = (componentName) => configure[componentName]?.clickCapture !== false @@ -561,14 +571,16 @@ const getBindProps = (schema, scope) => { return bindProps } -const renderDefault = (children, scope, parent) => - children.map?.((child) => - h(renderer, { - schema: child, - scope, - parent - }) - ) +const getLoopScope = ({ scope, index, item, loopArgs }) => { + return { + ...scope, + ...(parseLoopArgs({ + item, + index, + loopArgs + }) || {}) + } +} const renderGroup = (children, scope, parent) => { return children.map?.((schema) => { @@ -600,29 +612,18 @@ const renderGroup = (children, scope, parent) => { }) } -const getLoopScope = ({ scope, index, item, loopArgs }) => { - return { - ...scope, - ...(parseLoopArgs({ - item, - index, - loopArgs - }) || {}) - } -} - const getChildren = (schema, mergeScope) => { const { componentName, children } = schema const isNative = typeof component === 'string' - const isCustElm = custElements[componentName] + const isCustomElm = customElements[componentName] const isGroup = checkGroup(componentName) if (Array.isArray(children)) { - if (isNative || isCustElm) { + if (isNative || isCustomElm) { return renderDefault(children, mergeScope, schema) } else { - return isGroup ? renderGroup(children, mergeScope, schema) : renderSlot(children, mergeScope, schema, isCustElm) + return isGroup ? renderGroup(children, mergeScope, schema) : renderSlot(children, mergeScope, schema, isCustomElm) } } else { return parseData(children, mergeScope)