diff --git a/src/layouts/components/MenuContent.vue b/src/layouts/components/MenuContent.vue index 2bb2a08b..a51430a8 100644 --- a/src/layouts/components/MenuContent.vue +++ b/src/layouts/components/MenuContent.vue @@ -37,7 +37,7 @@ type ListItemType = MenuRoute; const { navData } = defineProps({ navData: { type: Array as PropType, - default: () => [], + default: (): MenuRoute[] => [], }, }); @@ -80,7 +80,7 @@ const getMenuList = (list: MenuRoute[], basePath?: string): ListItemType[] => { redirect: item.redirect, }; }) - .filter((item) => item.meta && item.meta.hidden !== true); + .filter((item) => item.meta && item.meta.hidden !== true && item.meta.title); }; const getHref = (item: MenuRoute) => { diff --git a/src/permission.ts b/src/permission.ts index c771e906..73018c73 100644 --- a/src/permission.ts +++ b/src/permission.ts @@ -26,8 +26,8 @@ router.beforeEach(async (to, from, next) => { try { await userStore.getUserInfo(); + // 后端权限控制 const { asyncRoutes } = permissionStore; - if (asyncRoutes && asyncRoutes.length === 0) { const routeList = await permissionStore.buildAsyncRoutes(); routeList.forEach((item: RouteRecordRaw) => { @@ -43,10 +43,23 @@ router.beforeEach(async (to, from, next) => { return; } } + + // 前端权限控制 + // const permissionStore = getPermissionStore(); + // const { routers } = permissionStore; + // if (routers.length === 0) { + // await permissionStore.initRoutes(userStore.roles); + // } + if (router.hasRoute(to.name)) { next(); } else { - next(`/`); + // 动态添加404 page + // router.addRoute(PAGE_NOT_FOUND_ROUTE); + // next(to.fullPath); + + // 不添加404 page,重定向到首页 + next({ path: '/' }); } } catch (error) { MessagePlugin.error(error.message); @@ -73,9 +86,9 @@ router.beforeEach(async (to, from, next) => { router.afterEach((to) => { if (to.path === '/login') { const userStore = useUserStore(); - const permissionStore = getPermissionStore(); - userStore.logout(); + + const permissionStore = getPermissionStore(); permissionStore.restoreRoutes(); } NProgress.done(); diff --git a/src/store/modules/permission-fe.ts b/src/store/modules/permission-fe.ts index aa089dd5..16207d61 100644 --- a/src/store/modules/permission-fe.ts +++ b/src/store/modules/permission-fe.ts @@ -3,30 +3,53 @@ import cloneDeep from 'lodash/cloneDeep'; import { defineStore } from 'pinia'; -import type { RouteRecordRaw } from 'vue-router'; +import type { RouteRecordName, RouteRecordRaw } from 'vue-router'; import router, { allRoutes } from '@/router'; import { store } from '@/store'; -function filterPermissionsRouters(routes: Array, roles: Array) { - const res: Array = []; - const removeRoutes: Array = []; +// 严格模式 默认所有路由不可访问 +const CHECK_ROLE_STRICT = false; +function filterPermissionsRouters( + routes: Array, + roles: Array, + whiteListRouters: Array, +): { + accessedRouters: Array; + removedRoutes: Array; +} { + if (routes.length === 0) return { accessedRouters: [], removedRoutes: [] }; + const accessedRouters: Array = []; + const removedRoutes: Array = []; routes.forEach((route) => { - const children: Array = []; - route.children?.forEach((childRouter) => { - const roleCode = childRouter.meta?.roleCode || childRouter.name; - if (roles.includes(roleCode)) { - children.push(childRouter); - } else { - removeRoutes.push(childRouter); - } - }); - if (children.length > 0) { - route.children = children; - res.push(route); + const roleCode = route.meta?.roleCode; + const hasPermission = CHECK_ROLE_STRICT ? roles.includes(roleCode) : !roleCode || roles.includes(roleCode); + const resolvedRoute = router.resolve(route); + const inWhiteList = whiteListRouters.includes(resolvedRoute.path); + if (!hasPermission && !inWhiteList) { + const removedRoute = cloneDeep(route); + removedRoutes.push(removedRoute); + return; + } + if (!route.children || route.children.length === 0) { + accessedRouters.push(route); + return; + } + const { accessedRouters: accessedChildren, removedRoutes: removedChildren } = filterPermissionsRouters( + route.children, + roles, + whiteListRouters, + ); + route.children = accessedChildren; + accessedRouters.push(route); + + if (removedChildren.length > 0) { + const removedRoute = cloneDeep(route); + removedRoute.children = removedChildren; + removedRoutes.push(removedRoute); } }); - return { accessedRouters: res, removeRoutes }; + return { accessedRouters, removedRoutes }; } export const usePermissionStore = defineStore('permission', { @@ -37,31 +60,65 @@ export const usePermissionStore = defineStore('permission', { }), actions: { async initRoutes(roles: Array) { - let accessedRouters = []; - - let removeRoutes: Array = []; + let accessedRouters: Array = []; + let removedRoutes: Array = []; // special token if (roles.includes('all')) { - accessedRouters = cloneDeep(allRoutes); + accessedRouters = allRoutes; } else { - const res = filterPermissionsRouters(allRoutes, roles); + const res = filterPermissionsRouters(allRoutes, roles, this.whiteListRouters); accessedRouters = res.accessedRouters; - removeRoutes = res.removeRoutes; + removedRoutes = res.removedRoutes; } + this.routers = cloneDeep(accessedRouters); + this.removeRoutes = removedRoutes; - this.routers = accessedRouters; - this.removeRoutes = removeRoutes; - - removeRoutes.forEach((item: RouteRecordRaw) => { - if (router.hasRoute(item.name)) { - router.removeRoute(item.name); + function checkNameInRoutes(name: RouteRecordName, routes: Array) { + if (routes.length === 0) return false; + for (const route of routes) { + if (route.name === name) { + return true; + } + if (!route.children || route.children.length === 0) { + return false; + } + if (checkNameInRoutes(name, route.children)) { + return true; + } + } + return false; + } + function removeRoutes(routes: Array) { + for (const route of routes) { + if (route.children && route.children.length > 0) { + removeRoutes(route.children); + } + const canRemoveRoute = !checkNameInRoutes(route.name, accessedRouters); + if (canRemoveRoute && router.hasRoute(route.name)) { + router.removeRoute(route.name); + } } - }); + } + removeRoutes(removedRoutes); }, - async restore() { - this.removeRoutes.forEach((item: RouteRecordRaw) => { - router.addRoute(item); - }); + async restoreRoutes() { + function addRemovedRoutes(routes: Array, parentName?: RouteRecordName) { + for (const route of routes) { + if (!router.hasRoute(route.name)) { + if (parentName) { + router.addRoute(parentName, route); + } else { + router.addRoute(route); + } + } + if (route.children && route.children.length > 0) { + addRemovedRoutes(route.children, route.name); + } + } + } + addRemovedRoutes(this.removeRoutes); + this.removeRoutes = []; + this.routers = []; }, }, }); diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index f68b4edc..d098e7cf 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -1,6 +1,5 @@ import { defineStore } from 'pinia'; -import { usePermissionStore } from '@/store'; import type { UserInfo } from '@/types/interface'; const InitUserInfo: UserInfo = { @@ -64,7 +63,7 @@ export const useUserStore = defineStore('user', { } return { name: 'td_dev', - roles: ['UserIndex', 'DashboardBase', 'login'], // 前端权限模型使用 如果使用请配置modules/permission-fe.ts使用 + roles: ['dev'], // 前端权限模型使用 如果使用请配置modules/permission-fe.ts使用 }; }; const res = await mockRemoteUserInfo(this.token); @@ -77,10 +76,6 @@ export const useUserStore = defineStore('user', { }, }, persist: { - afterRestore: () => { - const permissionStore = usePermissionStore(); - permissionStore.initRoutes(); - }, key: 'user', paths: ['token'], }, diff --git a/src/types/router.d.ts b/src/types/router.d.ts index f995e9a9..ae88563c 100644 --- a/src/types/router.d.ts +++ b/src/types/router.d.ts @@ -15,6 +15,6 @@ declare module 'vue-router' { keepAlive?: boolean; frameSrc?: string; frameBlank?: boolean; - // roleCode?: string; // 前端 roles 控制菜单权限 + roleCode?: string; // 前端 roles 控制菜单权限 } } diff --git a/src/utils/route/constant.ts b/src/utils/route/constant.ts index a3ec01c6..ac5e7da5 100644 --- a/src/utils/route/constant.ts +++ b/src/utils/route/constant.ts @@ -1,3 +1,5 @@ +import type { RouteRecordRaw } from 'vue-router'; + export const LAYOUT = () => import('@/layouts/index.vue'); export const BLANK_LAYOUT = () => import('@/layouts/blank.vue'); export const IFRAME = () => import('@/layouts/components/FrameBlank.vue'); @@ -7,8 +9,8 @@ export const PARENT_LAYOUT = () => resolve({ name: 'ParentLayout' }); }); -export const PAGE_NOT_FOUND_ROUTE = { - path: '/:w+', +export const PAGE_NOT_FOUND_ROUTE: RouteRecordRaw = { + path: '/:pathMatch(.*)*', name: '404Page', redirect: '/result/404', };