vue后台管理系统登录权限

做后台项目区别于做其它的项目,权限验证与安全性是非常重要的,可以说是一个后台项目一开始就必须考虑和搭建的基础核心功能。我们所要做到的是:不同的权限对应着不同的路由,同时侧边栏也需根据不同的权限,异步生成。这里先简单说一下,我实现登录和权限验证的思路。

  • 登录:当用户填写完账号和密码后向服务端验证是否正确,验证通过之后,服务端会返回一个token,拿到token之后(我会将这个token存贮到cookie中,保证刷新页面后能记住用户登录状态),前端会根据token再去拉取一个 user_info 的接口来获取用户的详细信息(如用户权限,用户名等等信息)。
  • 权限验证:通过token获取用户对应的路由,通过 router.addRoutes 动态挂载这些路由。

点击登录按钮之后触发的登录操作:

handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true
          this.$store.dispatch('user/login', this.loginForm).then((res) => {
            this.loading = false
            this.$router.push({ path: this.redirect || '/' })
          }).catch(() => {
            this.loading = false
          })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    }

login函数:

login({ commit }, userInfo) {
    const { username, password } = userInfo
    return new Promise((resolve, reject) => {
      login({ username: username.trim(), password: password }).then(response => {
        commit('SET_TOKEN', response.token)
        setToken(response.token)
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },

登录时只调用登录接口,将token使用setToken方法存入cokkie中,在调用的request中,封装axios方法,使用拦截器在每次调用接口前将token放入header中,这样封装好的调用接口方法不用每次都存入token,极大的简化了我们的代码

获取用户信息:
用户登录成功之后,我们会在全局钩子router.beforeEach中拦截路由,判断是否已获得token,在获得token之后我们就要去获取用户的基本信息了
在permission文件中:

router.beforeEach(async(to, from, next) => {
  NProgress.start()
  document.title = getPageTitle(to.meta.title)
  const hasToken = getToken()
  if (hasToken) {
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
      const hasGetUserInfo = store.getters.name
      if (hasGetUserInfo) {
        next()
      } else {
        try {
          await store.dispatch('user/getInfo')
          const accessRoute = await store.dispatch('router/getSysRouter')
          router.addRoutes(accessRoute)
          next({ ...to, replace: true })
        } catch (error) {
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

这样写可以在每次登录或者刷新页面的时候,发起获取用户信息的请求,以及路由信息的请求

  • 添加路由权限】

添加路由我们是由后端来控制的,通过请求路由接口,再动态将路由表生成,在上面的代码中我们已经在permission文件中发起了请求,接下来我们要把路由表放到vuex中,再通过请求出来的信息,将返回的信息拼接成我们需要的路由。
在之前通过后端动态返回前端路由一直很难做的,因为vue-router必须是要vue在实例化之前就挂载上去的,不太方便动态改变。不过好在vue2.2.0以后新增了router.addRoutes,有了这个我们就可相对方便的做权限控制了。
以下是router/index文件和在vuex中创建的router文件:

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)
import menuModule from '@/store/modules/router'
const createRouter = () => new Router({
  scrollBehavior: () => ({ y: 0 }),
  routes: menuModule.state.router
})
const router = createRouter()
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}
export default router

将router暴露出来,我们在上面的permission文件中会将router引入,使用addRoute将得到的路由动态加载出来

import { reqGet } from '@/api/httpReq'
const map = {
  layout: () => import('@/layout'),
  dashboardIndex: () => import('@/views/dashboard/index'),
  doBusinessIndex: () => import('@/views/doBusiness/index'),
  doBusinessAgentDetail: () => import('@/views/doBusiness/agentDetail'),
  carSearchIndex: () => import('@/views/carSearch/index'),
  carSearchDetail: () => import('@/views/carSearch/detail'),
  smartCarbetIndex: () => import('@/views/smartCarbet/index'),
  alarmSearchIndex: () => import('@/views/alarmSearch/index'),
  alarmSearchDetail: () => import('@/views/alarmSearch/detail'),
  tagSearchIndex: () => import('@/views/tagSearch/index')
}
const state = {
  router: [
    {
      path: '/login',
      component: () => import('@/views/login/index'),
      hidden: true
    },
    {
      path: '/404',
      component: () => import('@/views/404'),
      hidden: true
    },
    {
      path: '/',
      component: map['layout'],
      redirect: '/dashboard',
      children: [{
        path: 'dashboard',
        name: '业务看板',
        component: map['dashboardIndex'],
        meta: { title: '业务看板', icon: 'yingyongguanli' }
      }]
    }
  ]
}

const mutations = {
  pushRouterIn(state, data) {
    state.router.push(data)
  }
}

const actions = {
  getSysRouter(context) {
    return new Promise(resolve => {
      reqGet('/sys/menu/list', 'get').then(res => {
        var arr = res.menuList
        var asyncRoute = initRouter(arr, context.state.router)
        context.state.router = asyncRoute
        resolve(asyncRoute)
      })
    })
  }
}
function initRouter(arr, router) {
  var arr2 = router
  for (let i in arr) {
    let c1 = {}
    let a1 = arr[i]
    c1.meta = a1.meta
    c1.name = a1.name
    c1.path = a1.path
    c1.redirect = a1.redirect
    c1.component = map[a1.component]
    c1.children = []
    if (a1.children.length) {
      for (let n in a1.children) {
        let a2 = a1.children[n]
        let c2 = {}
        if (a2.meta.length) {
          c2.meta = a2.meta
        }
        c2.path = a2.path
        c2.name = a2.name
        c2.component = map[a2.component]
        c2.hidden = true
        c1.children.push(c2)
      }
    }
    arr2.push(c1)
  }
  return arr2
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

由于获取的路由信息为字符串,前端引入的component部分不能直接使用后台的信息,所以在这里使用了一个转换映射的过程,即将components的name 和 本地components 做一个映射
如:

const map={
 login:require('login/index').default // 同步的方式
 login:()=>import('login/index')      // 异步的方式
}
//你存在服务端的map类似于
const serviceMap=[
 { path: '/login', component: 'login', hidden: true }
]
//之后遍历这个map,动态生成asyncRouterMap
//并将 component 替换为map[component]

在路由router中,我们保留了必要的基础路由,其余的路由由后台获取的数据动态添加push进router中,将路由添加好之后,通过之前的使用svg的文章,我们可以很轻松的将左侧菜单渲染出来,路由一定要放到vuex中,这样就能在路由改变的时候将数据同步渲染到页面中,在permission获取路由信息中,大量的使用了async await Promise这样的方式将信息处理异步问题,以免由于异步调用接口,使代码路由还未生成就已经执行next()方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351