vue-admin-template后台管理之权限篇

前言:

在开发后台管理项目时,多用户多角色不同权限的场景可以说是非常普遍的。从零开始手写一个后台,要考虑的东西很多,这里直接拿网上大家比较熟悉的vue-admin-template后台模版来进行改造。
也可以看比较完整的前端开发https://www.jianshu.com/p/12ef029e3ab2
对应视频教程
先来看下vue-admin-template这个模版的代码目录结构

├── build                      # 构建相关
├── mock                       # 项目mock 模拟数据
├── public                     # 静态资源
│   │── favicon.ico            # favicon图标
│   └── index.html             # html模板
├── src                        # 源代码
│   ├── api                    # 所有请求
│   ├── assets                 # 主题 字体等静态资源
│   ├── components             # 全局公用组件
│   ├── icons                  # 项目所有 svg icons
│   ├── layout                 # 全局 layout
│   ├── router                 # 路由
│   ├── store                  # 全局 store管理
│   ├── styles                 # 全局样式
│   ├── utils                  # 全局公用方法
│   ├── views                  # views 所有页面
│   ├── App.vue                # 入口页面
│   ├── main.js                # 入口文件 加载组件 初始化等
│   └── permission.js          # 权限管理
├── tests                      # 测试
├── .env.xxx                   # 环境变量配置
├── .eslintrc.js               # eslint 配置项
├── .babelrc                   # babel-loader 配置
├── .travis.yml                # 自动化CI配置
├── vue.config.js              # vue-cli 配置
├── postcss.config.js          # postcss 配置
└── package.json               # package.json

该模版使用了mock数据,可能对于不会mock的人有点生涩,比如作者本人
所以,我打算把mock删了,自己模拟数据,就是那种自己写json数据,写死,后端接口好了,直接请求接口把这些假数据替换下来就ok了。接下来的教程可以比较啰嗦,各位挑着看吧,不喜勿喷,毕竟不是专业做教程的。

第一步,我们先来看看最后的成果展示


login.png

image.png

image.png

image.png
image.png

image.png

image.png

image.png

image.png

比如我们用财务这个用户去登录一下后台,(先给财务这个角色分配一下权限)

image.png

比如给他分配培训认证全部的权限
image.png

然后财务管理员登录后台,只渲染所拥有的权限动态渲染了侧边栏。
image.png

补充一下:这里是按钮权限的设置,全局配置一个指令,到时候
v-permission='['add']'就可以实现按钮是否显示。
image.png

基本上就是这么个东西,先添加菜单,在新建一个角色,再给这个角色分配一下权限,然后在新建个用户,账号密码配置一下,再给这个用户分配一个角色,最后这个用户登录后台,就展示所拥有的权限动态去渲染侧边栏

第二步 : 我们先来启动一下vue-admin-template这个模版,安装依赖 cnpm install启动:npm run dev

image.png

image.png

可以看到,全是英文的,我是受不了,(英文烂的一逼,看不懂),侧边栏是手写的英文,删掉就好了,element-ui 是英文的,这个要改一下配置。

修改路径  src/main.js
// Vue.use(ElementUI, { locale })
// 如果想要中文版 element-ui,按如下方式声明
Vue.use(ElementUI) 

这样就好了
image.png

当然要先实现正常登录,我这里用下真实的登录接口,修改一下src/api/user.js

image.png

.env.development
image.png

vue.config.js配置反向代理,然后重启项目

image.png

箭头标注的根据实际情况修改
image.png

这样实际是触发登录接口了,为啥没登录进去呢,因为登录成功后,会立即触发getinfo 这个接口,这个接口请求出错,就会清除token且又回到登录页了。
所以接下来要完善getinfo这个接口,先来看下逻辑
1.执行完登录请求后,会走 permission.js中的逻辑

image.png

可以看到getinfo,所以就要完善getinfo这个接口,同样换成真实的。
image.png

store/modules/user.js根据实际情况修改下getinfo这个方法,这里看自己公司要求,我们只是取到昵称和头像存起来,也可以在这个方法直接把该用户所拥有的权限拿到并保存到vuex,建议在起一个接口,模拟的话我先在这个方法里把路由信息写死,然后在定一个存储到vuex的方法

const getDefaultState = () => {
  return {
    token: getToken(),
    name: '',
    avatar: '',
    menus: "",//新增
  }
}

const mutations = {
  RESET_STATE: (state) => {
    Object.assign(state, getDefaultState())
  },
  SET_TOKEN: (state, token) => {
    state.token = token
  },
  SET_NAME: (state, name) => {
    state.name = name
  },
  SET_AVATAR: (state, avatar) => {
    state.avatar = avatar
  },
  // 新增
  SET_MENUS: (state, menus) => {
    state.menus = menus
  }
}


