vue-element-admin

介绍

vue-element-admin是一个后台前端解决方案,它基于 vue 和 element-ui实现。它使用了最新的前端技术栈,内置了 i18 国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。

官方网址:https://panjiachen.github.io/vue-element-admin-site/zh/guide/#%E5%8A%9F%E8%83%BD

目录结构

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

安装

# 克隆项目
git clone https://github.com/PanJiaChen/vue-element-admin.git

# 进入项目目录
cd vue-element-admin

# 安装依赖
npm install

# 建议不要用 cnpm 安装 会有各种诡异的bug 可以通过如下操作解决 npm 下载速度慢的问题
npm install --registry=https://registry.npm.taobao.org

# 本地开发 启动项目
npm run dev

Axios的拦截器介绍

请求拦截器原理如下图:

在这里插入图片描述

axios拦截器

axios作为网络请求的第三方工具, 可以进行请求和响应的拦截

通过create创建了一个新的axios实例

// 创建了一个新的axios实例
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 // 超时时间
})

请求拦截器

请求拦截器主要处理 token的统一注入问题

// axios的请求拦截器
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
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

响应拦截器

响应拦截器主要处理 返回的**数据异常** 和**数据结构**问题

// 响应拦截器
service.interceptors.response.use(
  response => {
    const res = response.data

    // if the custom code is not 20000, it is judged as an error.
    if (res.code !== 20000) {
      Message({
        message: res.message || 'Error',
        type: 'error',
        duration: 5 * 1000
      })
      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)
  }
)

这里为了后续更清楚的书写代码,我们将原有代码注释掉,换成如下代码

// 导出一个axios的实例  而且这个实例要有请求拦截器 响应拦截器
import axios from 'axios'
const service = axios.create() // 创建一个axios的实例
service.interceptors.request.use() // 请求拦截器
service.interceptors.response.use() // 响应拦截器
export default service // 导出axios实例

api模块的单独封装

我们习惯性的将所有的网络请求 放置在api目录下统一管理,按照模块进行划分

单独封装代码

import request from '@/utils/request'

export function login(data) {
  return request({
    url: '/vue-admin-template/user/login',
    method: 'post',
    data
  })
}

export function getInfo(token) {
  return request({
    url: '/vue-admin-template/user/info',
    method: 'get',
    params: { token }
  })
}

export function logout() {
  return request({
    url: '/vue-admin-template/user/logout',
    method: 'post'
  })
}

上面代码中,使用了封装的request工具,每个接口的请求都单独**导出**了一个方法,这样做的好处就是,任何位置需要请求的话,可以直接引用我们导出的请求方法

为了后续更好的开发,我们可以先将user.js代码的方法设置为空,后续在进行更正

// import request from '@/utils/request'

export function login(data) {

}

export function getInfo(token) {

}

export function logout() {

}

登录页面的基础布局

**目标**完成登录页面的基础布局

设置头部背景

<!-- 放置标题图片 @是设置的别名-->
<div class="title-container">
        <h3 class="title">
          <img src="@/assets/common/login-logo.png" alt="">
        </h3>
 </div>

本节注意@是我们在vue.config.js中设置的一个路径别名,指定src根目录,这样可以很方便的寻找文件

设置背景图片

/* reset element-ui css */
.login-container {
  background-image: url('~@/assets/common/login.jpg'); // 设置背景图片
  background-position: center; // 将图片位置设置为充满整个屏幕
}

本节注意: 如需要在样式表中使用**@别名的时候,需要在@前面加上一个~**符号,否则不识别

设置手机号和密码的字体颜色

$light_gray: #68b0fe;  // 将输入框颜色改成蓝色

设置输入表单整体背景色

  .el-form-item {
    border: 1px solid rgba(255, 255, 255, 0.1);
    background: rgba(255, 255, 255, 0.7); // 输入登录表单的背景色
    border-radius: 5px;
    color: #454545;
  }

设置错误信息的颜色

 .el-form-item__error {
    color: #fff
  }

