diff --git a/packages/canvas/render/src/builtin/CanvasRouterView.vue b/packages/canvas/render/src/builtin/CanvasRouterView.vue new file mode 100644 index 000000000..96beea7c2 --- /dev/null +++ b/packages/canvas/render/src/builtin/CanvasRouterView.vue @@ -0,0 +1,22 @@ + + + diff --git a/packages/canvas/render/src/builtin/builtin.json b/packages/canvas/render/src/builtin/builtin.json index ad56e67ab..4d2e487c1 100644 --- a/packages/canvas/render/src/builtin/builtin.json +++ b/packages/canvas/render/src/builtin/builtin.json @@ -2,6 +2,63 @@ "data": { "materials": { "components": [ + { + "icon": "Box", + "name": { + "zh_CN": "RouterView" + }, + "component": "RouterView", + "schema": { + "slots": {}, + "properties": [ + { + "label": { + "zh_CN": "基础信息" + }, + "description": { + "zh_CN": "基础信息" + }, + "collapse": { + "number": 6, + "text": { + "zh_CN": "显示更多" + } + }, + "content": [] + } + ], + "events": { + "onClick": { + "label": { + "zh_CN": "点击事件" + }, + "description": { + "zh_CN": "点击时触发的回调函数" + }, + "type": "event", + "functionInfo": { + "params": [], + "returns": {} + }, + "defaultValue": "" + } + }, + "shortcuts": { + "properties": [] + }, + "contentMenu": { + "actions": [] + } + }, + "configure": { + "loop": true, + "isContainer": true, + "nestingRule": { + "childWhitelist": [], + "descendantBlacklist": [] + } + } + }, { "icon": "Box", "name": { @@ -458,6 +515,17 @@ "zh_CN": "基础元素" }, "children": [ + { + "name": { + "zh_CN": "RouterView" + }, + "icon": "Box", + "screenshot": "", + "snippetName": "RouterView", + "schema": { + "componentName": "RouterView" + } + }, { "name": { "zh_CN": "文本" diff --git a/packages/canvas/render/src/builtin/index.js b/packages/canvas/render/src/builtin/index.js index f857ff1a1..3ab3b562b 100644 --- a/packages/canvas/render/src/builtin/index.js +++ b/packages/canvas/render/src/builtin/index.js @@ -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 +} diff --git a/packages/canvas/render/src/render.js b/packages/canvas/render/src/render.js index 1c5c4a36c..a90307e17 100644 --- a/packages/canvas/render/src/render.js +++ b/packages/canvas/render/src/render.js @@ -28,7 +28,8 @@ import { CanvasText, CanvasSlot, CanvasImg, - CanvasPlaceholder + CanvasPlaceholder, + CanvasRouterView } from './builtin' const { BROADCAST_CHANNEL } = constants @@ -69,7 +70,8 @@ const Mapper = { CanvasRow, CanvasCol, CanvasRowColContainer, - CanvasPlaceholder + CanvasPlaceholder, + CanvasRouterView } const { post } = useBroadcastChannel({ name: BROADCAST_CHANNEL.Notify }) diff --git a/packages/vue-generator/src/plugins/genRouterPlugin.js b/packages/vue-generator/src/plugins/genRouterPlugin.js index bccc98eae..869e8bf40 100644 --- a/packages/vue-generator/src/plugins/genRouterPlugin.js +++ b/packages/vue-generator/src/plugins/genRouterPlugin.js @@ -5,30 +5,98 @@ const defaultOption = { path: './src/router' } -const parseSchema = (schema) => { - const { pageSchema } = schema +const flattenRoutes = (routes, parentPath = '') => { + return routes.reduce((acc, route) => { + const fullPath = `${parentPath}${route.path}` + + if (route.path !== '/') { + if (route.component) { + // 如果存在 component,则直接添加路由 + const newRoute = { + path: fullPath, + component: route.component, + children: flattenRoutes(route.children) + } + const redirectChild = route.children.find((item) => item.isDefault) + + if (route.children && redirectChild) { + newRoute.redirect = `${fullPath}/${redirectChild.path}` + } + + acc.push(newRoute) + } else if (route.children && route.children.length > 0) { + // 如果不存在 component 但有 children,则递归处理 children + const children = flattenRoutes(route.children, fullPath + '/') + // 将处理后的 children 合并到上一层存在 component 的路由中 + acc.push(...children) + } + // 如果既没有 component 也没有 children,则不做任何处理 + } else { + acc.push(route) + } + + return acc + }, []) +} + +const convertToNestedRoutes = (schema) => { + const pageSchema = schema.pageSchema?.sort((a, b) => a.meta?.router?.length - b.meta?.router?.length) + const result = [] + let home = {} + let isGetHome = false + + pageSchema.forEach((item) => { + if ((item.meta.isHome || item.meta.isDefault) && !isGetHome) { + home = { + path: '/', + redirect: `/${item.meta.router}` + } + isGetHome = true + } + + const parts = item.meta?.router?.split('/').filter(Boolean) + let curretnLevel = result - const routes = pageSchema.map(({ meta: { isHome = false, router = '' } = {}, fileName, path }) => ({ - filePath: `@/views${path ? `/${path}` : ''}/${fileName}.vue`, - fileName, - isHome, - path: router?.startsWith?.('/') ? router : `/${router}` - })) + parts.forEach((part, index) => { + let found = false - const hasRoot = routes.some(({ path }) => path === '/') + for (let i = 0; i < curretnLevel.length; i++) { + if (curretnLevel[i].path === part) { + // 如果已经存在该路径部分,则进入下一层级 + curretnLevel = curretnLevel[i].children + found = true + break + } + } + + if (!found) { + // 如果不存在该路径部分,创建一个新节点 + const newNode = { + path: part, + children: [] + } + // 如果路径是最后一步,则设置组件和属性 + if (index === parts.length - 1) { + newNode.component = `() => import('@/views${item.path ? `/${item.path}` : ''}/${item.fileName}.vue')` + newNode.isDefault = item.meta.isDefault + } - if (!hasRoot && routes.length) { - const { path: homePath } = routes.find(({ isHome }) => isHome) || { path: routes[0].path } + curretnLevel.push(newNode) + curretnLevel = newNode.children + } + }) + }) - routes.unshift({ path: '/', redirect: homePath }) + if (home.path) { + result.unshift(home) } - return routes + return flattenRoutes(result) } +// 示例路由数组 function genRouterPlugin(options = {}) { const realOptions = mergeOptions(defaultOption, options) - const { path, fileName } = realOptions return { @@ -40,34 +108,21 @@ function genRouterPlugin(options = {}) { * @returns */ run(schema) { - const routesList = parseSchema(schema) + const routesList = convertToNestedRoutes(schema) + const resultStr = JSON.stringify(routesList, null, 2).replace( + /("component":\s*)"(.*?)"/g, + (match, p1, p2) => p1 + p2 + ) // TODO: 支持 hash 模式、history 模式 const importSnippet = "import { createRouter, createWebHashHistory } from 'vue-router'" const exportSnippet = ` -export default createRouter({ - history: createWebHashHistory(), - routes -})` - const routes = routesList.map(({ fileName, path, redirect, filePath }) => { - let pathAttr = `path: '${path}'` - let redirectAttr = '' - let componentAttr = '' - - if (redirect) { - redirectAttr = `redirect: '${redirect}'` - } - - if (fileName) { - componentAttr = `component: () => import('${filePath}')` - } - - const res = [pathAttr, redirectAttr, componentAttr].filter((item) => Boolean(item)).join(',') - - return `{${res}}` - }) + export default createRouter({ + history: createWebHashHistory(), + routes + })` - const routeSnippets = `const routes = [${routes.join(',')}]` + const routeSnippets = `const routes = ${resultStr}` const res = { fileType: 'js',