开发背景
来北京工作已经一年多了,大大小小的项目也写了不少,磕磕碰碰到了现在,记录一下
功能要点
- 1 公共网络请求封装
- 2 自定义dialog
- 3 base组件封装
- 4 多环境打包优化
- 5 路由按需加载,gzip开启
- 6 骨架屏功能实现
上干货
1 公共网络请求封装
这个功能其实在以前的文章中就有说到过,核心就是抽取接口地址的前缀,根据后台返回的接口内容,进行统一的出来(弹对话框,执行回调)
需要注意的地方就是多个网络时,多个遮罩层的处理方式
这个不多说,直接上代码(基于axios)
//开始加载动画效果
function startNetWork(msgText) {
//修复多接口访问无法关闭浮层BUG
if (document.body.contains(loadDialog)) {
document.body.removeChild(loadDialog);
}
loadDialog = document.createElement('div');
loadDialog.className = 'popContainer';
let loadImage = document.createElement('img');
loadImage.src = 'static/img/load.svg';
let loadMsg = document.createElement('label');
loadMsg.innerText = msgText ? msgText : '加载中...';
loadDialog.appendChild(loadImage);
loadDialog.appendChild(loadMsg);
document.body.appendChild(loadDialog)
}
//关闭加载动画效果
function stopNetWork() {
//修复多接口访问无法关闭浮层BUG
if (document.body.contains(loadDialog)) {
document.body.removeChild(loadDialog);
}
}
// get网络请求
function get(networkParam, data, vueObj, successCallback, errorCallback) {
let realUrl = preUrl + networkParam.url;
if (networkParam.showLoading) {
startNetWork();
}
let token = getUserData('token');
if (!token) {
// 跳转回登录页
vueObj.$router.push({path: '/'})
}
axios.get(realUrl, {
params: data,
headers: {
token: token
}
}).then(function (response) {
stopNetWork();
console.log(response);
successCallback(vueObj, response.data)
}).catch(function (response) {
console.log(response, 'get axios');
stopNetWork();
errorCallback(vueObj, response);
})
}
2 自定义Toast
这个功能的原理跟网络请求时的动画比较相似,通过js动态生成div,拼接到本页body标签下,然后通过定时器移除div,达到Toast的效果
let info = {
background: 'black',
color: 'white',
width: '80%',
content: '输出内容',
borderRadius: '10px',
duration: 2000
};
// toast相关配置见上方
function toast(toastInfo) {
let duration = 800;
let m = document.createElement('div');
let color = toastInfo.color ? toastInfo.color : info.color;
let width = toastInfo.width ? toastInfo.width : info.width;
let background = toastInfo.background ? toastInfo.background : info.background;
let borderRadius = toastInfo.borderRadius ? toastInfo.borderRadius : info.borderRadius;
m.innerHTML = toastInfo.content ? toastInfo.content : info.content;
m.style.cssText = "width: " + width + ";min-width: 150px;opacity: 1;height: 30px;color: " +
color + ";line-height: 30px;text-align: center;border-radius: " + borderRadius + ";position: fixed;top: 40%;left: 10%;z-index: 999999;background: " + background + ";font-size: 16px;";
document.body.appendChild(m);
setTimeout(function () {
let d = 0.5;
m.style.webkitTransition = '-webkit-transform ' + d + 's ease-in, opacity ' + d + 's ease-in';
m.style.opacity = '0';
setTimeout(function () {
document.body.removeChild(m)
}, d * 1000);
}, duration);
}
3 base组件封装
这个基vue存在的目的是为了解耦,项目中所有的页面文件不直接操作封装好的网络请求,等一些常用的util,而且通过这个基vue间接的操作,base起到了一个桥梁的作用,使得代码更加的规范
export default {
name: "base",
data() {
return {
toastInfo: {
background: "red",
color: "white",
width: "80%",
content: "ce",
borderRadius: "20px",
duration: 500
}
};
},
methods: {
// 设置遮罩层
setShade(msgText) {
showShade(msgText);
},
testLoading() {
startNetWork();
},
// 设置对话框背景颜色
setToastBackGroud(color) {
this.toastInfo.background = color;
},
// 设置对话框边框圆角 radiusPx 单位px
setToastBorderRadius(radiusPx) {
this.toastInfo.borderRadius = radiusPx;
},
// 弹出对话框 conten 弹出内容
showToast(content) {
this.toastInfo.content = content;
toast(this.toastInfo);
},
//带动画效果的GET请求
get(params, data, vueObj, successCallback, errorCallback) {
let networkParam = {
url: "",
showLoading: true
};
if (typeof params == "string") {
networkParam.url = params;
} else {
networkParam = params;
}
get(
networkParam,
data,
vueObj,
function(_this, data) {
if (data.status == -1) {
localStorage.clear();
_this.muiTost(data.msg);
_this.$router.push({
path: "/"
});
}
if (data.data != undefined && data.data != null) {
successCallback(_this, data);
} else {
_this.showToast("未知错误");
}
},
function(_this, response) {
console.log("errorCallback");
if (response.response) {
errorCallback(_this, response.response.data);
return;
}
_this.showToast("未知错误");
}
);
},
//带动画效果的POST请求
post(params, data, vueObj, successCallback, errorCallback) {
let networkParam = {
url: "",
showLoading: true
};
if (typeof params == "string") {
networkParam.url = params;
} else {
networkParam = params;
}
post(
networkParam,
data,
vueObj,
function(_this, data) {
if (data.status == undefined || data.status == null) {
_this.showToast("未知错误");
} else if (data.status == 0 && successCallback != undefined) {
successCallback(_this, data);
} else if (data.status == -1) {
localStorage.clear();
_this.muiTost(data.msg);
_this.$router.push({
path: "/"
});
} else {
_this.showToast(data.msg);
}
},
function(_this, response) {
console.log("errorCallback");
if (response.response) {
errorCallback(_this, response.response.data);
return;
}
_this.showToast("未知错误");
}
);
}
}
};
//其他vue通过extends来继承base
import base from '../page/base';
export default {
extends: base,
name: "Login"
}
4 多环境打包优化
由于公司上线流程比较规范,有将项目部署到多个环境的需求,所以将抽象出一个build.js文件来设置环境名。
// test test环境
// dev dev环境
// pre 预发布环境
// prod 生产环境
const buildTip = 'test';
module.exports.buildTip = buildTip;//将变量抛出,在路由文件以及打包文件等引入
其中路由文件以及打包文件中的相关配置是需要同步的
router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import {buildTip} from "../util/build";
//适配多种环境打包
let base;
switch (buildTip) {
case "test":
base = '/demo/';
break;
case "dev":
base = '/demo/';
break;
case "pre":
base = '/demo/';
break;
case "prod":
base = '/demo/';
break;
}
Vue.use(Router);
export default new Router({
base: base,//将变量设置给base
mode: 'history',
routes: [
]
})
config/index.js
const build = require("../src/util/build");//引入build.js中的环境名称
let indexDirName, assetsRoot, assetsPublicPath;
//适配多版本打包发布
switch (build.buildTip) {
case "test":
indexDirName = '../demo/index.html';
assetsRoot = '../demo/';
assetsPublicPath = '/demo/';
break;
case "dev":
indexDirName = '../demo/index.html';
assetsRoot = '../demo/';
assetsPublicPath = '/demo/';
break;
case "pre":
indexDirName = '../demo/index.html';
assetsRoot = '../demo/';
assetsPublicPath = '/demo/';
break;
case "prod":
indexDirName = '../demo/index.html';
assetsRoot = '../demo/';
assetsPublicPath = '/demo/';
break;
}
build: {
// Template for index.html
index: path.resolve(__dirname, indexDirName), //对应以上的三个变量
// Paths
assetsRoot: path.resolve(__dirname, assetsRoot),//对应以上的三个变量
assetsSubDirectory: 'static',
assetsPublicPath: assetsPublicPath,//对应以上的三个变量
/**
* Source Maps
*/
productionSourceMap: false,
// https://webpack.js.org/configuration/devtool/#production
devtool: '#source-map',
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: true,//同时需要在nginx服务器做出gzip配置
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
}
5 路由按需加载,gzip开启
这个方面的作用是为了提高页面访问速度,属于性能优化
routes: [
{
path: '/',
name: 'home',
component: function (resolve) {
require(['../page/home'], resolve)//路由按需加载
}
}
, {
path: '/scroller',
name: 'scroller',
component: function (resolve) {
require(['../page/scroller'], resolve)
}
}
]
至于gzip,起初以为前端配置就可以了,到最后才发现还需要服务端nginx的配合
productionGzip: true,//将false改为true,同时需要在nginx服务器做出gzip配置
productionGzipExtensions: ['js', 'css'],
nginx配置
gzip on; #开启gzip
gzip_min_length 1k; #低于1kb的资源不压缩
gzip_comp_level 5; #压缩级别【1-9】,越大压缩率越高,同时消耗cpu资源也越多,建议设置在4左右。
gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;
gzip_disable "MSIE [1-6]\."; #配置禁用gzip条件,支持正则。此处表示ie6及以下不启用gzip(因为ie低版本不支持)
gzip_vary on; #是否添加“Vary: Accept-Encoding”响应头
6 骨架屏功能实现
骨架屏,在用户体验方面的作用比较大,实现起来并不难,自定义组件+v-if就可以实现该功能了
具体实现如下
<template>
<div class="container login column-center">
<Skeleton v-if="!init"></Skeleton>
<button @click="changeToastBackground">将对话框颜色改为红色</button>
<button @click="toast">测试Toast对话框</button>
<button @click="testGet">测试Get请求</button>
<button @click="testPost">测试Post请求</button>
<button @click="testLoading">测试Loading</button>
<button @click="testShade">测试遮罩</button>
</div>
</template>
<script>
import base from '../page/base';
import Skeleton from "../personView/Skeleton"; //引入组件
export default {
extends: base,
name: "Login",
components: {
Skeleton
},
data() {
return {
init: false//设置相关变量
}
},
mounted() {
let that = this;
//模拟请求操作
setTimeout(function () {
that.init = true;
}, 2000)
},
methods: {
toast() {
this.showToast('修改成功');
},
testShade() {
this.setShade('测试')
},
testGet() {
this.get('User/detail', {}, this, function (that, data) {
console.log(data)
},
function (that, data) {
console.log(data);
if (!isNaN(data.responseCode)) {
that.showToast(data.responseMessage)
}
}
)
},
testPost() {
this.post('User/detail', {test: '1231'}, this, function (that, data) {
}, function (that, data) {
if (!isNaN(data.responseCode)) {
that.showToast(data.responseMessage)
}
})
},
changeToastBackground() {
this.setToastBackGroud('red')
}
}
}
</script>
<style scoped>
.login button {
border: 0;
padding: 0;
margin-top: 10px;
height: 40px;
width: 80%;
background-color: black;
color: white;
border-radius: 20px;
}
</style>
最后
附上封装的demo地址,其中还有自己实现的上拉加载搜索功能,需要的可以下载来看看
下载地址
网友评论