Vue3电影中后台开发纪实(六):权限管理

@登录开发

登录成功后将用户信息写入Vuex

const submitForm = formEl => {
   if (!formEl) return;

   formEl.validate(async valid => {
      if (valid) {
         console.log("submit!");

         const username = ruleForm.username;
         const password = ruleForm.pass;
         console.log(username, password);

         /* 执行登录,并将结果丢vuex */
         const { msg, user, token } = await login(username, password);
         ElMessage({
            message: msg,
            type: user ? "success" : "error",
         });

         /* 如果登录成功跳转 */
         if (user) {
            /* 将user和token丢vuex */
            store.dispatch("saveUserInfo", { user, token });

            setTimeout(() => {
               router.push("/");
            }, 500);
         }
      } else {
         console.log("error submit!");
         return false;
      }
   });
};

@接口权限

概述

  • [x] axios的响应拦截器里发现返回码是401/403,一脚踹到登录页;

axios的所有请求均携带token

/* 请求拦截器:统一添加鉴权token */
instance.interceptors.request.use(
   function (config) {
      // 在发送请求之前做些什么
      config.headers["Authorization"] = `Bearer ${store.state.token}`;
      return config;
   },

   function (error) {
      // 对请求错误做些什么
      return Promise.reject(error);
   }
);

服务端响应无权限时跳转登录页

instance.interceptors.response.use(
   function (res) {
      if(res.data.code===401 || res.data.code===403){
        router.push("/login")
      }
      return res.data;
   },

   function (error) {
      // 对请求错误做些什么
      return Promise.reject(error);
   }
);

@路由权限

概述

  • [x] 方案一:加载完整路由表,加全局守卫判断meta,无权限时一脚踹到登录页;
  • [x] 方案二:先挂载公共路由表,登录后再addRoute动态挂载权限路由表;

方案一:给路由加权限限制

  /* 热门城市 */
  {
     path: "/cinema/hot",
     name: "hot",
     // 当前路由的访问需要管理员权限
     meta: { menuIndex: "2-0-0", adminRequired: true },
     component: () => import("../views/cinema/HotCity.vue"),
  },

  /* 所有城市 */
  {
     path: "/cinema/all",
     name: "allcity",
     // 当前路由的访问需要管理员权限
     meta: { menuIndex: "2-1", adminRequired: true },
     component: () => import("../views/cinema/AllCity.vue"),
  },

方案一:在全局路由守卫中对无权限用户跳转登录页

/* 路由权限A方案:挂载所有路由 + 全局路由守卫判断权限 */
router.beforeEach((to, from, next) => {
   if (to.meta.adminRequired) {
      // 是管理员就放行
      if (store.state.user && store.state.user.admin) {
         next();
      } else {
         // 否则踹到登录页
         next({ name: "login" });
      }
   } else {
      // 不需要管理员权限直接放行
      next();
   }
});

方案二:先挂载公共路由

注意公共路由表以404结尾

router/routes/publicRoutes.js

export default [
   { path: "/", redirect: "/data" },

   /* demos */
   {
    path:"/demos",
    redirect:"/demos/element"
   },

   {
      path: "/demos",
      name: "demos",
      meta: { menuIndex: "4" },
      component: () => import("@demos/Index.vue"),

      children: [
         {
            path: "element",
            name: "element",
            component: () => import("@demos/ElementDemo.vue"),
         },
         {
            path: "vuex",
            name: "vuex",
            component: () => import("@demos/VuexDemo.vue"),
         },
      ],
   },
   
   ...

   /* 404 */
   {
      path: "/:pm(.*)*",
      name: "notfound",
      component: () => import("@views/NotFound.vue"),
   },
];

方案二:先挂载公共路由表

router/index.js

import publicRoutes from "./routes/publicRoutes";
import adminRoutes from "./routes/adminRoutes";

const router = createRouter({
   history: createWebHistory(import.meta.env.BASE_URL),
   routes: [...publicRoutes],
});

方案二:在全局守卫中动态挂载权限路由表

/* 路由权限B方案:挂载公共路由 + 全局路由守卫判断权限 + 动态挂载权限路由表 */
router.beforeEach((to, from, next) => {
   console.log("beforeEach",store.state.user);

   // 如果发现是管理员,就动态挂载管理员路由
   if (store.state.user && store.state.user.admin) {
      // 如果权限路由表未挂载就挂载之
      adminRoutes.forEach(r => {
         // 未挂载就挂载
         if (!router.hasRoute(r)) {
            router.addRoute(r);
            console.log(`挂载${r}完毕`);
         } else {
            console.log(`${r}已挂载`);
         }
      });
   }

   // 放行
   console.log("放行", to);
   next();
});

@菜单权限

