Vue学习笔记(10)

Vue项目

项目功能

  • 1 登录

  • 2 首页

  • 3 退出

  • 4 用户管理

    • 4.1 列表展示

    • 4.2 分页

    • 4.3 查询

    • 4.4 启用/禁用用户

    • 4.5 添加用户

    • 4.6 编辑用户

    • 4.7 删除用户

  • 5 权限管理

    • 5.1 权限列表

  • 6 角色管理

    • 6.1 角色列表

    • 6.2 编辑角色

    • 6.3 删除角色

    • 6.4 表格项展开,展示角色权限

    • 6.5 权限模块业务说明

    • 6.6 分配权限

  • 7 用户管理

    • 7.1 分配角色

  • 8 动态菜单

项目搭建

  • 1 vue init webpack shop_admin

Project name                            :默认
Project description                     :默认
Author                                  :默认
Vue build                               :选择 Runtime + Compiler
Install vue-router?                     :Y
Use ESLint to lint your code?           :Y 选择 Standard
Set up unit tests                       :n
Setup e2e tests with Nightwatch?        : n
Should we run `npm install` for you after the project has been created? (recommended) : Yes, use NPM
  • 2 进入项目:cd shop_admin

  • 3 运行项目:npm run dev

  • 将脚手架默认生成的内容去掉,然后,添加了一个 Login 组件

如何添加一个新的功能???

  • 1 在 components 中新建一个文件夹(login),在文件中创建组件(Login.vue)

  • 2 在 router/index.js 中导入组件(Login.vue)

  • 3 配置路由规则

在项目中使用 element-ui

// main.js

// 导入elementui - js
import ElementUI from 'element-ui'
// 导入elementui - css
import 'element-ui/lib/theme-chalk/index.css'
// 安装插件
Vue.use(ElementUI)

项目启动做了什么

  • 1 在终端中运行:npm run dev,实际上就是运行了:webpack-dev-server ...

  • 2 使用 webpack-dev-server 开启一个服务器

  • 3 根据指定的入口 src/main.js 开始分析入口中使用到的模块

  • 4 当遇到 import 的时候,webpack 就会加载这些模块内容(如果有重复模块,比如:Vue,实际上将来只会加载一次),遇到代码就执行这些代码

  • 5 创建 Vue 实例,将 App 组件作为模板进行编译,并且将 App 组件中 template 的内容渲染在页面 #app 的位置

登录功能

  • 1 安装:npm i -S axios

  • 2 在 Login.vue 组件中导入 axios

  • 3 使用 axios 根据接口文档来发送请求,完成登录

编程式导航

  • 就是通过 JS 代码来实现路由的跳转功能

// 注意:是 router 不是 route
// router用来实现路由跳转,route用来获取路由参数
// push 方法的参数为:要跳转到的路由地址(path)
this.$router.push('/home')

密码

  • 给输入框组件添加 type="password" 就变为密码框状态了

<el-input type="password" v-model="loginForm.password"></el-input>

登录拦截

  • 说明:在没有登录的情况不应该让用户来访问除登录以外的任何页面

登录和拦截的整个流程说明

  • 1 在登录成功以后,将 token 存储到 localStorage 中

  • 2 在 导航守卫 中先判断当前访问的页面是不是登录页面

  • 3 如果是登录页面,直接放行(next())

  • 4 如果不是登录页面,就从 localStorage 中获取 token,判断有没有登录

  • 5 如果登录了,直接放行(next())

  • 6 如果没有登录,就跳转到登录页面让用户登录(next('/login'))

token 机制的说明

  • 在项目中,如果登录成功了,那么,服务器会给我们返回一个 token

  • 这个 token 就是登录成功的标识

  • 这个 token 就相当于使用 cookie+session 机制中的 sessionid

公司人员和项目开发流程

  • 1 产品经理定制项目的需求

  • 2 分配任务:先将所有的任务分配到项目组,然后,再由项目组具体分配给每个开发人员

  • 3 开发:拿到 产品原型 + 需求文档 + UI 设计稿 资料,转化为 HTML 页面,完成功能

  • 4 功能完成后,自己测试有没有 Bug

  • 5 由测试人员来测试你的功能,当测试出 Bug 后,就会通过 禅道 这样的项目管理系统,来提出 Bug

  • 6 由 自己 修改 测试人员提出来的 bug

  • 7 最终,没有 bug 了,项目才会上线

产品经理(Product Manager)
  提需求
  产出: 产品原型 + 需求文档
  原型设计软件:Axure 、墨刀

UI(设计)
  将 产品经理 给的 原型图 设计为 好看的UI稿

FE(前端)front-end
  产品原型 + 需求文档 + UI设计稿 ===> HTML页面

BE(后端) back-end
  给前端提供数据接口

测试人员
  产品原型 + 需求文档 + UI设计稿 来测试我们写的功能
  发现你的功能 与 需求 不一致,那就说明除Bug了,那么,测试人员就会提Bug
  Bug系统: 禅道

项目经理(管理技术)
  技术攻坚,与其他项目组人员沟通,分配任务 等

vue 单文件组件中的 scoped

  • 作用:给 style 标签添加 scoped 属性以后,样式只会对当前组件中的结构生效,而不会影响到其他组件

vue 单文件组件中的 lang

  • 作用:添加 lang="less" 后,就可以使用 less 语法了

  • 但是需要自己安装:npm i -D less less-loader

VSCode 中使用 Vetur 插件格式化单文件组件的 template

  • 打开设置,配置:"vetur.format.defaultFormatter.html": "js-beautify-html"

接口调用的说明

  • 注意:所有接口都需要传递 token,只有传递了正确的 token,服务器才会将数据返回给前端

  • 如果没有传递token,服务器会返回 401 ,告诉接口调用者,没有权限来访问这个接口


cookie+session VS token

Git 使用

刚进入公司,一般都是先通过 git 将项目 clone 到本地:git clone

实现一些功能后,需要将本地写完的代码,提交到git服务器中:git push 仓库地址

每天去公司开发前,都会使用 git pull 仓库地址 来获取下最新的代码
# 克隆仓库
git clone [仓库地址]

# 推送
git push [仓库地址] master

