vite+ts+mock+vue-router+pinia实现vue的路由权限

0.权限管理

前端的权限管理主要分为如下:

  • 接口权限
  • 路由权限
  • 菜单权限
  • 按钮权限

权限是对特定资源的访问许可,所谓权限控制,也就是确保用户只能访问到被分配的资源

1.项目搭建

创建vite项目

yarn create vite

配置别名

npm install path --save

npm install @types/node --save-dev

tsconfig.json
在这里插入图片描述

    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["./src/components/*"]
    },

vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from "path";

// https://vitejs.dev/config/
export default defineConfig({
  resolve: {
    // 配置路径别名
    alias: {
      "@": path.resolve(__dirname, "./src"),
      "@components": path.resolve(__dirname, "./src/components"),
    },
  },
  plugins: [
    vue(),
  ]
  
})

创建基础路由

npm i vue-router@4
const routes = [
    {
      name: "Home",
      path: "/",
      component: () => import("@/views/Home.vue"),
    },
   
  ];
  export default routes; //导出
  
import { createRouter, createWebHistory } from "vue-router";
import routes from "./router";

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

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
const app =createApp(App)
app.use(router)
app.mount('#app')
<script setup lang="ts">
</script>

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

<style scoped>

</style>

配置mock

yarn add mockjs vite-plugin-mock -D
import { MockMethod } from 'vite-plugin-mock';
export default [
  {
    url: `/api/list`,
    method: 'get',
    response: () => {
      return [{
 			 name:'tom',
 			 age:16,
 			 nation:'USA'
			}];
    },
  },
 
] as MockMethod[];

vite.config.ts配置

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
import { viteMockServe } from 'vite-plugin-mock'

// https://vitejs.dev/config/
export default defineConfig({
  resolve: {
    // 配置路径别名
    alias: {
      "@": path.resolve(__dirname, "./src"),
      "@components": path.resolve(__dirname, "./src/components"),
    },
  },
  plugins: [
    vue(),
    viteMockServe({
	    mockPath: './src/mock'
	  })
  ]
})

使用

<script setup lang="ts">
import axios from 'axios';

axios.get('/api/list').then(res=>{
    console.log(res.data);

})
</script>

<template>
<h1>超市管理系统首页</h1>

</template>

<style scoped>

</style>

在这里插入图片描述

安装pinia

yarn add pinia
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'
const pinia = createPinia()


const app =createApp(App)
app.use(router)
app.use(pinia)
app.mount('#app')

import { defineStore } from 'pinia'

export const useMarkStore = defineStore('mark', {
    state: () => ({ count: 0 }),
    getters: {
      double: (state) => state.count * 2,
    },
    actions: {
      increment() {
        this.count++
      },
    },
})

<script setup lang="ts">
import { useMarkStore } from '@/store/store';
import axios from 'axios';

const store= useMarkStore()
axios.get('/api/list').then(res=>{
    console.log(res.data);

})
</script>

<template>
<h1>超市管理系统首页</h1>
<h1>{{ store.count }}</h1>

</template>

<style scoped>

</style>

在这里插入图片描述

安装js-cookie插件

yarn add js-cookie

2.路由权限(包括菜单权限和按钮权限)

目录结构:
在这里插入图片描述

mock

import { MockMethod } from "vite-plugin-mock";
export default [
  {
    url: `/api/list`,
    method: "post",
    response: ({ body }) => {
      return body;
    },
  },
  {
    url: `/api/login`, //登录逻辑
    method: "post",
    response: ({ body }) => {
      let data = {};
      if (body.username == "tom") {
        data = {
          id: "1111",
          token: "4566adasdqfrqwd",
        };
      } else if (body.username == "amy") {
        data = {
          id: "222",
          token: "45184adaczz52za",
        };
      }
      return data;
    },
  },
  {
    url: `/api/getRoutes`, //简单方案:根据用户返回不同路由,真实后端逻辑:根据登录用户的角色去表里查授权该角色的的菜单
    method: "post",
    response: ({ body }) => {
      console.log(body);
      const routes = [];
      if (body.id == "1111") {
        routes.push(
          {
            name: "page1",
            path: "/page1",
            component: "/Page1.vue",
          },
          {
            name: "page2",
            path: "/page2",
            component: "/Page2.vue",
          }
        );
      } else if (body.id == "222") {
        routes.push( {
          name: "page3",
          path: "/page3",
          component: "/Page3.vue",
        });
      }
      return routes;
    },
  },
] as MockMethod[];

