vue3+ts 纯前端实现动态路由
zs_ms 2024-07-30 14:03:03 阅读 76
实现流程
1.配置静态路由和需要权限控制的动态路由
2.路由前置守卫获取用户权限,递归查询动态路由
3.addRouter添加路由,pinia存贮路由表,用于导航栏渲染
路由配置
router/index.ts文件
引入RouteRecordRaw默认数据类型,防止编辑器报错import type { RouteRecordRaw } from 'vue-router';
静态路由
<code>export const staticRouter: RouteRecordRaw[] = [
{
path: '/',
redirect: 'login',
meta: {
showRoute: false
}
},
{
path: '/login',
name: 'login',
component: () => import('../views/login/index.vue'),
meta: {
showRoute: false
}
},
{
path: '/home',
name: 'home',
component: home,
meta: {
showRoute: true,
title: '个人中心'
}
},
{
path: '/404',
component: () => import('../views/404.vue'),
meta: {
showRoute: false
}
}
]
动态路由
export const dynamicRouter: RouteRecordRaw[] = [
{
path: '/VIP',
name: 'VIP',
meta: {
showRoute: true,
title: 'VIP',
roles: [0]
},
component: home,
},
{
path: '/system',
name: 'system',
meta: {
showRoute: true,
title: '系统管理'
},
children: [
{
path: '/system/log',
name: 'log',
component: () => import('../views/system/log.vue'),
meta: {
showRoute: true,
title: '日志管理'
},
},
{
path: '/system/user',
name: 'user',
component: () => import('../views/system/user.vue'),
meta: {
showRoute: true,
title: '用户管理',
roles: [0]
},
}
]
},
// vue3匹配404需要写正则 写在动态路由中防止动态路由添加前重定向404
{
path: "/:catchAll(.*)",
redirect: '/404',
meta: {
showRoute: false
}
}
]
路由守卫
router/permission.ts 仔细阅读备注,每一步都有介绍 记得在main.ts中导入
import router, { dynamicRouter, staticRouter } from '@/router/index'
import { getToken } from "@/utils/token"
import { useUserStore } from "@/stores"
import { getUserInfo } from '@/api/user'
// 引入vue-router 内置数据类型 防止编辑器报错
import type { RouteRecordRaw } from 'vue-router';
// 深拷贝函数
import { copyFn } from "@/utils/processing"
//定义路由白名单
const Whitelist = ['/', '/login', '/404']
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore()
const token = getToken()
if (token) {
if (to.path === '/login') next('/home')
// 每次刷新都会重新获取用户权限
else if (!userStore.userInfo) {
// 获取用户信息
const res = await getUserInfo()
// 用户权限存pinia不作持久化处理 防止手动更改
userStore.setUserInfo(res.result)
// role: 用户权限字段
const { role } = res.result
// 动态路由中component字段为函数JSON.parse,json.stringify无法实现深拷贝
// 如果路由数据由后端提供, 无需此操作
const dynamic_router = copyFn(dynamicRouter)
// 递归动态路由
const routers = routerFilter(dynamic_router, role)
// pinia 存储
userStore.setUserRouter([...staticRouter, ...routers])
// 路由添加 addRoutes以弃用
routers.forEach(v => {
router.addRoute(v)
})
// router.getRoutes()查看结果
// 首次或者刷新界面,next(...to, replace: true)会循环遍历路由,
// 如果to找不到对应的路由那么他会再执行一次beforeEach((to, from, next))直到找到对应的路由,
// 我们的问题在于页面刷新以后获取数据,直接执行next()感觉路由添加了,但是 是在next()之后执行的,所以我们没法导航到相应的界面。
// next()
next({ ...to, replace: true })
} else next()
} else {
if (!Whitelist.includes(to.path)) next('/login')
else next()
}
})
//递归查询函数
const routerFilter = (route_list: RouteRecordRaw[], role: number): RouteRecordRaw[] => {
// role === 0拥有所有权限 dynamicRouter定义的动态路由
if (role === 0) return dynamicRouter
// 有children字段 ? 递归查询 : 权限判断
const routes = route_list.filter(v => {
if (v.children) {
// 这一步操作会更改原数组,所以对动态路由进行了深拷贝处理
v.children = routerFilter(v.children, role)
}
// 没有定义roles字段表示所有用户都能访问
return !v.meta?.roles || (v.meta.roles as number[]).includes(role)
})
// 返回路由列表
return routes
}
用户退出登录,需要清空token和userInfo用户详细信息
实现效果
超级管理员登录
用户登录
初次加载,进入动态路由 浏览器会警告,不影响路由跳转
应该是执行next({ ...to, replace: true }),路由未添加完成,循环执行beforeEach后才添完成。有了解具体原因的小伙伴欢迎在评论区留言
代码补充
pinia存贮 stores/moudules/user.ts
<code>import { ref } from 'vue'
import { defineStore } from 'pinia'
import type { UserInfo } from '@/types/user'
import type { RouteRecordRaw } from 'vue-router';
export const useUserStore = defineStore('user', () => {
// 用户信息
const userInfo = ref<UserInfo>()
// 用户路由
const userRouter = ref<RouteRecordRaw[]>()
const setUserInfo = (info: UserInfo) => {
userInfo.value = info
}
const clearUserInfo = () => {
userInfo.value = undefined
}
const setUserRouter = (routes: RouteRecordRaw[]) => {
userRouter.value = routes
}
return {
userInfo,
userRouter,
setUserInfo,
clearUserInfo,
setUserRouter
}
})
深拷贝函数 utils/processing.ts
export const copyFn = <T>(value: T): T => {
/** 空 */
if (!value) return value;
/** 数组 */
if (Array.isArray(value)) return value.map((item) => copyFn(item)) as unknown as T;
/** 日期 */
if (value instanceof Date) return new Date(value) as unknown as T;
/** 普通对象 */
if (typeof value === 'object') {
return Object.fromEntries(
Object.entries(value).map(([k, v]: [string, any]) => {
return [k, copyFn(v)];
})
) as unknown as T;
}
/** 基本类型 */
return value;
}
导航栏渲染 app.vue
<script setup lang="ts">code>
import { useRouter, useRoute } from 'vue-router'
import MenuItem from '@/components/MenuItem.vue';
import { clearToken } from './utils/token';
import { useUserStore } from './stores';
const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
// const userRouter = userStore.userRouter
const loginOut = () => {
userStore.clearUserInfo()
clearToken()
router.push('/login')
}
</script>
<template>
<!-- 顶部导航显示 -->
<el-menu v-if="route.meta.showRoute" :default-active="route.path" class="el-menu-demo" mode="horizontal"code>
background-color="#545c64" text-color="#fff" active-text-color="#ffd04b" router>code>
<template v-for="item in userStore.userRouter" :key="item.path">code>
<!-- 导航递归组件 -->
<MenuItem v-if="item.meta?.showRoute" :item="item" />code>
</template>
</el-menu>
<el-button type="primary" @click="loginOut">退出</el-button>code>
<RouterView />
</template>
<style scoped></style>
作者踩坑
1. 路由守卫permission.ts一开始放在了文件根目录下,但是tsconfig.app.json 配置中"include": ["env.d.ts", "src/**/*", "src/**/*.vue"], "src/**/*"只识别src下的文件,导致我在文件中使用@别名,编辑器一直爆红,更改文件路径后修复
2.没有使用动态路由时,一直使用useRouter().options.routes渲染导航栏,但是这是一个只读属性,addRouter后值并不会更改,所以选择pinia存贮用户路由表
3.在pinia中,我使用ref()代理数据,当我调用setUserRouter设置路由表后,打印的又是reactive()函数包裹的对象(reactive()使用变量赋值会失去响应式,除非在嵌套一层:reactive(a:{xx: xxxx})),后面选择userStore.userRouter作为列表渲染,看上述app.vue代码
本篇文章到这里就结束了,有疑问或补充的小伙欢迎评论区留言!
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。