项目创建
搭建
创建项目
vue create 项目名称
配置项
css预处理器、babel、router、vuex
n(哈希模式)
less
配置的模块,单独放一个文件夹
不保存预设
下载
axios
yarn add axios
下载
vant
vant组件库配置
具体的可以查看官方文档,地址:
安装
yarn add vant@latest-v2 -S
自动按需引入组件
yarn add babel-plugin-import -D
在 babel.config.js
中配置
module.exports = {
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
]
}
重启项目即可查看效果。
配置
本项目是由 vue2
脚手架搭建,引用了 vant@2
组件库,主要做了以下几个方面:
vuex
:vuex
通过模块化管理,在大总管index.js
文件中统一引用导出。axios
:axios
二次封装,封装请求基准路径、超时时间、请求拦截器(设置请求头等)、响应拦截器(不同状态码的相应提示)等。components
:全局子组件一个个导入到main.js
文件不仅导致代码量冗余,且不利于后续维护,因此统一导入到index.js
大总管文件统一导出引用mixins
:部分功能函数多个页面需要使用,通过mixins
可以让代码复用,减少冗余,方便维护。filter
:过滤器,统一封装,main.js
文件引入使用。
vuex
getters
为了方便使用,对部分高频使用的变量通过 getters
引用:
export default {
token: state => state.login.token,
userinfo: state => state.login.userinfo,
}
封装挂载
vuex
模块化的好处是各个模块分工明确,统一管理,利于维护。先在 index.js
文件中导入相应的配置并导出,在 main.js
文件中导入使用。
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import login from './modules/login'
Vue.use(Vuex)
export default new Vuex.Store({
getters,
modules: {
login,
}
})
main.js
import store from './store'
const vue = new Vue({
store,
render: h => h(App)
}).$mount('#app')
使用
可通过辅助函数 mapState
、mapAction
、mapGetters
等获取数据使用。
axios
封装挂载
在相应的 js
文件中引入 axios
并导出,并设置基准路径、超时时间以及请求头。其中基准路径和超时时间通过外部动态导入获取:
import axios from 'axios'
import { Toast } from 'vant'
import { BASE_URL, TIME_OUT } from '@/config'
import store from '@/store'
import router from '@/router'
// 创建实例后修改默认值
// 创建axios实例
const instance = axios.create({
baseURL: BASE_URL,
timeout: TIME_OUT,
headers: {
post: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
})
export default instance
接下来设置请求拦截器和响应拦截器。
请求拦截器
请求拦截器主要封装以下几点:
- 设置请求的请求头,如部分
post
请求的请求头为application/json
,上传文件的请求头要为multipart/form-data
等。 - 携带
token
,获取token
并根据后端接口文档传给相应字段。 - (可选)显示
loading
效果,为了用户体验感更好,每次调接口时都显示loading
效果,表示正在请求中。
/**
* 请求拦截器
* 每次请求前,如果存在token则在请求头中携带token
*/
instance.interceptors.request.use(
(config) => {
Toast.loading({
duration: 9000,
message: '加载中...',
forbidClick: true,
})
// 判断当前post请求是否是application/json
if (config.type && config.type === 'json')
config.headers['Content-Type'] = 'application/json'
if (config.url === '/zhijiao-edu/upload/save')
config.headers['Content-Type'] = 'multipart/form-data'
// 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
// 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断
const token = store.getters.token
token && (config.headers['X-Access-Token'] = token)
return config
},
(error) => {
return Promise.error(error)
}
)
响应拦截器
响应拦截器主要封装以下几点:
- 根据状态码给予页面不同的反馈。
- 200:请求成功,获取数据渲染
- 401 / 403:用户未登录或用户没有该权限,跳转到登录页面提示用户登录
- 404:接口不存在,给予提示
- 500:服务器错误,给予提示
- 关闭请求拦截器显示的
loading
效果。
// 响应拦截器
instance.interceptors.response.use(
// 请求成功
(res) => {
Toast.clear()
// 状态码为200
if (res.status === 200 && res.data.code === 200) {
return Promise.resolve(res.data)
}
else {
errorHandle(res.data.code, res.data.msg || res.data.message)
return Promise.reject(res)
}
},
// 请求失败
(error) => {
Toast.clear()
const { response } = error
if (response) {
// 请求已发出,但是不在2xx的范围
errorHandle(response.status, response.data.message)
return Promise.reject(response)
}
else {
// 处理断网的情况
// eg:请求超时或断网时,更新state的network状态
// network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏
// 关于断网组件中的刷新重新获取数据,会在断网组件中说明
if (!window.navigator.onLine) {
}
else {
return Promise.reject(error)
}
}
}
)
提示、跳转登录、状态码判断单独抽出封装为函数,更利于后期的维护!
/**
* 提示函数
* 禁止点击蒙层、显示一秒后关闭
*/
function tip(msg) {
Toast({
message: msg,
duration: 1000,
forbidClick: true
})
}
/**
* 跳转登录页
* 携带当前页面路由,以期在登录页面完成登录后返回当前页面
*/
function toLogin() {
router.replace({
path: '/login'
})
}
/**
* 请求失败后的错误统一处理
* @param {number} status 请求失败的状态码
*/
function errorHandle(status, other) {
// 状态码判断
switch (status) {
// 401: 未登录状态,跳转登录页
case 401:
toLogin()
break
// 403 token过期
// 清除token并跳转登录页
case 403:
tip('登录过期,请重新登录')
localStorage.removeItem('token')
store.commit('loginSuccess', null)
setTimeout(() => {
toLogin()
}, 1000)
break
// 404请求不存在
case 404:
tip('请求的资源不存在')
break
// 500请求失败
case 500:
tip(other)
break
default:
break
}
}
使用
接口中引入封装好的 axios
,在 index.js
大总管文件中统一导入到一个对象内,导出到 main.js
入口文件。通过 prototype
挂载到 Vue
的原型上,后续可通过 this
直接获取到。
接口模块
js
文件jsimport axios from '@/utils/request' const data = { // 轮播图 bannerApi() { return axios.get('/client/banner/list') }, } export default data
JSON 格式接口发送格式
jsconst enterprise = { xxxApi(data) { return axios.post('/xxxx/xxxx/xxxx', data, { type: 'json' }) }, }
大总管
index.js
文件jsimport data from './modules/data' export default { data, }
入口文件
jsimport api from './api' Vue.prototype.$api = api
后续可通过
this.$api.data
获取到data.js
内的请求。
components
子组件复用是项目代码优化中常见的形式,如何高效、低代码实现全局子组件引入也是必要的,在 index.js
大总管文件通过 install(Vue) {}
统一注册子组件并导出。入口文件引入注册即可。
index.js
通过Vue.component()
注册js// 在此统一注册components的组件 import UploadImg from './modules/UploadImg.vue' export default { install(Vue) { Vue.component('UploadImg', UploadImg) } }
main.js
通过Vue.use()
注册js// 注册自己的插件 import components from '@/components' Vue.use(components)
拓展——
install
:Vue有一个概念:【插件】。插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:。官方文档请移步:
。插件 https://v2.cn.vuejs.org/v2/guide/plugins.html#%E5%BC%80%E5%8F%91%E6%8F%92%E4%BB%B6插件对外暴露一个
install
的方法,该方法第一个参数是Vue
构造器,因此可通过该参数开发新的插件及全局注册组件等。
mixins
当同一种功能多个页面都需要使用时,每个页面都写一个相同的函数显然不是一个优解。把这些封装到 mixins
中,全局或局部引入该 mixins
文件,通过 mixins
实现同一种函数多页面复用,减少代码冗余和杂糅。
封装挂载
在
mixins/index.js
文件中导出方法事件、变量等(该文件用来全局注册,因此只存放使用频率高的事件方法)jsexport default { data() { return { } }, created() { }, methods: { // 本地存储 setItem(name, value) { if (typeof value === 'object') localStorage.setItem(name, JSON.stringify(value)) else localStorage.setItem(name, value) }, // 获取本地存储 getItem(name) { return localStorage.getItem(name) }, // 删除本地存储 removeItem(name) { localStorage.removeItem(name) }, // 筛选数据字典获取数据 filterDict(arr, v) { const obj = arr.find(item => item.value === v) return obj ? obj.text : v }, // 过滤时间 filterTime(times, type) { const date = new Date(times) const year = date.getFullYear() const month = date.getMonth() + 1 const day = date.getDate() const hour = date.getHours() const minute = date.getMinutes() const second = date.getSeconds() switch (type) { case 'YYYY': return `${year}` case 'YYYY-MM': return `${year}-${month >= 10 ? month : `0${month}`}` case 'YYYY-MM-DD': return `${year}-${month >= 10 ? month : `0${month}`}-${day >= 10 ? day : `0${day}`}` case 'YYYY-MM-DD hh:mm': return `${year}-${month >= 10 ? month : `0${month}`}-${day >= 10 ? day : `0${day}`} ${hour >= 10 ? hour : `0${hour}`}:${minute >= 10 ? minute : `0${minute}`}` case 'YYYY-MM-DD hh:mm:ss': return `${year}-${month >= 10 ? month : `0${month}`}-${day >= 10 ? day : `0${day}`} ${hour >= 10 ? hour : `0${hour}`}:${minute >= 10 ? minute : `0${minute}`}:${second >= 10 ? second : `0${second}`}` default: return `${year}-${month >= 10 ? month : `0${month}`}-${day >= 10 ? day : `0${day}`}` } }, } }
入口文件全局导入
js// 全局mixin import mixins from '@/mixins' Vue.mixin(mixins)
使用
本项目后台是由低代码平台 jeecg-boot
开发,有用于传参的数据字典,因此有许多页面都会用到字典翻译,在 vuex
的 getters.js
文件中获取到登录时保存的字典数组 dict
,把字典和所要翻译的变量传递过来,返回要的翻译字段即可。
<script>
import { mapActions, mapGetters } from 'vuex'
export default {
computed: {
...mapGetters([
'userinfo',
'dict',
]),
},
}
</script>
<template>
<div v-if="isRole" class="type">
{{ filterDict(dict.user_role, userinfo.role) }}
</div>
</template>
filters
官方文档可见:
封装挂载
创建 filters/index.js
文件,用于存放过滤器。
export function a(e) {
return e + 1
}
main.js
文件全局注册过滤器。
// 注册过滤器
import * as filters from '@/filters'
Object.keys(filters).forEach((key) => {
Vue.filter(key, filters[key])
})
使用
<template>
<div class="home">
<div>{{ 'tydumpling' | a }}</div> // tydumpling1
<div>{{ b() }}</div>
</div>
</template>