# 简化推送地址
git remote add shop_admin [仓库地址]
git push -u shop_admin master
# 第一次执行上面两条命令,以后只需要输入以下命令即可
git push shop_admin

# 拉取
git pull [仓库地址] master
git pull shop_admin master

分页

  • 当前页 curPage

  • 每页大小 pageSize

  • 总条数 total

总页数 = Math.ceil(total / pageSize)

用户管理

启用或禁用用户

  • 注意:如果用户状态是禁用状态,那么这个用户是无法登录的

  • 新添加的用户是禁用的状态

  • 注意:不要将 admin 用户禁用!!!否则会导致无法操作

添加用户

打开添加用户对话框的步骤

  • 1 给添加用户按钮绑定单击事件,事件中让控制对话框展示的数据为 true

  • 2 在 dialog 对话框中添加一个 form 表单

    • form 表单的数据(userAddForm)

    • form 表单的验证规则(userAddRules),还要给每个表单项添加 prop

    • 等等

  • 3 点击用户添加对话框中的确定按钮,进行表单验证(this.$refs.userAddForm.validate...)

  • 4 点击取消按钮,重置表单(this.$refs.userAddForm.resetFields())

打开编辑用户对话框的步骤

  • 1 给表格中的编辑按钮添加单击事件,事件中完成两件事情:

    • 1.1 将控制对话框展示的数据设置为 true

    • 1.2 获取到当前用户数据,并展示在对话框中

  • 2 在 dialog 对话框中添加一个 form 表单

    • 编辑用户表单数据 + 验证规则

  • 3 点击编辑用户对话框中的确定按钮,进行表单验证

  • 4 点击取消按钮,重置表单


角色管理

删除角色

  • 1 点击按钮弹出确认对话框

    • 1.1 给按钮绑定单击事件

    • 1.2 弹出确认对话框

  • 2 点击确认按钮删除该角色

修改角色

  • 1 点击修改按钮弹出修改对话框

  • 2 将当前要修改的角色信息展示在对话框中

  • 3 点击确定按钮修改信息

给角色分配权限

  • 1 进入页面,就获取到所有的权限,并展示在tree中

  • 2 点击分配权限按钮后,打开分配权限对话框

  • 3 并且选中当前角色具有的权限

async 和 await 的说明

  • await 必须要出现在 async 修饰的函数中。并且 async 应该添加到 await 存在的那个方法中

权限模块

  • 权限模块中涉及到以下几个内容:权限角色用户

    • 用户 -> 角色 -> 权限

  • 权限和菜单是关联到一起,你有这个权限,才会看到对应的菜单!!!

每个用户都有自己的角色
每个角色又有自己的权限
只要用户有这个角色,那么,用户就拥有这个角色的权限
比如:
  小明经过多年努力,小明当上了财务主管,此时,小明就具有了 财务主管 角色的所有权限了
在项目中:
  1 用户 和 角色是一对一的关系
    也就是:一个用户只能有一个角色
  2 角色 和 权限是多对多的关系
    也就是:一个角色可以有多个权限
           一个权限也可以属于多个角色
用户角色权限
小明普通员工查看个人信息,查看自己的薪资
小红普通员工查看个人信息,查看自己的薪资
老王财务主管查看个人信息,查看自己的薪资,查看所有员工的薪资

1、Promise回顾

(1)data-a.txt

aaaa

(2)data-b.txt

bbbb

(3)data-c.txt

cccc

(4)data-d.txt

dddd

(5)index.js

const fs = require('fs')

// 使用Promise封装异步操作
function readFile(path) {
  const p = new Promise(function(resolve, reject) {
    fs.readFile(path, function(err, data) {
      if (err) {
        return reject(err)
      }

      resolve(data.toString())
    })
  })
  return p
}

// async 和 await 是异步编程的终极解决方案
async function fn() {
  // await 的返回值是 后面Promise 成功的结果,也就是 resolve 方法的参数
  try {
    const a = await readFile('./data/aa.txt')
    console.log(a)
  } catch (err) {
    console.log('出错了:', err)
  }

  const b = await readFile('./data/b.txt')
  const c = await readFile('./data/c.txt')
  const d = await readFile('./data/d.txt')

  console.log(b)
  console.log(c)
  console.log(d)
}

fn()

console.log('abc')

// Promise是可以无限调用  then 方法的
// 每一个后面的 then ,都可以获取到前一个 then 的返回值
/* 
readFile('./data/a.txt')
  .then(function(data) {
    console.log('a: ', data)
    return readFile('./data/b.txt')
  })
  .then(function(data) {
    console.log('b: ', data)
    return readFile('./data/c.txt')
  })
  .then(function(data) {
    console.log('c: ', data)
    return readFile('./data/d.txt')
  })
  .then(function(data) {
    console.log('d: ', data)
  })
 */
/* function readFile(path, callback) {
  fs.readFile(path, function(err, data) {
    callback(data)
  })
}
 */

02-Promise的内容补充.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <script>
    // Promise 的三种状态:
    // 1 pending 等待
    // 2 fulfilled(resolved) 成功
    // 3 rejected 失败

    // 状态只能是:
    // 1 pending -> fulfilled
    // 2 pending -> rejected

    // 状态只要发生改变了,就不会再次改变

    const p = new Promise((resolve, reject) => {
      setTimeout(() => {
        // resolve()
        reject()
      }, 5000)
    })

    p.then(() => {
      console.log('成功:', p)
    })
      .catch(() => {
        console.log('失败:', p)
      })

    console.log(p)
  </script>
</body>

</html>

3、Promise的两个常用的方法

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <script src="./axios.js"></script>
  <script>
    // 需求:有三个请求,要等到三个请求都完成之后,再执行一些操作
    // Promise.all([
    //   axios.get('http://localhost:3000/user'),
    //   axios.get('http://localhost:3000/student'),
    //   axios.get('http://localhost:3000/teacher')
    // ])
    //   .then(res => {
    //     console.log(res)
    //   })

    // 需求:有三个请求,只要有一个请求完成了,就执行一些操作
    // Promise.race([
    //   axios.get('http://localhost:3000/user'),
    //   axios.get('http://localhost:3000/student'),
    //   axios.get('http://localhost:3000/teacher')
    // ]).then(res => {
    //   console.log(res)
    // })

    // Promise 的其他方法介绍:
    // Promise.resolve() 方法用来直接获取到一个成功的Promise对象
    // Promise
    //   .resolve('123')
    //   .then(res => {
    //     console.log(res)
    //   })

    // 直接获取到一个失败的Promise对象
    // Promise.reject()
  </script>
