IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 从零开始开发权限管理系统 - 基于vue-admin-template搭建基础框架 -> 正文阅读

[JavaScript知识库]从零开始开发权限管理系统 - 基于vue-admin-template搭建基础框架

1. 项目准备

1.1 项目介绍

  • 本项目是基于vue-admin-template开发的权限管理脚手架
  • 项目 github地址 : rights-manager-front

1.2 模板下载

  • 下载 vue-admin-template

    git clone https://github.com/PanJiaChen/vue-admin-template.git
    
  • 下载完成以后改成自己项目的名字

  • 在项目目录下执行npm install

1.3 删除多余代码

1.3.1 删除多余的组件

  • 删除 view 目录下的 form、nested、table、tree组件 (也可以选择不删除, 如果用的上的话)
image-20220128190312358
  • 删除对应组件的路由 : 找到 router 目录下的 index.js 文件, 删除如下内容

    
      {
        path: '/example',
        component: Layout,
        redirect: '/example/table',
        name: 'Example',
        meta: { title: 'Example', icon: 'el-icon-s-help' },
        children: [
          {
            path: 'table',
            name: 'Table',
            component: () => import('@/views/table/index'),
            meta: { title: 'Table', icon: 'table' }
          },
          {
            path: 'tree',
            name: 'Tree',
            component: () => import('@/views/tree/index'),
            meta: { title: 'Tree', icon: 'tree' }
          }
        ]
      },
    
      {
        path: '/form',
        component: Layout,
        children: [
          {
            path: 'index',
            name: 'Form',
            component: () => import('@/views/form/index'),
            meta: { title: 'Form', icon: 'form' }
          }
        ]
      },
    
      {
        path: '/nested',
        component: Layout,
        redirect: '/nested/menu1',
        name: 'Nested',
        meta: {
          title: 'Nested',
          icon: 'nested'
        },
        children: [
          {
            path: 'menu1',
            component: () => import('@/views/nested/menu1/index'), // Parent router-view
            name: 'Menu1',
            meta: { title: 'Menu1' },
            children: [
              {
                path: 'menu1-1',
                component: () => import('@/views/nested/menu1/menu1-1'),
                name: 'Menu1-1',
                meta: { title: 'Menu1-1' }
              },
              {
                path: 'menu1-2',
                component: () => import('@/views/nested/menu1/menu1-2'),
                name: 'Menu1-2',
                meta: { title: 'Menu1-2' },
                children: [
                  {
                    path: 'menu1-2-1',
                    component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),
                    name: 'Menu1-2-1',
                    meta: { title: 'Menu1-2-1' }
                  },
                  {
                    path: 'menu1-2-2',
                    component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),
                    name: 'Menu1-2-2',
                    meta: { title: 'Menu1-2-2' }
                  }
                ]
              },
              {
                path: 'menu1-3',
                component: () => import('@/views/nested/menu1/menu1-3'),
                name: 'Menu1-3',
                meta: { title: 'Menu1-3' }
              }
            ]
          },
          {
            path: 'menu2',
            component: () => import('@/views/nested/menu2/index'),
            name: 'Menu2',
            meta: { title: 'menu2' }
          }
        ]
      },
    
      {
        path: 'external-link',
        component: Layout,
        children: [
          {
            path: 'https://panjiachen.github.io/vue-element-admin-site/#/',
            meta: { title: 'External Link', icon: 'link' }
          }
        ]
      },
    

1.4 修改登录页面

  • 找到 view 目录下的 login 目录的 index.vue 文件

1.4.1 修改页面标题

  • 在下面这个位置修改登陆标题
<div class="title-container">
        <h3 class="title">权限管理前端框架</h3>
</div>

