v3+ts——3、路由模块(包含权限验证)

前言:路由的权限验证方式为后端返回权限标识字段,前端通过对权限标识字段进行验证,验证通过则显示相应的路由,不通过则不显示。因此将路由分为需要权限验证和不需要权限验证两种。

1、基础准备

(1)创建文件

文件介绍: 

router文件夹

  1. actionRouter.ts 需要权限验证的路由
  2. index.ts  路由入口
  3. staticRouter.ts  不需要权限验证的路由 任何人都可以访问

stores

  1.  permissionRouter.ts   验证路由权限相关的状态管理内容

 utils文件夹

  1.  router.ts   编写路由守卫

2、路由页面

views/home/index.vue(布局容器

<template>
  <div class="common-layout height100">
    <el-container class="height100">
      <el-aside width="200px">
        <!-- 侧边内容 -->
        <sideBar />
      </el-aside>
      <el-container>
        <el-header>
          <!-- 标题内容 -->
          <headerBar/>
        </el-header>
        <el-main>
          <!-- 内容主体 -->
          <router-view></router-view>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>

<script setup lang="ts">
import headerBar from "@/components/headerBar/index.vue";
import sideBar from "@/components/sideBar/index.vue";

</script>

staticRouter.ts文件

export default [
  {
    path: '/home',
    name: 'home',
    component:()=>import('@/views/home/index.vue'),//home中包含布局容器,如果是需要布局容器,就在最外层路由中加入这个component,如果不需要布局容器,就像下面的/login一样单独写在外面并在meta中加入 hidden:true
    meta:{
      index:1,//标识是否为一级菜单
    },
    children:[
      {
        path: '/home/userDetails',
        component: () => import('@/views/testPool/index.vue'),
        meta:{
          title:'测试title-1'
        },
      },
      {
        path: '/home/abcd',
        component: () => import('@/views/testPool/index.vue'),
        meta:{
          title:'测试title-2'
        },
      },
    ],
  },
  {
    path: '/login',
    name: 'login',
    component: () => import('@/views/login/index.vue'),
    meta:{
      hidden:true,// 标识是否在侧边栏菜单中展示
    }
  },
]

actionRouter.ts 文件

export default [
  {
    path: '/userlist',
    name: 'userlist',
    component:()=>import('@/views/home/index.vue'),
    meta:{
      index:1,
      title:'列表'
    },
    children:[{
        path: '/userlist/userlist1-1',
        name: 'userlist1-1',
        component: () => import('@/views/writerlist/index.vue'),
        meta:{
          perStr:'userlist页面Str',
          title:'写手列表',
        },
      },{
        path: '/userlist/userlist2-2',
        name: 'userlist2-2',
        component: () => import('@/views/userList/index.vue'),
        meta:{
          perStr:'userlist页面Str',
          title:'用户列表',
        },
      }
    ],
  },
  {
    path: '/userDetails',
    component: () => import('@/views/testPool/index.vue'),
    meta:{
      hidden:true,
      perStr:'详情页面Str',
    },
  },
  
]

index.ts文件

import { createRouter, createWebHistory } from 'vue-router'
import staticRouter from "./staticRouter";
import actionRouter from "./actionRouter";

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    // 首先加载无需权限验证的路由
    ...staticRouter,
    {
      path:'/',
      component:()=>import('@/views/home/index.vue'),
    },
  ]
})

export const perRouter =  [    
  // 将需要验证的路由 以及404页面导出备用
  ...actionRouter,
  {// 匹配之前路由未能匹配到的内容,也就是路由输入错误的情况,这个路由必须写在所有路由最后面
    path:'/:path(.*)',
    component:() => import('@/views/404/index.vue'),
  }
]
export default router

utils/Auth.ts 

import Cookies from 'js-cookie'
export const setToken = (token:string)=>{
  return Cookies.set('myToken', token, { expires: 1 })
}
export const getToken = ()=>{
  return Cookies.get('myToken')
}
export const removeToken = ()=>{
  //在存储用户信息时加一个通用前缀,在退出登录时即可通过这种方式批量删除
  for (const key in localStorage) {
    if(key.includes('xinba-')){ 
      localStorage.removeItem(key)
    }
  }
  return Cookies.remove('myToken')
}

utils/router.ts(路由守卫-重要)

