Bootstrap

使用vue-admin-template的改造权限-动态路由

文章的开始我先膜拜一波花裤衩大佬,他真的很厉害我也是站在巨人的肩膀上看世界。

1.登录的改造

首先就是login文件夹下的index.vue

>     handleLogin() {
>       // 验证登录表单
>       this.$refs.loginForm.validate((valid) => {
>         // 如果验证成功
>         if (valid) {
>           // 显示加载动画
>           this.loading = true;
>           // 登录
>           this.$store
>             .dispatch("user/login", this.loginForm)
>             .then(() => {
>               // 跳转到指定路由
>               this.$router.push({ path: this.redirect || "/" });
>               // 隐藏加载动画
>               this.loading = false;
>             })
>             .catch(() => {
>               // 隐藏加载动画
>               this.loading = false;
>             });
>         } else {
>           // 如果验证失败,输出错误信息
>           console.log("error submit!!");
>           return false;
>         }
>       });
>     },

这里就是去vuex中调用登录接口成功之后跳转首页,在看vuex之前先看一下请求拦截器的改造位于utils文件夹下的request.js

import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
import moment from 'moment';
import CryptoJS from 'crypto-js';
// create an axios instance
// const service = axios.create({
//   baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
//   timeout: 5000 // request timeout
// })
const service = axios.create({
  baseURL: '/', // 替换为你的基础 URL 这里是原版的的mock地址
  timeout: 9000, // 请求超时时间
});