设置登录按钮的样式

.loginBtn {
  background: #407ffe;
  height: 64px;
  line-height: 32px;
  font-size: 24px;
}

修改显示的提示文本和登录文本

   <div class="tips">
        <span style="margin-right:20px;">账号: 13800000002</span>
        <span> 密码: 123456</span>
   </div>

登录表单的校验

**目标**对登录表单进行规则校验

在这里插入图片描述

用户名和密码的校验

为什么要对应? 因为基础模板采用的是**username的字段,但是实际接口中采用的是mobile的字段,为了更方便的写代码,所以我们将username改成mobile **

英文提示变成中文

基础模板中都是placeHolder占位符是英文,要变成中文

登录按钮文字同样需要换成中文

校验用户名和校验密码

基础模板中,已经做了校验,我们针对代码进行一些优化

 loginRules: {
        username: [{ required: true, trigger: 'blur', validator: validateUsername }],
        password: [
          { required: true, trigger: 'blur' },
          { min: 6, max: 12, trigger: 'blur', message: '密码长度应该在6-12位之间' }]
      },

**utils/validate.js**是一个专门存放校验工具方法的文件

Vue-Cli配置跨域代理

目标: 通过配置vue-cli的代理解决跨域访问的问题

为什么会出现跨域?

当下,最流行的就是**前后分离项目,也就是前端项目后端接口并不在一个域名之下,那么前端项目访问后端接口必然存在跨域**的行为.

请注意,我们所遇到的这种跨域是位于开发环境的,真正部署上线时的跨域是生产环境

解决开发环境的跨域问题

开发环境的跨域

开发环境的跨域,也就是在**vue-cli脚手架环境下开发启动服务时,我们访问接口所遇到的跨域问题,vue-cli为我们在本地开启了一个服务,可以通过这个服务帮我们代理请求,解决跨域问题,这就是vue-cli配置webpack的反向代理**

vue-cli的配置文件即**vue.config.js**,这里有我们需要的 代理选项

module.exports = {
  devServer: {
   // 代理配置
    proxy: {
        // 这里的api 表示如果我们的请求地址有/api的时候,就出触发代理机制
        // localhost:8888/api/abc  => 代理给另一个服务器
        // 本地的前端  =》 本地的后端  =》 代理我们向另一个服务器发请求 (行得通)
        // 本地的前端  =》 另外一个服务器发请求 (跨域 行不通)
        '/api': {
        target: 'www.baidu.com', // 我们要代理的地址
        changeOrigin: true, // 是否跨域 需要设置此值为true 才可以让本地服务代理我们发出请求
         // 路径重写
        pathRewrite: {
            // 重新路由  localhost:8888/api/login  => www.baidu.com/api/login
            '^/api': '' // 假设我们想把 localhost:8888/api/login 变成www.baidu.com/login 就需要这么做 
        }
      },
    }
  }
}

接下来,我们在代码中将要代理的后端地址变成 后端接口地址

 // 代理跨域的配置
    proxy: {
      // 当我们的本地的请求 有/api的时候,就会代理我们的请求地址向另外一个服务器发出请求
      '/api/private/v1/': {
       target: 'http://127.0.0.1:8888', // 跨域请求的地址
        changeOrigin: true // 只有这个值为true的情况下 才表示开启跨域
      }
    }

本节注意**:我们并没有进行pathRewrite,因为后端接口就是ihrm-java.itheima.net/api**这种格式,所以不需要重写

**vue.config.js**的改动如果要生效,需要进行重启服务

同时,还需要注意的是,我们同时需要注释掉 mock的加载,因为mock-server会导致代理服务的异常

// before: require('./mock/mock-server.js'),  // 注释mock-server加载

生产环境的跨域

生产环境表示我们已经开发完成项目,将项目部署到了服务器上,这时已经没有了vue-cli脚手架的**辅助了,我们只是把打包好的html+js+css交付运维人员,放到Nginx服务器而已,所以此时需要借助Nginx**的反向代理来进行