</body>

</html>

4、总结

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <script>
    // 权限模块:

    // 用户
    // 角色
    // 权限

    // 用户 -> 角色 -> 权限
    // 权限 和 菜单,是关联到一起的
    // 也就是说:有这个权限,才能访问到这个权限对应的菜单
  </script>
</body>

</html>

5、shop-admin

(1)Home-home.vue

<template>
  <el-container class="home-wrapper">
    <el-header>
      <el-row>
        <el-col :span="8" class="logo">
          <img src="@/assets/logo.png" alt="黑马logo">
        </el-col>
        <el-col :span="8">
          <h1 class="title">电商后台管理系统</h1>
        </el-col>
        <el-col :span="8">
          <div class="welcome">
            <span>欢迎上海前端25期星曜会员</span>
            <a href="javascript:;" @click.prevent="logout">退出</a>
          </div>
        </el-col>
      </el-row>
    </el-header>
    <el-container>
      <el-aside width="200px">
        <!--
          el-menu 表示菜单组件

            default-active 当前激活菜单的 index 值
            @open 菜单展开事件
            @close 菜单收起事件

          el-sub-menu 表示一组菜单
            index 是唯一的,不能重复!!!
         -->

        <el-menu :router="true" default-active="/home/users" class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b">
          <el-submenu index="1">
            <!--
              template: 用来包裹一级菜单,内部指定菜单的图标和菜单名称

              如果要给菜单添加 小图标,应该使用 template 来包裹整个内容
              -->
            <template slot="title">
              <i class="el-icon-location"></i>
              <span>用户管理</span>
            </template>

            <!-- 启用路由模式后,index就相当于 原来 router-link 中的to属性,用来指定导航的路径(哈希值) -->
            <!-- 可以使用 /home/users 或者 home/users -->
            <el-menu-item index="/home/users">
              <template slot="title">
                <i class="el-icon-menu"></i>
                <span>用户列表</span>
              </template>
            </el-menu-item>
          </el-submenu>

          <el-submenu index="2">
            <template slot="title">
              <i class="el-icon-location"></i>
              <span>权限管理</span>
            </template>

            <el-menu-item index="/home/roles">
              <template slot="title">
                <i class="el-icon-menu"></i>
                <span>角色列表</span>
              </template>
            </el-menu-item>
            <el-menu-item index="/home/rights">
              <template slot="title">
                <i class="el-icon-menu"></i>
                <span>权限列表</span>
              </template>
            </el-menu-item>
          </el-submenu>
        </el-menu>
      </el-aside>
      <el-main>
        <!-- 子路由出口 -->
        <router-view />
      </el-main>
    </el-container>
  </el-container>
</template>

