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;
}
});
};
@接口权限
概述
- 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);
}
);
@路由权限
概述
- 方案一:加载完整路由表,加全局守卫判断meta,无权限时一脚踹到登录页;
- 方案二:先挂载公共路由表,登录后再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();
});
@菜单权限
概述
- 由服务端返回菜单(或前端登录后从本地调取对应菜单)并动态渲染;
从后端或本地获取到与用户角色匹配的菜单
[
{
"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"
递归菜单 封装原理戳这里↓↓↓(面试很加分的哦 (`・ω・´)~)
@按钮权限
概述
- 隐藏无权限的按钮;
- v-if没权限就不显示;
- 自定义指令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> 删除
</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> 导出</el-button
>
@各种方案的利弊比较
- 接口控制策略 是服务端天然存在的,前端是否做配套处置看具体业务需求;
- 全路由表策略 适用于比较小型的工程;
- 半路由表策略(先加载公共路由表,登录后再动态补充加载权限路由表),适用于中大型项目,先期只加载一部分路由表能提升一些性能;
- 无论全路由策略还是半路由策略,由于全局守卫的存在,性能都好不到哪去;
- 菜单策略 一次性完成大面积的访问权限控制,作为一种宏观权限控制策略,个人认为要比路由控制更可取;
- 按钮显隐策略 适合打扫一些边边角角的地方,是一种有效且必要的补充机制;
😈 :点赞收藏加关注了吗就走?!
本项目源码 watch,follow,fork!!!
祝大家撸码愉快~
转载自:https://juejin.cn/post/7171197729438662692