Vue 登录权限配置
参考:https://juejin.im/post/591aa14f570c35006961acac一、思路1.登录:填写账号,密码后,验证是否正确,验证通过后,服务端返回前端一个token,拿到token后,将这个token存到cookie中,保证刷新页面能记住这个用户的登录状态,前端会根据这个token去拉取用户信息接口2.权限:通过token获取用户信息接口,获取用户的role,动态根...
参考:https://juejin.im/post/591aa14f570c35006961acac
一、思路
1.登录:填写账号,密码后,验证是否正确,验证通过后,服务端返回前端一个token,拿到token后,将这个token存到cookie中,保证刷新页面能记住这个用户的登录状态,前端会根据这个token去拉取用户信息接口
2.权限:通过token获取用户信息接口,获取用户的role,动态根据用户的role算出相应的权限路由,通过route.addRouters()动态挂载这些路由
注:为了保证安全性,后台的token有效期都是session,就是当浏览器关闭后就丢失了,重新打开浏览器需要重新验证,后台还会设定一个时间定期刷新token
另外,只在本地存储了token,用户的信息并没有存取(假设我把用户权限和用户名也存在了本地,但我这时候用另一台电脑登录修改了自己的用户名,之后再用这台存有之前用户信息的电脑登录,它默认会去读取本地 cookie 中的名字,并不会去拉去新的用户信息。当然如果是做了单点登录得功能的话,用户信息存储在本地也是可以的。当你一台电脑登录时,另一台会被提下线,所以总会重新登录获取最新的内容。)
二、具体实现
1.登录页面,点击按钮
click事件(vue:views/login/index.vue quasar:pages/login/index.vue)
handle() {
this.submitting = true;
const loginForm = {
username: this.username,
password: this.password,
};
this.$store
.dispatch('LoginPc', loginForm)
.then(() => {
this.submitting = false;
this.$q.notify({
type: 'positive',
position: 'top',
message: '登录成功',
});
this.$q.cookies.set('hasLogin', true);
this.$router.push('/');
return false;
})
.catch(() => {
this.submitting = false;
this.$q.notify({
type: 'negative',
position: 'top',
message: '登录失败',
});
});
},
actions:(store/modules/user.js)
A:Login
LoginPc({ dispatch, commit }, userInfo) {
const username = userInfo.username.trim();
return new Promise((resolve, reject) => {
axios({
method: 'post',
url: '/login',
data: {
username,
password: userInfo.password,
},
}).then((response) => {
if (response.data.status === 200) {
commit('SET_USER', response.data.data);
} else {
reject();
}
resolve();
}).catch((error) => {
reject(error);
});
});
},
2.获取用户信息
用户登录成功之后,我们会在全局钩子router.beforeEach中拦截路由,判断是否已获得token,在获得token之后我们就要去获取用户的基本信息了
if (!store.getters.userInfo) {//
store.dispatch('GetInfo').then(() => { // 拉取用户信息
store.dispatch('GenerateRoutes').then((res) => { // 根据roles权限生成可访问的路由表
// console.log('res', res)
Router.addRoutes(res) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
})
}).catch(() => {
Cookies.remove('hasLogin');
next('/loginPc');
});
} else {
next();
}
3.权限具体实现
a.创建vue实例的时候将vue-router挂载,但这个时候vue-router挂载一些登录或者不用权限的公用的页面。
b.当用户登录后,获取用role,将role和路由表每个页面的需要的权限作比较,生成最终用户可访问的路由表。
c.调用router.addRoutes(store.getters.addRouters)添加用户可访问的路由。
router.js
import { axios } from 'plugins/axios';
export var generateRoute = [{
path: '/login',
component: () =>
import('pages/login/Login.vue'),
meta: {
parentId: '6',
menuId: '1542522373047',
},
},
{
path: '/loginPc',
component: () =>
import('pages/login/loginPc.vue'),
meta: {
parentId: '7',
menuId: '1542522373047',
},
},
]
export var asyncToute = [
{
path: '/',
component: () =>
import('layouts/MyLayout.vue'),
meta: {
parentId: '1'
},
children: [{
path: '',
component: () =>
import('pages/Index.vue'),
meta: {
menuId: '1542522373047',
parentId: '1',
}
}, ],
},
// pc端订单列表
{
path: '/pc',
component: () =>
import('layouts/LayoutPc.vue'),
meta: {
parentId: '9'
},
children: [{
path: 'orderList',
component: () =>
import('pages/order/list.vue'),
meta: {
menuId: '1542522373047',
parentId: '9',
}
},
{
path: 'sdjcjOrder',
component: () =>
import('pages/order/sdjcjOrder.vue'),
meta: {
menuId: '1542522373047',
parentId: '9',
}
},
{
path: 'approval/myApproval',
name: 'myApproval',
component: () =>
import('pages/approval/ListForPc.vue'),
meta: {
menuId: '1542522373047',
parentId: '9',
}
},
{
path: 'approval/orgApproval',
name: 'orgApproval',
component: () =>
import('pages/approval/orgApprovalList.vue'),
meta: {
menuId: '1542522373047',
parentId: '9',
}
},
{
path: 'detail/:id',
name: 'PcOrderDetail',
component: () =>
import('pages/order/detail.vue'),
meta: {
menuId: '1542522373047',
parentId: '9',
}
},
{
path: 'itinerary',
name: 'itinerary',
component: () =>
import('pages/order/itinerary.vue'),
meta: {
menuId: '1542522373047',
parentId: '9',
}
},
{
path: 'user/edit',
name: 'userEdit',
component: () =>
import('pages/user/edit.vue'),
meta: {
menuId: '1542522373047',
parentId: '9',
}
},
{
path: 'user/editPassword',
name: 'userEditPassword',
component: () =>
import('pages/user/editPassword.vue'),
meta: {
menuId: '1542522373047',
parentId: '9',
}
},
{
path: 'user/certificateList',
name: 'certificateList',
component: () =>
import('pages/user/certificateList.vue'),
meta: {
menuId: '1542522373047',
parentId: '9',
}
},
{
path: 'user/memberList',
name: 'memberList',
component: () =>
import('pages/user/memberList.vue'),
meta: {
menuId: '1542522373047',
parentId: '9',
}
},
{
path: 'company/orderList',
name: 'companyOrder',
component: () =>
import('pages/order/orgList.vue'),
meta: {
menuId: '1542522373047',
parentId: '9',
}
},
{
path: 'company/approvalList',
name: 'companyApproval',
component: () =>
import('pages/approval/orgApprovalList.vue'),
meta: {
menuId: '1542522373047',
parentId: '9',
}
},
{
path: 'company/user',
name: 'companyUser',
component: () =>
import('components/userMaintain/index.vue'),
meta: {
menuId: '1544497353257',
parentId: '9',
}
},
{
path: 'company/orginazation',
name: 'companyOrginazation',
component: () =>
import('components/orgMaintain/index.vue'),
meta: {
menuId: '1544497353257',
parentId: '9',
}
},
],
},
// 移动端订单列表
{
path: '/mobile',
component: () =>
import('layouts/MyLayout.vue'),
meta: {
parentId: '2'
},
children: [{
path: 'orderList',
component: () =>
import('pages/order/listForMobile.vue'),
meta: {
menuId: '1542522373041',
parentId: '2',
}
},
{
path: 'detail/:id',
component: () =>
import('pages/order/detailForMobile.vue'),
meta: {
menuId: '1542522373041',
parentId: '2',
}
},
],
},
{
path: '/approval',
component: () =>
import('layouts/MyLayout.vue'),
meta: {
parentId: '3'
},
children: [{
path: 'list',
component: () =>
import('pages/approval/List.vue'),
meta: {
menuId: '1542522373041',
parentId: '3',
}
},
{
path: 'detail',
component: () =>
import('pages/approval/Detail.vue'),
meta: {
menuId: '1542522373041',
parentId: '3',
}
},
],
},
]
var routes = []
routes = generateRoute
if(process.env.MODE !== 'ssr') {
routes.push({
path: '*',
component: () =>
import('pages/Error404.vue'),
});
}
export default routes;
这里我们根据 vue-router官方推荐 的方法通过meta标签来标示改页面能访问的权限有哪些。如meta: { role: [‘admin’,‘super_editor’] }表示该页面只有admin和超级编辑才能有资格进入。
router.index
export default function (/* { store, ssrContext } */) {
const Router = new VueRouter({
scrollBehavior: () => ({ y: 0 }),
routes: routes,
// Leave these as is and change from quasar.conf.js instead!
// quasar.conf.js -> build -> vueRouterMode
mode: process.env.VUE_ROUTER_MODE,
base: process.env.VUE_ROUTER_BASE,
});
// 添加防止重复登录验证
Router.beforeEach((to, from, next) => {
if (document.body.clientWidth > 1000) {
if (to.path === '/') {
next('pc/orderList');
}
if (to.path === '/approval/list') {
next('/pc/approval');
}
// 增加订单页面宽度宽度适配
if (to.path === '/mobile/orderList') {
next('pc/orderList');
}
if (to.path === '/loginPc') {
next();
} else if (!store.getters.userInfo) {
store.dispatch('GetInfo').then(() => { // 拉取用户信息
store.dispatch('GenerateRoutes').then((res) => { // 根据roles权限生成可访问的路由表
// console.log('res', res)
Router.addRoutes(res) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
})
}).catch(() => {
Cookies.remove('hasLogin');
next('/loginPc');
});
} else {
next();
}
} else {
// 增加订单页面宽度宽度适配
if (to.path === '/pc/orderList') {
next('mobile/orderList');
}
if (to.path === '/login') {
next();
} else if (!store.getters.userInfo) {
store.dispatch('GetInfo').then(() => { // 拉取用户信息
store.dispatch('GenerateRoutes').then((res) => { // 根据roles权限生成可访问的路由表
// console.log('res', res)
Router.addRoutes(res) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
})
// next();
}).catch(() => {
Cookies.remove('hasLogin');
next('/login');
});
} else {
next();
}
}
});
return Router;
}
有了addRoutes之后就非常的方便,只需要挂载了用户有权限进入的页面,没权限,路由自动帮我跳转的404,省去了不少的判断。
这里还有一个小hack的地方,就是router.addRoutes之后的next()可能会失效,因为可能next()的时候路由并没有完全add完成,好在查阅文档发现
next('/') or next({ path: '/' }): redirect to a different location. The current navigation will be aborted and a new one will be started.
这样我们就可以简单的通过next(to)巧妙的避开之前的那个问题了。这行代码重新进入router.beforeEach这个钩子,这时候再通过next()来释放钩子,就能确保所有的路由都已经挂在完成了。
store/index.js
import { axios } from 'plugins/axios';
import { asyncToute, generateRoute } from '../../router/routes'
import router from '../../router/index'
export default {
state: {
userInfo: null,
addRouters: []
},
mutations: {
SET_USER: (state, obj) => {
state.userInfo = obj;
},
SET_ROUTE: (state, addRouters) => {
state.addRouters = addRouters
}
},
actions: {
GetInfo({ commit }) {
return new Promise((resolve, reject) => {
axios.get('/user/info').then((response) => {
const { data } = response;
if (data.status === 0 || data.status === null) {
commit('SET_USER', data);
sessionStorage.setItem('usersInfo', JSON.stringify(data))
resolve(response);
} else {
reject();
}
}).catch((error) => {
reject(error);
});
});
},
GenerateRoutes({ commit, state }) {
return new Promise((resolve, reject) => {
axios.get('/sys/permission/0/user_menu').then(response => {
const permissionPage = response.data
var result = []
var temp = asyncToute.map(n => {
const obj = {}
for (const key in n) {
if (key != 'children') {
obj[key] = n[key]
} else {
// 将没有menuId的菜单直接展示出来
const arrTemp = []
n.children.forEach(child => {
if (!child.meta.menuId) {
arrTemp.push(child)
}
})
obj.children = arrTemp
}
}
return obj
})
// debugger
permissionPage.forEach(a => {
asyncToute.forEach(b => {
b.children.forEach(c => {
if (c.meta.menuId == a.permission_id) {
temp.forEach(d => {
if (d.meta.parentId == b.meta.parentId) {
d.children.push(c)
}
})
}
})
})
})
// 删除children 长度为0 的对象
temp.forEach(n => {
if (n.children.length > 0) {
result.push(n)
}
})
result = generateRoute.concat(result)
state.addRouters = result
commit('SET_ROUTE', result)
// sessionStorage.setItem('userPer', JSON.stringify(result))
// console.log(result)
resolve(result)
}).catch(error => {
reject(error)
})
})
},
// 登录接口
Login({ commit }, userInfo) {
const username = userInfo.username.trim();
return new Promise((resolve, reject) => {
axios({
method: 'post',
url: '/wx/autoLogin',
data: {
username,
password: userInfo.password,
},
}).then((response) => {
if (response.data.status === 200) {
commit('SET_USER', response.data.data);
} else {
reject();
}
resolve();
}).catch((error) => {
reject(error);
});
});
},
// pc端登录接口
LoginPc({ dispatch, commit }, userInfo) {
const username = userInfo.username.trim();
return new Promise((resolve, reject) => {
axios({
method: 'post',
url: '/login',
data: {
username,
password: userInfo.password,
},
}).then((response) => {
if (response.data.status === 200) {
commit('SET_USER', response.data.data);
// console.log(dispatch)
} else {
reject();
}
// dispatch('GenerateRoutes').then(response => {
// console.log(router)
// Router.addRoutes(response)
// })
resolve();
}).catch((error) => {
reject(error);
});
});
},
},
};
GenerateRoutes这里的代码说白了就是干了一件事,通过用户的权限和之前在router.js里面asyncRouterMap的每一个页面所需要的权限做匹配,最后返回一个该用户能够访问路由有哪些。
补充:后台权限接口
{permission_id: 1, btn_id: "msystem"}, {permission_id: 5, btn_id: "morder"},…]
0: {permission_id: 1, btn_id: "msystem"}
1: {permission_id: 5, btn_id: "morder"}
2: {permission_id: 6, btn_id: "0"}
3: {permission_id: 7, btn_id: "0"}
4: {permission_id: 8, btn_id: "0"}
5: {permission_id: 9, btn_id: "0"}
6: {permission_id: 10, btn_id: "0"}
7: {permission_id: 2, btn_id: "0"}
8: {permission_id: 3, btn_id: "0"}
9: {permission_id: 1543285516414, btn_id: "0"}
10: {permission_id: 1540261167332, btn_id: "abc"}
11: {permission_id: 1540261409509, btn_id: "0"}
12: {permission_id: 1544496466466, btn_id: "0"}
13: {permission_id: 1540425367381, btn_id: "0"}
14: {permission_id: 1540425446383, btn_id: "0"}
15: {permission_id: 1543285920404, btn_id: "0"}
16: {permission_id: 1544497353257, btn_id: "0"}
17: {permission_id: 1544507387007, btn_id: "0"}
18: {permission_id: 4, btn_id: "0"}
19: {permission_id: 1540282017439, btn_id: "0"}
20: {permission_id: 1542522373047, btn_id: "0"}
21: {permission_id: 1542525078604, btn_id: "0"}
22: {permission_id: 1542526744593, btn_id: "0"}
23: {permission_id: 1542527250931, btn_id: "0"}
24: {permission_id: 1542527762756, btn_id: "0"}
25: {permission_id: 1545028726441, btn_id: "0"}
26: {permission_id: 1545879225748, btn_id: "0"}
27: {permission_id: 1546933679788, btn_id: "0"}
28: {permission_id: 1540425575033, btn_id: "0"}
29: {permission_id: 1540425613318, btn_id: "0"}
更多推荐
所有评论(0)