<script>
export default {
  methods: {
    // 退出功能
    logout() {
      // 1 弹出确认对话框
      // 2 用户点击确认
      //  2.1 跳回登录页面
      //  2.2 清除token

      this.$confirm('您是否确认退出?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      })
        // 点击确认按钮
        .then(() => {
          // 清除token
          localStorage.removeItem('token')
          // 跳回登录页面
          this.$router.push('/login')
        })
      // // 点击取消按钮
      // .catch(() => {
      //   this.$message({
      //     type: 'info',
      //     message: '已取消删除'
      //   })
      // })
    },

    handleOpen(key, keyPath) {
      console.log('open', key, keyPath)
    },
    handleClose(key, keyPath) {
      console.log('close', key, keyPath)
    }
  }
}
</script>

<style scoped lang="less">
.home-wrapper {
  height: 100%;

  .el-header {
    padding: 0;
    background-color: #b3c1cd;
    color: #333;
    text-align: center;

    .logo {
      text-align: left;
    }

    .title {
      margin: 0;
      line-height: 60px;
      color: #fff;
      font-size: 36px;
    }

    .welcome {
      line-height: 60px;
      font-weight: bold;
      text-align: right;
      padding-right: 30px;

      a {
        color: #b07a17;
        text-decoration: none;
      }
    }
  }

  .el-aside {
    background-color: #545c64;
    color: #333;
    line-height: 200px;
  }

  .el-main {
    background-color: #eaeef1;
    color: #333;
  }

  .el-container:nth-child(5) .el-aside,
  .el-container:nth-child(6) .el-aside {
    line-height: 260px;
  }

  .el-container:nth-child(7) .el-aside {
    line-height: 320px;
  }
}
</style>

(2)login-Login.vue

<template>
  <div class="login-wrapper">
    <!-- 登录
    <el-button type="primary">成功按钮</el-button> -->

    <!--
      el-form
        label-position="top" 设置label的位置
        :model 用来给表单设置数据模型(对象)
        :rules 用来设置表单验证规则的
        ref 用来引用当前的表单组件

      el-form-item
        label 当前表单项的名称
        prop 它的值是 model 对象中的一个属性
          当使用 表单验证 或者 表单重置 功能时,必须要提供该属性

      el-input
        v-model 实现双向数据绑定
     -->

    <!-- row 表示一行 -->
    <el-row type="flex" class="loginForm" justify="center" align="middle">
      <!-- col 表示一列 span 表示占用几份(共24份) -->
      <el-col :xs="12" :sm="10" :md="8" :lg="6" :xl="4" class="login-content">
        <el-form label-position="top" :model="loginForm" :rules="rules" ref="loginForm">
          <el-form-item label="用户名" prop="username">
            <el-input v-model="loginForm.username"></el-input>
          </el-form-item>
          <el-form-item label="密码" prop="password">
            <el-input type="password" v-model="loginForm.password"></el-input>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="submitForm">登录</el-button>
            <el-button @click="resetForm">重置</el-button>
          </el-form-item>
        </el-form>
      </el-col>
    </el-row>
  </div>
</template>

<script>
// 导入 axios
import axios from 'axios'

export default {
  data() {
    return {
      loginForm: {
        username: 'admin',
        password: '123456'
      },
      rules: {
        username: [
          // required 是否为必填项
          // message 当前规则校验失败时的提示
          // trigger 表单验证的触发实际,blur表示失去焦点时触发
          { required: true, message: '用户名为必填项', trigger: 'blur' },
          // min 最小长度
          // max 最大长度
          {
            min: 3,
            max: 6,
            message: '用户名长度在 3 到 6 个字符',
            trigger: 'blur'
          }
        ],
        password: [
          { required: true, message: '密码为必填项', trigger: 'blur' },
          {
            min: 3,
            max: 6,
            message: '密码长度在 3 到 6 个字符',
            trigger: 'blur'
          }
        ]
      }
    }
  },
  methods: {
    // 登录功能的实现
    /* login() {
      // 使用 axios 发送请求
      // http://localhost:8888/api/private/v1/login
      axios
        .post('http://localhost:8888/api/private/v1/login', this.loginForm)
        .then(res => {
          // console.log(res)

          // const data = res.data.data
          // const meta = res.data.meta

          // ES6中的解构,意思就是从 res.data 中取出属性 data 和 meta
          const { data, meta } = res.data
          // console.log(data)
          if (meta.status === 200) {
            // console.log('登录成功')
            // 将登录成功的标识(token)存储到localStorage中
            localStorage.setItem('token', data.token)
            // 登录成功,需要跳转到 后台管理的首页
            this.$router.push('/home')
          } else {
            // console.log('登录失败', meta.msg)
            // this.$message.error(meta.msg)
            this.$message({
              type: 'error',
              message: meta.msg,
              duration: 1000
            })
          }
        })
    }, */

    async login() {
      // 使用 axios 发送请求
      // http://localhost:8888/api/private/v1/login
      const res = await axios.post(
        'http://localhost:8888/api/private/v1/login',
        this.loginForm
      )

      console.log(res)

      const { data, meta } = res.data
      // console.log(data)
      if (meta.status === 200) {
        // console.log('登录成功')
        // 将登录成功的标识(token)存储到localStorage中
        localStorage.setItem('token', data.token)
        // 登录成功,需要跳转到 后台管理的首页
        this.$router.push('/home')
      } else {
        // console.log('登录失败', meta.msg)
        // this.$message.error(meta.msg)
        this.$message({
          type: 'error',
          message: meta.msg,
          duration: 1000
        })
      }
    },

    submitForm() {
      // ref 用在组件中,就表示当前组件
      // this.$refs.loginForm
      this.$refs.loginForm.validate(valid => {
        // valid 表示是否校验成功,如果成功就为:true
        // 如果失败就为:false
        if (valid) {
          // 成功:调用登录接口
          // alert('submit!')

          // 获取到用户名和密码
          // console.log(this.loginForm)
          this.login()
        } else {
          // 校验失败
          return false
        }
      })
    },
    resetForm() {
      this.$refs.loginForm.resetFields()
    }
  }
}
</script>

<style>
.login-wrapper,
.loginForm {
  height: 100%;
}

.loginForm {
  background-color: #2d434c;
}

.login-content {
  min-width: 240px;
  padding: 20px 35px;
  border-radius: 10px;
  background-color: #fff;
}
</style>

(3)rights-Rigth.vue

<template>
  <div>
    <el-breadcrumb separator-class="el-icon-arrow-right" class="rights-breadcrumb">
      <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
      <el-breadcrumb-item>权限管理</el-breadcrumb-item>
      <el-breadcrumb-item>权限列表</el-breadcrumb-item>
    </el-breadcrumb>

    <el-table :data="rightsList" stripe style="width: 100%">
      <!-- 添加 type="index" 表示给表格添加索引号 -->
      <el-table-column type="index" width="50">
      </el-table-column>
      <el-table-column prop="authName" label="权限名称" width="180">
      </el-table-column>
      <el-table-column prop="path" label="路径" width="180">
      </el-table-column>
      <el-table-column prop="level" label="层级">
        <template slot-scope="scope">
          <span v-if="scope.row.level === '0'">一级</span>
          <span v-else-if="scope.row.level === '1'">二级</span>
          <span v-else>三级</span>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      rightsList: []
    }
  },

  created() {
    this.getRightsList()
  },

  methods: {
    /**
     * 获取权限列表数据
     */
    async getRightsList() {
      const res = await this.$http.get('/rights/list')

      // console.log(res)
      const { data, meta } = res.data
      if (meta.status === 200) {
        this.rightsList = data
      }
    }
  }
}
</script>

<style>
.rights-breadcrumb {
  line-height: 40px;
  background-color: #d4dae0;
  font-size: 18px;
  padding-left: 10px;
}
</style>

(4)roles-Roles.vue

<template src="./template.html"></template>
<script src="./script.js"></script>
<style src="./style.less" lang="less"></style>

(5)script.js

