216  
查询码:00000797
华润App近期优化内容如下(一)
作者: 朱凡 于 2020年09月11日 发布在分类 / FM组 / FM_App 下,并于 2020年09月11日 编辑

注:华润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模式。

粘贴图片

二、使用scss编写样式代码

以750设计稿为准,页面适配不同分辨率。

注意:根据设计稿实际情况调整算法。

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';
	});
};


使用css3动画,水波纹效果。

注:动画效果可自行更改,下面只是示例说明。

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);
	}
}


三、使用muse-ui-toast,统一提示方式,从顶部弹出提示

粘贴图片

四、开发组件

普通滚动条组件:base-scroll

属性

属性名 类型 默认值 说明
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
		}
	}
})

侧边栏组件:drawer。

属性

属性名 类型 默认值 说明
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)
		}
	}
})



 推荐知识

 历史版本

修改日期 修改人 备注
2020-09-11 15:10:20[当前版本] 朱凡 创建版本

 附件

附件类型

JPGJPG PNGPNG

  目录
    知识分享平台 -V 4.8.7 -wcp