Vue 登录权限配置

参考: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"}

发布了62 篇原创文章 · 获赞 33 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/Silence_Sep/article/details/86512279
今日推荐