diff --git a/mock/asyncRoutes.ts b/mock/asyncRoutes.ts new file mode 100644 index 000000000..5d2bd9a88 --- /dev/null +++ b/mock/asyncRoutes.ts @@ -0,0 +1,52 @@ +// 根据角色动态生成路由 +import { MockMethod } from "vite-plugin-mock"; + +// http://mockjs.com/examples.html#Object +const systemRouter = { + path: "/system", + name: "system", + // component: Layout, + redirect: "/system/user", + meta: { + icon: "el-icon-setting", + title: "message.hssysManagement", + showLink: true, + savedPosition: true, + rank: 6, + }, + children: [ + { + path: "/system/user", + name: "user", + // component: () => import("/@/views/system/user/index.vue"), + meta: { + title: "message.hsBaseinfo", + showLink: false, + savedPosition: true, + }, + }, + { + path: "/system/dict", + name: "dict", + // component: () => import("/@/views/system/dict/index.vue"), + meta: { + title: "message.hsDict", + showLink: false, + savedPosition: true, + }, + }, + ], +}; + +export default [ + { + url: "/getAsyncRoutes", + method: "get", + response: () => { + return { + code: 0, + info: systemRouter, + }; + }, + }, +] as MockMethod[]; diff --git a/src/api/routes.ts b/src/api/routes.ts new file mode 100644 index 000000000..099ab2e86 --- /dev/null +++ b/src/api/routes.ts @@ -0,0 +1,5 @@ +import { http } from "../utils/http"; + +export const getAsyncRoutes = (data?: object): any => { + return http.request("get", "/getAsyncRoutes", data); +}; diff --git a/src/components/info/index.vue b/src/components/info/index.vue index 65267c4f2..c2eb3b069 100644 --- a/src/components/info/index.vue +++ b/src/components/info/index.vue @@ -130,7 +130,7 @@ export default defineComponent({ const noSecret = (): void => { storageSession.setItem("info", { - username: "测试用户", + username: "admin", accessToken: "eyJhbGciOiJIUzUxMiJ9.test" }); router.push("/"); diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue index f6e35d976..7c271cb85 100644 --- a/src/layout/components/Navbar.vue +++ b/src/layout/components/Navbar.vue @@ -198,14 +198,14 @@ export default defineComponent({ } } .el-dropdown-link { - width: 80px; + width: 70px; display: flex; align-items: center; justify-content: space-around; margin-right: 10px; cursor: pointer; p { - font-size: 13px; + font-size: 14px; } img { width: 22px; diff --git a/src/main.ts b/src/main.ts index a2e67933f..85719d39d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -49,12 +49,10 @@ export const getServerConfig = async (): Promise => { }); }; -getServerConfig().then(() => { - app - .use(router) - .use(store) - .use(useElementPlus) - .use(useTable) - .use(usI18n) - .mount("#app"); +getServerConfig().then(async () => { + app.use(router).use(store).use(useElementPlus).use(useTable).use(usI18n); + + await router.isReady(); + + app.mount("#app"); }); diff --git a/src/mockProdServer.ts b/src/mockProdServer.ts index 4c0088bec..02cf7bcca 100644 --- a/src/mockProdServer.ts +++ b/src/mockProdServer.ts @@ -1,9 +1,10 @@ -import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer' -import mapMock from '../mock/map' -import echartsMock from '../mock/echarts' +import { createProdMockServer } from "vite-plugin-mock/es/createProdMockServer"; +import mapMock from "../mock/map"; +import echartsMock from "../mock/echarts"; +import asyncRoutesMock from "../mock/asyncRoutes"; -export const mockModules = [...mapMock, ...echartsMock] +export const mockModules = [...mapMock, ...echartsMock, ...asyncRoutesMock]; export function setupProdMockServer() { - createProdMockServer(mockModules) + createProdMockServer(mockModules); } diff --git a/src/plugins/i18n/config.ts b/src/plugins/i18n/config.ts index 6e950cfc2..1bd11a0fb 100644 --- a/src/plugins/i18n/config.ts +++ b/src/plugins/i18n/config.ts @@ -38,6 +38,9 @@ export const menusConfig = { "hsmenu1-2-2": "菜单1-2-2", "hsmenu1-3": "菜单1-3", hsmenu2: "菜单2", + permission: "权限管理", + permissionPage: "页面权限", + permissionButton: "按钮权限", }, }, en: { @@ -70,6 +73,9 @@ export const menusConfig = { "hsmenu1-2-2": "Menu1-2-2", "hsmenu1-3": "Menu1-3", hsmenu2: "Menu2", + permission: "Permission Manage", + permissionPage: "Page Permission", + permissionButton: "Button Permission", }, }, }; diff --git a/src/router/index.ts b/src/router/index.ts index f64488a8b..cd380e211 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -5,27 +5,53 @@ import flowChartRouter from "./modules/flowchart"; import editorRouter from "./modules/editor"; import componentsRouter from "./modules/components"; import nestedRouter from "./modules/nested"; -import systemRouter from "./modules/system"; import errorRouter from "./modules/error"; -import remainingRouter from "./modules/remaining"; +import permissionRouter from "./modules/permission"; +import remainingRouter from "./modules/remaining"; //静态路由 +import Layout from "/@/layout/index.vue"; +import { getAsyncRoutes } from "/@/api/routes"; import { storageSession } from "../utils/storage"; import { i18n } from "/@/plugins/i18n/index"; -const routes: Array = [ +const constantRoutes: Array = [ homeRouter, flowChartRouter, editorRouter, componentsRouter, nestedRouter, - systemRouter, + permissionRouter, errorRouter, - ...remainingRouter, ]; +// 过滤后端传来的动态路由重新生成规范路由 +const addAsyncRoutes = (arrRoutes: Array) => { + if (!arrRoutes || !arrRoutes.length) return; + arrRoutes.forEach((v: any) => { + if (v.redirect) { + v.component = Layout; + } else { + // https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations + v.component = () => + import(/* @vite-ignore */ `/@/views${v.path}/index.vue`); + } + if (v.children) { + addAsyncRoutes(v.children); + } + }); + return arrRoutes; +}; + +// 按照路由中meta下的rank等级升序来排序路由 +function ascending(arr) { + return arr.sort((a: any, b: any) => { + return a?.meta?.rank - b?.meta?.rank; + }); +} + const router = createRouter({ history: createWebHashHistory(), - routes, + routes: ascending(constantRoutes).concat(...remainingRouter), scrollBehavior(to, from, savedPosition) { return new Promise((resolve, reject) => { if (savedPosition) { @@ -46,13 +72,31 @@ import NProgress from "../utils/progress"; const whiteList = ["/login", "/register"]; router.beforeEach((to, _from, next) => { + let isLogin = storageSession.getItem("info"); + // _from?.name; + if (isLogin && isLogin.username === "admin") { + // 异步路由 + getAsyncRoutes().then(({ info }) => { + addAsyncRoutes([info]).forEach((v: any) => { + // 防止重复添加路由 + if ( + router.options.routes.findIndex((value) => value.path === v.path) !== + -1 + ) + return; + // 切记将路由push到routes后还需要使用addRoute,这样路由才能正常跳转 + router.options.routes.push(v); + // 最终路由进行升序 + ascending(router.options.routes); + router.addRoute(v.name, v); + }); + }); + } NProgress.start(); const { t } = i18n.global; // @ts-ignore to.meta.title ? (document.title = t(to.meta.title)) : ""; // 动态title - whiteList.indexOf(to.path) !== -1 || storageSession.getItem("info") - ? next() - : next("/login"); // 全部重定向到登录页 + whiteList.indexOf(to.path) !== -1 || isLogin ? next() : next("/login"); // 全部重定向到登录页 }); router.afterEach(() => { diff --git a/src/router/modules/components.ts b/src/router/modules/components.ts index dcb96affc..d42f667a0 100644 --- a/src/router/modules/components.ts +++ b/src/router/modules/components.ts @@ -5,9 +5,17 @@ const componentsRouter = { name: "components", component: Layout, redirect: "/components/split-pane", + meta: { + icon: "el-icon-menu", + title: "message.hscomponents", + showLink: true, + savedPosition: true, + rank: 4, + }, children: [ { path: "/components/video", + name: "video", component: () => import("/@/views/components/video/index.vue"), meta: { title: "message.hsvideo", @@ -17,6 +25,7 @@ const componentsRouter = { }, { path: "/components/map", + name: "map", component: () => import("/@/views/components/map/index.vue"), meta: { title: "message.hsmap", @@ -26,6 +35,7 @@ const componentsRouter = { }, { path: "/components/draggable", + name: "draggable", component: () => import("/@/views/components/draggable/index.vue"), meta: { title: "message.hsdraggable", @@ -33,8 +43,10 @@ const componentsRouter = { savedPosition: true, }, }, + { - path: "/components/split-pane", + path: "/components/splitPane", + name: "splitPane", component: () => import("/@/views/components/split-pane/index.vue"), meta: { title: "message.hssplitPane", @@ -44,6 +56,7 @@ const componentsRouter = { }, { path: "/components/button", + name: "button", component: () => import("/@/views/components/button/index.vue"), meta: { title: "message.hsbutton", @@ -53,6 +66,7 @@ const componentsRouter = { }, { path: "/components/cropping", + name: "cropping", component: () => import("/@/views/components/cropping/index.vue"), meta: { title: "message.hscropping", @@ -62,6 +76,7 @@ const componentsRouter = { }, { path: "/components/countTo", + name: "countTo", component: () => import("/@/views/components/count-to/index.vue"), meta: { title: "message.hscountTo", @@ -71,6 +86,7 @@ const componentsRouter = { }, { path: "/components/selector", + name: "selector", component: () => import("/@/views/components/selector/index.vue"), meta: { title: "message.hsselector", @@ -80,6 +96,7 @@ const componentsRouter = { }, { path: "/components/seamlessScroll", + name: "seamlessScroll", component: () => import("/@/views/components/seamless-scroll/index.vue"), meta: { title: "message.hsseamless", @@ -89,6 +106,7 @@ const componentsRouter = { }, { path: "/components/contextmenu", + name: "contextmenu", component: () => import("/@/views/components/contextmenu/index.vue"), meta: { title: "message.hscontextmenu", @@ -97,12 +115,6 @@ const componentsRouter = { }, }, ], - meta: { - icon: "el-icon-menu", - title: "message.hscomponents", - showLink: true, - savedPosition: true, - }, }; export default componentsRouter; diff --git a/src/router/modules/editor.ts b/src/router/modules/editor.ts index 4a53685e8..63e4d2b6a 100644 --- a/src/router/modules/editor.ts +++ b/src/router/modules/editor.ts @@ -5,9 +5,17 @@ const editorRouter = { name: "editor", component: Layout, redirect: "/editor/index", + meta: { + icon: "el-icon-edit-outline", + title: "message.hseditor", + showLink: true, + savedPosition: true, + rank: 2, + }, children: [ { path: "/editor/index", + name: "editor", component: () => import("/@/views/editor/index.vue"), meta: { title: "message.hseditor", @@ -16,12 +24,6 @@ const editorRouter = { }, }, ], - meta: { - icon: "el-icon-edit-outline", - title: "message.hseditor", - showLink: true, - savedPosition: true, - }, }; export default editorRouter; diff --git a/src/router/modules/error.ts b/src/router/modules/error.ts index 2e40e4696..d702c67ca 100644 --- a/src/router/modules/error.ts +++ b/src/router/modules/error.ts @@ -5,9 +5,17 @@ const errorRouter = { name: "error", component: Layout, redirect: "/error/401", + meta: { + icon: "el-icon-position", + title: "message.hserror", + showLink: true, + savedPosition: true, + rank: 7, + }, children: [ { path: "/error/401", + name: "401", component: () => import("/@/views/error/401.vue"), meta: { title: "message.hsfourZeroOne", @@ -17,6 +25,7 @@ const errorRouter = { }, { path: "/error/404", + name: "404", component: () => import("/@/views/error/404.vue"), meta: { title: "message.hsfourZeroFour", @@ -25,12 +34,6 @@ const errorRouter = { }, }, ], - meta: { - icon: "el-icon-position", - title: "message.hserror", - showLink: true, - savedPosition: true, - }, }; export default errorRouter; diff --git a/src/router/modules/flowchart.ts b/src/router/modules/flowchart.ts index 1d08e54c3..4487756f0 100644 --- a/src/router/modules/flowchart.ts +++ b/src/router/modules/flowchart.ts @@ -5,9 +5,17 @@ const flowChartRouter = { name: "flowChart", component: Layout, redirect: "/flowChart/index", + meta: { + icon: "el-icon-set-up", + title: "message.hsflowChart", + showLink: true, + savedPosition: true, + rank: 1, + }, children: [ { path: "/flowChart/index", + name: "flowChart", component: () => import("/@/views/flow-chart/index.vue"), meta: { title: "message.hsflowChart", @@ -16,12 +24,6 @@ const flowChartRouter = { }, }, ], - meta: { - icon: "el-icon-set-up", - title: "message.hsflowChart", - showLink: true, - savedPosition: true, - }, }; export default flowChartRouter; diff --git a/src/router/modules/home.ts b/src/router/modules/home.ts index acf5076eb..eb438d4e4 100644 --- a/src/router/modules/home.ts +++ b/src/router/modules/home.ts @@ -5,6 +5,12 @@ const homeRouter = { name: "home", component: Layout, redirect: "/welcome", + meta: { + icon: "el-icon-s-home", + showLink: true, + savedPosition: false, + rank: 0, + }, children: [ { path: "/welcome", @@ -17,11 +23,6 @@ const homeRouter = { }, }, ], - meta: { - icon: "el-icon-s-home", - showLink: true, - savedPosition: false, - }, }; export default homeRouter; diff --git a/src/router/modules/nested.ts b/src/router/modules/nested.ts index 3a6be465a..66e20b8e1 100644 --- a/src/router/modules/nested.ts +++ b/src/router/modules/nested.ts @@ -10,6 +10,7 @@ const nestedRouter = { icon: "el-icon-s-data", showLink: true, savedPosition: false, + rank: 5, }, children: [ { diff --git a/src/router/modules/permission.ts b/src/router/modules/permission.ts new file mode 100644 index 000000000..75e138f5e --- /dev/null +++ b/src/router/modules/permission.ts @@ -0,0 +1,39 @@ +import Layout from "/@/layout/index.vue"; + +const permissionRouter = { + path: "/permission", + component: Layout, + redirect: "/permission/page", + name: "permission", + meta: { + title: "message.permission", + icon: "el-icon-lollipop", + showLink: true, + savedPosition: false, + rank: 3, + }, + children: [ + { + path: "/permission/page", + component: () => import("/@/views/permission/page.vue"), + name: "permissionPage", + meta: { + title: "message.permissionPage", + showLink: true, + savedPosition: false, + }, + }, + { + path: "/permission/button", + component: () => import("/@/views/permission/button.vue"), + name: "permissionButton", + meta: { + title: "message.permissionButton", + showLink: true, + savedPosition: false, + }, + }, + ], +}; + +export default permissionRouter; diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index cce855b0d..25820eb62 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -8,6 +8,7 @@ const remainingRouter = [ meta: { title: "message.hslogin", showLink: false, + rank: 101, }, }, { @@ -17,11 +18,13 @@ const remainingRouter = [ meta: { title: "message.hsregister", showLink: false, + rank: 102, }, }, { // 找不到路由重定向到404页面 path: "/:pathMatch(.*)", + name: "pathMatch", component: Layout, redirect: "/error/404", meta: { @@ -29,14 +32,24 @@ const remainingRouter = [ title: "message.hshome", showLink: false, savedPosition: false, + rank: 103, }, }, { path: "/redirect", + name: "redirect", component: Layout, + meta: { + icon: "el-icon-s-home", + title: "message.hshome", + showLink: false, + savedPosition: false, + rank: 104, + }, children: [ { path: "/redirect/:path(.*)", + name: "redirect", component: () => import("/@/views/redirect.vue"), }, ], diff --git a/src/router/modules/system.ts b/src/router/modules/system.ts deleted file mode 100644 index cf45ffbdf..000000000 --- a/src/router/modules/system.ts +++ /dev/null @@ -1,36 +0,0 @@ -import Layout from "/@/layout/index.vue"; - -const systemRouter = { - path: "/system", - name: "system", - component: Layout, - redirect: "/system/base", - children: [ - { - path: "/system/base", - component: () => import("/@/views/system/user.vue"), - meta: { - title: "message.hsBaseinfo", - showLink: false, - savedPosition: true, - }, - }, - { - path: "/system/dict", - component: () => import("/@/views/system/dict/index.vue"), - meta: { - title: "message.hsDict", - showLink: false, - savedPosition: true, - }, - }, - ], - meta: { - icon: "el-icon-setting", - title: "message.hssysManagement", - showLink: true, - savedPosition: true, - }, -}; - -export default systemRouter; diff --git a/src/views/permission/button.vue b/src/views/permission/button.vue new file mode 100644 index 000000000..36320aa6a --- /dev/null +++ b/src/views/permission/button.vue @@ -0,0 +1,15 @@ + + + + + diff --git a/src/views/permission/page.vue b/src/views/permission/page.vue new file mode 100644 index 000000000..c01936455 --- /dev/null +++ b/src/views/permission/page.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/src/views/system/dict/index.vue b/src/views/system/dict/index.vue index ad1c107df..3f6d33835 100644 --- a/src/views/system/dict/index.vue +++ b/src/views/system/dict/index.vue @@ -96,6 +96,7 @@ import { import Config from "./config.vue"; export default { + name: "dict", components: { Config }, diff --git a/src/views/system/user.vue b/src/views/system/user/index.vue similarity index 100% rename from src/views/system/user.vue rename to src/views/system/user/index.vue diff --git a/tsconfig.json b/tsconfig.json index 8fe323ef5..7fada94aa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -35,7 +35,8 @@ "types/**/*.ts", "types/global.d.ts", "types/shims-tsx.d.ts", - "types/shims-vue.d.ts" + "types/shims-vue.d.ts", + "mock/asyncRoutes.ts" ], "exclude": ["node_modules", "dist", "**/*.js"] }