server{
    # 监听9099端口
    listen 9099;
    # 本地的域名是localhost
    server_name localhost;
    #凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://baidu.com
    location ^~ /api {
        proxy_pass http://baidu.com;
    }    
}

封装单独的登录接口

目标 在单独请求模块中,单独封装登录接口

基础模板已经有了原来的登录代码,我们只需要进行简单的改造即可

export function login(data) {
  // 返回一个axios对象 => promise  // 返回了一个promise对象
  return request({
    url: 'login', // 因为所有的接口都要跨域 表示所有的接口要带 /api
    method: 'post',
    data
  })
}

封装Vuex的登录Action并处理token

**目标**在vuex中封装登录的action,并处理token

在Vuex中对token进行管理

在传统模式中,我们登录的逻辑很简单,如图
在这里插入图片描述

上图中,组件直接和接口打交道,这并没有什么问题,但是·用的**钥匙**,我们需要让vuex来介入,将用户的token状态共享,更方便的读取,如图

在这里插入图片描述

实现store/modules/user.js基本配置

// 状态
const state = {}
// 修改状态
const mutations = {}
// 执行异步
const actions = {}
export default {
  namespaced: true,
  state,
  mutations,
  actions
}

设置token的共享状态

const state = {
  token: null
}

在**utils/auth.js中,基础模板已经为我们提供了获取token,设置token,删除token**的方法,可以直接使用

只需要将存储的key放置成特定值即可

// import Cookies from 'js-cookie'

const TokenKey = 'xiaoyou'

export function getToken() {
  // return Cookies.get(TokenKey)
  return localStorage.getItem(TokenKey)
}

export function setToken(token) {
  // return Cookies.set(TokenKey, token)
  return localStorage.setItem(TokenKey, token)
}

export function removeToken() {
  // return Cookies.remove(TokenKey)
  return localStorage.removeItem(TokenKey)
}

初始化token状态 - store/modules/user.js

import { getToken, setToken, removeToken } from '@/utils/auth'
// 状态
// 初始化的时候从缓存中读取状态 并赋值到初始化的状态上
// Vuex的持久化 如何实现 ? Vuex和前端缓存相结合
const state = {
  token: getToken() // 设置token初始状态   token持久化 => 放到缓存中
}

提供修改token的mutations

// 修改状态
const mutations = {
  // 设置token
  setToken(state, token) {
    state.token = token // 设置token  只是修改state的数据  123 =》 1234
    // vuex变化 => 缓存数据
    setToken(token) // vuex和 缓存数据的同步
  },
  // 删除缓存
  removeToken(state) {
    state.token = null // 删除vuex的token
    removeToken() // 先清除 vuex  再清除缓存 vuex和 缓存数据的同步
  }
}

封装登录的Action

封装登录的action

登录action要做的事情,调用登录接口,成功后设置token到vuex,失败则返回失败

// 执行异步
const actions = {
  // 定义login action  也需要参数 调用action时 传递过来的参数
  async login(context, data) {
    const result = await login(data) // 实际上就是一个promise  result就是执行的结果
    // axios默认给数据加了一层data
    if (result.data.success) {
      // 表示登录接口调用成功 也就是意味着你的用户名和密码是正确的
      // 现在有用户token
      // actions 修改state 必须通过mutations
      context.commit('setToken', result.data.data)
    }
  }
}

上述代码中,我们使用了**async/await语法,如果用then**语法也是可以的

/ 为什么async/await 不用返回new Promise,因为 async函数本身就是 Promise,promise的值返回的值  
login(context, data) {
    return new Promise(function(resolve) {
      login(data).then(result => {
        if (result.data.success) {
          context.commit('setToken',  result.data.data) // 提交mutations设置token
          resolve()  // 表示执行成功了
        }
      })
    })
  }

除此之外,为了更好的让其他模块和组件更好的获取token数据,我们可以在**store/getters.js**中将token值作为公共的访问属性放出