export default {
  data() {
    return {
      rolesList: [],
      // 修改对话框的展示和隐藏
      roleEditDialog: false,
      roleEditForm: {
        id: -1,
        roleName: '',
        roleDesc: ''
      },

      // 分配权限对话框的展示和隐藏
      rightsDialog: false,
      // 所有的权限(树形结构)
      rightsTree: [],
      defaultProps: {
        // children 用来指定使用哪个属性来指定子节点
        children: 'children',
        // label 用来指定使用数据中的哪个属性展示树形控制中每个节点的名字
        label: 'authName'
      },
      // 当前分配权限的角色id
      curRoleId: -1
    }
  },

  created() {
    this.getRolesList()
    this.getAllRightsTree()
  },

  methods: {
    /**
     * 获取到所有的权限树形结构数据
     */
    async getAllRightsTree() {
      const res = await this.$http.get('/rights/tree')

      const { data, meta } = res.data
      if (meta.status === 200) {
        this.rightsTree = data
      }
    },

    /**
     * 获取角色列表数据
     */
    async getRolesList() {
      const res = await this.$http.get('/roles')

      // console.log(res)
      const { data, meta } = res.data
      if (meta.status === 200) {
        this.rolesList = data
      }
    },

    /**
     * 根据角色id删除角色
     * @param {number} id 要删除角色的id
     */
    async delRolesById(id) {
      try {
        // 等待用户点击确定或取消按钮
        await this.$confirm('确认删除该角色吗?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        })

        // console.log('点击确定删除')

        // 如果点击是确定按钮,就会执行以下代码:
        // 发送请求,删除当前角色
        const res = await this.$http.delete(`/roles/${id}`)
        if (res.data.meta.status === 200) {
          const index = this.rolesList.findIndex(item => item.id === id)
          this.rolesList.splice(index, 1)
        }
      } catch (err) {
        // 如果点击的取消按钮,就会执行以下代码:
        // 相当于处理 Promise 的catch()
        // console.log('点击取消')

        this.$message({
          type: 'info',
          message: '已取消删除'
        })
      }

      /* this.$confirm('确认删除该角色吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      })
        .then(async () => {
          // 发送请求,删除当前角色
          const res = await this.$http.delete(`/roles/${id}`)
          if (res.data.meta.status === 200) {
            const index = this.rolesList.findIndex(item => item.id === id)
            this.rolesList.splice(index, 1)
          }
        })
        .catch(() => {
          this.$message({
            type: 'info',
            message: '已取消删除'
          })
        }) */
    },

    /**
     * 展示修改角色对话框
     */
    showRoleEditDailog(curRole) {
      this.roleEditDialog = true

      // 获取到当前角色的数据
      for (const key in this.roleEditForm) {
        this.roleEditForm[key] = curRole[key]
      }
    },

    /**
     * 修改角色信息
     */
    async editRole() {
      const { id, roleName, roleDesc } = this.roleEditForm

      const res = await this.$http.put(`/roles/${id}`, {
        roleName,
        roleDesc
      })

      const { data, meta } = res.data
      if (meta.status === 200) {
        // 关闭对话框
        this.roleEditDialog = false
        // 更新列表数据
        const editRole = this.rolesList.find(item => item.id === id)
        editRole.roleName = data.roleName
        editRole.roleDesc = data.roleDesc
      }
    },

    /**
     * 删除指定角色的权限
     * @param {number} roleId 角色id
     * @param {number} rightId 权限id
     */
    async delRoleRightById(roleId, rightId) {
      // console.log('删除', roleId, rightId)
      const res = await this.$http.delete(`roles/${roleId}/rights/${rightId}`)

      // console.log(res)
      const { data, meta } = res.data
      if (meta.status === 200) {
        const curRole = this.rolesList.find(item => item.id === roleId)
        curRole.children = data
      }
    },

    showRightsDialog(curRoleRights, id) {
      // 展示对话框
      this.rightsDialog = true

      // 暂存当前角色id
      this.curRoleId = id
      // v-if 和 v-show
      // v-if 控制展示和隐藏,在隐藏的时候, Vue 是不会渲染这个DOM或组件的!!!
      // 由此,我们知道 visible 属性,是通过 v-if 来控制展示和隐藏

      // 因为tree是包裹在 dialog 中的,而 dialog 一开始是隐藏的
      // 并且,dialog 隐藏的时候, Vue 是不会渲染这个 dialog 组件
      // 因此,无法直接通过 $refs 来获取到tree

      // 如何获取到???
      // 在 $nextTick 回调函数中,就可以获取到的tree。
      // 因为上面通过设置 rightsDialog = true,将对话框展示出来,但是,Vue中的DOM更新
      // 是异步的,所以,必须等到DOM更新完成后,才能获取到的 tree
      // 当 nextTick 的回调函数执行的时候,DOM就已经完成更新了
      this.$nextTick(() => {
        // 三级菜单id数组
        const level3Ids = []
        curRoleRights.forEach(level1 => {
          level1.children.forEach(level2 => {
            level2.children.forEach(level3 => {
              level3Ids.push(level3.id)
            })
          })
        })

        // 指定选中节点的id数组
        this.$refs.rightsTree.setCheckedKeys(level3Ids)
      })
    },

    /**
     * 给角色分配权限
     */
    async assignRights() {
      // 1 获取到当前角色选中的权限id数组
      // 获取全选项
      const checkedKeys = this.$refs.rightsTree.getCheckedKeys()
      // 获取半选项
      const halfCheckedKeys = this.$refs.rightsTree.getHalfCheckedKeys()
      // 将全选的和半选的合并到一起
      const allCheckedIds = [...checkedKeys, ...halfCheckedKeys]

      const res = await this.$http.post(`/roles/${this.curRoleId}/rights`, {
        rids: allCheckedIds.join(',')
      })

      if (res.data.meta.status === 200) {
        this.rightsDialog = false
        // 注意:需要重新获取角色列表
        this.getRolesList()
      }
    }
  }
}

(6)style.less

.level1-row {
  padding: 20px 0;
  border-bottom: 1px dotted #ccc;

  &:last-child {
    border-bottom: 0;
  }
}

.level2-row {
  padding-bottom: 10px;
}

.level3-tag {
  margin-right: 3px;
}

(7)template.html

<div>
  <!-- 列表 -->
  <el-table :data="rolesList" stripe style="width: 100%">
    <!-- type="expand" 表示展开表格列 -->
    <el-table-column type="expand">
      <template slot-scope="scope">
        <el-row v-if="scope.row.children.length === 0">
          <el-col>
            暂无权限,请分配
          </el-col>
        </el-row>
        <!-- 遍历一级菜单 -->
        <el-row v-else class="level1-row" v-for="level1 in scope.row.children" :key="level1.id">
          <!-- 展示一级菜单的名字 -->
          <el-col :span="4">
            <!-- closable 带有关闭x号 type 表示颜色类型 -->
            <el-tag closable @close="delRoleRightById(scope.row.id, level1.id)">
              {{ level1.authName }}
            </el-tag>
            <i class="el-icon-arrow-right"></i>
          </el-col>

          <el-col :span="20">
            <!-- 遍历二级菜单 -->
            <el-row class="level2-row" v-for="level2 in level1.children" :key="level2.id">
              <el-col :span="4">
                <!-- 展示二级菜单的名字 -->
                <el-tag closable type="success" @close="delRoleRightById(scope.row.id, level2.id)">
                  {{ level2.authName }}
                </el-tag>
                <i class="el-icon-arrow-right"></i>
              </el-col>

              <!-- 遍历三级菜单 -->
              <el-col :span="20">
                <!-- 展示三级菜单的名字 -->
                <el-tag class="level3-tag" closable type="warning" v-for="level3 in level2.children" :key="level3.id"
                  @close="delRoleRightById(scope.row.id, level3.id)">
                  {{ level3.authName }}
                </el-tag>
              </el-col>
            </el-row>
          </el-col>
        </el-row>
      </template>
    </el-table-column>
    <el-table-column type="index"></el-table-column>
    <el-table-column prop="roleName" label="角色名称" width="180">
    </el-table-column>
    <el-table-column prop="roleDesc" label="描述" width="180">
    </el-table-column>
    <el-table-column label="操作">
      <template slot-scope="scope">
        <el-button type="primary" plain size="mini" icon="el-icon-edit" @click="showRoleEditDailog(scope.row)"></el-button>
        <el-button type="danger" plain size="mini" icon="el-icon-delete" @click="delRolesById(scope.row.id)"></el-button>
        <el-button type="success" icon="el-icon-check" plain size="mini" @click="showRightsDialog(scope.row.children, scope.row.id)">分配权限</el-button>
      </template>
    </el-table-column>
  </el-table>

  <!-- 修改角色对话框 -->
  <el-dialog title="修改角色" :visible.sync="roleEditDialog">
    <el-form :model="roleEditForm">
      <el-form-item label="角色名称" label-width="120px">
        <el-input v-model="roleEditForm.roleName" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item label="角色描述" label-width="120px">
        <el-input v-model="roleEditForm.roleDesc" autocomplete="off"></el-input>
      </el-form-item>
    </el-form>
    <div slot="footer" class="dialog-footer">
      <el-button @click="roleEditDialog = false">取 消</el-button>
      <el-button type="primary" @click="editRole">确 定</el-button>
    </div>
  </el-dialog>

  <!-- 分配权限对话框 -->
  <el-dialog title="分配权限" :visible.sync="rightsDialog">
    <!--
      树形控件

        data 用来指定数据
        show-checkbox 表示显示checkbox
        node-key 每个节点的唯一标识,使用 id 即可
        default-expand-all 默认展开所有的节点
        props 是一个对象,用来设置 子节点以及展示文字 的属性名称
    -->
    <el-tree ref="rightsTree" :data="rightsTree" show-checkbox node-key="id" :default-expand-all="true" :props="defaultProps">
    </el-tree>

    <div slot="footer" class="dialog-footer">
      <el-button @click="rightsDialog = false">取 消</el-button>
      <el-button type="primary" @click="assignRights">确 定</el-button>
    </div>
  </el-dialog>
</div>

(8)users-Users.vue

<template>
  <div>
    <el-breadcrumb separator-class="el-icon-arrow-right" class="user-breadcrumb">
      <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
      <el-breadcrumb-item>用户管理</el-breadcrumb-item>
      <el-breadcrumb-item>用户列表</el-breadcrumb-item>
    </el-breadcrumb>

    <el-row :gutter="20">
      <el-col :span="6">
        <el-input placeholder="请输入用户名" v-model="queryStr" class="input-with-select">
          <el-button slot="append" icon="el-icon-search" @click="queryUserList"></el-button>
        </el-input>
      </el-col>
      <el-col :span="4">
        <el-button type="success" plain @click="showUserAddDialog">添加用户</el-button>
      </el-col>
    </el-row>

    <!--
      el-table 表格组件
        data 用来给表格组件提供数据
        stripe 添加改属性后,启用隔行变色效果

      el-table-column 表格中的每一列
        label 每一列的标题名称
        width 每一列的宽度
        prop 表示数据中的属性名(字段名称)

    userList = [
      {}, {}, {}
    ]
     -->
    <el-table :data="userList" stripe>
      <el-table-column prop="username" label="姓名" width="180">
      </el-table-column>
      <el-table-column prop="email" label="邮箱" width="180">
      </el-table-column>
      <el-table-column prop="mobile" label="电话" width="180">
      </el-table-column>
      <el-table-column label="用户状态">
        <!-- scope.row 表示当前行的数据 -->
        <template slot-scope="scope">
          <!--
            v-model 用来绑定数据
            active-color="#409EFF" 启用时的颜色
            inactive-color="#C0CCDA" 禁用时的颜色
           -->
          <el-switch v-model="scope.row.mg_state" @change="changeUserState(scope.row.id, scope.row.mg_state)">
          </el-switch>
        </template>
      </el-table-column>
      <el-table-column label="操作">
        <template slot-scope="scope">
          <el-button type="primary" plain size="mini" icon="el-icon-edit" @click="showUserEditDailog(scope.row)"></el-button>
          <el-button type="danger" plain size="mini" icon="el-icon-delete" @click="delUserById(scope.row.id)"></el-button>
          <el-button type="success" icon="el-icon-check" plain size="mini" @click="showUserAssignDialog">分配角色</el-button>
        </template>
      </el-table-column>
    </el-table>

    <!--
      分页组件
        background 背景色
        layout 分页显示的内容
        total 总条数

        给 current-page 属性添加 .sync 修饰符后, 就可以设置当前页
    -->
    <el-pagination background layout="prev, pager, next" :total="total" :page-size="pageSize" :current-page.sync="curPage" @current-change="changePage">
    </el-pagination>

    <!-- 添加用户对话框 -->
    <el-dialog title="添加用户" :visible.sync="userAddDialog" @close="closeUserAddDialog">
      <el-form :model="userAddForm" :rules="userAddRules" ref="userAddForm">
        <el-form-item prop="username" label="用户名" label-width="120px">
          <el-input v-model="userAddForm.username" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item prop="password" label="密码" label-width="120px">
          <el-input v-model="userAddForm.password" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item prop="email" label="邮箱" label-width="120px">
          <el-input v-model="userAddForm.email" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item prop="mobile" label="手机" label-width="120px">
          <el-input v-model="userAddForm.mobile" autocomplete="off"></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="userAddDialog = false">取 消</el-button>
        <el-button type="primary" @click="addUser">确 定</el-button>
      </div>
    </el-dialog>

    <!-- 编辑用户对话框 -->
    <el-dialog title="编辑用户" :visible.sync="userEditDialog" @close="closeUserEditDialog">
      <el-form :model="userEditForm" :rules="userEditRules" ref="userEditForm">
        <el-form-item prop="username" label="用户名" label-width="120px">
          <el-input disabled :value="userEditForm.username"></el-input>
        </el-form-item>
        <el-form-item prop="email" label="邮箱" label-width="120px">
          <el-input v-model="userEditForm.email" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item prop="mobile" label="手机" label-width="120px">
          <el-input v-model="userEditForm.mobile" autocomplete="off"></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="userEditDialog = false">取 消</el-button>
        <el-button type="primary" @click="editUser">确 定</el-button>
      </div>
    </el-dialog>

    <!-- 给用户分配角色 -->
    <el-dialog title="分配角色" :visible.sync="userAssignDialog">
      <el-form :model="userAssignForm">
        <el-form-item label="用户名" label-width="120px">
          <el-input v-model="userAssignForm.username" disabled></el-input>
        </el-form-item>
        <el-form-item label="角色列表" label-width="120px">
          <el-select v-model="userAssignForm.roles" placeholder="请选择角色">
            <el-option label="区域一" value="shanghai"></el-option>
            <el-option label="区域二" value="beijing"></el-option>
          </el-select>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="userAssignDialog = false">取 消</el-button>
        <el-button type="primary" @click="userAssignDialog = false">确 定</el-button>
      </div>
    </el-dialog>

  </div>
</template>

<script>
// import axios from 'axios'

export default {
  created() {
    // console.log('axios: ', this.$http === axios)

    // 发送请求,获取数据
    this.getUserList()
  },

  data() {
    return {
      userList: [],
      // 每页大小
      pageSize: 3,
      // 当前页码
      curPage: 1,
      // 总条数
      total: 0,
      // 搜索内容
      queryStr: '',

      // 控制用户添加对话框的展示和隐藏
      userAddDialog: false,
      userAddForm: {
        username: '',
        password: '',
        email: '',
        mobile: ''
      },
      userAddRules: {
        username: [
          { required: true, message: '用户名为必填项', trigger: 'blur' },
          {
            min: 3,
            max: 6,
            message: '用户名长度在 3 到 6 个字符',
            trigger: 'blur'
          }
        ],
        password: [
          { required: true, message: '密码为必填项', trigger: 'blur' },
          {
            min: 3,
            max: 6,
            message: '密码长度在 3 到 6 个字符',
            trigger: 'blur'
          }
        ]
      },

      // 控制编辑用户对话框的展示和隐藏
      userEditDialog: false,
      userEditForm: {
        id: -1,
        username: '',
        email: '',
        mobile: ''
      },
      userEditRules: {
        mobile: [
          {
            pattern: /^(0|86|17951)?(13[0-9]|15[012356789]|166|17[3678]|18[0-9]|14[57])[0-9]{8}$/,
            message: '手机号码格式不正确',
            // 如果需要在值改变或者失去焦点的时候,都触发验证,可以传递两个
            // trigger: 'change, blur'

            // 当前值改变,就会触发
            trigger: 'change'
          }
        ]
      },

      // 分配角色
      userAssignDialog: false,
      userAssignForm: {
        // 用户id
        id: -1,
        // 用户角色id
        rid: -1,
        // 用户名
        username: '',
        // 角色列表
        roles: []
      }
    }
  },

  methods: {
    // 获取用户列表数据
    // curPage = 1 给参数添加默认值
    /* getUserList(curPage = 1) {
      this.$http
        .get('/users', {
          params: {
            // 当前页
            pagenum: curPage,
            // 每页展示多少条数据
            pagesize: 3,
            // 查询条件
            query: this.queryStr || ''
          }
          // 将 token 作为请求头,传递给服务器接口
          // 这样,才能正确的调用这个接口
          // headers: {
          //   Authorization: localStorage.getItem('token')
          // }
        })
        .then(res => {
          console.log('请求成功:', res)
          const { data, meta } = res.data
          if (meta.status === 200) {
            // 获取数据成功
            this.userList = data.users
            this.total = data.total
            this.curPage = data.pagenum
          }
        })
    }, */

    async getUserList(curPage = 1) {
      const res = await this.$http.get('/users', {
        params: {
          // 当前页
          pagenum: curPage,
          // 每页展示多少条数据
          pagesize: 3,
          // 查询条件
          query: this.queryStr || ''
        }
      })

      const { data, meta } = res.data
      if (meta.status === 200) {
        // 获取数据成功
        this.userList = data.users
        this.total = data.total
        this.curPage = data.pagenum
      }
    },

    /**
     * 分页获取数据
     * 参数 cruPage 表示当前点击的页码
     */
    changePage(curPage) {
      // console.log('当前页为:', curPage)
      this.getUserList(curPage)
    },

    // 搜索
    queryUserList() {
      // console.log(this.queryStr)
      this.curPage = 1
      this.getUserList()
    },

    // 启用或禁用用户
    async changeUserState(id, curState) {
      // console.log(id, curState)
      const res = await this.$http.put(`/users/${id}/state/${curState}`)

      const { data, meta } = res.data
      if (meta.status === 200) {
        this.$message({
          type: 'success',
          message: data.mg_state === 0 ? '禁用成功' : '启用成功',
          duration: 1000
        })
      } else {
        this.$message({
          type: 'error',
          message: meta.msg,
          duration: 1000
        })
      }
    },

    // 展示用户添加对话框
    showUserAddDialog() {
      this.userAddDialog = true
    },

    // 关闭对话框重置表单
    closeUserAddDialog() {
      // console.log('对话框关闭了')
      this.$refs.userAddForm.resetFields()
    },

    // 添加用户
    addUser() {
      this.$refs.userAddForm.validate(valid => {
        if (valid) {
          // 成功
          // console.log('验证成功')
          this.$http.post('/users', this.userAddForm).then(res => {
            // console.log(res)
            const { meta } = res.data
            if (meta.status === 201) {
              // 1 关闭对话框
              // 2 重置表单(只要关闭对话框,就会自动触发对话框的关闭事件来重置表单)
              this.userAddDialog = false

              // 3 重新获取列表数据
              this.total += 1
              const lastPage = Math.ceil(this.total / this.pageSize)
              this.getUserList(lastPage)
            }
          })
        } else {
          // console.log('验证失败')
          return false
        }
      })
    },

    // 根据用户id删除用户
    delUserById(id) {
      // console.log(id)
      this.$confirm('确认删除该用户吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      })
        .then(() => {
          this.$http.delete(`users/${id}`).then(res => {
            // console.log(res)
            const { meta } = res.data
            if (meta.status === 200) {
              this.$message({
                type: 'success',
                message: meta.msg
              })

              const index = this.userList.findIndex(item => item.id === id)
              this.userList.splice(index, 1)

              if (this.userList.length === 0) {
                this.getUserList(--this.curPage)
              }
            }
          })
        })
        .catch(() => {
          this.$message({
            type: 'info',
            message: '已取消删除'
          })
        })
    },

    // 展示编辑对话框
    showUserEditDailog(curUser) {
      // console.log(curUser)
      // 先获取到当前用户的数据
      // 数据交给 userEditForm 后,就会展示在编辑对话框中
      for (const key in this.userEditForm) {
        this.userEditForm[key] = curUser[key]
      }

      // 打开用户编辑对话框
      this.userEditDialog = true
    },

    // 关闭用户编辑对话框
    closeUserEditDialog() {
      this.$refs.userEditForm.resetFields()
    },

    // 点击确定按钮,修改用户数据
    editUser() {
      this.$refs.userEditForm.validate(valid => {
        if (valid) {
          const { id, email, mobile } = this.userEditForm
          // console.log('表单验证成功')
          this.$http
            .put(`/users/${id}`, {
              email,
              mobile
            })
            .then(res => {
              // console.log(res)
              const { data, meta } = res.data
              if (meta.status === 200) {
                // 更新该用户的数据
                const editUser = this.userList.find(item => item.id === id)
                editUser.email = data.email
                editUser.mobile = data.mobile

                // 关闭对话
                this.userEditDialog = false
              }
            })
        } else {
          // console.log('表单验证失败')
          return false
        }
      })
    },

    /**
     * 展示用户分配角色对话框
     */
    showUserAssignDialog() {
      this.userAssignDialog = true
    }
  }
}
</script>

<style>
.user-breadcrumb {
  line-height: 40px;
  background-color: #d4dae0;
  font-size: 18px;
  padding-left: 10px;
}
</style>

(9)router-index.js

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

// 导入 Login 组件(注意,不要添加 .vue 后缀)
import Login from '@/components/login/Login'
// 导入首页组件
import Home from '@/components/home/Home'
// 导入用户列表组件
import Users from '@/components/users/Users'
// 导入权限列表组件
import Rights from '@/components/rights/Rights'
// 导入角色列表组件
import Roles from '@/components/roles/Roles'

Vue.use(Router)

const router = new Router({
  routes: [
    // children 用来配置子路由,将来匹配的组件会展示在 Home 组件的 router-view 中
    {
      path: '/home',
      component: Home,
      children: [
        { path: 'users', component: Users },
        { path: 'rights', component: Rights },
        { path: 'roles', component: Roles }
      ]
    },
    { path: '/login', component: Login }
  ]
})

// 全局导航守卫
// 所有的路由都会先走守卫
// 添加导航守卫之后,不管是访问哪个路由,都会执行beforeEach回调函数中的代码
// 因为所有的路由,都会经过该导航守卫,所以,就可以在这个导航守卫的回调函数中
// 判断有没有登录了
router.beforeEach((to, from, next) => {
  // console.log('导航守卫在看门', to)
  // ...

  if (to.path === '/login') {
    // 如果访问的是login页面,直接放行,也就是任何人不管有没有登录
    // 都可以访问登录页面
    // 直接调用 next() 方法,表示:访问的是哪个页面,就展示这个页面的内容
    next()
  } else {
    // 访问的不是登录页面

    // 判断用户是否登录成功,
    // 1 当用户登录成功,直接调用 next() 方法放行
    // 2 当用户没有登录,应该调用 next('/login') 跳转到登录页面,让用户登录

    // 通过登录成功时候保存的token,来作为有没有登录成功的条件
    const token = localStorage.getItem('token')
    if (token) {
      // 有,登录成功
      next()
    } else {
      // 没有,登录失败
      next('/login')
    }
  }
})

export default router

(10)App.vue

<template>
  <div id="app">
    <!-- 路由出口 -->
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style>
#app {
  height: 100%;
}
</style>

(11)main.js

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'

// 导入全局样式
import '@/assets/index.css'

// 导入elementui - js
import ElementUI from 'element-ui'
// 导入elementui - css
import 'element-ui/lib/theme-chalk/index.css'
// 导入axios
import axios from 'axios'

// 配置公共路径
// 配置好公共路径后, 每次使用 axios 发送请求, 只需要写当前接口的路径(比如: /users) 就可以了
// axios 在发送请求之前, 会将 baseUrl + '/users' 得到完整路径, 才会发送请求
axios.defaults.baseURL = 'http://localhost:8888/api/private/v1'

// 只要配置了拦击器, 那么所有的请求都会走拦截器
// 因此,可以在拦截器中统一处理 headers

// 请求拦截器
axios.interceptors.request.use(function(config) {
  // 在请求发送之前做一些事情
  // endsWith 字符串的方法,用来判断是不是以参数为结尾,如果是返回值为true

  // 判断如果是登录接口,就不需要添加 Authorization 请求头
  if (!config.url.endsWith('/login')) {
    config.headers['Authorization'] = localStorage.getItem('token')
  }
  // console.log('请求拦截器', config)
  return config
})

// 响应拦截器
axios.interceptors.response.use(function(response) {
  // 在获取到响应数据的时候做一些事情
  // console.log('响应拦截器', response)
  if (response.data.meta.status === 401) {
    // 因为现在不是在组件中,因此无法通过 this.$router 来访问到路由实例
    // 但是,可以直接通过上面导入的路由模块中的 router (路由实例)来访问到路由对象
    router.push('/login')
    localStorage.removeItem('token')
  }

  return response
})

// 将 axios 添加到Vue的原型中
// 实例对象可以直接使用原型对象中的属性或方法
// 所有的组件都是Vue的实例
// 说明: 只要是像 axios 这样的第三方库(与Vue没有任何关系),都应该通过这种方式来统一导入
Vue.prototype.$http = axios

// 安装插件
Vue.use(ElementUI)

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,

  // 注册 App 组件为当前实例的局部组件,然后,才可以在template中使用该组件
  components: { App },
  template: '<App/>'
})

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值