项目结构介绍

                                  

vue3+ts+axios+pinia动态路由_List

模块名

说明

views

页面

stores

pinia全局状态管理

router

路由

request

axios请求封装

modules

模块数据格式封装

directive

自定义指令

components

组件

请求封装

request工具类封装

//导入axios
import axios from "axios";
import type{ AxiosInstance, AxiosResponse, InternalAxiosRequestConfig, AxiosRequestConfig,AxiosError } from "axios"
//element-plus 弹窗
import { ElMessage } from 'element-plus'
//响应结果类型
import type{result} from '@/modules/base/Base.ts'
//导入路由对象
import router from '@/router/index'
//导入token状态管理
import {useUserInfoStore} from '@/stores/UserInfo'
//根路径
export const config={
  baseURL: 'http://localhost:9001',  
  timeout: 10000,
  headers:{
      'Content-Type':'application/json'
  }
}
 
//请求类
export class HttpBase{
  // axios的实例
  private instance: AxiosInstance;
  // 初始化的作用
  constructor(configs: AxiosRequestConfig) {
    // 创建axios的实例
    this.instance = axios.create(configs)
    // 配置拦截器
    this.interceptors()
  }
  private interceptors() {
    // 拦截请求
    this.instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
        //获取token
        const token=useUserInfoStore().getToken
        if(token){
          config.headers.token=token
        }
        return config
    }), (error: any) => {
        error.data = {}
        error.data.message = '服务器异常请联系管理员'
        return error
    }
    // 拦截响应
    this.instance.interceptors.response.use((res: AxiosResponse) => {
        //获取token
        const token=res.headers.token;
        if(token){
          useUserInfoStore().setToken(token)
        }
        // 这里面可以做一些接口返回状态的判断
        //执行成功返回状态码以2开头,主要作用对于修改成功、新增成功等操作的消息提醒
        if ( /^2/.test(res.data.code)) {
          if(res.data.code!=200){
            ElMessage.success(res.data.message)
          }
            return Promise.resolve(res.data)
        } else {
          //执行异常的消息提醒
            ElMessage.error(res.data.message)
            return Promise.reject(res.data.message || '接口报错')
        }
    },(error: AxiosError) => {
        if(error.code==="ERR_NETWORK"){
            ElMessage.error('请检查您的网络')
 
        }else{
            const status=error.response.status;
            HttpBase.errorHandle(status)
        }
        
        return Promise.reject(error)
    })
  }
  // 下面是对几个请求方式的封装
  // post
  protected post<T = result>(url: string, data?: object): Promise<T> {
    return this.instance.post(url, data)
  }
  //put
  protected put<T = result>(url: string, data?: object): Promise<T> {
    return this.instance.put(url, data)
  }
  // get
  protected get<T = result>(url: string, params?: object): Promise<T> {
    return this.instance.get(url, { params })
  }
  // delete
  protected delete<T = result>(url: string): Promise<T> {
    return this.instance.delete(url)
  }
  // 图片上传
  protected upload<T = result>(url: string, params?: object): Promise<T> {
    return this.instance.post(url, params, {
        headers: {
            'Content-Type': 'multipart/form-data'
        }
    })
  }
  private static errorHandle(status:number) {
    // 状态码判断
    switch (status) {
      case 404:
        router.push('/404')
        break
      case 500:
        router.push('/500')
        break
      default:
    }
  }
}

菜单请求封装

import {HttpBase,config} from '@/request/base/index'
import type{ListMenu} from '@/modules/menu/MenuInfo'
// axios的配置项
const BaseUrl=config.baseURL+'/auth/menu'
 
class HttpMenu extends HttpBase{
  /**
   * 获取用户可访问权限
   * @returns 
   */
  async getMenuByUser():Promise<Array<ListMenu>>{
    //获取用户可访问菜单
    const url=BaseUrl+'/getMenuByUserId'
    return (await super.get(url)).data
  }
}
export default new HttpMenu(config);

数据实体封装

import type{BaseModule} from '@/modules/base/Base'
interface ListMenu extends BaseModule{
  menuName:string,//菜单名称
  menuCode:string,//菜单编码
  parentId:number,//菜单父id
  routerPath:string,//路由地址
  componentPath:string,//组件所在位置
  menuType:number,//菜单类型
  authorityCode:string,//权限编码
  requestUrl:string,//请求路径
  children:Array<ListMenu>//子级
  icon:string//菜单图标
 
}
 
 
export type{ListMenu} 