import router from "@/router/index";
import { setToken,getToken} from "@/utils/Auth";
import permissionRouter from '@/stores/permissionRouter'
let noTokenRouter = ['/login']
router.beforeEach(async(to,form,next)=>{
  // 如果是白名单路由,直接放行 主要用于一些无需登录页面,例如登录注册
  if(noTokenRouter.includes(to.path))next()

  // 如果没有token 直接去登录页
  let myToken = getToken()
  if(!myToken)next({path:'/login'})
  
  // 引入验证权限的状态管理
  const myPerStore = permissionRouter()
  // perRouterFlag 标识是否已经加载过需要权限验证的路由,false为未加载,就进行验证权限然后添加路由
  if(!myPerStore.perRouterFlag){
    // addRouteFn() 是状态管理中的 获取有权限的路由的方法 最后返回结果为通过验证的路由
    const addRouterList =  await myPerStore.addRouteFn()
    addRouterList.forEach(item=>{
      router.addRoute(item)
    })
    // 通过next({...to}) 重新跳转到当前路由。如果这一步不重新跳转,就会导致在新添加的路由刷新页面时页面会为空
    next({...to})
  }else{
    // 如果已经加载过路由,就正常跳转
    next()
  }
})

export default router

stores/permissionRouter.ts(权限状态管理-核心)

import { ref, computed, reactive } from 'vue'
import { defineStore } from 'pinia'
import {perRouter} from '@/router/index' //引入需要权限验证的路由
import type { RouteRecordRaw } from 'vue-router'

// 验证权限的方法 参数为 权限标识数组,路由数组
function filterRoute(permissionArr:string[],routerList:RouteRecordRaw[]){
  let newRouter:RouteRecordRaw[] = reactive([])
  routerList.forEach((item:RouteRecordRaw)=>{
    // 如果不包含 perStr ,说明不需要权限验证,可以直接显示
    // 如果包含,就验证改perStr是否在返回的权限列表里
    if(!item?.meta?.perStr||permissionArr.includes(item.meta.perStr as string)){
      if(item.children){
        // 如果有child,就递归调用
        item.children = filterRoute(permissionArr,item.children)
      }
      newRouter.push(item)
    }
  })
  return newRouter
}

export default defineStore('permission', () => {

  let permissionArr:string[] = reactive([]) // 存放权限标识的数组
  let perRouterFlag = ref(false); //路由是否加载完成

  // 验证路由权限的方法
  async function addRouteFn(){
    // 获取本地储存的权限标识,如果没有就调接口获取
    let catchUserPermission = JSON.parse(localStorage.getItem('xinba-userPermission')||"[]")
    if(catchUserPermission.length>0){
      catchUserPermission.forEach((item:string)=>{
        permissionArr.push(item)
      })
      perRouterFlag.value = true;//获取完成之后将这个值设置为 true,表示路由已经获取完成了
    }else{
      //调接口获取权限列表
      ['userlist页面Str'].forEach((item:string)=>{
        permissionArr.push(item)
      })
      perRouterFlag.value = true;//获取完成之后将这个值设置为 true,表示路由已经获取完成了
      localStorage.setItem('xinba-userPermission',JSON.stringify(permissionArr))
    }

    return filterRoute(permissionArr,perRouter)
  }
  
  return { addRouteFn,perRouterFlag }
})

3、侧边栏效果实现

components/sideBar/checkPermission.ts

import router from '@/router';
import type { RouteRecordRaw } from 'vue-router'
type RouArr = RouteRecordRaw[]
// 过滤掉路由中的meta:{hidden:true}
function filterRouter(router:RouArr):RouArr{
  return router.filter((item:RouteRecordRaw)=>{
    // 如果hidden为true,返回false,过滤掉
    if(item.meta?.hidden)return false
    // 如果有child,继续递归调用
    if(item.children&&item.children.length>0){
      item.children = filterRouter(item.children)
      return {...item}
    }else{
      return [item]
    }
  })
}
const allRouter = router.getRoutes()

export const routerArr = filterRouter(allRouter.filter(item=>item.meta.index==1))

components/sideBar/index.vue

<template>
  <div class="side-box">
    <el-menu
        :default-active="defaultActive"
        class="el-menu-vertical-demo"
        router
      >
      <template v-for="(route,index) in routerArr" :key="index">
        <el-sub-menu v-if="route.children&&route.children.length>0" :index="route.path">
          <template #title>
            <el-icon><location /></el-icon>
            <span>{{ route.name }}</span>
          </template>
          <el-menu-item  v-for="(item,i) in route.children" :key="i" :index="item.path">{{ item.meta?.title||'未知标题' }}</el-menu-item>
        </el-sub-menu>
        <el-menu-item v-else :index="route.path">
          <el-icon><Menu /></el-icon>
          <span>{{ route.name }}</span>
        </el-menu-item>
      </template>
    </el-menu>
  </div>
</template>

<script setup lang="ts">
import { computed, reactive, ref } from "vue";
import { useRoute } from "vue-router";
import { routerArr } from "./checkPermission";
const myUseRoute = useRoute()

const defaultActive = computed(()=>{
  return myUseRoute.path
})
</script>
<style lang="scss" scoped>
.side-box{
  height: 100%;
  .el-menu{
    height: 100%;
  }
}
</style>

页面效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值