From 35b4be9cfbd42c624f47f1d863c4073041a31880 Mon Sep 17 00:00:00 2001 From: chansee97 Date: Sun, 17 Mar 2024 00:08:27 +0800 Subject: [PATCH] perf: router system --- src/router/guard.ts | 73 +++++++++++++++++++ src/router/guard/dynamic.ts | 72 ------------------ src/router/guard/index.ts | 37 ---------- src/router/guard/permission.ts | 54 -------------- src/router/index.ts | 2 +- .../{routes/index.ts => routes.inner.ts} | 0 .../{staticRoutes.ts => routes.static.ts} | 0 src/store/auth.ts | 6 ++ src/store/route.ts | 72 ++++++++++++++++-- src/typings/env.d.ts | 2 +- 10 files changed, 148 insertions(+), 170 deletions(-) create mode 100644 src/router/guard.ts delete mode 100644 src/router/guard/dynamic.ts delete mode 100644 src/router/guard/index.ts delete mode 100644 src/router/guard/permission.ts rename src/router/{routes/index.ts => routes.inner.ts} (100%) rename src/router/{staticRoutes.ts => routes.static.ts} (100%) diff --git a/src/router/guard.ts b/src/router/guard.ts new file mode 100644 index 0000000..b5946c3 --- /dev/null +++ b/src/router/guard.ts @@ -0,0 +1,73 @@ +import type { Router } from 'vue-router' +import { useRouteStore, useTabStore } from '@/store' +import { local } from '@/utils' + +const title = import.meta.env.VITE_APP_NAME + +export function setupRouterGuard(router: Router) { + router.beforeEach(async (to, from, next) => { + // 判断是否是外链,如果是直接打开网页并拦截跳转 + if (to.meta.herf) { + window.open(to.meta.herf) + return false + } + // 开始 loadingBar + window.$loadingBar?.start() + + // 权限操作 + const routeStore = useRouteStore() + // 判断有无TOKEN,登录鉴权 + const isLogin = Boolean(local.get('token')) + if (!isLogin) { + if (to.name === 'login') + next() + + if (to.name !== 'login') { + const redirect = to.name === '404' ? undefined : to.fullPath + next({ path: '/login', query: { redirect } }) + } + return false + } + + // 判断路由有无进行初始化 + if (!routeStore.isInitAuthRoute) { + await routeStore.initAuthRoute() + // 动态路由加载完回到根路由 + if (to.name === '404') { + // 等待权限路由加载好了,回到之前的路由,否则404 + next({ + path: to.fullPath, + replace: true, + query: to.query, + hash: to.hash, + }) + return false + } + } + + // 判断当前页是否在login,则定位去首页 + if (to.name === 'login') { + next({ path: '/' }) + return false + } + + next() + }) + router.beforeResolve((to) => { + const routeStore = useRouteStore() + const tabStore = useTabStore() + // 设置菜单高亮 + routeStore.setActiveMenu(to.meta.activeMenu ?? to.fullPath) + // 添加tabs + tabStore.addTab(to) + // 设置高亮标签; + tabStore.setCurrentTab(to.name as string) + }) + + router.afterEach((to) => { + // 修改网页标题 + document.title = `${to.meta.title} - ${title}` + // 结束 loadingBar + window.$loadingBar?.finish() + }) +} diff --git a/src/router/guard/dynamic.ts b/src/router/guard/dynamic.ts deleted file mode 100644 index 99278fc..0000000 --- a/src/router/guard/dynamic.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router' -import { clone, construct, min } from 'radash' -import { BasicLayout } from '@/layouts/index' -import { useRouteStore } from '@/store' -import { usePermission } from '@/hooks' -import { arrayToTree } from '@/utils' - -// 引入所有页面 -const modules = import.meta.glob('../../views/**/*.vue') - -/* 含有子级的路由重定向到第一个子级 */ -function setRedirect(routes: AppRoute.Route[]) { - routes.forEach((route) => { - if (route.children) { - if (!route.redirect) { - // 过滤出没有隐藏的子元素集 - const visibleChilds = route.children.filter(child => !child.meta.hide) - - // 过滤出含有order属性的页面 - const orderChilds = visibleChilds.filter(child => child.meta.order) - - // 重定向页默认第一个子元素的路径 - let target = route.children[0] - if (orderChilds.length > 0) - // 有order则取最小者重定向 - target = min(orderChilds, i => i.meta.order as number) as AppRoute.Route - - route.redirect = target.path - } - - setRedirect(route.children) - } - }) -} -export function createDynamicRoutes(routes: AppRoute.RowRoute[]) { - const { hasPermission } = usePermission() - // 结构化meta字段 - let resultRouter = clone(routes).map(i => construct(i)) as AppRoute.Route[] - // 路由权限过滤 - resultRouter = resultRouter.filter(i => hasPermission(i.meta.roles)) - - // 生成需要keepAlive的路由name数组 - const routeStore = useRouteStore() - routeStore.cacheRoutes = resultRouter.filter((i) => { - return i.meta.keepAlive - }) - .map(i => i.name) - - // 生成路由,有redirect的不需要引入文件 - resultRouter = resultRouter.map((item: any) => { - if (item.componentPath && !item.redirect) - item.component = modules[`../../views${item.componentPath}`] - return item - }) - - resultRouter = arrayToTree(resultRouter) as AppRoute.Route[] - setRedirect(resultRouter) - const appRootRoute: RouteRecordRaw = { - path: '/appRoot', - name: 'appRoot', - redirect: import.meta.env.VITE_HOME_PATH, - component: BasicLayout, - meta: { - title: '首页', - icon: 'icon-park-outline:home', - }, - children: [], - } - // 根据角色过滤后的插入根路由中 - appRootRoute.children = resultRouter as unknown as RouteRecordRaw[] - return appRootRoute -} diff --git a/src/router/guard/index.ts b/src/router/guard/index.ts deleted file mode 100644 index c970135..0000000 --- a/src/router/guard/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { Router } from 'vue-router' -import { createPermissionGuard } from './permission' -import { useRouteStore, useTabStore } from '@/store' - -const title = import.meta.env.VITE_APP_NAME - -export function setupRouterGuard(router: Router) { - router.beforeEach(async (to, from, next) => { - // 判断是否是外链,如果是直接打开网页并拦截跳转 - if (to.meta.herf) { - window.open(to.meta.herf) - return false - } - // 开始 loadingBar - window.$loadingBar?.start() - // 权限操作 - await createPermissionGuard(to, from, next) - }) - - router.beforeResolve((to) => { - const routeStore = useRouteStore() - const tabStore = useTabStore() - // 设置菜单高亮 - routeStore.setActiveMenu(to.meta.activeMenu ?? to.fullPath) - // 添加tabs - tabStore.addTab(to) - // 设置高亮标签; - tabStore.setCurrentTab(to.name as string) - }) - - router.afterEach((to) => { - // 修改网页标题 - document.title = `${to.meta.title} - ${title}` - // 结束 loadingBar - window.$loadingBar?.finish() - }) -} diff --git a/src/router/guard/permission.ts b/src/router/guard/permission.ts deleted file mode 100644 index 6dc9adc..0000000 --- a/src/router/guard/permission.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router' -import { local } from '@/utils' -import { useRouteStore } from '@/store' - -export async function createPermissionGuard( - to: RouteLocationNormalized, - from: RouteLocationNormalized, - next: NavigationGuardNext, -) { - const routeStore = useRouteStore() - // 判断有无TOKEN,登录鉴权 - const isLogin = Boolean(local.get('token')) - if (!isLogin) { - if (to.name === 'login') - next() - - if (to.name !== 'login') { - const redirect = to.name === '404' ? undefined : to.fullPath - next({ path: '/login', query: { redirect } }) - } - return false - } - - // 判断路由有无进行初始化 - if (!routeStore.isInitAuthRoute) { - await routeStore.initAuthRoute() - // 动态路由加载完回到根路由 - if (to.name === '404') { - // 等待权限路由加载好了,回到之前的路由,否则404 - next({ - path: to.fullPath, - replace: true, - query: to.query, - hash: to.hash, - }) - return false - } - } - - // 权限路由已经加载,仍然未找到,重定向到404 - // 若是从404再次跳转,则跳过判断 - if (to.name === '404' && to?.redirectedFrom?.name !== '404') { - next({ name: '404', replace: true }) - return false - } - - // 判断当前页是否在login,则定位去首页 - if (to.name === 'login') { - next({ path: '/' }) - return false - } - - next() -} diff --git a/src/router/index.ts b/src/router/index.ts index d88280a..1c8518d 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,6 +1,6 @@ import type { App } from 'vue' import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router' -import { routes } from './routes' +import { routes } from './routes.inner' import { setupRouterGuard } from './guard' const { VITE_ROUTE_MODE = 'hash', VITE_BASE_URL } = import.meta.env diff --git a/src/router/routes/index.ts b/src/router/routes.inner.ts similarity index 100% rename from src/router/routes/index.ts rename to src/router/routes.inner.ts diff --git a/src/router/staticRoutes.ts b/src/router/routes.static.ts similarity index 100% rename from src/router/staticRoutes.ts rename to src/router/routes.static.ts diff --git a/src/store/auth.ts b/src/store/auth.ts index 5563d30..b59d503 100644 --- a/src/store/auth.ts +++ b/src/store/auth.ts @@ -1,4 +1,5 @@ import { useRouteStore } from './route' +import { useTabStore } from './tab' import { fetchLogin, fetchUserInfo } from '@/service' import { router } from '@/router' import { local } from '@/utils' @@ -34,7 +35,12 @@ export const useAuthStore = defineStore('auth-store', { // 清空路由、菜单等数据 const routeStore = useRouteStore() routeStore.resetRouteStore() + // 清空标签栏数据 + const tabStore = useTabStore() + tabStore.tabs.length = 0 + // 重制当前存储库 this.$reset() + // 重定向到登录页 if (route.meta.requiresAuth) { router.push({ name: 'login', diff --git a/src/store/route.ts b/src/store/route.ts index 8b75d66..629abba 100644 --- a/src/store/route.ts +++ b/src/store/route.ts @@ -1,13 +1,14 @@ import type { MenuOption } from 'naive-ui' import { RouterLink } from 'vue-router' import { h } from 'vue' -import { clone, construct } from 'radash' +import { clone, construct, min } from 'radash' +import type { RouteRecordRaw } from 'vue-router' import { arrayToTree, local, renderIcon } from '@/utils' -import { createDynamicRoutes } from '@/router/guard/dynamic' import { router } from '@/router' import { fetchUserRoutes } from '@/service' -import { staticRoutes } from '@/router/staticRoutes' +import { staticRoutes } from '@/router/routes.static' import { usePermission } from '@/hooks' +import { BasicLayout } from '@/layouts/index' interface RoutesStatus { isInitAuthRoute: boolean @@ -115,6 +116,67 @@ export const useRouteStore = defineStore('route-store', { return target }) }, + setRedirect(routes: AppRoute.Route[]) { + routes.forEach((route) => { + if (route.children) { + if (!route.redirect) { + // 过滤出没有隐藏的子元素集 + const visibleChilds = route.children.filter(child => !child.meta.hide) + + // 过滤出含有order属性的页面 + const orderChilds = visibleChilds.filter(child => child.meta.order) + + // 重定向页默认第一个子元素的路径 + let target = route.children[0] + if (orderChilds.length > 0) + // 有order则取最小者重定向 + target = min(orderChilds, i => i.meta.order as number) as AppRoute.Route + + route.redirect = target.path + } + + this.setRedirect(route.children) + } + }) + }, + createDynamicRoutes(routes: AppRoute.RowRoute[]) { + const { hasPermission } = usePermission() + // 结构化meta字段 + let resultRouter = clone(routes).map(i => construct(i)) as AppRoute.Route[] + // 路由权限过滤 + resultRouter = resultRouter.filter(i => hasPermission(i.meta.roles)) + + // 生成需要keepAlive的路由name数组 + this.cacheRoutes = resultRouter.filter((i) => { + return i.meta.keepAlive + }) + .map(i => i.name) + + // 生成路由,有redirect的不需要引入文件 + const modules = import.meta.glob('@/views/**/*.vue') + resultRouter = resultRouter.map((item: any) => { + if (item.componentPath && !item.redirect) + item.component = modules[`/src/views${item.componentPath}`] + return item + }) + + resultRouter = arrayToTree(resultRouter) as AppRoute.Route[] + this.setRedirect(resultRouter) + const appRootRoute: RouteRecordRaw = { + path: '/appRoot', + name: 'appRoot', + redirect: import.meta.env.VITE_HOME_PATH, + component: BasicLayout, + meta: { + title: '首页', + icon: 'icon-park-outline:home', + }, + children: [], + } + // 根据角色过滤后的插入根路由中 + appRootRoute.children = resultRouter as unknown as RouteRecordRaw[] + return appRootRoute + }, /* 初始化动态路由 */ async initDynamicRoute() { // 根据用户id来获取用户的路由 @@ -130,7 +192,7 @@ export const useRouteStore = defineStore('route-store', { if (!data) return // 根据用户返回的路由表来生成真实路由 - const appRoutes = createDynamicRoutes(data) + const appRoutes = this.createDynamicRoutes(data) // 生成侧边菜单 this.createMenus(data) // 插入路由表 @@ -139,7 +201,7 @@ export const useRouteStore = defineStore('route-store', { /* 初始化静态路由 */ initStaticRoute() { // 根据静态路由表来生成真实路由 - const appRoutes = createDynamicRoutes(staticRoutes) + const appRoutes = this.createDynamicRoutes(staticRoutes) // 生成侧边菜单 this.createMenus(staticRoutes) // 插入路由表 diff --git a/src/typings/env.d.ts b/src/typings/env.d.ts index d0776e7..156d2c8 100644 --- a/src/typings/env.d.ts +++ b/src/typings/env.d.ts @@ -31,7 +31,7 @@ interface ImportMetaEnv { /** hash路由模式 */ readonly VITE_ROUTE_MODE?: 'hash' | 'web' /** 路由加载模式 */ - readonly VITE_AUTH_ROUTE_MODE?: 'static' | 'dynamic' + readonly VITE_AUTH_ROUTE_MODE: 'static' | 'dynamic' /** 首次加载页面 */ readonly VITE_HOME_PATH: string