注:华润APP不能使用新框架开发,目前在原基础上优化,不使用新的框架推倒重来。
华润APP开发时间比较早,当时的技术选型也比较老,引用很多第三方包,导致整体项目过重,做到后面发现,页面文件,样式文件,js文件越来越多,且命名不规范,导致版本迭代过程中,改了一个小功能,其他地方也需要跟着改,代码不易维护,每一个页面引用的静态文件基本一致,从a.html 跳转到b.html是整个页面刷新,需要重新加载静态资源,每一次跳转都会创建新的vue实例,造成资源浪费,影响页面渲染速度,页面之间的跳转动画不好开发,消息提示不统一,页面没有做到屏幕适配,请求嵌套过多等,导致后面的问题接踵而至。
由于APP端不能更换现有框架,现在南宁项目启动,我以一南宁项目为优化试点,采用一个大模块一个页面,大模块之间跳转采用Mpa模式,每个大模块页面中的功能使用模板的形式,静态资源只加载一次,以路由的方式跳转,增加路由切换动画,列表过渡效果,每一步点击(点击按钮/列表点击/Tab切换)增加水波纹点击效果,提示信息统一从顶部弹出,将项目中的ajax请求方式替换为promise+axios的形式,使用这个形式主要解决请求多层嵌套,合并发送请求的问题。
优化过程中,删除用不到的第三方库,图片,css,js等静态资源,优化APP工程目录,压缩css以及JS文件,减少包的体积,提升用户体验。
登录,首页,南宁报修,南宁货物放行,日常巡检
一个功能一个页面,如add.html、edit.html、list.html,一个完整的功能模块有6到7个页面左右
每一个大模块是一个HTML界面,这个HTML界面里包含该模块下的所有功能,整个项目的页面数量,与大的模块数量基本一致,大模块之间跳转,例如:点击登录跳转到主页,使用Mpa模式,模块中的功能,例如:点击报修列表跳转到详情,使用Spa模式。
注意:根据设计稿实际情况调整算法。
1、scss文件添加中添加如下代码
@function px2rem($px) {
$rem: 37.5px;
@return ($px/ $rem) + rem;
}
2、全局的JS中添加如下代码
window.onload = function() {
//获取屏幕宽度
let htmlwidth = document.documentElement.clientWidth || document.body.clientWidth;
// 获取html的dom
let htmldom = document.getElementsByTagName('html')[0];
//设置html的font-size
htmldom.style.fontSize = htmlwidth / 10 + 'px';
window.addEventListener('resize', e => {
let htmlwidth = document.documentElement.clientWidth || document.body.clientWidth;
htmldom.style.fontSize = htmlwidth / 10 + 'px';
});
};
注:动画效果可自行更改,下面只是示例说明。
1、css代码示例如下
/* 点击列表或按钮水波纹效果 */
.ripple {
position: relative;
//隐藏溢出的径向渐变背景
overflow: hidden;
}
.ripple:after {
content: '';
display: block;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
pointer-events: none;
//设置径向渐变
background-image: radial-gradient(circle, #666 10%, transparent 10.01%);
background-repeat: no-repeat;
background-position: 50%;
transform: scale(10, 10);
opacity: 0;
transition: transform 0.3s, opacity 0.5s;
}
.ripple:active:after {
transform: scale(0, 0);
opacity: 0.3;
//设置初始状态
transition: 0s;
}
2、页面中在对应的标签上添加class
<button type="button" class="ripple" @click="searchParam">搜索</button>
1、css代码示例如下
/* 路由跳转动画 */
.slide-right-enter-active,
.slide-right-leave-active,
.slide-left-enter-active,
.slide-left-leave-active {
will-change: transform;
transition: all 250ms;
position: absolute;
}
.slide-right-enter {
opacity: 0;
transform: translate3d(-100%, 0, 0);
}
.slide-right-leave-active {
opacity: 0;
transform: translate3d(100%, 0, 0);
}
.slide-left-enter {
opacity: 0;
transform: translate3d(100%, 0, 0);
}
.slide-left-leave-active {
opacity: 0;
transform: translate3d(-100%, 0, 0);
}
2、js代码如下
$route(to, from) { if (to.meta.keepAlive == true) { if (from.name === "workorderlist") { this.transitionName = 'slide-left'; } else { this.transitionName = 'slide-right'; } } //如果to索引大于from索引,判断为前进状态,反之则为后退状态 console.log(to.meta.index > from.meta.index) if (to.meta.index > from.meta.index) { //设置动画名称 this.transitionName = 'slide-left'; } else { this.transitionName = 'slide-right'; }
}
css代码如下
body {
background: #f2f2f2;
padding-bottom: env(safe-area-inset-bottom);
-webkit-user-select: none;
/* 禁止选中文本(如无文本选中需求,此为必选项) */
user-select: none;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', STHeiti, Tahoma, Simsun, sans-serif, Roboto, Lato !important;
}
@supports (bottom: env(safe-area-inset-bottom)) {
.menu_wkBox.back_box {
padding-bottom: env(safe-area-inset-bottom);
}
}
属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| offset | Number | 80 | 默认高度 |
代码如下
Vue.component("base-scroll", { template: `<div class="yo-scroll"> <section class="inner" :style="{ transform: 'translate3d(0, ' + top + 'px, 0)'}"> <slot> </slot> </section> </div>`, props: { offset: { type: Number, default: 80 //默认高度 } }, data() { return { top: 0, state: 0, startX: 0, startY: 0, touching: false, infiniteLoading: false, downFlag: false, //用来显示是否加载中 } } })
属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| offset | Number | 80 | 默认高度 |
| enableInfinite | Boolean | true | 是否启用上拉加载 |
| enableRefresh | Boolean | true | 是否启用下拉刷新 |
| dataList | Boolean | false | 滑到底部是否显示“暂无更多数据” |
| onRefresh | Boolean | true | 下拉刷新 |
| onInfinite | Boolean | true | 上拉加载 |
方法
| 方法名 | 方法描述 |
|---|---|
| touchStart | 触屏开始 |
| touchMove | 触屏过程中 |
| touchEnd | 触屏结束 |
| refresh | 下拉刷新 |
| refreshDone | 下拉刷新结束 |
| infinite | 上拉加载 |
| infiniteDone | 上拉加载结束 |
代码如下
/* 上拉加载下拉刷新 */ Vue.component("v-scroll", { template: `<div class="yo-scroll" :class="{'down':(state===0),'up':(state==1),refresh:(state===2),touch:touching}" @touchstart="touchStart($event)" @touchmove="touchMove($event)" @touchend="touchEnd($event)"> <section class="inner" :style="{ transform: 'translate3d(0, ' + top + 'px, 0)' }"> <div class="pull-refresh"> <slot name="pull-refresh"> <span class="down-tip">下拉更新</span> <span class="up-tip">松开刷新数据</span> <span class="refresh-tip">加载中……</span> </slot> </div> <slot> </slot> <footer class="load-more"> <slot name="load-more"> <span v-show="downFlag === false">上拉加载更多</span> <span v-show="downFlag === true">加载中……</span> </slot> </footer> <div class="nullData" v-show="dataList.noFlag">暂无更多数据</div> </section> </div>`, props: { offset: { type: Number, default: 80 //默认高度 }, enableInfinite: { type: Boolean, default: true }, enableRefresh: { type: Boolean, default: true }, dataList: { default: false, required: false }, onRefresh: { type: Function, default: undefined, required: false }, onInfinite: { type: Function, default: undefined, require: false } }, data() { return { top: 0, state: 0, startX: 0, startY: 0, touching: false, infiniteLoading: false, downFlag: false, //用来显示是否加载中 } }, methods: { touchStart(e) { this.startY = e.targetTouches[0].pageY; this.startX = e.targetTouches[0].pageX; this.startScroll = this.$el.scrollTop || 0; this.touching = true; //留着有用,不能删除 this.dataList.noFlag = false; this.$el.querySelector('.load-more').style.display = 'block'; }, touchMove(e) { if (!this.enableRefresh || this.dataList.noFlag || !this.touching) { return } let diff = e.targetTouches[0].pageY - this.startY - this.startScroll if (diff > 0) e.preventDefault() this.top = Math.pow(diff, 0.8) + (this.state === 2 ? this.offset : 0) if (this.state === 2) { // in refreshing return } if (this.top >= this.offset) { this.state = 1 } else { this.state = 0 } let more = this.$el.querySelector('.load-more'); if (!this.top && this.state === 0) { more.style.display = 'block'; } else { more.style.display = 'none'; } }, touchEnd(e) { //alert(JSON.stringify(e)) if (!this.enableRefresh) { return } this.touching = false if (this.state === 2) { // in refreshing this.state = 2 this.top = this.offset return } if (this.top >= this.offset) { // do refresh this.refresh() } else { // cancel refresh this.state = 0 this.top = 0 } //用于判断滑动是否在原地 ----begin let endX = e.changedTouches[0].pageX, endY = e.changedTouches[0].pageY, dy = this.startY - endY, dx = endX - this.startX; //如果滑动距离太短 if (Math.abs(dx) < 2 && Math.abs(dy) < 2) { this.$el.querySelector('.load-more').style.display = 'none'; this.downFlag = false; return; } //--------end-------- let outerHeight = this.$el.clientHeight, innerHeight = this.$el.querySelector('.inner').clientHeight, scrollTop = this.$el.scrollTop, ptrHeight = this.onRefresh ? this.$el.querySelector('.pull-refresh').clientHeight : 0, bottom = innerHeight - outerHeight - scrollTop - ptrHeight; if (bottom <= this.offset && this.state === 0) { this.downFlag = true; this.infinite(); } else { this.$el.querySelector('.load-more').style.display = 'none'; this.downFlag = false; } }, refresh() { this.state = 2; this.top = this.offset; setTimeout(() => { this.onRefresh(this.refreshDone) }, 1000); }, refreshDone() { this.state = 0 this.top = 0 }, infinite() { this.infiniteLoading = true setTimeout(() => { this.onInfinite(this.infiniteDone); }, 2000); }, infiniteDone() { this.infiniteLoading = false } } })
属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| display | Boolean | / | 是否打开 |
| title | String | 标题 | 标题 |
| closable | Boolean | true | 是否显示关闭按钮 |
| mask | Boolean | true | 是否显示遮罩 |
| maskClosable | Boolean | true | 是否点击遮罩关闭 |
| width | String | 400px | 宽度 |
| inner | Boolean | false | 是否在父级元素中打开 |
方法
| 方法名 | 说明 |
|---|---|
| closeByMask | 关闭遮罩 |
| closeByButton | 点击右上角关闭 |
| maskClass | 控制遮罩显示隐藏 |
| mainClass | 控制侧边栏显示隐藏 |
| mainStyle | 控制侧边栏样式 |
代码如下
/* 侧边栏组件 */ Vue.component("drawer", { template: `<div class="drawer"> <div :class="maskClass" @click="closeByMask"></div> <div :class="mainClass" :style="mainStyle" class="main"> <div class="drawer-head"> <span>{{ title }}</span> <span class="close-btn" v-show="closable" @click="closeByButton">X</span> </div> <div class="drawer-body"> <slot/> </div> </div> </div>`, props: { // 是否打开 display: { type: Boolean }, // 标题 title: { type: String, default: '标题' }, // 是否显示关闭按钮 closable: { type: Boolean, default: true }, // 是否显示遮罩 mask: { type: Boolean, default: true }, // 是否点击遮罩关闭 maskClosable: { type: Boolean, default: true }, // 宽度 width: { type: String, default: '400px' }, // 是否在父级元素中打开 inner: { type: Boolean, default: false } }, computed: { maskClass: function() { return { 'mask-show': (this.mask && this.display), 'mask-hide': !(this.mask && this.display), 'inner': this.inner } }, mainClass: function() { return { 'main-show': this.display, 'main-hide': !this.display, 'inner': this.inner } }, mainStyle: function() { return { width: this.width, right: this.display ? '0' : `-${this.width}`, borderLeft: this.mask ? 'none' : '1px solid #eee' } } }, mounted() { if (this.inner) { let box = this.$el.parentNode box.style.position = 'relative' } }, methods: { closeByMask() { this.maskClosable && this.$emit('update:display', false) }, closeByButton() { this.$emit('update:display', false) } } })