概述

  • [x] 由服务端返回菜单(或前端登录后从本地调取对应菜单)并动态渲染;

从后端或本地获取到与用户角色匹配的菜单

[
    {
        "name":"数据看板",
        "path":"/data",
        "iconName":"PieChart"
    },
    {
        "name":"影片管理",
        "iconName":"Film",
        "submenu":[
            {
                "name":"正在热映",
                "iconName":"VideoCamera",
                "path":"/film/playing"
            },
            {
                "name":"即将上映",
                "iconName":"Loading",
                "path":"/film/coming"
            }
        ]
    },
    {
        "name":"影院管理",
        "iconName":"PictureFilled",
        "submenu":[
            {
                "name":"热门城市",
                "iconName":"Star",
                "submenu":[
                    {
                        "name":"北京",
                        "path":"/cinema/hot"
                    },
                    {
                        "name":"上海",
                        "path":"/cinema/hot"
                    },
                    {
                        "name":"广州",
                        "path":"/cinema/hot"
                    }
                ]
            },
            {
                "name":"所有城市",
                "iconName":"Location",
                "path":"/cinema/all"
            }
        ]
    },
    {
        "name":"用户管理",
        "iconName":"User",
        "path":"/user"
    },
    {
        "name":"案例管理",
        "iconName":"Grid",
        "path":"/demos"
    }
]

动态渲染该菜单

import adminMenu from "@api/menu.json";
const menu = ref(adminMenu)
<main>
  <div
     class="left"
     v-if="!$route.meta.hidePageHeader">

     <!-- 渲染动态菜单 -->
     <EpMenu
        :menu="menu"
        :activeIndex="currentMenuIndex"></EpMenu>
  </div>

  <!-- 右侧内容区 -->
  <div class="right">
     <router-view v-slot="{ Component }">
        <transition
           name="slide-fade"
           mode="out-in">
           <!-- <keep-alive :exclude="[`Detail`]"> -->
           <component :is="Component" />
           <!-- </keep-alive> -->
        </transition>
     </router-view>
  </div>
</main>

送大家一个超好使的菜单组件

npm i @steveouyang/super-ep
import { EpMenu } from "@steveouyang/super-ep"

递归菜单 封装原理戳这里↓↓↓(面试很加分的哦 (`・ω・´)~)

# Vue3电影中后台开发纪实(五):组件封装

@按钮权限

概述

  • [x] 隐藏无权限的按钮;
  • [x] v-if没权限就不显示;
  • [x] 自定义指令v-auth判断到无权限就直接删除当前按钮;

Vuex封装出是否管理员接口

const store = createStore({
   /* 状态(一手数据) */
   state() {
      return {
         count: 0,
         user: null,
         token: null,
      };
   },

   /* 二手数据:从一手数据换算而来 */
   // store.getters.isAdmin
   getters: {
      isAdmin(state){
         return state.user && state.user.admin
      }
   },
   
   ...
}

v-if判断不是管理员就不显示按钮

 <!-- 不是管理员就隐藏之 -->
 <el-button
    v-if="$store.getters.isAdmin"
    class="opBtn"
    type="danger"
    @click="patchDelete">
    <el-icon><Close /></el-icon>&nbsp;删除
 </el-button>

撸一个自定义指令来判断管理员权限

如果不是管理员就直接删除当前元素/组件

import store from "@store/index";

export default {
   name: "admin",

   /* 元素挂载时 */
   mounted(el, binding, vnode) {
      console.log("admin mounted");

      // 看看当前用户是否管理员
      if (!store.getters.isAdmin) {
         // 不是就el删除
         el.remove();
      }
   },
   
};

全局注册一下

import admin from "@directives/admin";

/* 注册全局自定义指令 */
app.directive(admin.name, admin);

在元素/组件上使用:不是管理员就直接删除目标元素

 <el-button
    v-admin
    class="opBtn"
    type="success"
    ><el-icon><DocumentCopy /></el-icon>&nbsp; 导出</el-button
 >

@各种方案的利弊比较

  • 接口控制策略 是服务端天然存在的,前端是否做配套处置看具体业务需求;
  • 全路由表策略 适用于比较小型的工程;
  • 半路由表策略(先加载公共路由表,登录后再动态补充加载权限路由表),适用于中大型项目,先期只加载一部分路由表能提升一些性能;
  • 无论全路由策略还是半路由策略,由于全局守卫的存在,性能都好不到哪去;
  • 菜单策略 一次性完成大面积的访问权限控制,作为一种宏观权限控制策略,个人认为要比路由控制更可取;
  • 按钮显隐策略 适合打扫一些边边角角的地方,是一种有效且必要的补充机制;

😈 :点赞收藏加关注了吗就走?!

image.png

本项目源码 watch,follow,fork!!!

祝大家撸码愉快~

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容