起步
使用vue-cli脚手架安装一个vue3项目(安装时带上路由模块)
前言:做管理系统必定绕不开权限管理这一块,本文详细介绍了对路由权限,接口权限,菜单栏权限,动态路由设置,按钮权限五个模块。码字不易,点赞支持!!!
GitHub地址:https://github.com/cwjbjy/vue3-admin
一、后端设计
在用户登录时,将token,用户等级以及菜单栏列表返回给前端;
token用来判别前端是否登录,用户等级决定前端动态路由,菜单栏列表决定前端菜单栏展示
1. 搭建node服务
-
在vue3项目中,新建一个server文件夹(与src同级)
-
新建一个终端,通过命令行
cd server
进入server文件夹
-
运行
npm init -y
初始化packag.json包 -
安装koa
npm i koa -s
-
新建server/index.js
const Koa = require("koa");
const app = new Koa();
app.use(async (ctx, next) => {
ctx.body = "这是一个应用中间件";
await next();
});
app.listen(4000, () => {
console.log("server is listening on port 4000");
});
-
安装nodemon
npm i nodemon -g
-
修改server/package.json
"scripts": {
"dev": "nodemon index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
运行:npm run dev 访问:127.0.0.1:4000,可以看到页面显示这是一个应用中间件
2. 使用路由中间件
-
安装
npm i koa-router -S
-
新建server/routes/index.js
const router = require("koa-router")();
let accessToken = "init_s_token"; //定义token
let role = ""; //定义用户等级
let menus = []; //定义菜单列表
/* 5s刷新一次token */
setInterval(() => {
accessToken = "s_tk" + Math.random();
}, 5000);
/* 登录接口获取token */
router.get("/login", async (ctx) => {
const { name } = ctx.query;
switch (name) {
case "admin":
role = "admin";
menus = ["home", "about", "manage"]; //管理员能看到首页,说明页和管理页
break;
default:
role = "visitor";
menus = ["home", "about"]; //游客只能看到首页,说明页
break;
}
ctx.body = {
accessToken,
role,
menus,
};
});
/* 获取应用数据 */
router.get("/getData", async (ctx) => {
let { authorization } = ctx.headers;
if (authorization !== accessToken) {
ctx.body = {
returncode: 104,
info: "token过期,重新登录",
};
} else {
ctx.body = {
code: 200,
returncode: 0,
data: { id: Math.random() },
};
}
});
module.exports = router;
- 修改server/index.js
//删除
app.use(async (ctx, next) => {
ctx.body = "这是一个应用中间件";
await next();
});
//新增
const index = require("./routes/index");
app.use(index.routes(), index.allowedMethods());
3. 跨域处理
-
安装
npm i koa2-cors
-
修改server/index.js
//新增
const cors = require("koa2-cors");
app.use(cors());
最终server/index.js文件
const Koa = require("koa");
const app = new Koa();
const index = require("./routes/index");
const cors = require("koa2-cors");
app.use(cors());
app.use(index.routes(), index.allowedMethods());
app.listen(4000, () => {
console.log("server is listening on port 4000");
});
目录结构:
重新运行 npm run dev,这时服务端已准备好
从截图中可以看出起了两个服务,一个前端本地服务,一个node服务
二、前端设计
技术准备:
- 定义使用到的常量
新建src/config/constant.js
//localStorage存储字段
export const ACCESS_TOKEN = "tk"; //存token
export const ROLE = 'role'; //存用户等级
export const MENUS = 'menus';//存菜单列表
//HTTP请求头字段
export const AUTH = "Authorization";
新建src/config/returnCodeMap.js
//接口状态码
export const CODE_LOGGED_OTHER = 106;// 在其它客户端被登录
export const CODE_RELOGIN = 104;// 重新登陆
新建src/config/menus.js
const menus = [
{
path: "/home",
key: "home",
name: "首页",
},
{
path: "/about",
key: "about",
name: "说明页",
},
{
path: "/manage",
key: "manage",
name: "管理页",
},
];
export default menus
- 安装axios服务
安装 npm i axios
新建src/service/index.js
//axios服务
import axios from "axios";
const service = axios.create({
baseURL: "//127.0.0.1:4000",
timeout: 30000,
});
export default service;
新建src/service/api.js
//定义接口
import service from "./index";
const API = {};
/* 登录接口 */
API.getLogin = (params) => {
return service.get("/login", { params: params });
};
/* 获取应用数据接口 */
API.getData = () => {
return service.get("/getData");
};
export default API;
- 调整目录结构
一个管理系统需要登录页,登录之后使用嵌套路由,layout用来布局,展示左侧菜单栏和头部用户信息,右侧用来展示页面内容,这样在路由切换时,菜单栏和头部可以保持不变
新建src/Layout.vue,src/views/Login.vue,src/views/Manage.vue(管理员才能访问的页面),还有创建项目自带的About.vue和Home.vue
- 调整路由
新建src/router/routes.js
//配置路由
const Login = () =>
import(/* webpackChunkName: "login" */ "../views/Login.vue");
const Home = () =>
import(/* webpackChunkName: "home" */ "../views/Home.vue");
const About = () =>
import(/* webpackChunkName: "about"*/ "../views/About.vue")
const Layout = () =>
import(/* webpackChunkName: "layout" */ "../Layout.vue");
const routes = [
{
path: "/",
redirect: "/home",
},
{
path: "/login",
component: Login,
},
{
path: "/layout",
name: "Layout",
component: Layout,
children: [
{
path: "/home",
name: "Home",
component: Home,
//存放按钮权限信息
meta: {
btnPermissions: ['admin', 'visitor']
},
},
{
path: "/about",
name: "About",
component: About,
meta: {
btnPermissions: ['admin']
},
},
],
},
];
export default routes;
修改src/router/index.js
import { createRouter, createWebHistory } from "vue-router";
import routes from "./routes";
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
新建src/router/dynamicRoute.js
//动态路由
const manage = {
path: "/manage",
name: "manage",
component: () =>
import(/* webpackChunkName:"manage" */ "../views/Manage.vue"),
};
export default manage
新建终端,启动前端服务。这时页面能出来了,接下来进行权限管理
1. 路由权限设置
情景:当用户没有登录,直接访问页面时,重定向到登录页登录
思路:在路由全局前置钩子中,增加鉴权功能
修改src/router/index.js
import { ACCESS_TOKEN, ROLE, MENUS } from "../config/constant";
router.beforeEach((to, from, next) => {
if (to.path === "/login") {
//在登录页清除存储信息
localStorage.removeItem(ACCESS_TOKEN);
localStorage.removeItem(ROLE);
localStorage.removeItem(MENUS);
}
let token = localStorage.getItem(ACCESS_TOKEN);
//没有token,则重定向到登录页
if (!token && to.path !== "/login") {
next({
path: "/login",
});
} else {
next();
}
});
2. 接口权限设置
情景:当token过期时,需用户重新登录
思路:在请求拦截器中,将token添加到请求头中;在响应拦截器中,判断状态码决定是否跳转到登录页
- 增加请求拦截器和响应拦截器
修改src/service/index.js
//新增
import { CODE_LOGGED_OTHER, CODE_RELOGIN } from "../config/returnCodeMap";
import { ACCESS_TOKEN, AUTH } from "../config/constant";
import router from "../router";
service.interceptors.request.use(
(config) => {
let { headers } = config;
const tk = localStorage.getItem(ACCESS_TOKEN);
tk &&
Object.assign(headers, {
[AUTH]: tk,
});
return config;
},
(error) => {
return Promise.reject(error);
}
);
service.interceptors.response.use(
(res) => {
let { data } = res;
if (
data.returncode === CODE_RELOGIN ||
data.returncode === CODE_LOGGED_OTHER
) {
router.push("/login");
//清除动态路由缓存
location.reload();
}
return res;
},
(error) => {
return Promise.reject(error);
}
);
- 登录页
修改src/views/Login.vue
<template>
<div style="height: 170px; margin-top: 60px; text-align: center">
XXXX管理系统
</div>
<div style="text-align: center">
姓名:<input v-model="user.name" />
<br />
密码:<input v-model="user.password" />
<br />
<button @click="sumbit">提交</button>
</div>
</template>
<script> import { reactive } from "vue"; import { useRouter } from "vue-router"; import API from "../service/api"; import { ACCESS_TOKEN, ROLE, MENUS } from "../config/constant"; import manageRoute from "../router/dynamicRoute"; export default { setup() { const router = useRouter(); const user = reactive({ name: "", password: "" }); const sumbit = () => { API.getLogin(user).then((res) => { localStorage.setItem(ACCESS_TOKEN, res.data.accessToken); localStorage.setItem(ROLE, res.data.role); localStorage.setItem(MENUS, JSON.stringify(res.data.menus)); if (res.data.role === "admin") { router.addRoute("Layout", manageRoute); } router.push("/home"); }); }; return { user, sumbit }; }, }; </script>
- 首页
修改src/views/Home.vue
<template>
<div class="home">
Home页面
<button @click="sumbit">提交</button>
</div>
</template>
<script> import { reactive } from "vue"; import API from "../service/api"; export default { setup() { const user = reactive({ name: "", password: "" }); const sumbit = () => { console.log(user); API.getData('/getData') }; return { user, sumbit }; }, } </script>
访问http://127.0.0.1:8080/login 进入登录页,登录之后过5s在首页点击获取数据按钮(token在服务端上设置了5s的过期时间),后端判断token是否过期,过期返回过期状态码,响应拦截器根据状态码跳转到登录页
3. 菜单栏权限设置
情景:不同级别用户看到不同菜单栏
思路:前端通过返回的菜单栏列表,去封装一个新的菜单栏数组
修改src/Layout.vue
<template>
<div id="home">
<header>
<button style="float: right" @click="exit">退出</button>
</header>
<main>
<aside>
<ul style="list-style: none">
<li v-for="(item,index) in newMenus" :key="index">
<router-link :to="item.path">{{item.name}}</router-link>
</li>
</ul>
</aside>
<article>
<router-view />
</article>
</main>
</div>
</template>
<script> import menus from "./config/menus"; import { MENUS } from "./config/constant"; export default { data() { return { newMenus:[] }; }, created() { const menuKeys = JSON.parse(localStorage.getItem(MENUS)); menus.forEach((item) => { if (item.key && menuKeys.includes(item.key)) this.newMenus.push(item); }); }, methods: { exit() { this.$router.push("/login"); //清除动态路由缓存 location.reload(); }, }, }; </script>
<style> #home { height: 100vh; } header { background: #f4f4f5; height: 70px; } main { display: flex; height: 100%; } aside { width: 150px; background: gray; height: 100%; } article { flex: 1; } </style>
这时通过用户名为 admin 的账户登录能看到三个菜单,其他用户只能看到两个
4. 动态路由设置
情景:管理员能访问管理页面路由,非管理员不能访问该路由
思路:通过router.addRoute添加动态路由
- 修改src/App.vue
<template>
<router-view />
</template>
<style> body { margin: 0; } </style>
- 管理员页面
修改src/views/Manage.vue
<template>
<div>管理员才能看到的页面</div>
</template>
- 添加动态路由
在src/views/Login.vue中新增(上面Login.vue文件已经加上了,这里单独拎出来展示)
import manageRoute from '../router/manageRoute'
//如果是管理员,添加管理员页面路由
if (res.data.role === "admin") {
router.addRoute("Layout", manageRoute);
}
提示:这里用的是vue3,对应的vue-router是4.x版本,使用addRoute添加动态路由。vue2对应的vue-router是3.x版本,使用addRoutes添加动态路由
- 解决刷新页面,动态路由丢失
原因:刷新页面,路由初始化,动态路由会丢失
思路:通过监听路由的变化,当刷新时,添加动态路由并定位到管理页面
修改src/App.vue
//新增
<script> import { ROLE } from "./config/constant"; import manage from "./router/dynamicRoute"; export default { watch: { $route: { async handler(newVal) { console.log("newVal", newVal); const role = localStorage.getItem(ROLE); if (role && role === "admin") { /* 在4.x版本中需手动调用router.replace方法重定向, 因为动态路由页面刷新时,matched的值为空; 在3.x版本中,刷新页面添加异步路由,matched有值,不需要再重定向 */ this.$router.addRoute("Layout", manage); /* 在动态路由页面刷新时,matched数组为空 */ if (!newVal.matched.length && newVal.fullPath === "/manage") { await this.$router.replace("/manage"); } } }, }, }, }; </script>
5. 按钮权限设置
情景:根据不同的用户,一些页面功能进行显示或者隐藏
思路:在路由元信息上定义权限信息,通过自定义指令删除一些DOM节点
- 定义路由元信息(上面routes.js中已经添加了)
{
path: "/about",
name: "About",
component: About,
meta: {
btnPermissions: ['admin']
},
},
- 增加判断方法
新建src/utils/index.js
import { ROLE } from "../config/constant";
// 权限检查方法
export function has(value) {
let isExist = false;
// 获取用户按钮权限
let btnPermissionsStr = localStorage.getItem(ROLE);
if (btnPermissionsStr == undefined || btnPermissionsStr == null) {
return false;
}
if (value.indexOf(btnPermissionsStr) > -1) {
isExist = true;
}
return isExist;
}
- 新建自定义指令
修改src/main.js
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { has } from "./utils";
const app = createApp(App);
app.directive("has", {
mounted(el) {
// 获取页面按钮权限
const btnPermissionsArr = router.currentRoute._value.meta.btnPermissions;
if (!has(btnPermissionsArr)) {
if (el.parentNode) {
el.parentNode.removeChild(el);
}
}
},
});
app.use(router).mount("#app");
- 在about页面使用v-has指令
修改src/views/About.vue
<template>
<div class="about">
<h1>This is an about page</h1>
<button type="button" v-has>管理员按钮</button>
</div>
</template>
效果演示:
今天的文章vue管理系统权限管理(从后端到前端整个流程)分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/20987.html