router

在这里插入图片描述

store

import { defineStore } from "pinia";
import Cookies from "js-cookie";
import axios from "axios";
import routes from '@/router/router'
const modules = import.meta.glob("../views/**/*.vue");

export const useMarkStore = defineStore("mark", {
  state: () => ({
    pageRoutes: <any>[], //当前页面缓存路由
    asyncRoutes: <any>[],//从接口获取到的路由数组
  }),
  getters: {
  },
  actions: {
    SET_ROUTES(_routes: any[]){
    //设置state中的值
        this.$state.asyncRoutes=_routes
        this.$state.pageRoutes=routes.concat(_routes)

    },
    getRouter() {
    //从后端接口获取到动态路由
      let _id = Cookies.get("id");
      if (_id) {
        return new Promise((resolve, reject) => {
          axios.post("/api/getRoutes", { id: _id }).then((res) => {
            console.log(res);
            let _data = res.data;
            let newData = this.parseRouter(_data);
            this.SET_ROUTES(newData)
            resolve(newData);
          });
        });
      }
    },
    parseRouter(_data: Array<any>) {
      //处理后端返回的路由数据=》vite项目能解析的路由格式
      let _newArr: Array<any> = [];
      _data.forEach((item: any) => {
        let newItem = Object.assign({}, item);
        let comp = item.component;

        newItem.component = modules[`../views${comp}`];
        _newArr.push(newItem);
      });
      return _newArr;
    },
  },
  getButtonCode(){
    //按钮权限思路:
    //1.在登录的时候拉取按钮权限编码code['EXPORT_LIST','OPEN_MODAL']
    //2.将编码缓存本地
    //3.页面通过v-if控制按钮或是自义定指令控制按钮

  }
});

permission.ts

import router from "@/router";
import { useMarkStore } from "@/store/store";
import Cookies from "js-cookie";


//获取view下所有的vue文件
// const modules = import.meta.glob('../views/**/*.vue')


//   export const getCurrRoutes=(name:string)=>{

//   }

// await axios.post('/api/getRoutes',{username:'tom'}).then(res=>{
//     console.log(res);
//     let _data=res.data
//     _data.forEach((item:any)=>{
//         let newItem=Object.assign({},item)
//         let comp=item.component

//         newItem.component=modules[`../views${comp}`]
//         router.addRoute(newItem)
//     })

// })
//白名单
const whiteList=['/about','/new','/login']

//路由守卫
router.beforeEach(async(to,from,next)=>{
    const store= useMarkStore()

    const token=Cookies.get("token")
    console.log(token);
   
    if(token){
        if(to.path=='login'){
            next('/')
        }else{
            //判断是否拿了路由规则
            if(store.asyncRoutes.length==0){
                //格式化好的路由
              const _temp:any=  await store.getRouter()
              _temp.forEach((item:any)=>router.addRoute(item))
              //继续跳转
              next(to.path)

            }else{
                if(to.matched.length!=0){
                    next()
                }else{
                    alert('无页面权限')
                    next(from.path)
                }
            }
        }
    }else{
        if(whiteList.indexOf(to.path)!= -1){
            next()
        }else{
            next('/login')
        }
        
    }
})

Login.vue

<script setup lang="ts">
import axios from 'axios';
import { ref } from 'vue';
import Cookies from 'js-cookie'
import { useRouter } from 'vue-router';
const router =useRouter()
const username=ref()

const login=()=>{
    axios.post('/api/login',{username:username.value}).then(res=>{
    console.log(res);
    if(res.data.token){
        Cookies.set('token',res.data.token)
        Cookies.set('id',res.data.id)
        router.push('/')
    }

})  
  axios.post('/api/getRoutes',{username:username.value}).then(res=>{
    console.log(res);

})
}
</script>