// request interceptor使用 service.interceptors.request.use 方法注册一个请求拦截器。这个方法接收两个参数:一个成功回调函数和一个错误回调函数。
service.interceptors.request.use(
  config => {
    // 如果有token,将token添加到请求头
    if (store.getters.token) {
      config.headers['Token'] = getToken();
    }
    // 获取请求的url
    const url = config.url;
    // 获取请求的端口
    const port = new URL(config.baseURL).port;
    // 定义一个变量,用于记录appMessage
    let appMessage = '';
    // 根据端口的不同,appMessage值不同
    if (port === '8080' || port === '8440') {
      appMessage = '密码';
    } else if (port === '8081' || port === '8441') {
      appMessage = '密码';
    // 记录当前时间
    const appDate = moment().format('YYYY-MM-DD HH:mm:ss');
    // console.log(config.url,'config.url');
    console.log(url,'url');
    // 如果请求的url长度大于等于8,并且url的最后一个字符串是'strwhere',则将url的最后一个字符串删除
    if (url.length >= 8 && url.slice(-8) === 'strwhere') {
      config.url = url + '=';
    }
    // 对请求的url和appMessage以及appDate进行MD5加密,得到md5
    const decodedStrrole = decodeURIComponent(config.url);
    // console.log(decodedStrrole,'decodedStrrole');
    const md5 = CryptoJS.MD5(decodedStrrole + appMessage + appDate).toString().toLowerCase();
    // console.log(config.url,'config.url');
    // 将appDate和md5添加到请求头
    config.headers['app-date'] = appDate;
    config.headers['app-message'] = md5;
    return config;
  },
  error => {
    console.log(error); // for debug
    return Promise.reject(error);
  }
);

// response interceptor
// service.interceptors.response.use(
//   // 对响应数据进行处理
//   response => {
//     const res = response.retCode
//     // 判断响应状态码是否为200
//     if (res.code !== 20000) {
//       // 弹出错误提示
//       Message({
//         message: res.message || 'Error',
//         type: 'error',
//         duration: 5 * 1000
//       })
//       // 判断响应状态码是否为50008、50012、50014
//       if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
//         // to re-login
//         // 弹出确认框,询问是否重新登录
//         MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
//           confirmButtonText: 'Re-Login',
//           cancelButtonText: 'Cancel',
//           type: 'warning'
//         }).then(() => {
//           // 重新登录
//           store.dispatch('user/resetToken').then(() => {
//             location.reload()
//           })
//         })
//       }
//       // 返回响应状态码
//       return Promise.reject(new Error(res.message || 'Error'))
//     } else {
//       // 返回响应数据
//       return res
//     }
//   },
//   // 对响应错误进行处理
//   error => {
//     console.log('err' + error) // for debug
//     // 弹出错误提示
//     Message({
//       message: error.message,
//       type: 'error',
//       duration: 5 * 1000
//     })
//     // 返回响应错误
//     return Promise.reject(error)
//   }
// )
export default service

我这里配置md5加密,如果不需要直接配置baseURL就好,


API文件夹下的user.js配置,

//登录
export function login(loginName, loginPassword) {
  return request({
    url: `yourname/loginuserlogin/${loginName}/${loginPassword}`,
    method: 'get'
  })
}

接下来就到了store文件夹下的modules下的user.js,正式使用登录接口进行权限的存贮等

import { login, logout, getInfo, getPermission, HaveAuthority } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router'

// 获取默认状态
const getDefaultState = () => {
  return {
    token: getToken(),
    name: '',
    avatar: '',
    roles: []
  }
}

// 初始化状态
const state = getDefaultState()

// 状态变更
const mutations = {
  // 重置状态
  RESET_STATE: (state) => {
    Object.assign(state, getDefaultState())
  },
  // 设置token
  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
  }
}
const actions = {
  // 用户登录
  // 登录
  login({ commit }, userInfo) {
    const { LoginName, LoginPassword } = userInfo
    return new Promise((resolve, reject) => {
      login(LoginName, LoginPassword).then(response => {
        const data = response.data.retMsg;
        const regex = /-(\d+)/;
        const result = data.match(regex);

        // 将 token 存储到 vuex
        commit('SET_TOKEN', result[1])
        setToken(result[1])
        resolve()
      }).catch(error => {
        reject(error)
      })

    })
  },
  // 获取用户信息
  getInfo({ commit, state }) {
    // 返回一个 Promise 对象,以便在组件中处理异步操作
    return new Promise((resolve, reject) => {
      // 调用 getInfo API 函数,传入当前的 token
      // console.log(state.token, 'state.token!');
      getInfo(state.token).then(response => {
        // console.log(response, 'response');
        const data = {
          roles: response.data[0].roleID,
          name: response.data[0].roleName,
        }
        // 如果没有返回数据,则拒绝 Promise 并提示错误
        if (!data) {
          reject('Verification failed, please Login again.');
        }

        const { roles, name } = data;

        // 如果 roles 不是非空数组,则拒绝 Promise 并提示错误
        if (!roles || roles.length <= 0) {
          reject('getInfo: roles must be a non-null array!');
        }

        // 提交 SET_ROLES、SET_NAME 和 SET_AVATAR mutations,设置角色、用户名和头像
        commit('SET_ROLES', roles);
        commit('SET_NAME', name);
        resolve(data);
      }).catch(error => {
        // 失败时,reject Promise
        reject(error);
      });
    });
  },

  // 用户登出
  logout({ commit, state }) {
    // 返回一个 Promise 对象,以便在组件中处理异步操作
    return new Promise((resolve, reject) => {
      // 调用 logout API 函数,传入当前的 token
      logout(state.token).then(() => {
        // 移除本地存储的 token
        removeToken();
        // 重置路由
        resetRouter();
        // 提交 RESET_STATE mutation,重置状态
        commit('RESET_STATE');
        // 成功时,resolve Promise
        resolve();
      }).catch(error => {
        // 失败时,reject Promise
        reject(error);
      });
    });
  },

  // 移除 token
  resetToken({ commit }) {
    // 返回一个 Promise 对象,以便在组件中处理异步操作
    return new Promise(resolve => {
      // 移除本地存储的 token
      removeToken();
      // 提交 RESET_STATE mutation,重置状态
      commit('RESET_STATE');
      // 成功时,resolve Promise
      resolve();
    });
  }
};
export default {
  namespaced: true,
  state,
  mutations,
  actions
}

