1. 接口模块处理
1.1 axios二次封装
这里封装的依据是后台传的JWT,已封装好的请跳过。
import axios from 'axios' import router from '../router' import {MessageBox, Message} from 'element-ui' let loginUrl = '/login' // 根据环境切换接口地址 axios.defaults.baseURL = process.env.VUE_APP_API axios.defaults.headers = {'X-Requested-With': 'XMLHttpRequest'} axios.defaults.timeout = 60000 // 请求拦截器 axios.interceptors.request.use( config => { if (router.history.current.path !== loginUrl) { let token = window.sessionStorage.getItem('token') if (token == null) { router.replace({path: loginUrl, query: {redirect: router.currentRoute.fullPath}}) return false } else { config.headers['Authorization'] = 'JWT ' + token } } return config }, error => { Message.warning(error) return Promise.reject(error) })
紧接着的是响应拦截器(即异常处理)
axios.interceptors.response.use( response => { return response.data }, error => { if (error.response !== undefined) { switch (error.response.status) { case 400: MessageBox.alert(error.response.data) break case 401: if (window.sessionStorage.getItem('out') === null) { window.sessionStorage.setItem('out', 1) MessageBox.confirm('会话已失效! 请重新登录', '提示', {confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning'}).then(() => { router.replace({path: loginUrl, query: {redirect: router.currentRoute.fullPath}}) }).catch(action => { window.sessionStorage.clear() window.localStorage.clear() }) } break case 402: MessageBox.confirm('登陆超时 !', '提示', {confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning'}).then(() => { router.replace({path: loginUrl, query: {redirect: router.currentRoute.fullPath}}) }) break case 403: MessageBox.alert('没有权限!') break // ...忽略 default: MessageBox.alert(`连接错误${error.response.status}`) } return Promise.resolve(error.response) } return Promise.resolve(error) })
这里做的处理分别是会话已失效和登陆超时,具体的需要根据业务来作变更。
最后是导出基础请求类型封装。
export default { get (url, param) { if (param !== undefined) { Object.assign(param, {_t: (new Date()).getTime()}) } else { param = {_t: (new Date()).getTime()} } return axios({method: 'get', url, params: param}) }, // 不常更新的数据用这个 getData (url, param) { return axios({method: 'get', url, params: param}) }, post (url, param, config) { return axios.post(url, param, config) }, put: axios.put, _delete: axios.delete }
其中给get请求加上时间戳参数,避免从缓存中拿数据。 除了基础请求类型,还有很多类似下载、上传这种,需要特殊的的请求头,此时可以根据自身需求进行封装。
浏览器缓存是基于url进行缓存的,如果页面允许缓存,则在一定时间内(缓存时效时间前)再次访问相同的URL,浏览器就不会再次发送请求到服务器端,而是直接从缓存中获取指定资源。
1.2 请求按模块合并
模块的请求:import http from '@/utils/request' export default { A (param) { return http.get('/api/', param) }, B (param) { return http.post('/api/', param) } C (param) { return http.put('/api/', param) }, D (param) { return http._delete('/api/', {data: param}) }, }
utils/api/index.js:
import http from '@/utils/request' import account from './account' // 忽略... const api = Object.assign({}, http, account, \*...其它模块*\) export default api
1.3 global.js中的处理
在global.js中引入:
import Vue from 'vue' import api from './api/index' // 略... const errorHandler = (error, vm) => { console.error(vm) console.error(error) } Vue.config.errorHandler = errorHandler export default { install (Vue) { // 添加组件 // 添加过滤器 }) // 全局报错处理 Vue.prototype.$throw = (error) => errorHandler(error, this) Vue.prototype.$http = api // 其它配置 } }
写接口的时候就可以简化为:
async getData () { const params = {/*...key : value...*/} let res = await this.$http.A(params) res.code === 4000 ? (this.aSata = res.data) : this.$message.warning(res.msg) }
2. Vue组件动态注册
我们写组件的时候通常需要引入另外的组件:
<template> <BaseInput v-model="searchText" @keydown.enter="search"/> <BaseButton @click="search"> <BaseIcon name="search"/> </BaseButton> </template> <script> import BaseButton from './baseButton' import BaseIcon from './baseIcon' import BaseInput from './baseInput' export default { components: { BaseButton, BaseIcon, BaseInput } } </script>
写小项目这么引入还好,但等项目一臃肿起来...啧啧。 这里是借助webpack,使用 require.context() 方法来创建自己的模块上下文,从而实现自动动态require组件。
这个方法需要3个参数:
- 要搜索的文件夹目录
- 是否还应该搜索它的子目录
- 一个匹配文件的正则表达式。
在你放基础组件的文件夹根目录下新建componentRegister.js:
import Vue from 'vue' /** * 首字母大写 * @param str 字符串 * @example heheHaha * @return {string} HeheHaha */ function capitalizeFirstLetter (str) { return str.charAt(0).toUpperCase() + str.slice(1) } /** * 对符合'xx/xx.vue'组件格式的组件取组件名 * @param str fileName * @example abc/bcd/def/basicTable.vue * @return {string} BasicTable */ function validateFileName (str) { return /^\S+\.vue$/.test(str) && str.replace(/^\S+\/(\w+)\.vue$/, (rs, $1) => capitalizeFirstLetter($1)) } const requireComponent = require.context('./', true, /\.vue$/) // 找到组件文件夹下以.vue命名的文件,如果文件名为index,那么取组件中的name作为注册的组件名 requireComponent.keys().forEach(filePath => { const componentConfig = requireComponent(filePath) const fileName = validateFileName(filePath) const componentName = fileName.toLowerCase() === 'index' ? capitalizeFirstLetter(componentConfig.default.name) : fileName Vue.component(componentName, componentConfig.default || componentConfig) })
最后我们在main.js中
import 'components/componentRegister.js'
我们就可以随时随地使用这些基础组件,无需手动引入了。