// get user info
  getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      getInfo().then(response => {
        //用户信息是根据token返回的,
        //我们把token放到header里自动带过去了,这里就把state.token删掉了
        const { data } = response

        if (!data) {
          return reject('Verification failed, please Login again.')
        }

        const { nickname, avatar } = data
        // 模拟请求数据
        const menus = [
          {
            "path": "/system",
            "redirect": "/menu",
            "component": "Layout",
            "meta": {
              "title": "系统管理",
              "icon": "form"
            },
            "children": [{
              "path": "/menu",
              "name": "menu",
              "component": "menu/index",
              "meta": {
                "title": "菜单管理",
                "icon": "table",
              }
            },
            {
              "path": "/roles",
              "name": "roles",
              "component": "roles/index",
              "meta": {
                "title": "角色管理",
                "icon": "table",
              }
            },
            {
              "path": "/administrator",
              "name": "administrator",
              "component": "dashboard/index",
              "meta": {
                "title": "用户管理",
                "icon": "table"
              }
            }
            ]
          }

        ]
        //如果需要404 页面,请在此处添加
        menus.push({
          path: "/404",
          component: "404",
          hidden: true
        }, {
          path: "*",
          redirect: "/404",
          hidden: true
        })
        commit('SET_NAME', nickname)
        commit('SET_AVATAR', avatar)
        commit("SET_MENUS", menus) // 触发vuex SET_MENUS 保存路由表到vuex
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  }

getters.js

const getters = {
  sidebar: state => state.app.sidebar,
  device: state => state.app.device,
  token: state => state.user.token,
  avatar: state => state.user.avatar,
  name: state => state.user.name,
  menus: state => state.user.menus //新增
}
export default getters

登录成功后,可以看到vuex里已经把昵称和头像和路由表信息存进去了。

image.png

下面把 router.js中的路由先删掉,保留必要的路由。

export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: 'Dashboard', icon: 'dashboard' }
    }]
  }
]
image.png

继续,可以看到,后端返给我们的是 "component": "Layout",是一个字符串,我们要把过滤下这段路由表信息,并且addrouter到路由里去,并全局挂载一个global.antRouter ,渲染的时候会用到

先在router这个目录下新建两个js文件,开发环境和生产环境导入组件的方式略有不同

_import_development.js

// 开发环境导入组件
module.exports = file => require('@/views/' + file + '.vue').default // vue-loader at least v13.0.0+

_import_production.js

// 生产环境导入组件
module.exports = file => () => import('@/views/' + file + '.vue')

然后在permission.js中引入刚创建的js文件

const _import = require('./router/_import_' + process.env.NODE_ENV) // 获取组件的方法

permission.js改造成下面这样,不明白的地方看注释

import router from './router'
import store from './store'
import {
  Message
} from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import {
  getToken
} from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'
import Layout from '@/layout'
const _import = require('./router/_import_' + process.env.NODE_ENV) // 获取组件的方法

NProgress.configure({
  showSpinner: false
}) // NProgress Configuration

const whiteList = ['/login'] // no redirect whitelist

router.beforeEach(async (to, from, next) => {
  // start progress bar
  NProgress.start()

  // set page title
  document.title = getPageTitle(to.meta.title)

  // determine whether the user has logged in
  const hasToken = getToken()

  if (hasToken) {
    if (to.path === '/login') {
      // if is logged in, redirect to the home page
      next({
        path: '/'
      })
      NProgress.done()
    } else {
      const hasGetUserInfo = store.getters.name
      if (hasGetUserInfo) {
        next()
      } else {
        try {
          // get user info
          await store.dispatch('user/getInfo') // 请求获取用户信息
          if (store.getters.menus.length < 1) {
            global.antRouter = []
            next()
          }
          const menus = filterAsyncRouter(store.getters.menus) // 1.过滤路由
          console.log(menus);
          
          router.addRoutes(menus) // 2.动态添加路由
          global.antRouter = menus // 3.将路由数据传递给全局变量,做侧边栏菜单渲染工作
          next({
            ...to,
            replace: true
          })
          // next()
        } catch (error) {
          // remove token and go to login page to re-login
          console.log(error);
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    /* has no token*/

    if (whiteList.indexOf(to.path) !== -1) {
      // in the free login whitelist, go directly
      next()
    } else {
      // other pages that do not have permission to access are redirected to the login page.
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  // finish progress bar
  NProgress.done()
})
// // 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap) {
  const accessedRouters = asyncRouterMap.filter(route => {
    if (route.component) {
      if (route.component === 'Layout') {
        route.component = Layout
      } else {
        route.component = _import(route.component) // 导入组件
      }
    }
    if (route.children && route.children.length) {
      route.children = filterAsyncRouter(route.children)
    }
    return true
  })
  
  return accessedRouters
}

下面在改造下layout/components/Sidebar/index.vue

computed: {
    ...mapGetters([
      'sidebar'
    ]),
    routes() {
      //return this.$router.options.routes
      return this.$router.options.routes.concat(global.antRouter) //把路由concat进去
    },

这时候登录成功会报错


image.png

原因就是 没有提前建好页面。新建一下路由表内的几个页面就行了
然后就成功了 有木有!!


image.png

下面我打算换成真实的接口数据,真正实现开篇的成果展示,下面的打算录制成视频,对应视频教程,点我去看视频

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