Vue和Vue-Element-Admin(十一):axios,mockjs和vuex
axios,mockjs和vuex在开发/生产环境基本是耦合的,axios完成请求发送/接收及封装,mockjs进行数据的拦截/模拟,vuex进行本地数据存储及全局变量维护。
以登录场景为例,登录表单el-form标签绑定form.item数据,el-input分别对应item.username和item.passwd,点击登录的button触发login()方法,入参即item数据,login()方法使用被axios统一封装的request(增加baseurl,请求头等)发起http请求,mockjs拦截请求返回模拟数据,axios拿到返回后封装respond(增加或修改相应code,处理权限等),vuex存储模拟的token,其他components比如router可以直接引用token来判断是否有权限跳转。
实现如下的居中登录输入框,登录触发login请求,图中/api/menu/getMenu的url就是login方法
form表单对应的username和password接收参数:
<template> <el-form :model="form" status-icon :rules="rules" ref="form" label-width="100px" class="login-container" > <h3 class="login_title">系统登陆</h3> <el-form-item label="用户名" label-width="80px" prop="username" class="username" > <el-input type="input" v-model="form.username" auto-complete="off" placeholder="请输入账号" ></el-input> </el-form-item> <el-form-item label="密码" label-width="80px" prop="password"> <el-input type="password" v-model="form.password" auto-complete="off" placeholder="请输入密码" ></el-input> </el-form-item> <el-form-item class="login_submit"> <el-button type="primary" @click="login" class="login_submit" >登陆</el-button > </el-form-item> </el-form></template><script>// import Mock from 'mockjs'import { getMenu } from '../../api/menuRequest'export default { name: 'login', data() { return { form: {}, rules: { username: [ { required: true, message: '请输入用户名', trigger: 'blur' }, { min: 3, message: '用户名长度不能小于3位', trigger: 'blur' } ], password: [ { required: true, message: '请输入密码', trigger: 'blur' } ] } } }, methods: { login() { getMenu(this.form).then(({ data: res }) => { console.log(res, 'res') if (res.code == 20000) { this.$store.commit('clearMenu') this.$store.commit('setMenu', res.menu) this.$store.commit('setToken', res.token) this.$store.commit('addMenu', this.$router) this.$router.push({ name: 'main' }) } else { this.$message.warning(res.data.message) } }) } }}</script><style lang="less" scoped>.login-container { border-radius: 15px; background-clip: padding-box; //margin: 180px auto; position: absolute; width: 350px; padding: 35px 35px 15px 35px; background-color: #fff; border: 1px solid #eaeaea; box-shadow: 0 0 25px #cac6c6; left: 50%; top: 50%; transform: translate(-60%, -60%);}.login_title { margin: 0px auto 40px auto; text-align: center; color: #505458;}.login_submit { margin: 10px auto 0 auto;}</style>
axios
引入axios
main.js中
import http from 'axios'Vue.prototype.$http = http
封装request和response
axios中文文档
使用axios.create([config])自定义配置新建一个 axios 实例service ,拦截器在文档中这样描述:
// 添加请求拦截器axios.interceptors.request.use(function (config) { // 在发送请求之前做些什么 return config; }, function (error) { // 对请求错误做些什么 return Promise.reject(error); });// 添加响应拦截器axios.interceptors.response.use(function (response) { // 对响应数据做点什么 return response; }, function (error) { // 对响应错误做点什么 return Promise.reject(error); });
这里发起请求时候添加messagebox和store判断是否有token,没有提醒,拿到响应后如果是20000那么说明响应ok,但是如果是50008和其他code,说明token过期了,你要重新登录。
import axios from 'axios'import { MessageBox, Message } from 'element-ui'import store from '../store'const service = axios.create({ baseURL: '', timeout: 5000 // request timeout})service.interceptors.request.use(config => { // do something before request is sent if (store.getters.token) { config.headers = { URL: this.baseURL } } return config})// response interceptorservice.interceptors.response.use( response => { const res = response.data if (res.code !== 20000) { Message({ message: res.message || 'Error', type: 'error', duration: 5 * 1000 }) // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired; if (res.code === 50008 || res.code === 50012 || res.code === 50014) { 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
定义login接口
有统一axios模块,只需要实例化request就是api接口,那么这个request就有url,method,data三个参数,其中data和param分别是body和路径传参。
import request from './request'export function getMenu(param) { return request({ url: '/api/menu/getMenu', method: 'post', data: param })}
mockjs
mockjs,mockjs是如何自动拦截请求的,答案是url路径,路径指的是baseurl+request.url拼接后的全路径,普通的vue项目在根目录的config目录中有dev.env.js和prod.env.js,dev增加MOCK为ture:
'use strict'const merge = require('webpack-merge')const prodEnv = require('./prod.env')module.exports = merge(prodEnv, { NODE_ENV: '"development"', MOCK: true, BASE_URL: 'http://127.0.0.1:9999/'})
在main.js中增加动态判断引入:
// 根据config下的配置文件类型,判断是否使用mock数据process.env.MOCK && require('../src/mock/index')
/src/mock/index.js文件如下:
import Mock from 'mockjs'import menuApi from './mockData/menuApi'Mock.mock('/api/menu/getMenu', menuApi.getMenu)// mock拦截按照完整路径匹配,这里正则表达式拦截带参数的get请求
vuex
vuex就是用来全局引用的,但是main.js中不需要挂载,直接在store目录先新建index.js,如下把同级modules中的所有需要存储到vuex中都引入:
import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)const modulesFiles = require.context('./modules', true, /\.js$/)const modules = modulesFiles.keys().reduce((modules, modulePath) => { // set './app.js' => 'app' const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1') const value = modulesFiles(modulePath) modules[moduleName] = value.default return modules}, {})const store = new Vuex.Store({ modules})export default store
每个需要维护的一类状态包括state和mutation,mutation被调用来修改state:
import Cookies from 'js-cookie'export default { state: { token: '' }, mutations: { setToken(state, val) { state.token = val Cookies.set('token', val) }, clearToken(state) { state.token = '' Cookies.remove('token') }, getToken(state) { state.token = state.token || Cookies.get('token') } }}