阅读 1199

vue admin template最详细的动态菜单路由权限改造+按钮权限

写在最前:

我站在巨人的肩膀上,总结并分享了如何改造花裤衩大佬vue admin template后台模板的动态菜单路由以及权限管理,文中有我mock的后台接口,能够模拟正式环境登陆退出,希望对你有帮助。

第一步:git命令拉取代码

git clone https://github.com/PanJiaChen/vue-admin-template.git
复制代码

第二步:剔除烦人的eslint及删除多余路由

//vue.config.js文件
修改第30行 lintOnSave: false
复制代码
// router目录下的index.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' }
    }]
  }
]
复制代码

执行npm i下载依赖自动运行: 可以看到左侧菜单栏只剩dashboard一个菜单

0846e5570947eb9b391e88e0e438cd6.png

第三步:配置跨域

// vue.config.js文件在第39行删除before: require('./mock/mock-server.js')
//加入下列代码
proxy: { 
    '/api': { 
    target: '',//****这里写上后端提供的基础地址 *****
    // ws: true, //是否允许websocket 
    // secure: false, 
    changeOrigin: true, 
    pathRewrite: {'^/api': ''} 
    } 
 },
复制代码

第四步:修改生产和开发环境

//文件.env.development修改
VUE_APP_BASE_API = '/api'


//文件.env.production修改
VUE_APP_BASE_API = '/api'
复制代码

第五步:修改登录接口和获取用户信息接口

export function login(obj) {
  return request({
    url: '/user/login', // url = base url + request url
    method: 'post',
    params: obj
  })
}

export function getInfo(token) {
  return request({
    url: '/user/getUserInfoByToken?token=' + token,
    method: 'get'
  })
}
复制代码

第六步:修改actions里面的login接口地址,根据字段正确存储token

// store/modules/user.js

