From 2322fc629c91ae5b783ab693cfa38a4c24c1c2b4 Mon Sep 17 00:00:00 2001 From: rhlin Date: Mon, 9 Dec 2024 03:32:50 -0800 Subject: [PATCH 01/16] refactor(canvas/render): ts support, separate context functions from RenderMain and Render --- package.json | 1 + .../build/vite-config/src/default-config.js | 2 +- packages/canvas/.eslintrc.cjs | 10 +- packages/canvas/render/{index.js => index.ts} | 0 packages/canvas/render/src/RenderMain.js | 459 ----------- packages/canvas/render/src/RenderMain.ts | 141 ++++ .../render/src/application-function/bridge.ts | 16 + .../application-function/data-source-map.ts | 54 ++ .../src/application-function/global-state.ts | 35 + .../render/src/application-function/index.ts | 4 + .../render/src/application-function/utils.ts | 74 ++ .../canvas/render/src/builtin/CanvasBox.vue | 2 +- .../src/builtin/{helper.js => helper.ts} | 0 .../render/src/builtin/{index.js => index.ts} | 0 .../src/{ => canvas-function}/CanvasEmpty.vue | 0 .../render/src/canvas-function/controller.ts | 7 + .../src/canvas-function/custom-renderer.ts | 39 + .../render/src/canvas-function/design-mode.ts | 13 + .../src/canvas-function/global-notify.ts | 7 + .../render/src/canvas-function/index.ts | 4 + packages/canvas/render/src/context.js | 71 -- packages/canvas/render/src/context.ts | 105 +++ .../canvas/render/src/data-function/index.ts | 1 + .../canvas/render/src/data-function/parser.ts | 307 ++++++++ packages/canvas/render/src/data-utils.ts | 12 + .../render/src/{lowcode.js => lowcode.ts} | 10 +- .../render/src/material-function/configure.ts | 4 + .../render/src/material-function/index.ts | 4 + .../src/material-function/material-getter.ts | 128 +++ ...upport-block-slot-data-for-webcomponent.ts | 3 + .../material-function/support-collection.ts | 15 + .../src/page-block-function/accessor-map.ts | 54 ++ .../render/src/page-block-function/css.ts | 18 + .../render/src/page-block-function/index.ts | 6 + .../render/src/page-block-function/methods.ts | 26 + .../render/src/page-block-function/props.ts | 44 ++ .../render/src/page-block-function/schema.ts | 136 ++++ .../render/src/page-block-function/state.ts | 42 + packages/canvas/render/src/render.js | 733 ------------------ packages/canvas/render/src/render.ts | 262 +++++++ .../render/src/{runner.js => runner.ts} | 16 +- ...{supportUmdBlock.js => supportUmdBlock.ts} | 14 +- packages/canvas/render/type.d.ts | 12 + 43 files changed, 1611 insertions(+), 1280 deletions(-) rename packages/canvas/render/{index.js => index.ts} (100%) delete mode 100644 packages/canvas/render/src/RenderMain.js create mode 100644 packages/canvas/render/src/RenderMain.ts create mode 100644 packages/canvas/render/src/application-function/bridge.ts create mode 100644 packages/canvas/render/src/application-function/data-source-map.ts create mode 100644 packages/canvas/render/src/application-function/global-state.ts create mode 100644 packages/canvas/render/src/application-function/index.ts create mode 100644 packages/canvas/render/src/application-function/utils.ts rename packages/canvas/render/src/builtin/{helper.js => helper.ts} (100%) rename packages/canvas/render/src/builtin/{index.js => index.ts} (100%) rename packages/canvas/render/src/{ => canvas-function}/CanvasEmpty.vue (100%) create mode 100644 packages/canvas/render/src/canvas-function/controller.ts create mode 100644 packages/canvas/render/src/canvas-function/custom-renderer.ts create mode 100644 packages/canvas/render/src/canvas-function/design-mode.ts create mode 100644 packages/canvas/render/src/canvas-function/global-notify.ts create mode 100644 packages/canvas/render/src/canvas-function/index.ts delete mode 100644 packages/canvas/render/src/context.js create mode 100644 packages/canvas/render/src/context.ts create mode 100644 packages/canvas/render/src/data-function/index.ts create mode 100644 packages/canvas/render/src/data-function/parser.ts create mode 100644 packages/canvas/render/src/data-utils.ts rename packages/canvas/render/src/{lowcode.js => lowcode.ts} (91%) create mode 100644 packages/canvas/render/src/material-function/configure.ts create mode 100644 packages/canvas/render/src/material-function/index.ts create mode 100644 packages/canvas/render/src/material-function/material-getter.ts create mode 100644 packages/canvas/render/src/material-function/support-block-slot-data-for-webcomponent.ts create mode 100644 packages/canvas/render/src/material-function/support-collection.ts create mode 100644 packages/canvas/render/src/page-block-function/accessor-map.ts create mode 100644 packages/canvas/render/src/page-block-function/css.ts create mode 100644 packages/canvas/render/src/page-block-function/index.ts create mode 100644 packages/canvas/render/src/page-block-function/methods.ts create mode 100644 packages/canvas/render/src/page-block-function/props.ts create mode 100644 packages/canvas/render/src/page-block-function/schema.ts create mode 100644 packages/canvas/render/src/page-block-function/state.ts delete mode 100644 packages/canvas/render/src/render.js create mode 100644 packages/canvas/render/src/render.ts rename packages/canvas/render/src/{runner.js => runner.ts} (86%) rename packages/canvas/render/src/{supportUmdBlock.js => supportUmdBlock.ts} (85%) create mode 100644 packages/canvas/render/type.d.ts diff --git a/package.json b/package.json index af2d9e7d92..69f335f60b 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "devDependencies": { "@babel/eslint-parser": "^7.21.3", "@types/node": "^18.0.0", + "@vue/eslint-config-typescript": "^11.0.3", "chokidar": "^3.5.3", "concurrently": "^8.2.0", "cross-env": "^7.0.3", diff --git a/packages/build/vite-config/src/default-config.js b/packages/build/vite-config/src/default-config.js index 54f0b86b52..e19a1e7785 100644 --- a/packages/build/vite-config/src/default-config.js +++ b/packages/build/vite-config/src/default-config.js @@ -29,7 +29,7 @@ const getDefaultConfig = (engineConfig) => { base: './', publicDir: path.resolve(root, './public'), resolve: { - extensions: ['.js', '.jsx', '.vue'], + extensions: ['.js', '.jsx', '.vue', '.ts', '.tsx'], alias: {} }, server: { diff --git a/packages/canvas/.eslintrc.cjs b/packages/canvas/.eslintrc.cjs index 7e042cff2a..af5f18db96 100644 --- a/packages/canvas/.eslintrc.cjs +++ b/packages/canvas/.eslintrc.cjs @@ -9,7 +9,6 @@ * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. * */ - module.exports = { env: { browser: true, @@ -17,8 +16,10 @@ module.exports = { node: true, jest: true }, - extends: ['eslint:recommended', 'plugin:vue/vue3-essential'], + extends: ['eslint:recommended', 'plugin:vue/vue3-essential', '@vue/eslint-config-typescript'], parser: 'vue-eslint-parser', + // 忽略 expected 中的内容 + ignorePatterns: ['type.d.ts'], parserOptions: { parser: '@babel/eslint-parser', ecmaVersion: 'latest', @@ -26,7 +27,7 @@ module.exports = { requireConfigFile: false, babelOptions: { parserOpts: { - plugins: ['jsx'] + plugins: ['jsx', 'typescript'] } } }, @@ -37,6 +38,7 @@ module.exports = { 'space-before-function-paren': 'off', 'vue/multi-word-component-names': 'off', 'no-use-before-define': 'error', - 'no-unused-vars': ['error', { ignoreRestSiblings: true, varsIgnorePattern: '^_', argsIgnorePattern: '^_' }] + 'no-unused-vars': ['error', { ignoreRestSiblings: true, varsIgnorePattern: '^_', argsIgnorePattern: '^_' }], + 'import/no-inner-modules': 'off' } } diff --git a/packages/canvas/render/index.js b/packages/canvas/render/index.ts similarity index 100% rename from packages/canvas/render/index.js rename to packages/canvas/render/index.ts diff --git a/packages/canvas/render/src/RenderMain.js b/packages/canvas/render/src/RenderMain.js deleted file mode 100644 index bb41b5fc74..0000000000 --- a/packages/canvas/render/src/RenderMain.js +++ /dev/null @@ -1,459 +0,0 @@ -/** - * Copyright (c) 2023 - present TinyEngine Authors. - * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. - * - * Use of this source code is governed by an MIT-style license. - * - * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, - * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR - * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. - * - */ - -import { h, provide, inject, nextTick, shallowReactive, reactive, ref, watch, watchEffect } from 'vue' -import { I18nInjectionKey } from 'vue-i18n' -import TinyVue from '@opentiny/vue' -import * as TinyVueIcon from '@opentiny/vue-icon' -import { useBroadcastChannel } from '@vueuse/core' -import { constants, utils as commonUtils } from '@opentiny/tiny-engine-utils' -import renderer, { parseData, setConfigure, setController, globalNotify, isStateAccessor } from './render' -import { - getNode as getNodeById, - clearNodes, - getRoot, - setContext, - getContext, - setCondition, - getCondition, - getConditions, - context, - setNode, - getDesignMode, - setDesignMode -} from './context' -import CanvasEmpty from './CanvasEmpty.vue' - -const { BROADCAST_CHANNEL } = constants -const { parseFunction: generateFunction } = commonUtils - -const reset = (obj) => { - Object.keys(obj).forEach((key) => delete obj[key]) -} - -const refreshKey = ref(0) -const methods = {} -const schema = reactive({}) -const state = shallowReactive({}) -const bridge = {} -const utils = {} -const props = {} - -const globalState = ref([]) -const stores = shallowReactive({}) -const dataSourceMap = shallowReactive({}) - -watchEffect(() => { - reset(stores) - globalState.value.forEach(({ id, state = {}, getters = {} }) => { - const computedGetters = Object.keys(getters).reduce( - (acc, key) => ({ - ...acc, - [key]: parseData(getters[key], state, acc) - }), - {} - ) - - stores[id] = { ...state, ...computedGetters } - }) -}) - -const getUtils = () => utils - -const setUtils = (data, clear, isForceRefresh) => { - if (clear) { - reset(utils) - } - const utilsCollection = {} - // 目前画布还不具备远程加载utils工具类的功能,目前只能加载TinyVue组件库中的组件工具 - data?.forEach((item) => { - const util = TinyVue[item.content.exportName] - if (util) { - utilsCollection[item.name] = util - } - - // 此处需要把工具类中的icon图标也加入utils上下文环境 - const utilIcon = TinyVueIcon[item.content.exportName] - if (utilIcon) { - utilsCollection[item.name] = utilIcon - } - - // 解析函数式的工具类 - if (item.type === 'function') { - const defaultFn = () => {} - utilsCollection[item.name] = generateFunction(item.content.value, context) || defaultFn - } - }) - Object.assign(utils, utilsCollection) - - // 因为工具类并不具有响应式行为,所以需要通过修改key来强制刷新画布 - if (isForceRefresh) { - refreshKey.value++ - } -} - -const updateUtils = (data) => { - setUtils(data, false, true) -} - -const deleteUtils = (data) => { - data?.forEach((item) => { - if (utils[item.name]) { - delete utils[item.name] - } - }) - setUtils([], false, true) -} - -const setBridge = (data, clear) => { - clear && reset(bridge) - Object.assign(bridge, data) -} - -const getBridge = () => bridge - -const getMethods = () => methods - -const setMethods = (data = {}, clear) => { - clear && reset(methods) - // 这里有些方法在画布还是有执行的必要的,比如说表格的renderer和formatText方法,包括一些自定义渲染函数 - Object.assign( - methods, - Object.fromEntries( - Object.keys(data).map((key) => { - return [key, parseData(data[key], {}, getContext())] - }) - ) - ) - setContext(methods) -} - -const getState = () => state - -const deleteState = (variable) => { - delete state[variable] -} - -const generateAccessor = (type, accessor, property) => { - const accessorFn = generateFunction(accessor[type].value, context) - - return { property, accessorFn, type } -} - -// 这里缓存状态变量对应的访问器,用于watchEffect更新和取消监听 -const stateAccessorMap = new Map() - -// 缓存区块属性的访问器 -const propsAccessorMap = new Map() - -const generateStateAccessors = (type, accessor, key) => { - const stateWatchEffectKey = `${key}${type}` - const { property, accessorFn } = generateAccessor(type, accessor, key) - - // 将之前已有的watchEffect取消监听,这里操作很有必要,不然会造成数据混乱 - stateAccessorMap.get(stateWatchEffectKey)?.() - - // 更新watchEffect监听 - stateAccessorMap.set( - stateWatchEffectKey, - watchEffect(() => { - try { - accessorFn() - } catch (error) { - globalNotify({ - type: 'warning', - title: `状态变量${property}的访问器函数:${accessorFn.name}执行报错`, - message: error?.message || `状态变量${property}的访问器函数:${accessorFn.name}执行报错,请检查语法` - }) - } - }) - ) -} - -const setState = (data, clear) => { - clear && reset(state) - if (!schema.state) { - schema.state = data - } - - Object.assign(state, parseData(data, {}, getContext()) || {}) - - // 在状态变量合并之后,执行访问器中watchEffect,为了可以在访问器函数中可以访问其他state变量 - Object.entries(data || {})?.forEach(([key, stateData]) => { - if (isStateAccessor(stateData)) { - const accessor = stateData.accessor - if (accessor?.getter?.value) { - generateStateAccessors('getter', accessor, key) - } - - if (accessor?.setter?.value) { - generateStateAccessors('setter', accessor, key) - } - } - }) -} - -const getDataSourceMap = () => { - return dataSourceMap.value -} - -const setDataSourceMap = (list) => { - dataSourceMap.value = list.reduce((dMap, config) => { - const dataSource = { config: config.data } - - const result = { - code: '', - msg: 'success', - data: {} - } - result.data = - dataSource.config.type === 'array' - ? { items: dataSource?.config?.data, total: dataSource?.config?.data?.length } - : dataSource?.config?.data - - dataSource.load = () => Promise.resolve(result) - dMap[config.name] = dataSource - - return dMap - }, {}) -} - -const getGlobalState = () => { - return globalState.value -} - -const setGlobalState = (data = []) => { - globalState.value = data -} - -const setProps = (data, clear) => { - clear && reset(props) - Object.assign(props, data) -} - -const getProps = () => props - -const initProps = (properties = []) => { - const props = {} - const accessorFunctions = [] - - properties.forEach(({ content = [] }) => { - content.forEach(({ defaultValue, property, accessor }) => { - // 如果没有设置defaultValue就是undefined这和vue处理方式一样 - props[property] = defaultValue - - // 如果区块属性有访问器accessor,则先解析getter和setter函数 - if (accessor?.getter?.value) { - // 此处不能直接执行watchEffect,需要在上下文环境设置好之后去执行,此处只是收集函数 - accessorFunctions.push(generateAccessor('getter', accessor, property)) - } - - if (accessor?.setter?.value) { - accessorFunctions.push(generateAccessor('setter', accessor, property)) - } - }) - }) - - setProps(props, true) - - return accessorFunctions -} - -const getSchema = () => schema - -const setPagecss = (css = '') => { - const id = 'page-css' - let element = document.getElementById(id) - const head = document.querySelector('head') - - document.body.setAttribute('style', '') - - if (!element) { - element = document.createElement('style') - element.setAttribute('type', 'text/css') - element.setAttribute('id', id) - - element.innerHTML = css - head.appendChild(element) - } else { - element.innerHTML = css - } -} - -const setSchema = async (data) => { - const newSchema = JSON.parse(JSON.stringify(data || schema)) - reset(schema) - // 页面初始化的时候取消所有状态变量的watchEffect监听 - stateAccessorMap.forEach((stateAccessorFn) => { - stateAccessorFn() - }) - - // 区块初始化的时候取消所有的区块属性watchEffect监听 - propsAccessorMap.forEach((propsAccessorFn) => { - propsAccessorFn() - }) - - // 清空存状态变量和区块props访问器的缓存 - stateAccessorMap.clear() - propsAccessorMap.clear() - - const context = { - utils, - bridge, - stores, - state, - props, - dataSourceMap: {}, - emit: () => {} // 兼容访问器中getter和setter中this.emit写法 - } - Object.defineProperty(context, 'dataSourceMap', { - get: getDataSourceMap - }) - // 此处提升很重要,因为setState、initProps也会触发画布重新渲染,所以需要提升上下文环境的设置时间 - setContext(context, true) - - // 设置方法调用上下文 - setMethods(newSchema.methods, true) - - // 如果是区块则需要设置对外暴露的props - const accessorFunctions = initProps(newSchema.schema?.properties) - - // 这里setState(会触发画布渲染),是因为状态管理里面的变量会用到props、utils、bridge、stores、methods - setState(newSchema.state, true) - clearNodes() - await nextTick() - setPagecss(data.css) - Object.assign(schema, newSchema) - - // 当上下文环境设置完成之后再去处理区块属性访问器的watchEffect - accessorFunctions.forEach(({ property, accessorFn, type }) => { - const propsWatchEffectKey = `${property}${type}` - propsAccessorMap.set( - propsWatchEffectKey, - watchEffect(() => { - try { - accessorFn() - } catch (error) { - globalNotify({ - type: 'warning', - title: `区块属性${property}的访问器函数:${accessorFn.name}执行报错`, - message: error?.message || `区块属性${property}的访问器函数:${accessorFn.name}执行报错,请检查语法` - }) - } - }) - ) - }) - - return schema -} - -const getNode = (id, parent) => (id ? getNodeById(id, parent) : schema) - -let canvasRenderer = null - -const defaultRenderer = function () { - // 渲染画布增加根节点,与出码和预览保持一致 - const rootChildrenSchema = { - componentName: 'div', - // 手动添加一个唯一的属性,后续在画布选中此节点时方便处理额外的逻辑。由于没有修改schema,不会影响出码 - props: { ...schema.props, 'data-id': 'root-container' }, - children: schema.children - } - - return h( - 'tiny-i18n-host', - { - locale: 'zh_CN', - key: refreshKey.value, - ref: 'page', - className: 'design-page' - }, - schema.children?.length ? h(renderer, { schema: rootChildrenSchema, parent: schema }) : [h(CanvasEmpty)] - ) -} - -const getRenderer = () => canvasRenderer || defaultRenderer - -const setRenderer = (fn) => { - canvasRenderer = fn -} - -export default { - setup() { - provide('rootSchema', schema) - - const { locale } = inject(I18nInjectionKey).global - const { data } = useBroadcastChannel({ name: BROADCAST_CHANNEL.CanvasLang }) - const { post } = useBroadcastChannel({ name: BROADCAST_CHANNEL.SchemaLength }) - - watch(data, () => { - locale.value = data.value - }) - - watch( - () => schema?.children?.length, - (length) => { - post(length) - } - ) - - // 这里监听schema.methods,为了保证methods上下文环境始终为最新 - watch( - () => schema.methods, - (value) => { - setMethods(value, true) - }, - { - deep: true - } - ) - }, - render() { - return getRenderer().call(this) - } -} - -export const api = { - getUtils, - setUtils, - updateUtils, - deleteUtils, - getBridge, - setBridge, - getMethods, - setMethods, - setController, - setConfigure, - getSchema, - setSchema, - getState, - deleteState, - setState, - getProps, - setProps, - getContext, - getNode, - getRoot, - setPagecss, - setCondition, - getCondition, - getConditions, - getGlobalState, - getDataSourceMap, - setDataSourceMap, - setGlobalState, - setNode, - getRenderer, - setRenderer, - getDesignMode, - setDesignMode -} diff --git a/packages/canvas/render/src/RenderMain.ts b/packages/canvas/render/src/RenderMain.ts new file mode 100644 index 0000000000..fce1b8adf6 --- /dev/null +++ b/packages/canvas/render/src/RenderMain.ts @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. + * + * Use of this source code is governed by an MIT-style license. + * + * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, + * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR + * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + * + */ + +import { provide, inject, watch, defineComponent, WritableComputedRef } from 'vue' +import { I18nInjectionKey } from 'vue-i18n' + +import { useBroadcastChannel } from '@vueuse/core' +import { constants } from '@opentiny/tiny-engine-utils' +import { + getNode as getNodeById, + clearNodes, + getRoot, + setContext, + getContext, + setCondition, + getCondition, + getConditions, + context, + setNode +} from './context' +import { getDesignMode, setDesignMode, setController, useCustomRenderer } from './canvas-function' +import { setConfigure } from './material-function' +import { useUtils, useBridge, useDataSourceMap, useGlobalState } from './application-function' +import { useSchema } from './page-block-function' + +const { BROADCAST_CHANNEL } = constants +const { refreshKey, utils, getUtils, setUtils, updateUtils, deleteUtils } = useUtils(context) +const { bridge, setBridge, getBridge } = useBridge() +const { getDataSourceMap, setDataSourceMap } = useDataSourceMap() +const { getGlobalState, setGlobalState, stores } = useGlobalState() + +const { + schema, + getSchema, + setSchema, + getState, + setState, + deleteState, + getProps, + setProps, + getMethods, + setMethods, + setPagecss +} = useSchema( + { + context, + getContext, + setContext, + clearNodes + }, + { + utils, + bridge, + stores, + getDataSourceMap + } +) + +const { getRenderer, setRenderer } = useCustomRenderer(schema, refreshKey) + +const getNode = (id, parent) => (id ? getNodeById(id, parent) : schema) + +export default defineComponent({ + setup() { + provide('rootSchema', schema) + + const { locale } = inject(I18nInjectionKey).global + const { data } = useBroadcastChannel({ name: BROADCAST_CHANNEL.CanvasLang }) + const { post } = useBroadcastChannel({ name: BROADCAST_CHANNEL.SchemaLength }) + + watch(data, () => { + ;(locale as WritableComputedRef).value = data.value + }) + + watch( + () => schema?.children?.length, + (length) => { + post(length) + } + ) + + // 这里监听schema.methods,为了保证methods上下文环境始终为最新 + watch( + () => schema.methods, + (value) => { + setMethods(value, true) + }, + { + deep: true + } + ) + }, + render() { + return getRenderer().call(this) + } +}) + +export const api = { + getUtils, + setUtils, + updateUtils, + deleteUtils, + getBridge, + setBridge, + getMethods, + setMethods, + setController, + setConfigure, + getSchema, + setSchema, + getState, + deleteState, + setState, + getProps, + setProps, + getContext, + getNode, + getRoot, + setPagecss, + setCondition, + getCondition, + getConditions, + getGlobalState, + getDataSourceMap, + setDataSourceMap, + setGlobalState, + setNode, + getRenderer, + setRenderer, + getDesignMode, + setDesignMode +} diff --git a/packages/canvas/render/src/application-function/bridge.ts b/packages/canvas/render/src/application-function/bridge.ts new file mode 100644 index 0000000000..0acc6651c2 --- /dev/null +++ b/packages/canvas/render/src/application-function/bridge.ts @@ -0,0 +1,16 @@ +import { reset } from '../data-utils' + +export function useBridge() { + const bridge = {} + const setBridge = (data, clear = false) => { + clear && reset(bridge) + Object.assign(bridge, data) + } + + const getBridge = () => bridge + return { + bridge, + setBridge, + getBridge + } +} diff --git a/packages/canvas/render/src/application-function/data-source-map.ts b/packages/canvas/render/src/application-function/data-source-map.ts new file mode 100644 index 0000000000..6e80f70da6 --- /dev/null +++ b/packages/canvas/render/src/application-function/data-source-map.ts @@ -0,0 +1,54 @@ +import { shallowReactive } from 'vue' + +export interface IDataSourceResult { + code: string + msg: string + data: any +} + +export interface IDataSource { + load?: () => Promise + config?: { + type: string + data: any + total?: number + } +} + +export interface IDataSourceMap { + value?: Record +} + +export function useDataSourceMap() { + const dataSourceMap = shallowReactive({}) + + const getDataSourceMap = () => { + return dataSourceMap.value + } + + const setDataSourceMap = (list) => { + dataSourceMap.value = list.reduce((dMap, config) => { + const dataSource: IDataSource = { config: config.data } + + const result = { + code: '', + msg: 'success', + data: {} + } + result.data = + dataSource.config.type === 'array' + ? { items: dataSource?.config?.data, total: dataSource?.config?.data?.length } + : dataSource?.config?.data + + dataSource.load = () => Promise.resolve(result) + dMap[config.name] = dataSource + + return dMap + }, {}) + } + return { + dataSourceMap, + getDataSourceMap, + setDataSourceMap + } +} diff --git a/packages/canvas/render/src/application-function/global-state.ts b/packages/canvas/render/src/application-function/global-state.ts new file mode 100644 index 0000000000..5443ab4ef0 --- /dev/null +++ b/packages/canvas/render/src/application-function/global-state.ts @@ -0,0 +1,35 @@ +import { ref, shallowReactive, watchEffect } from 'vue' +import { reset } from '../data-utils' + +const Func = Function + +export function useGlobalState() { + const globalState = ref([]) + const getGlobalState = () => { + return globalState.value + } + + const setGlobalState = (data = []) => { + globalState.value = data + } + const stores = shallowReactive({}) + watchEffect(() => { + reset(stores) + globalState.value.forEach(({ id, state = {}, getters = {} }) => { + const computedGetters = Object.keys(getters).reduce( + (acc, key) => ({ + ...acc, + [key]: new Func('return ' + getters[key].value)().call(acc, state) + }), + {} + ) + stores[id] = { ...state, ...computedGetters } + }) + }) + return { + globalState, + getGlobalState, + setGlobalState, + stores + } +} diff --git a/packages/canvas/render/src/application-function/index.ts b/packages/canvas/render/src/application-function/index.ts new file mode 100644 index 0000000000..5bb00a2e65 --- /dev/null +++ b/packages/canvas/render/src/application-function/index.ts @@ -0,0 +1,4 @@ +export * from './bridge' +export * from './data-source-map' +export * from './global-state' +export * from './utils' diff --git a/packages/canvas/render/src/application-function/utils.ts b/packages/canvas/render/src/application-function/utils.ts new file mode 100644 index 0000000000..c972b456de --- /dev/null +++ b/packages/canvas/render/src/application-function/utils.ts @@ -0,0 +1,74 @@ +import { ref } from 'vue' +import TinyVue from '@opentiny/vue' +import * as TinyVueIcon from '@opentiny/vue-icon' +import { generateFunction, reset } from '../data-utils' + +export interface IUtil { + name: string + type: 'function' | string + content: { + exportName?: string + [props: string]: any + } + [props: string]: any +} + +export function useUtils(context: Record) { + const refreshKey = ref(0) + const utils: Record = {} + const getUtils = () => utils + + const setUtils = (data: Array, clear = false, isForceRefresh = false) => { + if (clear) { + reset(utils) + } + const utilsCollection = {} + // 目前画布还不具备远程加载utils工具类的功能,目前只能加载TinyVue组件库中的组件工具 + data?.forEach((item) => { + const util = TinyVue[item.content.exportName] + if (util) { + utilsCollection[item.name] = util + } + + // 此处需要把工具类中的icon图标也加入utils上下文环境 + const utilIcon = TinyVueIcon[item.content.exportName] + if (utilIcon) { + utilsCollection[item.name] = utilIcon + } + + // 解析函数式的工具类 + if (item.type === 'function') { + const defaultFn = () => {} + utilsCollection[item.name] = generateFunction(item.content.value, context) || defaultFn + } + }) + Object.assign(utils, utilsCollection) + + // 因为工具类并不具有响应式行为,所以需要通过修改key来强制刷新画布 + if (isForceRefresh) { + refreshKey.value++ + } + } + + const updateUtils = (data: Array) => { + setUtils(data, false, true) + } + + const deleteUtils = (data: Array) => { + data?.forEach((item) => { + if (utils[item.name]) { + delete utils[item.name] + } + }) + setUtils([], false, true) + } + + return { + refreshKey, + utils, + getUtils, + setUtils, + updateUtils, + deleteUtils + } +} diff --git a/packages/canvas/render/src/builtin/CanvasBox.vue b/packages/canvas/render/src/builtin/CanvasBox.vue index 96beea7c2b..0b9cda5f0b 100644 --- a/packages/canvas/render/src/builtin/CanvasBox.vue +++ b/packages/canvas/render/src/builtin/CanvasBox.vue @@ -6,7 +6,7 @@ - diff --git a/packages/canvas/render/src/builtin/builtin.json b/packages/canvas/render/src/builtin/builtin.json index ad56e67abb..b2d53a0fb5 100644 --- a/packages/canvas/render/src/builtin/builtin.json +++ b/packages/canvas/render/src/builtin/builtin.json @@ -132,6 +132,32 @@ "isContainer": true } }, + { + "icon": "Router", + "name": { + "zh_CN": "RouterView" + }, + "component": "RouterView", + "schema": { + "properties": [ + { + "label": { + "zh_CN": "基础信息" + }, + "description": { + "zh_CN": "基础信息" + }, + "collapse": { + "number": 6, + "text": { + "zh_CN": "显示更多" + } + }, + "content": [] + } + ] + } + }, { "icon": "Collection", "name": { @@ -520,6 +546,18 @@ "props": {} } }, + { + "name": { + "zh_CN": "路由视图" + }, + "screenshot": "", + "snippetName": "RouterView", + "icon": "RouterView", + "schema": { + "componentName": "RouterView", + "props": {} + } + }, { "name": { "zh_CN": "数据源容器" diff --git a/packages/canvas/render/src/builtin/index.ts b/packages/canvas/render/src/builtin/index.ts index f857ff1a18..3ab3b562b2 100644 --- a/packages/canvas/render/src/builtin/index.ts +++ b/packages/canvas/render/src/builtin/index.ts @@ -17,5 +17,15 @@ import CanvasIcon from './CanvasIcon.vue' import CanvasSlot from './CanvasSlot.vue' import CanvasImg from './CanvasImg.vue' import CanvasPlaceholder from './CanvasPlaceholder.vue' +import CanvasRouterView from './CanvasRouterView.vue' -export { CanvasText, CanvasBox, CanvasCollection, CanvasIcon, CanvasSlot, CanvasImg, CanvasPlaceholder } +export { + CanvasText, + CanvasBox, + CanvasCollection, + CanvasIcon, + CanvasSlot, + CanvasImg, + CanvasPlaceholder, + CanvasRouterView +} From 0524ad6b727b36e44f74b3d48f2f9f03f2f2fb1c Mon Sep 17 00:00:00 2001 From: rhlin Date: Fri, 13 Dec 2024 04:04:58 -0800 Subject: [PATCH 04/16] fea(canvas/render): multiple instantiable RenderMain, handle multi page scoped css, add RenderView logic to render, add page-getter, extra useLocale --- packages/canvas/package.json | 6 +- packages/canvas/render/src/RenderMain.ts | 187 ++++++++++------- .../render/src/canvas-function/canvas-api.ts | 80 ++++++++ .../src/canvas-function/custom-renderer.ts | 47 +++-- .../render/src/canvas-function/index.ts | 1 + .../render/src/canvas-function/locale.ts | 13 ++ .../src/canvas-function/page-switcher.ts | 19 ++ .../material-function/handle-scoped-css.ts | 6 + .../src/material-function/material-getter.ts | 6 +- .../src/material-function/page-getter.ts | 52 +++++ .../src/material-function/scope-css-plugin.ts | 193 ++++++++++++++++++ .../render/src/page-block-function/context.ts | 32 ++- .../render/src/page-block-function/methods.ts | 3 +- .../render/src/page-block-function/schema.ts | 19 +- packages/canvas/render/src/render.ts | 64 ++++-- 15 files changed, 583 insertions(+), 145 deletions(-) create mode 100644 packages/canvas/render/src/canvas-function/canvas-api.ts create mode 100644 packages/canvas/render/src/canvas-function/locale.ts create mode 100644 packages/canvas/render/src/canvas-function/page-switcher.ts create mode 100644 packages/canvas/render/src/material-function/handle-scoped-css.ts create mode 100644 packages/canvas/render/src/material-function/page-getter.ts create mode 100644 packages/canvas/render/src/material-function/scope-css-plugin.ts diff --git a/packages/canvas/package.json b/packages/canvas/package.json index 4c0ef210ea..81e3288cab 100644 --- a/packages/canvas/package.json +++ b/packages/canvas/package.json @@ -38,13 +38,17 @@ "@babel/core": "7.18.13", "@opentiny/tiny-engine-builtin-component": "workspace:*", "@opentiny/tiny-engine-common": "workspace:*", + "@opentiny/tiny-engine-dsl-vue": "workspace:2.0.0-rc.4", "@opentiny/tiny-engine-i18n-host": "workspace:*", "@opentiny/tiny-engine-meta-register": "workspace:*", "@opentiny/tiny-engine-utils": "workspace:*", "@opentiny/tiny-engine-webcomponent-core": "workspace:*", "@vue/babel-plugin-jsx": "1.1.1", "@vue/shared": "^3.3.4", - "@vueuse/core": "^9.6.0" + "@vueuse/core": "^9.6.0", + "add": "^2.0.6", + "postcss": "^8.4.31", + "postcss-selector-parser": "^7.0.0" }, "devDependencies": { "@opentiny/tiny-engine-vite-plugin-meta-comments": "workspace:*", diff --git a/packages/canvas/render/src/RenderMain.ts b/packages/canvas/render/src/RenderMain.ts index 823f10e919..88a3b852bc 100644 --- a/packages/canvas/render/src/RenderMain.ts +++ b/packages/canvas/render/src/RenderMain.ts @@ -10,36 +10,43 @@ * */ -import { provide, inject, watch, defineComponent, WritableComputedRef } from 'vue' -import { I18nInjectionKey } from 'vue-i18n' +import { provide, watch, defineComponent, PropType } from 'vue' import { useBroadcastChannel } from '@vueuse/core' import { constants } from '@opentiny/tiny-engine-utils' -import { - getNode as getNodeById, - clearNodes, - getRoot, - setContext, - getContext, - setCondition, - getCondition, - getConditions, - context, - setNode -} from './page-block-function/context' + import { getDesignMode, setDesignMode, setController, useCustomRenderer } from './canvas-function' import { setConfigure } from './material-function' import { useUtils, useBridge, useDataSourceMap, useGlobalState } from './application-function' -import { useSchema } from './page-block-function' +import { IPageSchema, useContext, usePageContext, useSchema } from './page-block-function' +import { api, setCurrentApi } from './canvas-function/canvas-api' const { BROADCAST_CHANNEL } = constants -const { refreshKey, utils, getUtils, setUtils, updateUtils, deleteUtils } = useUtils(context) + +// global-context singleton +const { context: globalContext, setContext: setGlobalContext } = useContext() +const { refreshKey, utils, getUtils, setUtils, updateUtils, deleteUtils } = useUtils(globalContext) const { bridge, setBridge, getBridge } = useBridge() const { getDataSourceMap, setDataSourceMap } = useDataSourceMap() const { getGlobalState, setGlobalState, stores } = useGlobalState() +const updateGlobalContext = () => { + const context = { + utils, + bridge, + stores + } + Object.defineProperty(context, 'dataSourceMap', { + // TODO: 理论上无法枚举, 先保留写法 + get: getDataSourceMap, + enumerable: true + }) + setGlobalContext(context, true) +} +updateGlobalContext() +const activePageContext = usePageContext() const { - schema, + schema: activeSchema, getSchema, setSchema, getState, @@ -50,61 +57,16 @@ const { getMethods, setMethods, setPagecss -} = useSchema( - { - context, - getContext, - setContext, - clearNodes - }, - { - utils, - bridge, - stores, - getDataSourceMap - } -) - -const { getRenderer, setRenderer } = useCustomRenderer(schema, refreshKey) - -const getNode = (id, parent) => (id ? getNodeById(id, parent) : schema) - -export default defineComponent({ - setup() { - provide('rootSchema', schema) - - const { locale } = inject(I18nInjectionKey).global - const { data } = useBroadcastChannel({ name: BROADCAST_CHANNEL.CanvasLang }) - const { post } = useBroadcastChannel({ name: BROADCAST_CHANNEL.SchemaLength }) - - watch(data, () => { - ;(locale as WritableComputedRef).value = data.value - }) - - watch( - () => schema?.children?.length, - (length) => { - post(length) - } - ) - - // 这里监听schema.methods,为了保证methods上下文环境始终为最新 - watch( - () => schema.methods, - (value) => { - setMethods(value, true) - }, - { - deep: true - } - ) - }, - render() { - return getRenderer().call(this) - } +} = useSchema(activePageContext, { + utils, + bridge, + stores, + getDataSourceMap }) - -export const api = { +const { getRenderer, setRenderer } = useCustomRenderer() +const getNode = (id, parent) => (id ? activePageContext.getNode(id, parent) : activeSchema) +const { getContext, getRoot, setNode, setCondition, getCondition, getConditions } = activePageContext +setCurrentApi({ getUtils, setUtils, updateUtils, @@ -138,4 +100,85 @@ export const api = { setRenderer, getDesignMode, setDesignMode -} +}) + +export default defineComponent({ + props: { + entry: { + // 页面入口 + type: Boolean, + require: false, + default: true + }, + cssScopeId: { + type: String, + require: false, + default: null + }, + parentContext: { + type: Object, + require: false, + default: null + }, + renderSchema: { + type: Object as PropType, + require: false, + default: null + }, + active: { + type: Boolean, + default: true + } + }, + setup(props) { + const pageContext = props.active ? activePageContext : usePageContext() + provide('pageContext', pageContext) + pageContext.setContextParent(props.parentContext) + + let schema = activeSchema + let setCurrentSchema + if (!props.active) { + const { schema: inActiveSchema, setSchema: setInactiveSchema } = useSchema(pageContext, { + utils, + bridge, + stores, + getDataSourceMap + }) + schema = inActiveSchema + setCurrentSchema = setInactiveSchema + } + + provide('rootSchema', schema) + + const { post } = useBroadcastChannel({ name: BROADCAST_CHANNEL.SchemaLength }) + watch( + () => schema?.children?.length, + (length) => { + post(length) + } + ) + + // 这里监听schema.methods,为了保证methods上下文环境始终为最新 + watch( + () => schema.methods, + (value) => { + setMethods(value, true) + }, + { + deep: true + } + ) + watch( + () => props.active, + (active) => { + if (!active) { + setCurrentSchema(props.renderSchema) + } + } + ) + + return () => getRenderer()(schema, refreshKey, props.entry) + } +}) + +export { api } diff --git a/packages/canvas/render/src/canvas-function/canvas-api.ts b/packages/canvas/render/src/canvas-function/canvas-api.ts new file mode 100644 index 0000000000..ca31e1b686 --- /dev/null +++ b/packages/canvas/render/src/canvas-function/canvas-api.ts @@ -0,0 +1,80 @@ +import type { useBridge, useDataSourceMap, useGlobalState, useUtils } from '../application-function' +import type { IPageContext, useSchema } from '../page-block-function' +import type { useCustomRenderer } from './custom-renderer' +import type { setConfigure } from '../material-function' +import type { getDesignMode, setDesignMode } from './design-mode' +import type { setController } from './controller' + +export interface IApplicationFunctionAPI + extends Pick, 'getUtils' | 'setUtils' | 'updateUtils' | 'deleteUtils'>, + Pick, 'getBridge' | 'setBridge'>, + Pick, 'getGlobalState' | 'setGlobalState'>, + Pick, 'getDataSourceMap' | 'setDataSourceMap'> {} +export interface IPageFunctionAPI + extends Pick< + ReturnType, + | 'getSchema' + | 'setSchema' + | 'setState' + | 'deleteState' + | 'getState' + | 'getProps' + | 'setProps' + | 'getMethods' + | 'setMethods' + | 'setPagecss' + > {} +export interface IPageContextAPI + extends Pick< + IPageContext, + 'getContext' | 'getNode' | 'getRoot' | 'setNode' | 'setCondition' | 'getCondition' | 'getConditions' + > {} +export interface ICanvasFunctionAPI extends Pick, 'getRenderer' | 'setRenderer'> { + getDesignMode: typeof getDesignMode + setDesignMode: typeof setDesignMode + setController: typeof setController + setConfigure: typeof setConfigure +} +export type IInnerCanvasAPI = IApplicationFunctionAPI & IPageFunctionAPI & ICanvasFunctionAPI & IPageContextAPI + +let currentApi: IInnerCanvasAPI + +export function setCurrentApi(activeApi) { + currentApi = activeApi +} + +export const api: IInnerCanvasAPI = { + getUtils: (...args) => currentApi?.getUtils(...args), + setUtils: (...args) => currentApi?.setUtils(...args), + updateUtils: (...args) => currentApi?.updateUtils(...args), + deleteUtils: (...args) => currentApi?.deleteUtils(...args), + getBridge: (...args) => currentApi?.getBridge(...args), + setBridge: (...args) => currentApi?.setBridge(...args), + getMethods: (...args) => currentApi?.getMethods(...args), + setMethods: (...args) => currentApi?.setMethods(...args), + setController: (...args) => currentApi?.setController(...args), + setConfigure: (...args) => currentApi?.setConfigure(...args), + getSchema: (...args) => currentApi?.getSchema(...args), + setSchema: (...args) => currentApi?.setSchema(...args), + getState: (...args) => currentApi?.getState(...args), + deleteState: (...args) => currentApi?.deleteState(...args), + setState: (...args) => currentApi?.setState(...args), + getProps: (...args) => currentApi?.getProps(...args), + setProps: (...args) => currentApi?.setProps(...args), + getContext: (...args) => currentApi?.getContext(...args), + getNode: (...args) => currentApi?.getNode(...args), + getRoot: (...args) => currentApi?.getRoot(...args), + setPagecss: (...args) => currentApi?.setPagecss(...args), + setCondition: (...args) => currentApi?.setCondition(...args), + getCondition: (...args) => currentApi?.getCondition(...args), + getConditions: (...args) => currentApi?.getConditions(...args), + getGlobalState: (...args) => currentApi?.getGlobalState(...args), + getDataSourceMap: (...args) => currentApi?.getDataSourceMap(...args), + setDataSourceMap: (...args) => currentApi?.setDataSourceMap(...args), + setGlobalState: (...args) => currentApi?.setGlobalState(...args), + setNode: (...args) => currentApi?.setNode(...args), + getRenderer: (...args) => currentApi?.getRenderer(...args), + setRenderer: (...args) => currentApi?.setRenderer(...args), + getDesignMode: (...args) => currentApi?.getDesignMode(...args), + setDesignMode: (...args) => currentApi?.setDesignMode(...args) +} diff --git a/packages/canvas/render/src/canvas-function/custom-renderer.ts b/packages/canvas/render/src/canvas-function/custom-renderer.ts index 58b08048d1..6432f20c71 100644 --- a/packages/canvas/render/src/canvas-function/custom-renderer.ts +++ b/packages/canvas/render/src/canvas-function/custom-renderer.ts @@ -2,32 +2,35 @@ import { h } from 'vue' import CanvasEmpty from './CanvasEmpty.vue' import renderer from '../render' -const getDefaultRenderer = (schema, refreshKey) => - function () { - // 渲染画布增加根节点,与出码和预览保持一致 - const rootChildrenSchema = { - componentName: 'div', - // 手动添加一个唯一的属性,后续在画布选中此节点时方便处理额外的逻辑。由于没有修改schema,不会影响出码 - props: { ...schema.props, 'data-id': 'root-container' }, - children: schema.children - } - - return h( - 'tiny-i18n-host', - { - locale: 'zh_CN', - key: refreshKey.value, - ref: 'page', - className: 'design-page' - }, - schema.children?.length ? h(renderer, { schema: rootChildrenSchema, parent: schema }) : [h(CanvasEmpty)] - ) +function defaultRenderer(schema, refreshKey, entry) { + // 渲染画布增加根节点,与出码和预览保持一致 + const rootChildrenSchema = { + componentName: 'div', + componentType: entry ? 'PageStart' : undefined, + // 手动添加一个唯一的属性,后续在画布选中此节点时方便处理额外的逻辑。由于没有修改schema,不会影响出码 + props: { ...schema.props, 'data-id': 'root-container' }, + children: schema.children + } + if (!entry) { + return schema.children?.length ? h(renderer, { schema: rootChildrenSchema, parent: schema }) : [h(CanvasEmpty)] } -export function useCustomRenderer(schema, refreshKey) { + return h( + 'tiny-i18n-host', + { + locale: 'zh_CN', + key: refreshKey.value, + ref: 'page', + className: 'design-page' + }, + schema.children?.length ? h(renderer, { schema: rootChildrenSchema, parent: schema }) : [h(CanvasEmpty)] + ) +} + +export function useCustomRenderer() { let canvasRenderer = null - const getRenderer = () => canvasRenderer || getDefaultRenderer(schema, refreshKey) + const getRenderer = () => canvasRenderer || defaultRenderer const setRenderer = (fn) => { canvasRenderer = fn } diff --git a/packages/canvas/render/src/canvas-function/index.ts b/packages/canvas/render/src/canvas-function/index.ts index c511cfc8af..2c5e3d4f33 100644 --- a/packages/canvas/render/src/canvas-function/index.ts +++ b/packages/canvas/render/src/canvas-function/index.ts @@ -2,3 +2,4 @@ export * from './controller' export * from './design-mode' export * from './global-notify' export * from './custom-renderer' +export * from './locale' diff --git a/packages/canvas/render/src/canvas-function/locale.ts b/packages/canvas/render/src/canvas-function/locale.ts new file mode 100644 index 0000000000..9973baea9c --- /dev/null +++ b/packages/canvas/render/src/canvas-function/locale.ts @@ -0,0 +1,13 @@ +import { inject, watch, WritableComputedRef } from 'vue' +import { I18nInjectionKey } from 'vue-i18n' +import { useBroadcastChannel } from '@vueuse/core' +import { constants } from '@opentiny/tiny-engine-utils' + +const { BROADCAST_CHANNEL } = constants +export function useLocale() { + const { locale } = inject(I18nInjectionKey).global + const { data } = useBroadcastChannel({ name: BROADCAST_CHANNEL.CanvasLang }) + watch(data, () => { + ;(locale as WritableComputedRef).value = data.value + }) +} diff --git a/packages/canvas/render/src/canvas-function/page-switcher.ts b/packages/canvas/render/src/canvas-function/page-switcher.ts new file mode 100644 index 0000000000..5b84894572 --- /dev/null +++ b/packages/canvas/render/src/canvas-function/page-switcher.ts @@ -0,0 +1,19 @@ +import { reactive } from 'vue' +import { IPageContext } from '../page-block-function' + +export interface ICurrentPage { + pageId: string | number + schema: any + pageContext: IPageContext +} +export const currentPage = reactive({ + pageId: null, + schema: null, + pageContext: null +}) + +export function setCurrentPage({ pageId, schema, pageContext }: ICurrentPage) { + currentPage.pageId = pageId + currentPage.pageContext = pageContext + currentPage.schema = schema +} diff --git a/packages/canvas/render/src/material-function/handle-scoped-css.ts b/packages/canvas/render/src/material-function/handle-scoped-css.ts new file mode 100644 index 0000000000..0f259507cd --- /dev/null +++ b/packages/canvas/render/src/material-function/handle-scoped-css.ts @@ -0,0 +1,6 @@ +import postcss from 'postcss' +import scopedPlugin from './scope-css-plugin' + +export function handleScopedCss(id: string, content: string) { + return postcss([scopedPlugin(id)]).process(content) +} diff --git a/packages/canvas/render/src/material-function/material-getter.ts b/packages/canvas/render/src/material-function/material-getter.ts index 8146864381..00e906628c 100644 --- a/packages/canvas/render/src/material-function/material-getter.ts +++ b/packages/canvas/render/src/material-function/material-getter.ts @@ -10,7 +10,8 @@ import { CanvasText, CanvasSlot, CanvasImg, - CanvasPlaceholder + CanvasPlaceholder, + CanvasRouterView } from '../builtin' import { getController } from '../canvas-function/controller' import { generateCollection } from './support-collection' @@ -28,7 +29,8 @@ export const Mapper = { CanvasRow, CanvasCol, CanvasRowColContainer, - CanvasPlaceholder + CanvasPlaceholder, + RouterView: CanvasRouterView } const getNative = (name) => { return window.TinyLowcodeComponent?.[name] diff --git a/packages/canvas/render/src/material-function/page-getter.ts b/packages/canvas/render/src/material-function/page-getter.ts new file mode 100644 index 0000000000..5fc7283dc1 --- /dev/null +++ b/packages/canvas/render/src/material-function/page-getter.ts @@ -0,0 +1,52 @@ +import { defineComponent, h } from 'vue' +import { getController } from '../canvas-function' +import RenderMain from '../RenderMain' +import { handleScopedCss } from './handle-scoped-css' +import { currentPage } from '../canvas-function/page-switcher' + +const pageSchema: Record = {} + +async function fetchPageSchema(pageId: string) { + return getController() + .getPageById(pageId) + .then((res) => { + return res.page_content + }) +} +const styleSheetMap = new Map() +export function initStyle(key: string, content: string) { + if (styleSheetMap.get(key) || !content) { + return + } + const styleSheet = new CSSStyleSheet() + styleSheetMap.set(key, styleSheet) + document.adoptedStyleSheets.push(styleSheet) + handleScopedCss(key, content).then((scopedCss) => { + styleSheet.replaceSync(scopedCss) + }) +} +export const wrapPageComponent = (pageId: string) => { + const key = `data-te-page-${pageId}` + const asyncData = ref(null) + fetchPageSchema(pageId).then((data) => { + asyncData.value = data + initStyle(key, data.css) + }) + const isCurrentPage = pageId === currentPage.pageId + pageSchema[pageId] = defineComponent({ + name: 'page-${pageId}', + setup() { + return () => + asyncData.value + ? h(RenderMain, { + cssScopeId: key, + renderSchema: isCurrentPage ? null : asyncData.value, + active: isCurrentPage + }) + : null + } + }) +} +export const getPage = (pageId: string) => { + return pageSchema[pageId] || wrapPageComponent(pageId) +} diff --git a/packages/canvas/render/src/material-function/scope-css-plugin.ts b/packages/canvas/render/src/material-function/scope-css-plugin.ts new file mode 100644 index 0000000000..8f56f6535d --- /dev/null +++ b/packages/canvas/render/src/material-function/scope-css-plugin.ts @@ -0,0 +1,193 @@ +/** @ref {@vue/compiler-sfc@2.7.16/src/stylePlugins/scoped.ts } */ +/* eslint-disable no-use-before-define, prefer-const*/ +import { PluginCreator, Rule, AtRule } from 'postcss' +import selectorParser from 'postcss-selector-parser' + +const animationNameRE = /^(-\w+-)?animation-name$/ +const animationRE = /^(-\w+-)?animation$/ + +const scopedPlugin: PluginCreator = (id = '') => { + const keyframes = Object.create(null) + const shortId = id.replace(/^data-v-/, '') + + return { + postcssPlugin: 'vue-sfc-scoped', + Rule(rule) { + processRule(id, rule) + }, + AtRule(node) { + if (/-?keyframes$/.test(node.name) && !node.params.endsWith(`-${shortId}`)) { + // register keyframes + keyframes[node.params] = node.params = node.params + '-' + shortId + } + }, + OnceExit(root) { + if (Object.keys(keyframes).length) { + // If keyframes are found in this + diff --git a/packages/canvas/render/src/canvas-function/custom-renderer.ts b/packages/canvas/render/src/canvas-function/custom-renderer.ts index 6432f20c71..8b7ba8edfe 100644 --- a/packages/canvas/render/src/canvas-function/custom-renderer.ts +++ b/packages/canvas/render/src/canvas-function/custom-renderer.ts @@ -2,17 +2,25 @@ import { h } from 'vue' import CanvasEmpty from './CanvasEmpty.vue' import renderer from '../render' -function defaultRenderer(schema, refreshKey, entry) { +function defaultRenderer(schema, refreshKey, entry, active, isPage = true) { // 渲染画布增加根节点,与出码和预览保持一致 const rootChildrenSchema = { componentName: 'div', - componentType: entry ? 'PageStart' : undefined, // 手动添加一个唯一的属性,后续在画布选中此节点时方便处理额外的逻辑。由于没有修改schema,不会影响出码 - props: { ...schema.props, 'data-id': 'root-container' }, + props: { ...schema.props, 'data-id': 'root-container', 'data-page-active': active }, children: schema.children } + if (!entry) { - return schema.children?.length ? h(renderer, { schema: rootChildrenSchema, parent: schema }) : [h(CanvasEmpty)] + return schema.children?.length || !active + ? h(renderer, { schema: rootChildrenSchema, parent: schema }) + : [h(CanvasEmpty)] + } + + const PageStartSchema = { + componentName: 'div', + componentType: 'PageStart', + props: { 'data-id': 'root-container' } } return h( @@ -23,7 +31,11 @@ function defaultRenderer(schema, refreshKey, entry) { ref: 'page', className: 'design-page' }, - schema.children?.length ? h(renderer, { schema: rootChildrenSchema, parent: schema }) : [h(CanvasEmpty)] + isPage + ? h(renderer, { schema: PageStartSchema, parent: schema }) + : schema.children?.length + ? h(renderer, { schema: rootChildrenSchema, parent: schema }) + : [h(CanvasEmpty)] ) } diff --git a/packages/canvas/render/src/material-function/page-getter.ts b/packages/canvas/render/src/material-function/page-getter.ts index 5fc7283dc1..c63bb4feb8 100644 --- a/packages/canvas/render/src/material-function/page-getter.ts +++ b/packages/canvas/render/src/material-function/page-getter.ts @@ -1,8 +1,7 @@ -import { defineComponent, h } from 'vue' +import { defineComponent, h, ref } from 'vue' import { getController } from '../canvas-function' import RenderMain from '../RenderMain' import { handleScopedCss } from './handle-scoped-css' -import { currentPage } from '../canvas-function/page-switcher' const pageSchema: Record = {} @@ -32,21 +31,35 @@ export const wrapPageComponent = (pageId: string) => { asyncData.value = data initStyle(key, data.css) }) - const isCurrentPage = pageId === currentPage.pageId pageSchema[pageId] = defineComponent({ - name: 'page-${pageId}', + name: `page-${pageId}`, setup() { return () => asyncData.value ? h(RenderMain, { cssScopeId: key, - renderSchema: isCurrentPage ? null : asyncData.value, - active: isCurrentPage + renderSchema: asyncData.value, + active: pageId === getController().getBaseInfo().pageId, + pageId: pageId, + entry: false }) : null } }) + return pageSchema[pageId] } export const getPage = (pageId: string) => { return pageSchema[pageId] || wrapPageComponent(pageId) } + +export async function getPageAncestors(pageId?: string) { + if (!pageId) { + return [] + } + if (!getController().getPageAncestors) { + // 如果不支持查询祖先 则返回自己 + return [pageId] + } + const pageChain = await getController().getPageAncestors(pageId) + return [...pageChain, pageId] +} diff --git a/packages/canvas/render/src/page-block-function/context.ts b/packages/canvas/render/src/page-block-function/context.ts index 7cea77e687..49242f6356 100644 --- a/packages/canvas/render/src/page-block-function/context.ts +++ b/packages/canvas/render/src/page-block-function/context.ts @@ -10,7 +10,7 @@ * */ -import { shallowReactive } from 'vue' +import { ref, shallowReactive } from 'vue' import { utils } from '@opentiny/tiny-engine-utils' export function useContext() { @@ -94,12 +94,15 @@ export function usePageContextParent() { export function useCssScopeId() { let cssScopeId = null - function setCssCopeId(id) { + function setCssScopeId(id: string) { cssScopeId = id } + function getCssScopeId() { + return cssScopeId + } return { - cssScopeId, - setCssCopeId + getCssScopeId, + setCssScopeId } } export function usePageContext() { @@ -113,7 +116,9 @@ export function usePageContext() { ...nodeExpose, ...conditionExpose, ...contextParentExpose, - ...cssCopeIdExpose + ...cssCopeIdExpose, + pageId: '', + active: false } } export type IPageContext = ReturnType diff --git a/packages/canvas/render/src/page-block-function/css.ts b/packages/canvas/render/src/page-block-function/css.ts index 2241cc699e..28ed32ba15 100644 --- a/packages/canvas/render/src/page-block-function/css.ts +++ b/packages/canvas/render/src/page-block-function/css.ts @@ -1,18 +1,7 @@ -export function setPagecss(css = '') { - const id = 'page-css' - let element = document.getElementById(id) - const head = document.querySelector('head') - - document.body.setAttribute('style', '') - - if (!element) { - element = document.createElement('style') - element.setAttribute('type', 'text/css') - element.setAttribute('id', id) - - element.innerHTML = css - head.appendChild(element) - } else { - element.innerHTML = css - } +import { initStyle } from '../material-function/page-getter' +import { getController } from '../render' +export function setPagecss(css = '', pageId?) { + const cssPageId = pageId ?? getController().getBaseInfo().pageId + const key = `data-te-page-${cssPageId}` + initStyle(key, css) } diff --git a/packages/canvas/render/src/page-block-function/schema.ts b/packages/canvas/render/src/page-block-function/schema.ts index cb072a771a..30477a8bf7 100644 --- a/packages/canvas/render/src/page-block-function/schema.ts +++ b/packages/canvas/render/src/page-block-function/schema.ts @@ -14,7 +14,7 @@ export function useSchema( { context: globalContext, setContext, getContext, clearNodes, getNode }, { utils, bridge, stores, getDataSourceMap } ) { - const schema = reactive({}) + const schema = reactive>({}) const { generateAccessor, stateAccessorMap, propsAccessorMap, generateStateAccessors } = useAccessorMap(globalContext) const { state, getState, setState, deleteState } = useState(schema, { diff --git a/packages/canvas/render/src/render.ts b/packages/canvas/render/src/render.ts index 65d227519b..6e9a06e469 100644 --- a/packages/canvas/render/src/render.ts +++ b/packages/canvas/render/src/render.ts @@ -76,21 +76,23 @@ const checkGroup = (componentName) => configure[componentName]?.nestingRule?.chi const clickCapture = (componentName) => configure[componentName]?.clickCapture !== false -const getBindProps = (schema, scope, context) => { +const getBindProps = (schema, scope, context, pageContext) => { const { id, componentName } = schema const invalidity = configure[componentName]?.invalidity || [] if (componentName === 'CanvasPlaceholder') { return {} } - + const { active, getCssScopeId } = pageContext + const cssScopeId = getCssScopeId() const bindProps = { ...parseData(schema.props, scope, context), - [DESIGN_UIDKEY]: id, + ...(cssScopeId ? { [cssScopeId]: '' } : {}), + ...(active ? { [DESIGN_UIDKEY]: id } : {}), [DESIGN_TAGKEY]: componentName } - if (getDesignMode() === DESIGN_MODE.DESIGN) { + if (getDesignMode() === DESIGN_MODE.DESIGN && active) { bindProps.onMouseover = stopEvent bindProps.onFocus = stopEvent } @@ -100,7 +102,7 @@ const getBindProps = (schema, scope, context) => { } // 在捕获阶段阻止事件的传播 - if (clickCapture(componentName) && getDesignMode() === DESIGN_MODE.DESIGN) { + if (clickCapture(componentName) && getDesignMode() === DESIGN_MODE.DESIGN && active) { bindProps.onClickCapture = stopEvent } @@ -113,7 +115,9 @@ const getBindProps = (schema, scope, context) => { delete bindProps.className // 使画布中元素可拖拽 - bindProps.draggable = true + if (active) { + bindProps.draggable = true + } // 过滤在门户网站上配置的画布丢弃的属性 invalidity.forEach((prop) => delete bindProps[prop]) @@ -169,7 +173,7 @@ const renderGroup = (children, scope, parent, pageContext) => { return h( getComponent(componentName), - getBindProps(schema, mergeScope, pageContext.context), + getBindProps(schema, mergeScope, pageContext.context, pageContext), Array.isArray(renderChildren) ? renderSlot(renderChildren, mergeScope, schema) : parseData(renderChildren, mergeScope, pageContext.context) @@ -202,7 +206,8 @@ const getChildren = (schema, mergeScope, pageContext) => { } } function getRenderPageId(currentPageId, isPageStart) { - const pagePathFromRoot = [] // TODO: 从查询api来 + const pagePathFromRoot = (inject('page-ancestors') as Ref).value + function getNextChild(currentPageId) { const index = pagePathFromRoot.indexOf(currentPageId) if (index + 1 < pagePathFromRoot.length) { @@ -223,11 +228,16 @@ export const renderer = defineComponent({ }, setup(props) { provide('schema', props.schema) + const currentPageContext = props.pageContext || inject('pageContext') + return { + currentPageContext + } }, render() { const { scope, schema, parent } = this const { componentName, loop, loopArgs, condition } = schema - const pageContext = this.pageContext || inject('pageContext') + const pageContext = this.currentPageContext + const ancestors = inject('page-ancestors') as Ref // 处理数据源和表格fetchData的映射关系 generateCollection(schema) @@ -238,10 +248,11 @@ export const renderer = defineComponent({ const isPageStart = schema.componentType === 'PageStart' const isRouterView = componentName === 'RouterView' - if (isPageStart || isRouterView) { + if (ancestors.value.length && (isPageStart || isRouterView)) { const renderPageId = getRenderPageId(pageContext.pageId, isPageStart) if (renderPageId) { return h(getPage(renderPageId), { + key: ancestors.value, [DESIGN_TAGKEY]: `${componentName}` }) } @@ -278,7 +289,7 @@ export const renderer = defineComponent({ return h( component, - getBindProps(schema, mergeScope, pageContext.context), + getBindProps(schema, mergeScope, pageContext.context, pageContext), getChildren(schema, mergeScope, pageContext) ) } diff --git a/packages/plugins/block/src/Main.vue b/packages/plugins/block/src/Main.vue index 43085bebb3..ba92f1beb8 100644 --- a/packages/plugins/block/src/Main.vue +++ b/packages/plugins/block/src/Main.vue @@ -301,6 +301,7 @@ export default { url.searchParams.delete('pageid') url.searchParams.set('blockid', block.id) window.history.pushState({}, '', url) + useBlock().postHistoryChanged({ blockId: block.id }) } else { confirm({ message: '当前画布内容尚未保存,是否要继续切换?', diff --git a/packages/plugins/block/src/composable/useBlock.js b/packages/plugins/block/src/composable/useBlock.js index 09e67ac8aa..c529f3fe3c 100644 --- a/packages/plugins/block/src/composable/useBlock.js +++ b/packages/plugins/block/src/composable/useBlock.js @@ -29,6 +29,9 @@ import { getMergeMeta, META_SERVICE } from '@opentiny/tiny-engine-meta-register' +import { useMessage } from '@opentiny/tiny-engine-meta-register' +const { publish } = useMessage() +const postHistoryChanged = (data) => publish({ topic: 'historyChanged', data }) const { SORT_TYPE, SCHEMA_DATA_TYPE, BLOCK_OPENNESS } = constants @@ -720,6 +723,7 @@ export default function () { NODE_TYPE_PAGE, DEFAULT_GROUP_ID, DEFAULT_GROUP_NAME, + postHistoryChanged, selectedGroup, selectedBlock, selectedBlockArray, diff --git a/packages/plugins/page/src/PageTree.vue b/packages/plugins/page/src/PageTree.vue index 6ac0762799..fc547f4cbf 100644 --- a/packages/plugins/page/src/PageTree.vue +++ b/packages/plugins/page/src/PageTree.vue @@ -74,8 +74,14 @@ export default { setup(props, { emit }) { const { confirm } = useModal() const { initData, pageState, isBlock, isSaved } = useCanvas() - const { pageSettingState, changeTreeData, isCurrentDataSame, STATIC_PAGE_GROUP_ID, COMMON_PAGE_GROUP_ID } = - usePage() + const { + pageSettingState, + changeTreeData, + isCurrentDataSame, + STATIC_PAGE_GROUP_ID, + COMMON_PAGE_GROUP_ID, + postHistoryChanged + } = usePage() const { fetchPageList, fetchPageDetail } = http const { setBreadcrumbPage } = useBreadcrumb() const pageTreeRefs = ref([]) @@ -175,6 +181,7 @@ export default { url.searchParams.delete('blockid') url.searchParams.set('pageid', id) window.history.pushState({}, '', url) + postHistoryChanged({ pageId: id }) } const getPageDetail = (pageId) => { diff --git a/packages/plugins/page/src/composable/usePage.js b/packages/plugins/page/src/composable/usePage.js index c99072cc4c..a2368309be 100644 --- a/packages/plugins/page/src/composable/usePage.js +++ b/packages/plugins/page/src/composable/usePage.js @@ -13,6 +13,10 @@ import { reactive, ref } from 'vue' import { extend, isEqual } from '@opentiny/vue-renderless/common/object' +import { useMessage } from '@opentiny/tiny-engine-meta-register' +const { publish } = useMessage() +const postHistoryChanged = (data) => publish({ topic: 'historyChanged', data }) + const DEFAULT_PAGE = { app: '', name: '', @@ -135,6 +139,7 @@ const COMMON_PAGE_GROUP_ID = 1 export default () => { return { DEFAULT_PAGE, + postHistoryChanged, selectedTemplateCard, pageSettingState, isTemporaryPage, From cd18d18c15dd09882549420d8f6606cbabd54eab Mon Sep 17 00:00:00 2001 From: rhlin Date: Fri, 20 Dec 2024 23:28:53 -0800 Subject: [PATCH 08/16] fix(canvas/render): fix modify parent page and switch to child page, child page's parent view is not updated to latest version --- .../src/material-function/page-getter.ts | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/packages/canvas/render/src/material-function/page-getter.ts b/packages/canvas/render/src/material-function/page-getter.ts index c63bb4feb8..f14b055ca9 100644 --- a/packages/canvas/render/src/material-function/page-getter.ts +++ b/packages/canvas/render/src/material-function/page-getter.ts @@ -1,4 +1,4 @@ -import { defineComponent, h, ref } from 'vue' +import { defineComponent, h, onMounted, onUnmounted, ref, watch } from 'vue' import { getController } from '../canvas-function' import RenderMain from '../RenderMain' import { handleScopedCss } from './handle-scoped-css' @@ -27,19 +27,42 @@ export function initStyle(key: string, content: string) { export const wrapPageComponent = (pageId: string) => { const key = `data-te-page-${pageId}` const asyncData = ref(null) - fetchPageSchema(pageId).then((data) => { - asyncData.value = data - initStyle(key, data.css) - }) + const updateSchema = () => { + fetchPageSchema(pageId).then((data) => { + asyncData.value = data + initStyle(key, data.css) + }) + } + updateSchema() pageSchema[pageId] = defineComponent({ name: `page-${pageId}`, setup() { + const active = ref(pageId === getController().getBaseInfo().pageId) + const stop = getController().getHistoryDataChanged(() => { + const newValue = pageId === getController().getBaseInfo().pageId + if (active.value !== newValue) { + active.value = newValue + } + }) + const watchStop = watch( + () => active.value, + (activeValue) => { + if (!activeValue) { + updateSchema() + } + } + ) + onUnmounted(() => { + stop() + watchStop() + }) + return () => - asyncData.value + active.value || asyncData.value ? h(RenderMain, { cssScopeId: key, renderSchema: asyncData.value, - active: pageId === getController().getBaseInfo().pageId, + active: active.value, pageId: pageId, entry: false }) From e9c3cd81fa2d5f1bd1a60294f0d34107fa8c09c0 Mon Sep 17 00:00:00 2001 From: rhlin Date: Sat, 21 Dec 2024 01:31:28 -0800 Subject: [PATCH 09/16] feat(canvas/render): add builtin router-link material --- .../render/src/builtin/CanvasRouterLink.vue | 42 ++++++++ .../canvas/render/src/builtin/builtin.json | 97 ++++++++++++++++++- packages/canvas/render/src/builtin/index.ts | 4 +- .../src/material-function/material-getter.ts | 6 +- .../src/material-function/page-getter.ts | 2 +- 5 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 packages/canvas/render/src/builtin/CanvasRouterLink.vue diff --git a/packages/canvas/render/src/builtin/CanvasRouterLink.vue b/packages/canvas/render/src/builtin/CanvasRouterLink.vue new file mode 100644 index 0000000000..ed3a9f2f20 --- /dev/null +++ b/packages/canvas/render/src/builtin/CanvasRouterLink.vue @@ -0,0 +1,42 @@ + + diff --git a/packages/canvas/render/src/builtin/builtin.json b/packages/canvas/render/src/builtin/builtin.json index b2d53a0fb5..44ce9c25e6 100644 --- a/packages/canvas/render/src/builtin/builtin.json +++ b/packages/canvas/render/src/builtin/builtin.json @@ -133,7 +133,7 @@ } }, { - "icon": "Router", + "icon": "RouterView", "name": { "zh_CN": "RouterView" }, @@ -158,6 +158,79 @@ ] } }, + { + "icon": "RouterLink", + "name": { + "zh_CN": "RouterLink" + }, + "component": "RouterLink", + "schema": { + "properties": [ + { + "label": { + "zh_CN": "基础信息" + }, + "description": { + "zh_CN": "基础信息" + }, + "collapse": { + "number": 6, + "text": { + "zh_CN": "显示更多" + } + }, + "content": [ + { + "property": "to", + "type": "String", + "label": { + "text": { + "zh_CN": "跳转页面" + } + }, + "cols": 12, + "widget": { + "component": "InputConfigurator", + "props": {} + } + }, + { + "property": "activeClass", + "type": "String", + "label": { + "text": { + "zh_CN": "激活样式类" + } + }, + "cols": 12, + "widget": { + "component": "InputConfigurator", + "props": {} + } + }, + { + "property": "exactActiveClass", + "type": "String", + "label": { + "text": { + "zh_CN": "准确激活样式雷" + } + }, + "cols": 12, + "widget": { + "component": "InputConfigurator", + "props": {} + } + } + ] + } + ] + }, + "configure": { + "loop": true, + "isContainer": true + } + }, { "icon": "Collection", "name": { @@ -558,6 +631,28 @@ "props": {} } }, + { + "name": { + "zh_CN": "路由链接" + }, + "screenshot": "", + "snippetName": "RouterLink", + "icon": "RouterLink", + "schema": { + "componentName": "RouterLink", + "props": { + "to": "" + }, + "children": [ + { + "componentName": "Text", + "props": { + "text": "路由文本" + } + } + ] + } + }, { "name": { "zh_CN": "数据源容器" diff --git a/packages/canvas/render/src/builtin/index.ts b/packages/canvas/render/src/builtin/index.ts index 3ab3b562b2..0f176d64a6 100644 --- a/packages/canvas/render/src/builtin/index.ts +++ b/packages/canvas/render/src/builtin/index.ts @@ -18,6 +18,7 @@ import CanvasSlot from './CanvasSlot.vue' import CanvasImg from './CanvasImg.vue' import CanvasPlaceholder from './CanvasPlaceholder.vue' import CanvasRouterView from './CanvasRouterView.vue' +import CanvasRouterLink from './CanvasRouterLink.vue' export { CanvasText, @@ -27,5 +28,6 @@ export { CanvasSlot, CanvasImg, CanvasPlaceholder, - CanvasRouterView + CanvasRouterView, + CanvasRouterLink } diff --git a/packages/canvas/render/src/material-function/material-getter.ts b/packages/canvas/render/src/material-function/material-getter.ts index 00e906628c..be9da1e82e 100644 --- a/packages/canvas/render/src/material-function/material-getter.ts +++ b/packages/canvas/render/src/material-function/material-getter.ts @@ -11,7 +11,8 @@ import { CanvasSlot, CanvasImg, CanvasPlaceholder, - CanvasRouterView + CanvasRouterView, + CanvasRouterLink } from '../builtin' import { getController } from '../canvas-function/controller' import { generateCollection } from './support-collection' @@ -30,7 +31,8 @@ export const Mapper = { CanvasCol, CanvasRowColContainer, CanvasPlaceholder, - RouterView: CanvasRouterView + RouterView: CanvasRouterView, + RouterLink: CanvasRouterLink } const getNative = (name) => { return window.TinyLowcodeComponent?.[name] diff --git a/packages/canvas/render/src/material-function/page-getter.ts b/packages/canvas/render/src/material-function/page-getter.ts index f14b055ca9..810ee650aa 100644 --- a/packages/canvas/render/src/material-function/page-getter.ts +++ b/packages/canvas/render/src/material-function/page-getter.ts @@ -1,4 +1,4 @@ -import { defineComponent, h, onMounted, onUnmounted, ref, watch } from 'vue' +import { defineComponent, h, onUnmounted, ref, watch } from 'vue' import { getController } from '../canvas-function' import RenderMain from '../RenderMain' import { handleScopedCss } from './handle-scoped-css' From 777a428e243a3fe37758798e637fdd021587c199 Mon Sep 17 00:00:00 2001 From: rhlin Date: Mon, 23 Dec 2024 05:09:24 -0800 Subject: [PATCH 10/16] fix(canvas/render): fix when page ancestors contain number id cause recursive load pathFromRoot[0] page issue --- packages/canvas/render/src/material-function/page-getter.ts | 2 +- packages/canvas/render/src/render.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/canvas/render/src/material-function/page-getter.ts b/packages/canvas/render/src/material-function/page-getter.ts index 810ee650aa..d09e17232e 100644 --- a/packages/canvas/render/src/material-function/page-getter.ts +++ b/packages/canvas/render/src/material-function/page-getter.ts @@ -84,5 +84,5 @@ export async function getPageAncestors(pageId?: string) { return [pageId] } const pageChain = await getController().getPageAncestors(pageId) - return [...pageChain, pageId] + return [...pageChain.map((id: number | string) => id + ''), pageId] } diff --git a/packages/canvas/render/src/render.ts b/packages/canvas/render/src/render.ts index 6e9a06e469..ee4ed470da 100644 --- a/packages/canvas/render/src/render.ts +++ b/packages/canvas/render/src/render.ts @@ -210,7 +210,7 @@ function getRenderPageId(currentPageId, isPageStart) { function getNextChild(currentPageId) { const index = pagePathFromRoot.indexOf(currentPageId) - if (index + 1 < pagePathFromRoot.length) { + if (index > -1 && index + 1 < pagePathFromRoot.length) { return pagePathFromRoot[index + 1] } return null From 7db650487a507e53201b180445e6b92b8904a7e5 Mon Sep 17 00:00:00 2001 From: rhlin Date: Mon, 23 Dec 2024 22:15:55 -0800 Subject: [PATCH 11/16] fix(canvas/render): inactive page should not inject placeholder for empty children, remove builtin CanvasBox placeholder --- packages/canvas/render/src/builtin/CanvasBox.vue | 8 +------- packages/canvas/render/src/render.ts | 4 ++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/canvas/render/src/builtin/CanvasBox.vue b/packages/canvas/render/src/builtin/CanvasBox.vue index 0b9cda5f0b..b4414c5cd5 100644 --- a/packages/canvas/render/src/builtin/CanvasBox.vue +++ b/packages/canvas/render/src/builtin/CanvasBox.vue @@ -1,17 +1,11 @@