const getters = {
  sidebar: state => state.app.sidebar,
  device: state => state.app.device,
  token: state => state.user.token // 在根级的getters上 开发子模块的属性给别人看 给别人用
}
export default getters

Token失效的主动介入

目标: 处理当token失效时业务

主动介入token处理的业务逻辑

在这里插入图片描述

流程图转化代码 src/utils/auth.js

const timeKey = 'hrsaas-timestamp-key' // 设置一个独一无二的key

// 获取时间戳
export function getTimeStamp() {
  return Cookies.get(timeKey)
}
// 设置时间戳
export function setTimeStamp() {
  Cookies.set(timeKey, Date.now())
}

src/utils/request.js

import axios from 'axios'
import store from '@/store'
import router from '@/router'
import { Message } from 'element-ui'
import { getTimeStamp } from '@/utils/auth'
const TimeOut = 3600 // 定义超时时间

const service = axios.create({
// 当执行 npm run dev  => .evn.development => /api => 跨域代理
  baseURL: process.env.VUE_APP_BASE_API, // npm  run dev  => /api npm run build =>  /prod-api
  timeout: 5000 // 设置超时时间
})
// 请求拦截器
service.interceptors.request.use(config => {
  // config 是请求的配置信息
  // 注入token
  if (store.getters.token) {
    // 只有在有token的情况下 才有必要去检查时间戳是否超时
    if (IsCheckTimeOut()) {
      // 如果它为true表示 过期了
      // token没用了 因为超时了
      store.dispatch('user/logout') // 登出操作
      // 跳转到登录页
      router.push('/login')
      return Promise.reject(new Error('token超时了'))
    }
    config.headers['Authorization'] = store.getters.token
  }
  return config // 必须要返回的
}, error => {
  return Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(response => {
  // axios默认加了一层data
  const { success, message, data } = response.data
  //   要根据success的成功与否决定下面的操作
  if (success) {
    return data
  } else {
    // 业务已经错误了 还能进then ? 不能 ! 应该进catch
    Message.error(message) // 提示错误消息
    return Promise.reject(new Error(message))
  }
}, error => {
  Message.error(error.message) // 提示错误信息
  return Promise.reject(error) // 返回执行错误 让当前的执行链跳出成功 直接进入 catch
})
// 是否超时
// 超时逻辑  (当前时间  - 缓存中的时间) 是否大于 时间差
function IsCheckTimeOut() {
  var currentTime = Date.now() // 当前时间戳
  var timeStamp = getTimeStamp() // 缓存时间戳
  return (currentTime - timeStamp) / 1000 > TimeOut
}
export default service

注意:我们在调用登录接口的时候 一定是没有token的,所以token检查不会影响登录接口的调用

在登录的时候,如果登录成功,我们应该设置时间戳

  // 定义login action  也需要参数 调用action时 传递过来的参数
  // async 标记的函数其实就是一个异步函数 -> 本质是还是 一个promise
  async login(context, data) {
    // 经过响应拦截器的处理之后 这里的result实际上就是 token
    const result = await login(data) // 实际上就是一个promise  result就是执行的结果
    // axios默认给数据加了一层data
    // 表示登录接口调用成功 也就是意味着你的用户名和密码是正确的
    // 现在有用户token
    // actions 修改state 必须通过mutations
    context.commit('setToken', result)
    // 写入时间戳
    setTimeStamp() // 将当前的最新时间写入缓存
  }

Token失效的被动处理

目标: 实现token失效的被动处理

除了token的主动介入之外,我们还可以对token进行被动的处理,如图

在这里插入图片描述

token超时的错误码是**400**

代码实现 src/utils/request.js

error => {
  // error 信息 里面 response的对象
  if (error.response && error.response.data && error.response.data.meta.status === 400) {
    // 当等于400的时候 表示 后端告诉我token超时了
    store.dispatch('user/logout') // 登出action 删除token
    router.push('/login')
  } else {
    Message.error(error.message) // 提示错误信息
  }
  return Promise.reject(error)
}
Logo

前往低代码交流专区

更多推荐