2、vue后台系统-动态菜单

122 阅读2分钟

一、动态路由数据结构(mock数据,不限层级)

export default [
  {
    url: '/api/routerInfo', // 注意,这里只能是string格式
    method: 'get',
    response: () => {
      return {
        code: 200,
        message: 'ok',
        data: [
          {
            path: "/study",
            component: 'Layout',
            meta: { title: '学习管理'},
            name: "Study",
            children:[
              {
                path: "study1",
                name: "Study1",
                component:'/study/index',
                meta: { title: '学习1' },
              }
            ]
          },
          // 多层级
          {
            path: "/multiStage",
            component: 'Layout',
            meta: { title: '多层级'},
            name:'MultiStage',
            children:[
              {
                path: "stage1",
                name: "One",
                component:'/multiStage/index',
                meta: { title: '一层-01' },
              },
              {
                path: "stage2",
                name: "two",
                meta: { title: '一层-02' },
                children:[
                  {
                    path: "one1",
                    name: "One-1",
                    component:'/multiStage/one/index',
                    meta: { title: '02-1' },
                  },
                  {
                    path: "one2",
                    name: "One-2",
                    component:'/multiStage/one/two',
                    meta: { title: '02-2' }
                  }
                ]
              }
            ]
          }
        ]
      }
    }
  },
]

二、静态路由数据

import { createWebHistory, createRouter } from "vue-router";

import Layout from "@/layout/index.vue"

export const routes = [
  {
    path: "/",
    redirect:'/dashboard',
    name:'Dashboard',
    component: Layout,
    meta: { title: '首页' },
    children:[
      {
        path: "dashboard",
        name: "Dashboard",
        component: () => import('@/pages/dashboard/index.vue'),
        meta: { title: '首页1' },
      }
    ]
  },
  {
    path: "/login",
    name: "Login",
    component: () => import('@/pages/login/index.vue'),
    hidden:true
  }
];

export const errorRoute = [
  {
    path: '/:pathMatch(.*)*',
    name: '404',
    hidden: true,
    component: () => import('@/pages/404/index.vue'),
    meta: { title: 'P404' }
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;

三、登录后调用

// stores->userInfo.ts

import { defineStore } from 'pinia'
import { ref} from 'vue'
import {getUser,getRouterInfo} from '@/api/user.ts'
import {routes,errorRoute} from '@/router' // 静态router
import {dealAsyncRouter} from '@/utils/dealRouter.ts'

export const useUserInfoStore = defineStore('userInfo', () => {
  
  const userInfo = ref({})

  const routerList = ref([])

   async function getUserRouterInfoAction() {
    const res = await getUser()
    userInfo.value = res?.data
    await getRouterInfoAction()
  }

  async function getRouterInfoAction(){
    await getRouterInfo().then((res:any)=>{
      const asyncRouters = dealAsyncRouter(res.data)
      routerList.value = routes.concat(asyncRouters)
    }).catch(()=>{
      routerList.value = routes
    })
    // 404路由单独处理:页面刷新后,动态路由获取需要时间,会先匹配到静态404。所以加路由时,404放在最后加
    routerList.value = routerList.value.concat(errorRoute)
  }

  return { userInfo,routerList, getUserRouterInfoAction }
})
// utils->dealRouter.ts

// 处理后台返回的路由数据,列:路由匹配对应页面

import Layout from "@/layout/index.vue"

interface RouteObj {
  path:string
  name:string
  redirect?:string
  component:any,
  meta?:MetaObj
  children?: any
  hidden?:boolean
}

interface MetaObj {
  title:string
  icon?:string
}

export const dealAsyncRouter = (data:RouteObj[])=>{
  let routers = dealRouter(data)
  return routers
}

const dealRouter = (data:RouteObj[])=>{
  let routers = data
  routers.forEach(item=>{
    if(item.component==='Layout'){
      item.component = Layout
    }else if(item.component){
      item.component = dealFilePath(item.component)
    }
    if(item.children){
      item.children = dealRouter(item.children)
    }
  })
  return routers
}

// 处理组件引入路径
const dealFilePath = (data:string)=>{
  // return () =>import(/* @vite-ignore */`/src/pages${data}.vue`) 
  return () =>import(/* @vite-ignore */`/src/pages${data}.vue`) 
}

四、在main.ts注入

import { createApp } from 'vue'
import './styles/index.scss'
import App from './App.vue'
import router from './router/index.js'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import 'element-plus/theme-chalk/dark/css-vars.css'
import { createPinia } from 'pinia'
import '@/permission'

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.use(router)
app.use(ElementPlus)

app.mount('#app')

五、App.vue- 视图展示

<template>
  <router-view />
</template>

六、permission.ts- 路由拦截

import router from './router'
import {useUserInfoStore} from '@/stores/userInfo.ts'
import {isEmpty} from 'lodash'
// 进度条
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'

// 路由加载前
router.beforeEach(async (to:any, from:any, next:any) => {
  NProgress.start()

  const store = useUserInfoStore()
  const token = localStorage.getItem('token')
  if(token){
    if(to.path==='/login'){
      next({path:'/'})
    }else{
      // 有无路由判断
      if(isEmpty(store.userInfo)){
        await store.getUserRouterInfoAction()
        if(!isEmpty(store.routerList)){
          store.routerList.forEach((item:any)=>{
            router.addRoute(item)
          })
          // 如果 addRoutes 并未完成,路由守卫会一层一层的执行执行,直到 addRoutes 完成,找到对应的路由
          // replace: true=>告诉VUE本次操作后,不能通过浏览器后退按钮,返回前一个路由
          next({...to, replace: true})
        }
      }
      next()
    }
  }else{
    if(to.path==='/login'){
      next()
    }else{
      next({path:'/login'})
    }
  }
})

router.afterEach(()=>{
  NProgress.done()
})

备注:可疯狂吐槽(咸鱼不不翻身)