perf: router system

This commit is contained in:
chansee97 2024-03-17 00:08:27 +08:00
parent 1a2635af58
commit 35b4be9cfb
10 changed files with 148 additions and 170 deletions

73
src/router/guard.ts Normal file
View File

@ -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()
})
}

View File

@ -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
}

View File

@ -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()
})
}

View File

@ -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()
}

View File

@ -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

View File

@ -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',

View File

@ -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)
// 插入路由表

View File

@ -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