const getDefaultState = () => {
   return {
     token: getToken(),
     name: '',
     avatar: '',
     roles: [], // 角色权限控制按钮显示
     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_ROLES: (state, roles) => {
     state.roles = roles // 角色权限
   },
   SET_MENUS: (state, menus) => {
     state.menus = menus // 菜单权限
   }
}

const actions = {
   // 登录
   login({ commit }, userInfo) {
     const { username, password } = userInfo
     return new Promise((resolve, reject) => {
       login({ username: username.trim(), password: password }).then(response => {
         const { token } = response.data.data.data
         commit('SET_TOKEN', token) // vuex存储token
         setToken(token) // cookie存储token
         resolve()
       }).catch(error => {
         reject(error)
       })
     })
},

// 获取用户信息
getInfo({ commit, state }) {
   return new Promise((resolve, reject) => {
     getInfo(state.token).then(response => {
       const { data } = response.data.data
       if (!data) {
         return reject('Verification failed, please Login again.')
       }
       const { name, avatar, roles, menus } = data
       /**
        * 获取异步路由后加入404路由能解决刷新后【丢失路由跳转404页面】问题
        (因为异步获取路由优先级比静态路由表低,导致404路由在异步添加的路由之前)
        */
       menus.push({ 'path': '/404', 'component': '404', 'hide': 'true' }, {
         'path': '*',
         'redirect': '/404',
         'hidden': 'true'
       })
       commit('SET_NAME', name)
       commit('SET_AVATAR', avatar)
       commit('SET_ROLES', roles) // 角色权限
       commit('SET_MENUS', menus) // 菜单权限
       resolve(data)
     }).catch(error => {
       reject(error)
     })
   })
},

// 登出
logout({ commit, state }) {
   return new Promise((resolve, reject) => { // 如果后端有退出接口写在下面即可,这里直接退出也没问题
     removeToken() // 必须先移除token
     resetRouter()
     commit('RESET_STATE')
     resolve()
   })
},
复制代码

第七步:修改getters.js存储信息

// store/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, // 菜单权限
  roles: state => state.user.roles // 角色权限控制按钮
}
export default getters
复制代码

第八步:修改request.js

// utils/request.js

import router from '@/router'
const BASEURL = process.env.NODE_ENV === 'production' ? '' : '/api'

// 创建axios 赋给变量service
const service = axios.create({
  baseURL: BASEURL, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 5000 // 5s请求超时
})

// 请求拦截器
service.interceptors.request.use(
  config => {
    if (store.getters.token) {
      config.headers['Authorization'] = 'Bearer ' + getToken() // 改成后端要求的token键值
    }
    return config
  },
  error => {
    console.log(error)
    return Promise.reject(error)
  }
)

// 响应拦截器
service.interceptors.response.use(
  response => {
    const message = response.data.data.message
    const code = response.data.data.code
    if (code !== 200) { // 改成后端响应成功的状态码
      switch (code) { // 响应错误判断
        case 401:
          if (router.currentRoute.name === 'login') {
            return Promise.reject(new Error(message))
          } else {
            Message({
              message: '您没有权限访问该资源',
              type: 'error'
            })
          }
          break
        case 403:
          Message({
            message: message,
            type: 'error'
          })
          break
        case 500:
          Message({
            message: '服务异常,请联系管理员',
            type: 'error'
          })
          break
        default:
          Message({
            message: message,
            type: 'error'
          })
      }
      // store.dispatch('user/resetToken').then(() => {
      //   location.reload()  // 重复登录的情况
      // })
      return Promise.reject(new Error(message || 'Error'))
    } else {
      return response
    }
  },
  error => {
    console.log('err' + error) // 响应错误
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)
复制代码

第九步:新增导入组件的方法

// 在router目录下新建_import_development.js文件

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

// 在router目录下新建_import_proudction.js文件

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

第十步:修改permission.js

import Layout from '@/layout' // 引入Layout
const _import = require('./router/_import_' + process.env.NODE_ENV) // 引入获取组件的方法

// 路由拦截器
router.beforeEach(async(to, from, next) => {
  // 进度条加载
  NProgress.start()
  // 获取page标题
  document.title = getPageTitle(to.meta.title)
  // 获取token决定用户是否可以登录
  const hasToken = getToken()
  if (hasToken) {
    if (to.path === '/login') {
      // 如果已登录,则重定向到主页
      next({ path: '/' })
      NProgress.done()
    } else {
      const hasGetUserInfo = store.getters.name
      if (hasGetUserInfo) {
        next()
      } else {
        try { 
        // 每次刷新都会执行try获取用户信息,所以getInfo必须要有,后端不提供怎么办?揍他
          await store.dispatch('user/getInfo')
          // **在这里做动态路由**
          if (store.getters.menus.length < 1) {
            global.antRouter = []
            next()
          }
          const menus = filterAsyncRouter(store.getters.menus) // 过滤路由
          router.addRoutes(menus) // 动态添加路由
          global.antRouter = menus // 将路由数据传递给全局变量,做侧边栏菜单渲染工作
          next({ ...to, replace: true })
        } catch (error) {
          // 移除token去登录页
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    /* 没有token */
    if (whiteList.indexOf(to.path) !== -1) {
      // 如果该路由在白名单内, 放行
      next()
    } else {
      // 不在白名单内不允许通过重定向到登录页
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})


// 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap) {
  const accessedRouters = asyncRouterMap.filter(route => {
    if (route.component) {
      if (route.component === 'Layout') { // 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

routes() {
  return this.$router.options.routes.concat(global.antRouter) // 新路由连接
},
复制代码

控制台执行npm run dev: 可以看到左侧菜单栏已经动态添加了菜单

600906f88404312cde38091e4f073ea.png

写在最后:

无偿提供接口地址:

基础地址: https://www.fastmock.site/mock/48611e3434449d37f501fc4a60f141a8/mock
登录api: /user/login
根据token获取用户信息api(包含姓名,头像,菜单权限,角色):/user/getUserInfoByToken

username: admin/editor password: 123456

复制代码

按钮权限控制:

还是得看花裤衩大佬的原文;只有见过大佬写的code,才明白什么叫细到极致啊~

第一步:站在巨人的肩膀看世界

去花裤衩大佬githubsrc文件下找到directive文件并复制粘贴到自己的src文件下,保留permission文件,删除其他。

bd90292d317c31c66e0eab1b7adbfa3.png

第二步:测试按钮权限

//在dashboard/index.vue下新增两个测试按钮
<el-tooltip class="item" effect="dark" content="admin权限显示" placement="top-start">
  <el-button v-permission="['admin']">admin</el-button>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="admin&editor权限都能显示" placement="top-start">
  <el-button v-permission="['admin','editor']">editor</el-button>
</el-tooltip>

// 当然你也可以为了方便使用,将它注册到全局
import permission from '@/directive/permission/index.js' // 权限判断指令


export default {
  name: 'Dashboard',
  computed: {
    ...mapGetters([
      'name'
    ])
  },
  directives: { permission } // 自定义指令
}

复制代码

保存后看到登录admin账户时两个按钮都能显示,当登录editor账户时只能显示editor按钮。使用上面mock地址可以实现动态添加路由的需求,希望能够帮助到掘友萌,有问题的小伙伴可以私信我。

image.png

文章分类
前端
文章标签