前言:
发送ajax,fetch,websocket请求获取服务端的数据,配置代理是必须的环节
登录功能和菜单权限是后台管理系统中非常经典且十分重要的业务,这里涉及的知识点也是比较多的,坑也多,面试也是很重要的一环。
这里必须得会,没错是必须。
配置服务代理
创建两个node服务
在和src平级的目录创建一个服务器文件,我这里给的名字是node_server,里面创建两个服务
下载express
npm i express
服务1:server1.js
const express = require('express')
const app1 = express()
app1.get('/server1',(req,res)=>{
res.send('我是server1')
})
//开启3001接口的服务
app1.listen(3001)
服务2:server2.js
const express = require('express')
const app2 = express()
app2.get('/server2',(req,res)=>{
res.send('我是server2')
})
//开启3000接口的服务
app2.listen(3002)
然后打开两个终端,分别启用这两个服务
如图所示,server2.js同理
测试自己的服务
没问题,模拟了最简单的服务
vue工程配置代理服务器(核心代码)
在vue.config.js中配置devserver
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
//关闭语法检查
lintOnSave: false,
//配置代理(可配置多个)
devServer:{
port:8080,
open:true,
proxy:{
//代理1
'/server1':{
target:'http://localhost:3001',
ws:false,
changeOrigin:true,
pathRewrite:{
'^/server1':''
}
},
//代理2
'/serve2':{
target:'http://localhost:3002',
ws:false,
changeOrigin:true,
pathRewrite:{
'^/server2':''
}
}
}
}
})
配置axios请求工具
axios文档:axios中文文档
下载axios
npm i axios
创建一个request.js文件,对axios进行基础的配置
import axios from 'axios'
const instance = axios.create({
// baseURL: '',
// timeout: 1000,
// headers: {'X-Custom-Header': 'foobar'}
});
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
return config;
}, function (error) {
return Promise.reject(error);
});
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
//在这里修改一下,将response改为response.data,方便提取响应数据
return response.data;
}, function (error) {
return Promise.reject(error);
});
console.log(instance,'axios实例')
export default instance
在main.js中引入并配置到vue实例上
//...
import axios from '@/utils/request'
Vue.prototype.$axios = axios
new Vue({
//...
axios,
render: h => h(App),
}).$mount('#app')
测试demo
created(){
this.$axios.get('/server1/login').then(res=>{
console.log(res,'接口1请求回来的数据')
})
this.$axios.get('/server2/getRole').then(res=>{
console.log(res,'接口2请求回来的数据')
})
}
登录功能和token校验
创建登录接口
server1.js(登录接口)
const express = require('express')
const app1 = express()
app1.get('/login', (req, res) => {
switch (req.query.user) {
case 'admin': //管理员
res.send({
userName: 'admin',
token: 'admin_token'
})
break
case 'common': //普通
res.send({
userName: 'common',
token: 'common_token'
})
break
case 'temporary': //临时
res.send({
userName: 'temporary',
token: 'temporary_token'
})
break
}
})
//开启3001接口的服务
app1.listen(3001)
极简版登录页面(将就着看吧)
<template>
<div class="loginCom">
<div class="loginForm">
<el-button @click="login('admin')" type="primary">管理人员登录</el-button>
<el-button @click="login('common')" type="primary">普通用户登录</el-button>
<el-button @click="login('temporary')" type="primary">临时用户登录</el-button>
</div>
</div>
</template>
<script>
export default {
name:'login',
data(){
return{
}
},
methods:{
login(userType){
this.$axios.get(`/server1/login?user=${userType}`,).then(res=>{
//存储用户信息
this.$store.dispatch('changeUserInfo',res)
localStorage.setItem('vue2_userInfo',JSON.stringify(res))
localStorage.setItem('vue2_token',res.token)
this.$router.push('/dashboard')
})
}
},
created(){
}
}
</script>
<style lang="less" scoped>
.loginCom{
display: flex;
width: 100%;
height: 100vh;
justify-content: center;
align-items: center;
}
</style>
使用路由拦截器进行token校验(核心代码)
在router/index.js中配置
//...
const router = new Router({
routes:RouteList,
mode:'history'
})
router.beforeEach((to,from,next)=>{
let token = localStorage.getItem('vue2_token')
//判断是否有token
if(token){
//如果是去往登录页面,直接返回到主页
if(to.path === '/login'){
next('/dashboard')
}else{
//去原定跳转的页面
next()
}
}else{
//没有token的情况下,所有路由都指向登录页面
if(to.path === '/login'){
next()
}else{
next('/login')
}
}
})
export default router
这里不做过多解释,仅展示具体代码和demo效果
有需要的可以去vue-router官方文档去看,那里有很详细的解释
路由权限设计
创建权限接口
server2.js(权限接口)
const express = require('express')
const app2 = express()
app2.get('/getRole', (req, res) => {
switch (req.query.userType) {
case 'admin': //管理人员权限
res.send([
'instructions', 'communication', 'common', 'admin'
])
break
case 'common': //普通用户权限
res.send([
'instructions', 'communication', 'common'
])
break
case 'temporary': //临时用户权限
res.send([
'instructions', 'communication'
])
}
})
//开启3000接口的服务
app2.listen(3002)
给vuex配置新的全局值
const userMode = {
state:()=>({
//用户信息
userInfo:{
name:'wjt',
age:28
},
//权限字段
userRole:[],
//符合权限的异步路由
asyncRouteList:[]
}),
mutations:{
CHANGE_USEINFO:(state,info)=>{
state.userInfo = info
},
CHANGE_USERROLE:(state,info)=>{
state.userRole = info
},
CHANGE_ASYNCROUTELIST:(state,info)=>{
state.asyncRouteList = info
}
},
actions:{
changeUserInfo:(({commit},data)=>{
commit('CHANGE_USEINFO',data)
}),
changeUserRole:(({commit},data)=>{
commit('CHANGE_USERROLE',data)
}),
changeAsyncRouteList:(({commit},data)=>{
commit('CHANGE_ASYNCROUTELIST',data)
})
}
}
export default userMode
新增路由
普通用户页面和管理员页面都是根据用户角色的权限去展示的
const list = [
{
name:'dashboard',
path:'/dashboard',
meta:{
name:'主页',
},
component:()=>import('@/pages/dashboard/index.vue'),
children:[
{
name:'communication',
path:'/dashboard/communication',
meta:{
name:'组件通信',
icon:'el-icon-phone'
},
component:()=>import('@/pages/communication/index.vue')
},
{
name:'instructions',
path:'/dashboard/instructions',
meta:{
name:'指令',
icon:'el-icon-thumb'
},
component:()=>import('@/pages/instructions/index.vue')
},
{
name:'common',
path:'/dashboard/common',
meta:{
name:'普通用户页面',
icon:'el-icon-user'
},
component:()=>import('@/pages/common/index.vue')
},
{
name:'admin',
path:'/dashboard/admin',
meta:{
name:'管理员页面',
icon:'el-icon-s-custom'
},
component:()=>import('@/pages/admin/index.vue')
},
]
},
]
export default list
在路由守卫中根据权限动态添加路由(核心代码)
import Vue from 'vue'
import Router from 'vue-router'
import asyncRouteList from './asyncRouteList'
import globalRouteList from './globalRouteList'
import store from '@/store/index'
import {getUserRoleFun} from '@/globalFun/index'
import { cloneDeep } from 'lodash'
const RouteList = [].concat(globalRouteList)
let initAsyncList = cloneDeep(asyncRouteList)
Vue.use(Router)
const router = new Router({
routes:RouteList,
mode:'history'
})
if(localStorage.getItem('vue2_userInfo')){
store.dispatch('changeUserInfo',JSON.parse(localStorage.getItem('vue2_userInfo')))
}
//根据后台返回权限字段匹配路由
function addAsyncRouterFun(roleInfo){
let filerOverList = []
filterRouteFun(initAsyncList[0].children,roleInfo,filerOverList)
let allAsyncRouteList = asyncRouteList[0]
allAsyncRouteList.children = filerOverList
router.options.routes = [].concat(globalRouteList)
router.addRoutes([allAsyncRouteList]) //动态添加路由
router.options.routes.push(allAsyncRouteList) //给$router的options添加权限路由
store.dispatch('changeAsyncRouteList',allAsyncRouteList.children)
}
//递归匹配多级路由
function filterRouteFun(initAsyncList,roleInfo,filerOverList){
initAsyncList.forEach(item=>{
if(roleInfo.includes(item.name)){
let RouteItem = cloneDeep(item)
if(RouteItem.children){
RouteItem.children = []
filterRouteFun(item.children,roleInfo,RouteItem.children)
}
filerOverList.push(RouteItem)
}
})
}
router.beforeEach(async(to,from,next)=>{
let token = localStorage.getItem('vue2_token')
if(token){
let roleList = store.state.user.userRole
//没有权限数据
if(roleList.length<1){
let role = await getUserRoleFun(store.state.user.userInfo.userName) //请求权限数据
store.dispatch('changeUserRole',role)
addAsyncRouterFun(role) //根据权限字段值过滤符合添加的路由配置项
next({...to,replace:true}) //重新访问,走一遍前置守卫
}else{
if(to.path === '/login'){
next('/dashboard')
}else{
next()
}
}
}else{
if(to.path === '/login'){
next()
}else{
next('/login')
}
}
})
export default router
获取权限参数的方法globalFun/index.js
import axios from '@/utils/request'
export function getUserRoleFun(userType){
return new Promise(resolve=>{
axios.get(`/server2/getRole?userType=${userType}`)
.then(res=>{
resolve(res)
}).catch(error=>{
console.log(error,'error')
})
},reject=>{
reject('error')
})
}
退出登录清理用户信息
outLogin(){
this.$store.dispatch('changeUserInfo',{
name:'wjt'
})
this.$store.dispatch('changeUserRole',[])
this.$store.dispatch('changeAsyncRouteList',[])
localStorage.removeItem('vue2_userInfo')
localStorage.removeItem('vue2_token')
this.$router.push('/login')
}
效果如下
管理员
普通用户
临时用户
ok,结束!
后语及存在的坑点
这里写的东西还是比较多的,虽然功能常见,网上也一大堆子,但是我这里的代码基本都能直接用,你只具体的核心代码也都给标注出来了。
其实很多人在实际开发开发业务中未必会有机会写真实的登录和路由权限场景,但是还是必须得会,很重要。
而且这里是有坑的,比如很多人在开发时会遇到两个问题
1.是和devserver有关的一个遮蔽框,虽然你可以把它关掉,但是还是很坑
你需要关闭开发阶段的报错和警告(这里是说框架提供的,而非你业务代码里的)
解决方法:vue.config.js
devServer:{
client:{
overlay:false
},
...
}
2.addRoutes不生效白屏的问题(这里的代码已经解决了这个问题,但我没有解释)
其实很多人对路由前置守卫中动态添加路由没有太多的理解,开发中不撞几次墙都不行的,这里有一个解释我没有纯手敲,而是引用别人的博客内容,感觉他写的比较清晰明白,大家请看:
Vue 动态添加用户的权限路由(动态路由) - 知乎
懒得去看的直接看我截图总结
我对这篇文章绝对是用心了,感谢大家给个赞或收藏