<template>
<h1>登录</h1>
<div>用户名:<input type="text" v-model="username"></div>
<button @click="login">登录</button>

</template>

<style scoped>

</style>

Home.vue

<script setup lang="ts">
import { useMarkStore } from '@/store/store';
import axios from 'axios';
import Cookies from 'js-cookie'

const store= useMarkStore()
axios.post('/api/list',{params:{name:'aaa'}}).then(res=>{
    console.log(res);

})

import { useRouter } from 'vue-router';
const router =useRouter()
const loginout=()=>{
    Cookies.remove('token')
    Cookies.remove('id')
    router.push('/login')
}

</script>

<template>
    <div @click="loginout">登出</div>
<h1>超市管理系统首页</h1>
<div>当前用户{{  }}</div>
<div>当前用户可用菜单:</div>
<div v-if="store.asyncRoutes" style="display: flex;flex-direction: column;" >
    <a v-for="item in store.asyncRoutes" :href="item.path">{{ item?.name }}</a>
</div>

</template>

<style scoped>

</style>

main.ts

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'
import  '@/util/permission'
const pinia = createPinia()

const app =createApp(App)
app.use(router)
app.use(pinia)
app.mount('#app')

按钮权限思路

 getButtonCode(){
    //按钮权限思路:
    //1.在登录的时候拉取按钮权限编码code['EXPORT_LIST','OPEN_MODAL']
    //2.将编码缓存本地
    //3.页面通过v-if控制按钮或是自义定指令控制按钮

  }

前端控制权限

//1.在路由配置中配置白名单
{
   name: "Login",
   path: "/login",
   component: () => import("@/views/Login.vue"),
   meta:{
		whiteList:['admin','tom']
	}
 },
 //2.在路由守卫beforeEach中判断当前用户角色是否在meta中,是就next()

效果

在这里插入图片描述
tom登录
在这里插入图片描述
amy登录

在这里插入图片描述

3.接口权限

登录完拿到token,将token存起来,通过axios请求拦截器进行拦截,每次请求的时候头部携带token

axios.interceptors.request.use(config => {
    config.headers['token'] = cookie.get('token')
    return config
})
axios.interceptors.response.use(res=>{},{response}=>{
    if (response.data.code === 40099 || response.data.code === 40098) { //token过期或者错误
        router.push('/login')
    }
})

4.注意点

mock


const login=()=>{//这里传参有三种方式:data,params,{}
    axios.post('/api/login',{username:'tom'}).then(res=>{
    console.log(res);

}) 
import { MockMethod } from "vite-plugin-mock";
export default [
  {
    url: `/api/login`, //登录逻辑
    method: "post",
    response: ({ body }) => {//获取传的参数使用body
      return body;
    },
  },
 
] as MockMethod[];

addRoute

动态添加路由在vite中不能使用以下方法:

// 路由拼接
function loadView(view:string) {
    return () => import(`@/views/${view}`)
}

上面的代码会报错:TypeError: Failed to resolve module specifier,应该采用import.meta.glob方式

import router from "@/router";
import axios from "axios";
//获取view下所有的vue文件
const modules = import.meta.glob('../views/**/*.vue')

//这里需要将异步获取的值改为同步
await axios.post('/api/getRoutes',{username:'tom'}).then(res=>{
    console.log(res);
    let _data=res.data
    _data.forEach((item:any)=>{
        let newItem=Object.assign({},item)
        let comp=item.component
        newItem.component=modules[`../views${comp}`]
        router.addRoute(newItem)
    })

})

5.源码地址

https://gitee.com/beekim/vue-route-mgr

参考:
https://cloud.tencent.com/developer/article/1794300
https://github.com/vitejs/vite/discussions/2746
https://blog.csdn.net/lucklymm/article/details/125420877
https://blog.csdn.net/weixin_43239880/article/details/129922664
https://blog.csdn.net/qq_36651686/article/details/116520731

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值