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代码

本篇文章到这里就结束了,有疑问或补充的小伙欢迎评论区留言!



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。