最近因为失业了,有了挺多时间的,决定写一些有关前端的技术点,希望能对还从事前端开发的人有所帮助,也给自己从事前端画上一个句号吧。vue权限控制方面有前端路由和后端路由,前端路由一般所有的路由信息都是在前端路由文件中,判断不同的用户展示不同的路由信息,后端路由是不同的用户的路由信息都是后端通过接口返回,前端将每个不同登录用户的路由信息添加到路由中,进而实现不同用户不同路由不同菜单不同权限,第一中方式实现相对简单,目前企业大部分vue项目都使用的是第二种方式,所以我这里要对第二种方式进行详细讲解
案例中使用的一些前端技术
前端:vue3、element-plus、js-cookie(存储cookie)、pinia、vue-router、vite、sass
服务端:node.js、express、cors(解决跨域)
通过上述构建:
node.js服务的目录结构
server.js
const express = require('express');
const app = express();
const cors = require('cors');
app.use(cors());
app.use(express.json())
app.post('/login', (req, res) => {
let username = req.body.username;
let password = req.body.password;
if (username === 'admin' && password === '12345') {
res.json({
id: 1,
token: 'asbddfghhhhhss',
})
} else if (username === 'user1' && password === '22222') {
res.json({
id: 2,
token: 'dsfldsjljlj',
})
} else {
res.json({
mes: '用户名密码错误'
})
}
})
app.get('/buttonPower', (req, res) => {
let id = req.query.id;
if (id === '1') {
res.json([
'1000',
'1001',
'1002',
'1003'
])
} else if (id === '2') {
res.json([
'1004',
'1005',
'1006',
'1007'
])
}
})
app.get('/routes', (req, res) => {
let id = req.query.id;
if (id === '1') {
res.json([
{
path: '/',
name: 'page1',
component: 'page1',
children: []
},
{
path: '/page2',
name: 'page2',
component: '',
children: [
{
path: '/page3',
name: '文件a',
component: 'page3',
},
{
path: '/page4',
name: '文件b',
component: 'page4',
}
]
},
{
path: '/page5',
name: 'page5',
component: '',
children: [
{
path: '/page6',
name: '文件c',
component: 'page6',
},
{
path: '/page7',
name: '文件d',
component: 'page7',
}
]
}
//name 值相同导致前端to.matched为空哈哈
// {
// path: '/page3',
// name: 'page3',
// component: 'page3'
// },
// {
// path: '/page4',
// name: 'page4',
// component: 'page4'
// }
])
} else if (id === '2') {
res.json([
{
path: '/',
name: 'page1',
component: 'page1'
},
{
path: '/page2',
name: 'page2',
component: 'page2'
},
{
path: '/page4',
name: 'page4',
component: 'page4'
}
])
}
})
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
vue目录结构
vue的权限控制:页面权限控制(根据用户的权限动态的决定路由规则)、按钮权限控制(根据用户权限显示或隐藏按钮)
页面权限控制流程
用户登录
//提交表单引用的函数
const submitForm = () => {
//校验表单
loginFormRef.value.validate((valid) => {
if (valid) {
axios.post("http://127.0.0.1:3000/login", loginForm).then((res) => {
if (res.data.id) {
Cookie.set('id', res.data.id); //存储id
Cookie.set('token', res.data.token); //存储token
let buttonPinia = useButton();
buttonPinia.getButton();
router.push('/') //跳转到首页
} else {
ElMessage.error("用户名密码错误!");
}
});
}
});
};
存储token和id
//提交表单引用的函数
const submitForm = () => {
//校验表单
loginFormRef.value.validate((valid) => {
if (valid) {
axios.post("http://127.0.0.1:3000/login", loginForm).then((res) => {
if (res.data.id) {
Cookie.set('id', res.data.id); //存储id
Cookie.set('token', res.data.token); //存储token
let buttonPinia = useButton();
buttonPinia.getButton();
router.push('/') //跳转到首页
} else {
ElMessage.error("用户名密码错误!");
}
});
}
});
};
跳转到首页
//提交表单引用的函数
const submitForm = () => {
//校验表单
loginFormRef.value.validate((valid) => {
if (valid) {
axios.post("http://127.0.0.1:3000/login", loginForm).then((res) => {
if (res.data.id) {
Cookie.set('id', res.data.id); //存储id
Cookie.set('token', res.data.token); //存储token
let buttonPinia = useButton();
buttonPinia.getButton();
router.push('/') //跳转到首页
} else {
ElMessage.error("用户名密码错误!");
}
});
}
});
};
拦截跳转,触发action )
跳转拦截router.beforeEach()
(vux中action一般用来异步请求这里我们用pinia实现相应请求路由数据)
创建相关文件目录
/src/stores/routers.js
/src/router/index.js
/src/perssion.js
index.js
// router.js
import { createRouter, createWebHistory } from 'vue-router';
// 创建router实例
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/mainbox',name: 'mainbox',redirect:'/', component: ()=> import('../views/mainBox.vue')},
// { path: '/',name: 'index',component: ()=> import('../views/mainBox.vue')},
// { path: '/page4',name:'page4', component: () => import('../views/page4.vue')},
{ path: '/about', name: 'about', component: () => import('../views/about.vue')},
{ path: '/login',name:'login', component: () => import('../views/login.vue')}
],//不需要进行权限验证的路由
});
export default router;
perssion.js文件中的routerPinia.getRouter()触发了相应路由的异步请求
import router from './router/index'
import Cookies from 'js-cookie'
import { useRoutersStore } from './stores/routers'
let whiteList = ['/about', '/login']
router.beforeEach(async (to, from, next) => {
let token = Cookies.get('token')
if (token) {
//如果已经登录还要去登录页面,则不允许跳转到登录页面
if (to.path === '/login') {
next('/')
} else {
let routerPinia = useRoutersStore();
if (routerPinia.routerList.length > 0) {
//如果长度大于0代表着,已经请求过权限了
// console.log(to.matched)
if (to.matched.length > 0) {//路由中没有相应的路径的话to.matched.length 返回值是0
//能匹配上代表有这个页面
next()
} else {
// //没有权限
alert('你没有权限访问')
next(from.path);
}
} else {
//等于0 则代表没有, 就请求
let _newArr = await routerPinia.getRouter();
//添加动态路由
// router.addRoute('mainbox',_newArr)
_newArr.forEach((item) => {
// console.log(item)
router.addRoute('mainbox',item)
})
// console.log(router)
//继续跳转
next(to.path);
}
}
} else {
//如果在白名单,则直接访问
if (whiteList.indexOf(to.path) != -1) {
next();
} else {
//如果不在,代表需要权限,则打回登录
next('/login')
}
}
})
pinia实现的routers.js(其实和vux类似)
// stores/counterStore.js
import { ref, } from 'vue'
import axios from 'axios'
import Cookies from 'js-cookie'
import { defineStore } from 'pinia'
import {parseRouter} from '@/hooks/parseRouter.js'
export const useRoutersStore = defineStore('routers', () => {
const routerList = ref([])
function getRouter() {
let id = Cookies.get('id')
return new Promise((resolve, reject) => {
//看看本地有没有存储当前用户的菜单信息
let _local = JSON.parse(localStorage.getItem('routerList' + id));
if (_local) {
let _newArr = parseRouter(_local)
//更新到state
routerList.value = _newArr
//给出去
resolve(_newArr);
} else {
axios.get('http://127.0.0.1:3000/routes?id=' + id).then(res => {
//形成符合路由规则的新数组
let _newArr = parseRouter(res.data)
//更新到state
routerList.value = _newArr
//存到localstore中解决重复请求数据问题
localStorage.setItem('routerList' + id, JSON.stringify(res.data))
//给出去
resolve(_newArr);
})
}
})
}
function resetRouter() {
routerList.value = []
}
return { routerList, getRouter, resetRouter }
})
获取规则,并格式化
在pinia的routers.js文件中的自定义parseRouter()方法实现数据格式化 ,并且我这里将这个方法单独抽离了出来
let module = import.meta.glob('@/views/*.vue')
import { reactive } from 'vue';
//格式化路由方法
export function parseRouter(menuList) {
// 用于保存存在子路由的路由数据
let routes = reactive([])
let route = []
menuList.forEach((item) => {
let _newRouteRul = Object.assign({}, item);
if (_newRouteRul.children && _newRouteRul.children.length > 0) {
// 获取路由的基本格式
route = obj(_newRouteRul)
// 递归处理子路由数据,并返回,将其作为路由的 children 保存
route.children = parseRouter(_newRouteRul.children)
// 保存存在子路由的路由
routes.push(route)
} else {
// 保存普通路由
routes.push(obj(_newRouteRul))
}
})
// 返回路由结果
return routes
}
// 返回路由的基本格式
const obj = (item) => {
let route = {
// 路由的路径
path: item.path,
// 路由名
name: item.name,
// 路由所在组件
// component: (resolve) => require([`@/layout/Index`], resolve),
component: module[`/src/views/${item.component}.vue`],
// meta: {
// id: item.id,
// icon: item.icon
// },
// 路由的子路由
children: []
}
// 返回 route
return route
}
addRoute添加菜单
存储到state
存储菜单到本地
实现效果
代码地址:司徒飞/vue-permissions-node-serverhttps://gitee.com/situ_fei/vue-permissions-node-server.git
司徒飞/vue-permissionshttps://gitee.com/situ_fei/vue-permissions.git