1.4.2 页面背景

  • 在下面的位置修改添加页面背景图片, 背景图片放在 assets 目录下面

    /* reset element-ui css */
    .login-container {
      // 修改背景图片
      background: url("../../assets/bg.jpg");
      // 设置背景图片覆盖样式
      background-size: cover;
      .el-input {
    

1.4.3 修改密码验证规则

export default {
  name: 'Login',
  data() {
    // 对用户名进行验证
    const validateUsername = (rule, value, callback) => {
      if (!validUsername(value)) {
        callback(new Error('Please enter the correct user name'))
      } else {
        callback()
      }
    }
    // 对密码进行验证
    const validatePassword = (rule, value, callback) => {
      if (value.length < 6) {
        callback(new Error('The password can not be less than 6 digits'))
      } else {
        callback()
      }
    }

1.5 修改配置环境

1.5.1 环境配置

  • 找到 utils 目录下的 request.js 文件

    // create an axios instance
    const service = axios.create({
      baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
      // withCredentials: true, // send cookies when cross-domain requests
      timeout: 5000 // request timeout
    })
    
  • process.env.VUE_APP_BASE_API 对应的是 .env.development 或者 .env.production 文件

    # just a flag
    # TODO process.env.NODE_ENV
    ENV = 'development'
    
    # base api
    VUE_APP_BASE_API = '/dev-api'
    
    
  • 我们需要手动修改成自己的公共api

1.5.1 修改请求token

  • 在下面的位置修改, 将 config.headers['X-Token'] 中的 X-Token 修改为我们自己常用的, 一般是 token

    service.interceptors.request.use(
      config => {
        // do something before request is sent
    
        if (store.getters.token) {
          // let each request carry token
          // ['X-Token'] is a custom headers key
          // please modify it according to the actual situation
          // TODO 修改请求头的 token 字段名, 每次发送请求都会将token放到请求头里
          config.headers['X-Token'] = getToken()
        }
        return config
      },
    
  • 这个地方的作用是, 我们登陆成功以后, 后端会生成一个toke存储在本地, 我们每次发送请求都需要携带这个token

1.5.2 修改登陆失败提示

  • 在下面 if (res.code !== 20000 && res.code !== 200) { 这个位置加上 && res.code !== 200 根据自己的项目情况修改, 有的登陆成功的信息是 200, 有的是 20000

      response => {
        const res = response.data
    
        // if the custom code is not 20000, it is judged as an error.
        // TODO : 这里需要根据实际情况修改, 如果请求成功状态码不是 20000 或者 200 直接给出报错提示
        if (res.code !== 20000 && res.code !== 200) {
          Message({
            message: res.message || 'Error',
            type: 'error',
            duration: 5 * 1000
          })
    
  • 如果登陆失败会给出相应的提示!

1.6 修改代理

1.6.1 禁用mock数据

  • 根据自己的情况来, 知道即可, 不一定非要注释掉, 后端没开发完时,可以使用mock数据
  • 修改 vue.config.js , 注释掉 before: require('./mock/mock-server.js')
  devServer: {
    port: port,
    open: true,
    overlay: {
      warnings: false,
      errors: true
    },
    /**
     * TODO 这里需要注释掉, 我们不使用 mock 数据, 禁用Mock数据
     */
   // before: require('./mock/mock-server.js')

1.6.2 添加代理

  • before: require('./mock/mock-server.js') 下面添加 代理配置, 配置根据自己的后端进行配置

      devServer: {
        port: port,
        open: true,
        overlay: {
          warnings: false,
          errors: true
        },
        /**
         * TODO 这里需要注释掉, 我们不使用 mock 数据, 禁用Mock数据
         */
        // before: require('./mock/mock-server.js')
        // TODO : 添加代理
        proxy: {
          // 这里可以配置多个代理, 用于区分开发和生产环境
          '/dev-api': {
            target: 'http://127.0.0.1:8081',
            pathRewrite: {
              // 将 dev-api 替换为 ''
              // 这个配置的意思是 当请求 : /dev-api/user/add 转换为 => http://127.0.0.1/user/add
              '^/dev-api': ''
            }
          }
        }
      },
    
  • 这个配置的意思是 当请求 : /dev-api/user/add 转换为 => http://127.0.0.1:8081/user/add

    proxy: {
          // 这里可以配置多个代理, 用于区分开发和生产环境
          '/dev-api': {
            target: 'http://127.0.0.1:8081',
            pathRewrite: {
              // 将 dev-api 替换为 ''
              // 
              '^/dev-api': ''
            }
          }
        }
    

1.6.3 修改api请求

  • 找到 api 目录下的user.js修改为自己真正的请求路径

  • 比如 user.js中的

    import request from '@/utils/request'
    
    export function login(data) {
      // TODO url 不要写成 login, 而是 /login, 因为最终路径是 VUE_APP_BASE_API + URL 拼接成的
      // 然后在 vue.config.js 里的 proxy 把 dev-api 替换为 ''
      return request({
        url: '/login',
        method: 'post',
        data
      })
    }
    
    export function getInfo(token) {
      return request({
        url: '/user/info',
        method: 'get',
        params: { token }
      })
    }
    
    export function logout() {
      return request({
        url: '/logout',
        method: 'post'
      })
    }
    
    

1.7 修改Vuex

1.7.1 修改用户信息

  • 打开 store 目录下的 modules 文件夹里的 user.js 文件
  • 将下面设置名称和头像的代码封装成一个方法
  SET_NAME: (state, name) => {
    state.name = name
  },
  SET_AVATAR: (state, avatar) => {
    state.avatar = avatar
  },
  • 完整代码如下 SET_USERINFO
const mutations = {
  RESET_STATE: (state) => {
    Object.assign(state, getDefaultState())
  },
  SET_TOKEN: (state, token) => {
    state.token = token
  },
  // 登陆成功以后获取用户信息, 并设置
  SET_USERINFO(state, userInfo){
    state.name = userInfo.name
    state.avatar = userInfo.avatar
  }
}
  • 然后再下面的 getInfo 方法里配置使用 SET_USERINFO 设置用户名称和头像
  // get user info
  getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      getInfo(state.token).then(response => {
        const { data } = response

        if (!data) {
          return reject('Verification failed, please Login again.')
        }
        // TODO 这里也要修改为 SET_USERINFO
        commit('SET_USERINFO', data)
        // const { name, avatar } = data
        // commit('SET_NAME', name)
        // commit('SET_AVATAR', avatar)
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  },

1.8 修改头像下拉菜单

1.8.1 修改下拉菜单

  • 当我们点击头像的时候会有下拉菜单 分别的 Home, Github … 这些我们可以自行修改
  • 找到 layout => components => Navbar.vue, 在下面的位置可以修改
    <div class="right-menu">
      <el-dropdown class="avatar-container" trigger="click">
        <div class="avatar-wrapper">
          <img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
          <i class="el-icon-caret-bottom" />
        </div>
        <el-dropdown-menu slot="dropdown" class="user-dropdown">
          <router-link to="/">
            <el-dropdown-item>
              首页
            </el-dropdown-item>
          </router-link>
          <!-- TODO : 这里可以删除点击头像的下拉菜单 -->
          <a target="_blank" href="https://github.com/PanJiaChen/vue-admin-template/">
            <el-dropdown-item>Github</el-dropdown-item>
          </a>
          <a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/">
            <el-dropdown-item>Docs</el-dropdown-item>
          </a>
          <el-dropdown-item divided @click.native="logout">
            <span style="display:block;">退出登陆</span>
          </el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>

1.8.2 路由名称修改

  • 以下面的路由配置为例, 如果 name 和 meta的 title一致的话, 会有冲突, 控制台会有报错

  • 所以我们一般将 name 设置的和 path一致, 首字母大写

    {
        path: '/',
        component: Layout,
        redirect: '/dashboard',
        children: [{
          path: 'dashboard',
          // TODO : 在集合里需要注意的是 name 和 title 不能一样, 否则会报错,二者冲突
          name: 'Dashboard',
          component: () => import('@/views/dashboard/index'),
          meta: { title: '首页', icon: 'dashboard' }
    }
    

1.8.3 修改header上的路径

  • 找到 commponent => Breadcrumb => index.vue, 这里 title 也要修改

    getBreadcrumb() {
          // only show routes with meta.title
          let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
          const first = matched[0]
    
          // TODO 这里需要修改, 否则Header上路由的路径首页显示的是Dashboard, 而不是 首页
          if (!this.isDashboard(first)) {
            matched = [{ path: '/dashboard', meta: { title: '首页' }}].concat(matched)
          }
    

1.9 开发和生产环境的配置

1.9.1 配置生产环境

  • 找到 package.json 文件 如下位置
  "scripts": {
    "dev": "vue-cli-service serve",
    "dev-production": "vue-cli-service serve --mode production",
    "build:prod": "vue-cli-service build",
  • 添加代码 "dev-production": "vue-cli-service serve --mode production",
  • "--mode production", 就是 读取生产环境配置 , 读取 .env.production 配置文件
  • 我们在启动的时候可以直接使用 npm run dev-production

1.9.2 不同环境的Mock数据

  • vue-admin-template 使用的有两种方式的 mock数据
  • 开发环境使用的是启动一个 mock-server
  • 线上(生产)环境使用的是 mockjs
  • 二者的区别是 mockjs 是直接重写浏览器的XMLHttpRequest对象,从而才能拦截所有请求,代理到本地, 所以开发者工具的网络选项里看不到发送的请求, 还有一些别的兼容问题
  • 推荐使用 mock-server
  • 参考文档 : MockData 新方案

2. 系统管理

  • 新建用户管理, 角色管理, 权限管理路由组件

    image-20220129142453751
  • 配置系统管理的一级路由

      {
        path: '/system',
        // 首先需要显示一级路由组件
        component: Layout,
        name: 'System',
        meta: { title: '系统管理', icon: 'el-icon-s-tools' },
      }
    

2.1 路由配置

  • 配置完整的路由如下, 这里注意, 由于我创建的每一个目录下都是 index.vue, 所以可以直接使用

  • @/views/system/user 这个路径, 当然 @/views/system/user/index.vue 也是可以的

      // TODO 系统管理路由
      {
        path: '/system',
        // 首先需要显示一级路由组件
        component: Layout,
        name: 'System',
        meta: { title: '系统管理', icon: 'el-icon-s-tools' },
        children: [
          {
            path: 'user/list',
            name: 'User',
            meta: { title: '用户管理', icon: 'el-icon-user-solid' },
            component: () => import('@/views/system/user')
          },
          {
            path: 'role/list',
            name: 'Role',
            meta: { title: '角色管理', icon: 'el-icon-s-help' },
            component: () => import('@/views/system/role')
          },
          {
            path: 'rights/list',
            name: 'Rights',
            meta: { title: '权限管理', icon: 'el-icon-menu' },
            component: () => import('@/views/system/rights')
          }
        ]
      },
    
  • 显示结果

    image-20220129145606539

2.2 权限管理核心概念

2.2.1 用户登录成功信息

  • 用户登录成功, 后端返回token, 前端拿到token, 根据token发送请求获取用户权限信息

    {
        code: 20000,
       	data: {
            avatar: '' // 头像
        	buttons: [user.add, user.delete, user.update .... ] // 按钮级权限信息
    		roltes: ['admin' ... ] // 角色信息
        	routes: ['User', 'Role', 'Rights' ... ] // 路由信息, 根据此信息动态生成路由
        } ,
    	message: '成功',
    	...
    }
    
  • 其中最重要的是 routes 前端接收到以后, 根据这个 routes 过滤路由表, 确定左侧栏显示的菜单列表

2.2.2 权限获取流程

image-20220129163108935

2.2.3 路由表动态生成

  • 一般分为常量路由、异步路由、静态路由
    • 常量路由就是所有用户不需要任何权限也可以看到的路由
    • 异步路由就是自己定义的, 需要用户有相应的权限才可以看到的路由
    • 任意路由是: 用户输入的所有非法路由都会转到404路由界面,注册这个路由的时候, 一定要放到最后面,放后面的原因是, 路由是从上到下匹配的, 上面匹配不到就是非法路由,非法路由在最后面拦截到 404 页面

2.3 路由配置

2.3.1 常量路由

  • 常量路由就是所有用户不需要任何权限也可以看到的路由

    // 常量路由就是所有用户不需要任何权限也可以看到的路由
    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',
          // TODO : 在集合里需要注意的是 name 和 title 不能一样, 否则会报错,二者冲突
          name: 'Dashboard',
          component: () => import('@/views/dashboard/index'),
          meta: { title: '首页', icon: 'dashboard' }
        }]
      }
    ]
    

2.3.2 异步路由

  • 异步路由, 一般是自己定义的, 需要用户有相应的权限, 也就是配置里面的 name 才能访问的路由

    // 异步路由, 一般是自己定义的, 需要用户有相应的权限才能访问的路由
    export const allAsyncRoutes = [
      // TODO 系统管理路由
      {
        path: '/system',
        // 首先需要显示一级路由组件
        component: Layout,
        name: 'System',
        // 点击系统管理会自动重定向到用户管理路由
        redirect: 'user/list',
        meta: { title: '系统管理', icon: 'el-icon-s-tools' },
        children: [
          {
            path: 'user/list',
            name: 'User',
            meta: { title: '用户管理', icon: 'el-icon-user-solid' },
            component: () => import('@/views/system/user/index')
          },
          {
            path: 'role/list',
            name: 'Role',
            meta: { title: '角色管理', icon: 'el-icon-s-help' },
            component: () => import('@/views/system/role/index')
          },
          {
            path: 'rights/list',
            name: 'Rights',
            meta: { title: '权限管理', icon: 'el-icon-menu' },
            component: () => import('@/views/system/rights/index')
          }
        ]
      }
    ]
    

2.3.3 任意路由

  • 任意路由, 用户输入的所有非法路由都会转到404路由界面

  • 注册这个路由的时候, 一定要放到最后面

  • 放后面的原因是, 路由是从上到下匹配的, 上面匹配不到就是非法路由,非法路由在最后面拦截到 404 页面

    export const anyRoute = { path: '*', redirect: '/404', hidden: true }
    

2.4 动态路由表生成

2.4.1 添加路由state

  • 找到 store目录下的 user.js 文件, 添加 roles, buttons, routes

  • 后端返回的是路由名称的数组 : [‘User’, ‘Role’, ‘Rights’] 也就是路由配置中的 name 属性

  • asyncRoutes 保存用户当前所属的路由配置数组(router里面的), 而不是后端返回的routes(这个是路由名称数组), 注意区分

    const getDefaultState = () => {
      return {
        // 登陆成功以后的token存储
        token: getToken(),
        // 登陆成功以后存储用户的名称
        name: '',
        // 登陆成功以后存储用户的头像
        avatar: '',
        // 保存用户角色权限信息
        roles: [],
        // 保存用户的按钮权限信息
        buttons: [],
        // 保存当前用户返回的name数组(routes)对应的异步路由数组(返回的数据是异步路由名称的数组)
        asyncRoutes: [],
        // 保存用户要用的所有路由, 包括常量路由, 异步路由, 任意路由
        routes: []
      }
    }
    
    

2.4.2 修改 SET_USEROINFO

  • 修改下面的 SET_USEROINFO, 为 roles 和 buttons 赋值

  SET_USERINFO(state, userInfo) {
    state.name = userInfo.name
    state.avatar = userInfo.avatar
    state.roles = userInfo.roles
    state.buttons = userInfo.buttons
  }

2.4.3 过滤异步路由

  • 通过返回的用户异步路由名称数组,从所有的异步路由数组当中,过滤出用户的异步路由数组

  • const actions = {} 上面创建方法 filterAsyncRoutes, 用于过滤异步路由

  • allAsyncRoutes : 是所有的异步路由

  • routeNames : 是后端传过来的用户所拥有的权限的路由名称数组

    // 通过返回的用户异步路由名称数组,从所有的异步路由数组当中,过滤出用户的异步路由数组
    function filterAsyncRoutes(allAsyncRoutes, routeNames) {
      // allAsyncRoutes 是所有的异步路由, 现在要根据从后端获取的路由数组名称进行过滤
      // routeNames 是从后端获取的用户路由名称数组
      const asyncRoutes = allAsyncRoutes.filter(item => {
        // 如果当前路由是用户拥有的
        if (routeNames.indexOf(item.name) !== -1) {
          // 考虑子路由情况 , 当前路由有子路由并且子路由数组数量大于0
          if (item.children && item.children.length > 0) {
            // 如果当前这个路由是有子路由的,子路由也要去过滤出用户路由名称包含的
            item.children = filterAsyncRoutes(item.children, routeNames)
          }
          return true
        }
      })
      return asyncRoutes
    }
    
    // 在 action 上面
    const actions = {
    

2.4.4 配置过滤的路由

  • 编写完过滤方法后, 我们需要调用这个方法去过滤路由数据, 位置在 actions 里的 getInfo 方法

  • 使用 commit('SET_ROUTES', filterAsyncRoutes(allAsyncRoutes, data.routes))调用

  • 导入 import cloneDeep from 'lodash/cloneDeep' 深拷贝工具


  // get user info
  getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      getInfo(state.token).then(response => {
        const { data } = response

        if (!data) {
          return reject('Verification failed, please Login again.')
        }
        // TODO 这里也要修改为 SET_USERINFO
        commit('SET_USERINFO', data)
        // TODO 需要把 data.routes 数组替换为 router 里面配置的路由数组而不是 路由名称 数组
        // data.routes返回的是这个用户所有的路由权限信息, 就是所有这个用户要注册的异步路由的名称name数组
        // 这里需要注意使用 cloneDeep 深拷贝一份 allAsyncRoutes 进行过滤, 否则如果我们直接改变allAsyncRoutes的值,
        // 下次登陆的话我们拿到的就是不完整的allAsyncRoutes, 因为上次登陆被过滤过了
        commit('SET_ROUTES', filterAsyncRoutes(cloneDeep(allAsyncRoutes), data.routes))
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  },
  • 导入常量路由以及任意路由

    import { resetRouter, allAsyncRoutes, anyRoute, constantRoutes } from '@/router'
    import router from '@/router'
    
  • mutations 添加 SET_ROUTES ,

      SET_ROUTES(state, asyncRoutes) {
        state.asyncRoutes = asyncRoutes
        // 拼接常量路由、异步路由和任意路由, 根据这个总的路由生成菜单
        state.routes = constantRoutes.concat(asyncRoutes, anyRoute)
        // 动态给路由器添加路由, 参数必须符合路由数组
        router.addRoutes([...asyncRoutes, anyRoute])
      }
    
  • 路由总配置参考

    import { login, logout, getInfo } from '@/api/user'
    import { getToken, setToken, removeToken } from '@/utils/auth'
    import { resetRouter, allAsyncRoutes, anyRoute, constantRoutes } from '@/router'
    import router from '@/router'
    import cloneDeep from 'lodash/cloneDeep'
    
    const getDefaultState = () => {
      return {
        // 登陆成功以后的token存储
        token: getToken(),
        // 登陆成功以后存储用户的名称
        name: '',
        // 登陆成功以后存储用户的头像
        avatar: '',
        // 保存用户角色权限信息
        roles: [],
        // 保存用户的按钮权限信息
        buttons: [],
        // 保存当前用户返回的name数组(routes)对应的异步路由数组(返回的数据是异步路由名称的数组)
        asyncRoutes: [],
        // 保存用户要用的所有路由, 包括常量路由, 异步路由, 任意路由
        routes: []
      }
    }
    
    const state = getDefaultState()
    
    const mutations = {
      RESET_STATE: (state) => {
        Object.assign(state, getDefaultState())
      },
      // TODO 这里的 SET_AVATAR, SET_NAME 封装成一个 SET_USERINFO
      SET_TOKEN: (state, token) => {
        state.token = token
      },
      // 登陆成功以后获取用户信息, 并设置
      SET_USERINFO(state, userInfo) {
        state.name = userInfo.name
        state.avatar = userInfo.avatar
        state.roles = userInfo.roles
        state.buttons = userInfo.buttons
      },
      // TODO 拼接路由
      SET_ROUTES(state, asyncRoutes) {
        state.asyncRoutes = asyncRoutes
        // 拼接常量路由、异步路由和任意路由, 根据这个总的路由生成菜单
        state.routes = constantRoutes.concat(asyncRoutes, anyRoute)
        // 动态给路由器添加路由, 参数必须符合路由数组
        router.addRoutes([...asyncRoutes, anyRoute])
      }
    }
    
    // TODO 过滤异步路由方法
    // 通过返回的用户异步路由名称数组,从所有的异步路由数组当中,过滤出用户的异步路由数组
    function filterAsyncRoutes(allAsyncRoutes, routeNames) {
      // allAsyncRoutes 是所有的异步路由, 现在要根据从后端获取的路由数组名称进行过滤
      // routeNames 是从后端获取的用户路由名称数组
      const asyncRoutes = allAsyncRoutes.filter(item => {
        // 如果当前路由是用户拥有的
        if (routeNames.indexOf(item.name) !== -1) {
          // 考虑子路由情况 , 当前路由有子路由并且子路由数组数量大于0
          if (item.children && item.children.length > 0) {
            // 如果当前这个路由是有子路由的,子路由也要去过滤出用户路由名称包含的
            item.children = filterAsyncRoutes(item.children, routeNames)
          }
          return true
        }
      })
      return asyncRoutes
    }
    
    const actions = {
      // user login
      // 这里的 login 是 vuex 的action, 而不是发请求的api
      login({ commit }, userInfo) {
        const { username, password } = userInfo
        return new Promise((resolve, reject) => {
          // 这里的是发请求的 api
          login({ username: username.trim(), password: password }).then(response => {
            const { data } = response
            // 登陆成功以后先设置token
            commit('SET_TOKEN', data.token)
            // TODO 存储token到cookie当中
            setToken(data.token)
            resolve()
          }).catch(error => {
            reject(error)
          })
        })
      },
    
      // get user info
      getInfo({ commit, state }) {
        return new Promise((resolve, reject) => {
          getInfo(state.token).then(response => {
            const { data } = response
    
            if (!data) {
              return reject('Verification failed, please Login again.')
            }
            // TODO 这里也要修改为 SET_USERINFO
            commit('SET_USERINFO', data)
            // TODO 需要把 data.routes 数组替换为 router 里面配置的路由数组而不是 路由名称 数组
            // data.routes返回的是这个用户所有的路由权限信息, 就是所有这个用户要注册的异步路由的名称name数组
            // 这里需要注意使用 cloneDeep 深拷贝一份 allAsyncRoutes 进行过滤, 否则如果我们直接改变allAsyncRoutes的值,
            // 下次登陆的话我们拿到的就是不完整的allAsyncRoutes, 因为上次登陆被过滤过了
            commit('SET_ROUTES', filterAsyncRoutes(cloneDeep(allAsyncRoutes), data.routes))
            resolve(data)
          }).catch(error => {
            reject(error)
          })
        })
      },
    
      // user logout
      logout({ commit, state }) {
        return new Promise((resolve, reject) => {
          logout(state.token).then(() => {
            removeToken() // must remove  token  first
            resetRouter() // 重置路由
            commit('RESET_STATE')
            resolve()
          }).catch(error => {
            reject(error)
          })
        })
      },
    
      // remove token
      resetToken({ commit }) {
        return new Promise(resolve => {
          removeToken() // must remove  token  first
          commit('RESET_STATE')
          resolve()
        })
      }
    }
    
    export default {
      // 命名空间, 开启后, 访问action getters 需要加上名称前缀 user/login
      // 开启命名空间后, 可以让多个模块有相同的 getter, action
      namespaced: true,
      state,
      mutations,
      actions
    }
    
    
    

2.4.5 NavBar读取配置路由

  • 上面配置完但是还并不能生效, 我们需要修改NavBar的代码

  • 在 store 下 getter.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,
      // 暴露路由数据
      roles: state => state.user.roles,
      buttons: state => state.user.buttons,
      routes: state => state.user.routes
    }
    export default getters
    
    
  • 找到 layout=>component=>Sidebar 下的 index.vue 文件, 在 mapGetters 里添加 routes

  • 然后删除掉下面的 routes() 方法

    export default {
      components: { SidebarItem, Logo },
      computed: {
        ...mapGetters([
          'sidebar',
          // TODO 修改侧边栏路由菜单
          'routes'
        ]),
        // 这里注释掉
        // routes() {
        //   return this.$router.options.routes
        // },
    

2.5 修改 permission.js

  • 找到 permission.js 文件 将 next() 修改为 next({ ...to }) , 这是为了解决页面刷新空白的bug

      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')
              // TODO 这里注意: 这里获取到用户信息后放行, 这是不行的, 因为动态路由不会刷新
              // 需要手动指定跳转路径
              // next()
              next({ ...to })
            } catch (error) {
              // 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()
            }
          }
        }
    

3. Mock工具的使用

3.1 创建MockApi文件

  • 在 mock 目录下创建一个 role.js 文件, user 前面加上 * 表示匹配任意前缀, 这个很有必要加上

  • 因为这样我们可以任意的在 mock数据和真实后端接口数据切换, 因为项目会默认在请求路径前加上 \dev-api , 如果不加 * 我们每次都要在 api 请求上加上 前缀, 因为使用mock数据时, dev-api 是不会被替换的

    module.exports = [
      {
        url: '*/user/login',
        type: 'post',
        response: config => {
          return {
            code: 20000,
            data: 'success'
          }
        }
      }
    ]
    
    
  • 如上面代码 :

    • url: 请求的路径
    • type : 请求类型
    • config : 请求体 请求参数之类的. 可以使用 config.body 得到请求参数, 可以参考 user.js
    • return {} : 响应数据
  • 然后需要我们在 api 请求里保证二者一致即可

    export function getInfo(token) {
      return request({
        url: '/user/login',
        method: 'get',
        params: { token }
      })
    }
    

3.2 配置Mock

  • 在index.js中配置

    const user = require('./user')
    const table = require('./table')
    const role = require('./role')
    
    const mocks = [
      ...user,
      ...table,
      ...role
    ]
    
  • 如上述代码所示, 添加 const role = require('./role') , 以及下面的 mocks

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-01-30 18:50:47  更:2022-01-30 18:52:02 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 13:43:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码