pinia全局状态管理封装

import { defineStore } from 'pinia'
import {computed, reactive, ref} from 'vue'
import type{ListMenu} from '@/modules/menu/MenuInfo'
import HttpMenu from '@/request/menu/Menu';
export const useMenuInfoStore = defineStore('menuInfo', () => {
  //获取标识符
    const menuFlag=ref<Boolean>(false)
    //获取菜单是否获取标识符
    const getMenuFlag=computed(()=>menuFlag.value)
    
    //响应式菜单集合数据
    const menuListInfo=reactive<Array<ListMenu>>([]);
 
    //获取菜单权限
    const getMenu=computed(async ()=>{
      //判断是否有权限
        if(!menuFlag.value){
          await getMenuByUser();
        }
        menuFlag.value=true;
        return menuListInfo;
    })
    //发送请求获取用户可访问菜单权限
    async function getMenuByUser(){
      try{
        const result=await HttpMenu.getMenuByUser()
        menuListInfo.push(...result)
     }catch(e){
        console.log(e)
     }
    }
    return {getMenu,getMenuFlag}
})

路由封装

import { createRouter, createWebHistory } from 'vue-router'
//登录页
import LoginView from '@/views/login/LoginView.vue'
//首页
import IndexView from '@/views/index/IndexView.vue'
//导入token 状态管理
import {useUserInfoStore} from '@/stores/UserInfo'
//导入获取权限状态管理
import { useMenuInfoStore } from '@/stores/MenuInfo'
 
 
 
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path:'/',
      redirect:'/login'
    },
    {
      path:'/login',
      name:'login',
      component: LoginView
    },
    {
      path:'/index',
      name:'index',
      component: IndexView
    }
  ]
})
router.beforeEach( async (to,from,next)=>{
  
  const userStore=useUserInfoStore();
  const menuStore=useMenuInfoStore();
  //验证是否登录
  if(to.path!='/login'){
    //获取token
    const token=userStore.getToken
    if(!token){
      router.push('/login')
      return ;
    }
    const menuFlag=menuStore.getMenuFlag;
    //判断是否为首页并且没有获取过菜单权限
    if(!menuFlag ){
    const modules = import.meta.glob("@/views/*/*.vue");
    //获取菜单权限
    const menuList= await menuStore.getMenu
    //循环遍历菜单
    for(const menuInfo of menuList){
     
      const children=menuInfo.children
      
      if(children){
      
        for(const childrenInfo of children){
            const menu={ 
              path:childrenInfo.routerPath,
              name:childrenInfo.menuCode,
              // component :() => import(`..${childrenInfo.componentPath}.vue`)
              component :modules[`/src${childrenInfo.componentPath}.vue`]
            }
            router.addRoute('index',menu)
        }
      }
    }
   
    next({...to,replace:true})
    return
  }
}
  next()
})
export default router

菜单渲染

<template>
  <el-row class="tac">
    <el-col >
      <el-menu class="el-menu-vertical-demo" :router="true">
        <el-menu-item index="/index">
          <el-icon><House /></el-icon>
          <span>首页</span>
        </el-menu-item>
        <el-sub-menu v-for="menu in menuList" :key="menu.id" :index="menu.id">
          <template #title>
            <el-icon><component :is="menu.icon"></component></el-icon>
            <span>{{ menu.menuName }}</span>
          </template><el-menu-item-group>
            <template v-for="children in menu.children" :key="children.id">
              <el-menu-item :index="children.id" :route="children.routerPath">
                {{ children.menuName }}
              </el-menu-item>
            </template>
          </el-menu-item-group>
        </el-sub-menu>
      </el-menu>
    </el-col>
  </el-row>
</template>
 
<script lang="ts" setup>
import { useMenuInfoStore } from '@/stores/MenuInfo'
import { reactive } from 'vue'
import type { ListMenu } from '@/modules/menu/MenuInfo'
import { onMounted } from 'vue'
//菜单集合
let menuList = reactive<Array<ListMenu>>([])
  //菜单全局管理对象
const menuStore=useMenuInfoStore();
//获取菜单集合
const getMenu = async () => {
  const menuInfoList = await menuStore.getMenu
  menuList.push(...menuInfoList)
}
onMounted(() => {
  getMenu()
})
</script>
<style scoped>
.el-row {
  height: 100%;
}
.el-menu {
  height: 100%;
}
 
</style>