From da318420774ba84f945ea9506baeb849286cdc36 Mon Sep 17 00:00:00 2001 From: Gzx1999 Date: Tue, 2 Jan 2024 18:07:04 +0800 Subject: [PATCH] support dynamic plugin iframe --- frontend/src/main.ts | 2 +- frontend/src/request/plugin.ts | 8 + frontend/src/router/index.ts | 460 ++++++++++++---------- frontend/src/views/Home/Home.vue | 26 +- frontend/src/views/Plugin/PluginFrame.vue | 43 ++ frontend/src/views/Plugin/plugin.ts | 56 +++ frontend/vite.config.ts | 6 + 7 files changed, 385 insertions(+), 216 deletions(-) create mode 100644 frontend/src/views/Plugin/PluginFrame.vue create mode 100644 frontend/src/views/Plugin/plugin.ts diff --git a/frontend/src/main.ts b/frontend/src/main.ts index a68287a..99ac707 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -9,7 +9,7 @@ import * as ElementPlusIconsVue from '@element-plus/icons-vue' import App from './App.vue' import router from './router' -const app = createApp(App) +export const app = createApp(App) app.use(createPinia()) app.use(router) diff --git a/frontend/src/request/plugin.ts b/frontend/src/request/plugin.ts index 9eedae8..7e19bed 100644 --- a/frontend/src/request/plugin.ts +++ b/frontend/src/request/plugin.ts @@ -7,4 +7,12 @@ export function getPluginsPaged(data: any) { method: 'get', params: data, }) +} + +// 获取插件列表 +export function getPlugins() { + return request({ + url: '/plugins', + method: 'get', + }) } \ No newline at end of file diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index fba25da..87b533a 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -1,247 +1,281 @@ import { createRouter, createWebHistory } from 'vue-router'; const commonRoutes = [ - { - path: '/', - redirect: '/home', - }, - { - path: '/login', - name: 'login', - component: () => import('@/views/Login/Login.vue'), - }, + { + path: '/', + redirect: '/home', + }, + { + path: '/login', + name: 'login', + component: () => import('@/views/Login/Login.vue'), + }, ]; let sidebarRoutes = [ - { - path: '/home', - name: 'home', - redirect: '/overview', - component: () => import('@/views/Home/Home.vue'), - children: [ - { - path: '/overview', - name: 'overview', - component: () => import('@/views/Overview/Overview.vue'), - meta: { - title: 'overview', - header_title: '概览', - panel: 'overview', - icon: 'HomeFilled', - breadcrumb: [{ name: '概览' }], - }, - }, - { - path: '/cluster', - meta: { title: 'cluster', header_title: "系统", panel: "cluster", icon: 'Platform' }, + { + path: '/home', + name: 'home', + redirect: '/overview', + component: () => import('@/views/Home/Home.vue'), children: [ - { - path: '', - redirect: '/cluster/macList' - }, - { - path: '/cluster/macList', - name: 'macList', - component: () => import('../views/Cluster/Cluster.vue'), - meta: { - header_title: "机器列表", - panel: "/cluster/macList", - breadcrumb: [ - { - name: '系统', path: '/cluster', children: [ - { name: 'createBatch', menuName: '创建批次' }, - ] + { + path: '/overview', + name: 'overview', + component: () => import('@/views/Overview/Overview.vue'), + meta: { + title: 'overview', + header_title: '概览', + panel: 'overview', + icon: 'HomeFilled', + breadcrumb: [{ name: '概览' }], }, - { name: '机器列表' }, - ], - icon: '', - } - }, - { - path: '/cluster/machine/:uuid', - name: 'MacDetail', - component: () => import('../views/Cluster/MachineDetail/Index.vue'), - meta: { - header_title: "机器详情", - panel: "/cluster/macList", - breadcrumb: [ - { - name: '系统', path: '/cluster', children: [ - { name: 'createBatch', menuName: '创建批次' }, - ] - }, - { name: '机器列表', path: '/cluster/' }, - { name: '机器详情' } - ], - icon: '', - ignore: true, - } - }, - { - path: '/cluster/createBatch', - name: 'createBatch', - component: () => import('../views/Cluster/CreateBatch.vue'), - meta: { - header_title: "创建批次", - panel: "/cluster/createBatch", - breadcrumb: [ - { - name: '系统', path: '/cluster', children: [ - { name: 'macList', menuName: '机器列表' }, - ] + }, + { + path: '/cluster', + meta: { title: 'cluster', header_title: "系统", panel: "cluster", icon: 'Platform' }, + children: [ + { + path: '', + redirect: '/cluster/macList' + }, + { + path: '/cluster/macList', + name: 'macList', + component: () => import('../views/Cluster/Cluster.vue'), + meta: { + header_title: "机器列表", + panel: "/cluster/macList", + breadcrumb: [ + { + name: '系统', path: '/cluster', children: [ + { name: 'createBatch', menuName: '创建批次' }, + ] + }, + { name: '机器列表' }, + ], + icon: '', + } + }, + { + path: '/cluster/machine/:uuid', + name: 'MacDetail', + component: () => import('../views/Cluster/MachineDetail/Index.vue'), + meta: { + header_title: "机器详情", + panel: "/cluster/macList", + breadcrumb: [ + { + name: '系统', path: '/cluster', children: [ + { name: 'createBatch', menuName: '创建批次' }, + ] + }, + { name: '机器列表', path: '/cluster/' }, + { name: '机器详情' } + ], + icon: '', + ignore: true, + } + }, + { + path: '/cluster/createBatch', + name: 'createBatch', + component: () => import('../views/Cluster/CreateBatch.vue'), + meta: { + header_title: "创建批次", + panel: "/cluster/createBatch", + breadcrumb: [ + { + name: '系统', path: '/cluster', children: [ + { name: 'macList', menuName: '机器列表' }, + ] + }, + { name: '创建批次' }, + ], + icon: '' + } + } + ] + }, + { + path: '/batch', + meta: { + title: 'batch', header_title: "批次", panel: "batch", icon: 'DocumentCopy', + breadcrumb: [ + { name: '批次' }, + ] }, - { name: '创建批次' }, - ], - icon: '' - } - } - ] - }, - { - path: '/batch', - meta: { - title: 'batch', header_title: "批次", panel: "batch", icon: 'DocumentCopy', - breadcrumb: [ - { name: '批次' }, - ] - }, - children: [ - { - path: '', - redirect: '/batch/list' - }, - { - path: '/batch/list', - name: 'BatchList', - component: () => import('../views/Batch/Batch.vue'), - meta: { - header_title: "批次列表", - panel: "batch", - breadcrumb: [ - { name: '批次', path: '/batch' }, - { name: '批次列表' } - ], - icon: '' + children: [ + { + path: '', + redirect: '/batch/list' + }, + { + path: '/batch/list', + name: 'BatchList', + component: () => import('../views/Batch/Batch.vue'), + meta: { + header_title: "批次列表", + panel: "batch", + breadcrumb: [ + { name: '批次', path: '/batch' }, + { name: '批次列表' } + ], + icon: '' + }, + }, + { + path: '/batch/detail/:id', + name: 'BatchDetail', + component: () => import('../views/Batch/Detail.vue'), + meta: { + ignore: true, + header_title: "批次详情", + panel: "batch", + breadcrumb: [ + { name: '批次', path: '/batch' }, + { name: '批次详情' } + ], + icon: '' + } + }, + ] + }, + { + path: '/user', + name: 'User', + component: () => import('../views/User/User.vue'), + meta: { + title: 'user', header_title: "用户管理", panel: "user", icon: 'UserFilled', + breadcrumb: [ + { name: '用户管理' }, + ], + } }, - }, - { - path: '/batch/detail/:id', - name: 'BatchDetail', - component: () => import('../views/Batch/Detail.vue'), - meta: { - ignore: true, - header_title: "批次详情", - panel: "batch", - breadcrumb: [ - { name: '批次', path: '/batch' }, - { name: '批次详情' } - ], - icon: '' + { + path: '/role', + name: 'Role', + component: () => import('../views/Role/Role.vue'), + meta: { + title: 'role', header_title: "角色管理", panel: "role", icon: 'Ticket', + breadcrumb: [ + { name: '角色管理' }, + ], + } + }, + { + path: '/audit', + name: 'Audit', + component: () => import('../views/Audit/Audit.vue'), + meta: { + title: 'audit', header_title: "审计日志", panel: "audit", icon: 'View', + breadcrumb: [ + { name: '审计日志' }, + ], + } + }, + { + path: '/plugin', + name: 'Plugin', + component: () => import('../views/Plugin/Plugin.vue'), + meta: { + title: 'plugin', header_title: "插件管理", panel: "plugin", icon: 'Menu', + breadcrumb: [ + { name: '插件管理' }, + ], + } } - }, - ] - }, - { - path: '/user', - name: 'User', - component: () => import('../views/User/User.vue'), - meta: { - title: 'user', header_title: "用户管理", panel: "user", icon: 'UserFilled', - breadcrumb: [ - { name: '用户管理' }, - ], - } - }, - { - path: '/role', - name: 'Role', - component: () => import('../views/Role/Role.vue'), - meta: { - title: 'role', header_title: "角色管理", panel: "role", icon: 'Ticket', - breadcrumb: [ - { name: '角色管理' }, - ], - } - }, - { - path: '/audit', - name: 'Audit', - component: () => import('../views/Audit/Audit.vue'), - meta: { - title: 'audit', header_title: "审计日志", panel: "audit", icon: 'View', - breadcrumb: [ - { name: '审计日志' }, - ], - } - }, - { - path: '/plugin', - name: 'Plugin', - component: () => import('../views/Plugin/Plugin.vue'), - meta: { - title: 'plugin', header_title: "插件管理", panel: "plugin", icon: 'Menu', - breadcrumb: [ - { name: '插件管理' }, - ], - } - } - ], - }, + ], + }, ]; const router = createRouter({ - history: createWebHistory(import.meta.env.BASE_URL), - routes: [...commonRoutes, ...sidebarRoutes], + history: createWebHistory(import.meta.env.BASE_URL), + routes: [...commonRoutes, ...sidebarRoutes], }); export default router; +import { ref, onMounted, watchEffect, shallowRef } from "vue"; import { routerStore, type Menu } from '@/stores/router'; +import { iframeComponents } from "@/views/Plugin/plugin"; +import PluginFrame from "@/views/Plugin/PluginFrame.vue"; + +import { app } from "@/main"; +import { useRouter } from "vue-router"; + export function updateSidebarItems() { - let menus = []; - for (let route of sidebarRoutes[0].children) { - let subMenus = [] - if (route.children != null) { - for (let item of route.children) { - if (item.meta != null) { - if ('ignore' in item.meta && item.meta.ignore === true) { - continue - } - let obj: Menu = { + + let menus = generateLocalMenus(); + + for (let item of iframeComponents.value) { + let obj: Menu = { path: item.path, - title: item.meta.header_title, + title: item.name, hidden: false, - panel: item.meta.panel, - icon: item.meta.icon, + panel: item.name, + icon: "Menu", subMenus: null, - } - subMenus.push(obj) } - } + console.log("new iframe component",obj) + menus.push(obj) + + app.component(item.name, PluginFrame); + + router.addRoute("home",{ + path: item.path, + component: shallowRef(PluginFrame), + }) } + routerStore().menus = menus; +} + +function generateLocalMenus() { // 迭代 /home 下的所有组件 - let obj: Menu = { - path: route.path, - title: route.meta.header_title, - hidden: false, - panel: route.meta.panel, - icon: route.meta.icon, - subMenus: subMenus.length > 0 ? subMenus : null, - }; - - menus.push(obj); - } - - routerStore().menus = menus; + let menus = []; + for (let route of sidebarRoutes[0].children) { + let subMenus = [] + if (route.children != null) { + for (let item of route.children) { + if (item.meta != null) { + if ('ignore' in item.meta && item.meta.ignore === true) { + continue + } + let obj: Menu = { + path: item.path, + title: item.meta.header_title, + hidden: false, + panel: item.meta.panel, + icon: item.meta.icon, + subMenus: null, + } + subMenus.push(obj) + } + } + } + + let obj: Menu = { + path: route.path, + title: route.meta.header_title, + hidden: false, + panel: route.meta.panel, + icon: route.meta.icon, + subMenus: subMenus.length > 0 ? subMenus : null, + }; + + menus.push(obj); + } + + return menus; } router.beforeEach((to, from) => { - if (to.meta && to.meta.header_title) { - document.title = to.meta.header_title as string - } + if (to.meta && to.meta.header_title) { + document.title = to.meta.header_title as string + } }) export function directTo(to: any) { - router.push(to) + router.push(to) } \ No newline at end of file diff --git a/frontend/src/views/Home/Home.vue b/frontend/src/views/Home/Home.vue index 4db7fd5..5c26a07 100644 --- a/frontend/src/views/Home/Home.vue +++ b/frontend/src/views/Home/Home.vue @@ -33,7 +33,18 @@ - + + + {{ console.log("router-view component:", Component) }} + + + +
+ + +