login方法这里就是将登录以后的token进行存贮之后,后续需要根据存贮的token进行菜单的条件查询等,
getInfo根据login方法存储的token进行角色信息的查询,在permission.js也有使用,后面会讲到这里
logout和resetToken就不去说了,就是简单的移除token等


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'

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()
  // console.log(hasToken,'hasToken');
  //如果存在token,即存在已登陆的令牌
  if (hasToken) {
    // console.log(hasToken,'存在token');
    if (to.path === '/login') {
      // console.log('用户在登录界面准备进入');
      //如果用户存在令牌的情况请求登录页面,就让用户直接跳转到首页,避免存在重复登录的情况
      // 直接跳转到首页,当然取决于你的路由重定向到哪里
      next({ path: '/' })
      NProgress.done()//一定要关闭进度条
    } else {
      //如果已经有令牌的用户请求的不是登录页,是其他页面
      //就从Vuex里拿到用户的信息,这里也证明用户不是第一次登录了
      // console.log('用户有token但是不在登录页面');
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
      if (hasRoles) {
        next() //信息拿到后,用户请求哪就跳转哪
      } else {
        try {
          // console.log('取到身份信息');
          const { roles } = await store.dispatch('user/getInfo') //去获取身份信息
          // console.log('在做权限的判断');
          console.log(roles,'roles---permission');
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles) //权限的判断

          console.log(accessRoutes,'accessRoutes---permission');
          //像路由中添加数据
          router.addRoutes(accessRoutes)
          //放行
          next({ ...to, replace: true })
        } catch (error) {
          // console.log('3333333');
          // remove token and go to login page to re-login
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    /* has no token*/
    //这里是没有令牌的情况
    //还记得上面的白名单吗,现在起作用了
    //whiteList.indexOf(to.path) !== -1)判断用户请求的路由是否在白名单里
    if (whiteList.indexOf(to.path) !== -1) {
      // 不是-1就证明存在白名单里,不管你有没有令牌,都直接去到白名单路由对应的页面
      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() // 每次请求结束后都需要关闭进度条
})

这里做了什么已经写了注释了,大概内容就是设置标题,判断token等

 const { roles } = await store.dispatch('user/getInfo') //去获取身份信息
      // console.log('在做权限的判断');
      console.log(roles,'roles---permission');
      const accessRoutes = await store.dispatch('permission/generateRoutes', roles) //权限的判断
      console.log(accessRoutes,'accessRoutes---permission');
      //像路由中添加数据
      router.addRoutes(accessRoutes)
      //放行
      next({ ...to, replace: true })

重点就是这段 通过user中的getInfo方法/去获取身份信息然后通过roles参数去进行permission/generateRoutes方法的去获取路由的信息等,

store/modules/permission.js

import { asyncRoutes, constantRoutes } from '@/router'
import { HaveAuthority } from '@/api/user'
/**
 * Use meta.role to determine if the current user has permission
 * @param roles
 * @param route
 */
/**
 * Filter asynchronous routing tables by recursion
 * @param routes asyncRoutes
 * @param roles
 */

const state = {
    // 路由
    routes: [],
    // 添加的路由
    addRoutes: []
}

const mutations = {
    // 设置路由
    SET_ROUTES: (state, routes) => {
        // 设置addRoutes
        state.addRoutes = routes
        // 设置routes
        state.routes = constantRoutes.concat(routes)
        console.log(constantRoutes,'constantRoutes-------');
    }
}

const actions = {
    // 生成路由
    async generateRoutes({ commit }, roles) {
        // 获取当前用户的角色
        const res = await HaveAuthority(roles);
        console.log(res, 'res获取路由');

        // 获取访问的路由
        let accessedRoutes = asyncRoutes.filter(route => {
            // 检查 route.path 是否在 res.data 中
            return res.data.some(item => item.pojoOne.path === route.path || item.pojoTwo.some(child => child.path === route.path));
        });

        // 处理路由数据 因为我的接口返回的数据不能直接用所以这里进行了数据的处理 根据自己的数据结果而定
        const processedRoutes = accessedRoutes.map(route => {
            const pojoOne = res.data.find(item => item.pojoOne.path === route.path);
            const pojoTwo = res.data.find(item => item.pojoTwo.some(child => child.path === route.path));
            if (pojoOne && pojoTwo) {
                route.meta = { ...pojoOne.pojoOne, ...pojoTwo.pojoTwo };
            }

            // 只保留与接口返回数据匹配的子路由
            if (route.children) {
                route.children = route.children.filter(child => {
                    return res.data.some(item => item.pojoTwo.some(pojoTwoChild => pojoTwoChild.path === child.path));
                });
            }

            return route;
        });

        console.log(accessedRoutes,'accessedRoutes--------accessedRoutes');
        // 设置路由
        commit('SET_ROUTES', processedRoutes)
        // 返回路由
        return processedRoutes
    }
}

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

这里就是根据角色拿到相应的路由信息之后和本地的异步路由信息进行比对之后并返回
说到异步路由信息就要看一眼vuerouter了
router/index.js

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
/* Layout */
import Layout from '@/layout'

export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },

  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },

  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: '首页', icon: 'dashboard' }
    }]
  },
  {
    path: '/systemManagement',
    component: Layout,
    redirect: '/systemManagement/Rolemanagement',
    name: 'systemManagement',
    meta: {
      title: '系统管理',
      icon: 'tree',
    },
    children: [
      {
        path: 'UserData',
        component: () => import('@/views/systemManagement/UserData/index'),
        meta: { title: '用户信息管理' }
      }
    ]
  },
]

export const asyncRoutes = [
  {
    path: '/example',
    // 路由对应的组件
    component: Layout,
    // 重定向
    redirect: '/example/tree',
    // 路由名称
    name: 'Example',
    // meta 属性,用于指定页面的标题和图标
    meta: { title: '仓库管理', icon: 'el-icon-s-help' },
    // 子路由
    children: [
      {
        // 子路由的 path
        path: 'table',
        // 子路由的名称
        name: 'Table',
        // 子路由对应的组件
        component: () => import('@/views/table/index'),
        // 子路由的 meta 属性
        meta: { title: '其他维护' },
      },
      {
        path: 'details',
        name: 'Details',
        component: () => import('@/views/table/details/index'),
        meta: { title: '维护详情' },
        hidden: true
      },
      {
        path: 'tree',
        name: 'Tree',
        component: () => import('@/views/tree/index'),
        meta: { title: '维护工单' }
      },
      {
        path: 'teedetails',
        name: 'TeeDetails',
        component: () => import('@/views/tree/details/index'),
        meta: { title: '工单详情' },
        hidden: true
      }
    ]
  },

  {
    path: 'external-link',
    component: Layout,
    children: [
      {
        path: 'https://panjiachen.github.io/vue-element-admin-site/#/',
        meta: { title: 'External Link', icon: 'link' }
      }
    ]
  },

  // 404 page must be placed at the end !!!
  { path: '*', redirect: '/404', hidden: true }
]

const createRouter = () => new Router({
  //这是一个箭头函数的声明,用于创建一个路由器。这里使用了 Router 类的构造函数,创建一个新的路由器对象。
  // mode: 'history', // require service support
  scrollBehavior: () => ({ y: 0 }),//这是一个滚动行为(scrollBehavior)的设置,它定义了在路由切换时滚动条的位置。这里的设置是每次路由切换时将垂直滚动条的位置设为 0,也就是页面顶部。
  routes: constantRoutes //这是路由器的路由配置,其中 constantRoutes 是一个常量,可能是一个数组,包含了应用的所有路由信息。
})
//这段代码的主要功能是创建一个包含指定滚动行为和路由配置的路由器对象。但需要注意的是,在这段代码中,注释部分 mode: 'history', 被注释掉了,它是可选的配置项,如果需要启用路由器的历史模式,就可以取消注释这行代码。

const router = createRouter()

export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router 路由重置的操作
}

export default router

路由信息为asyncRoutes异步路由信息,constantRoutes常规路由信息,
asyncRoutes定义的是需要权限的页面等,constantRoutes就是不需要权限所有人都可以看的路由信息如:404 登录 首页等。
因为我这里使用vue-admin-template进行改造的所以可以不用考虑菜单的渲染问题,直接添加router就可以

;