每年都会有大型的 H5 项目上线,这一些项目的逻辑在一样平常的情形下,它们的差别不会很大,但是每一次都会有不同的样式、条件和玩法。
如果每一次大活动都是写去世逻辑且不可复用,下一次 H5 项目过来又再写一次实在便是很没有必要的事情。

如果能把这一些做过的组件做成通用可配置的,即插即用。
那么肯定是会极大的提高开拓效率,同时项目的稳定性也有担保。
只不过组件的代码逻辑就会比较繁芜,开拓难度会比较高,就单单组件内的一个按钮就须要考虑到这个按钮的颜色,大小,按钮内的字体各种样式和背景色以及这按钮是不是设计状态变革,若有还要考虑这一个状态变革的逻辑或者是一些联动的可能。
以是,一个通用组件须要考虑和实现的逻辑就很多。

举个例子,比如今年的 H5 有一个 “我的奖品” 模块 ( 页面 or 弹窗 ),这个模块里面有的奖品的信息展示、韶光的展示、数量的展示以及底部还有一些其他的按钮。
以下是我截取两次不同的活动的“我的奖品”列表展示模块。

html5数字变化效果 HTML5  年夜型 H5 项目标组件化开辟思虑与总结 Webpack

这两种样式的组件,大致的框架上都是一样的,点击侧边栏的 “我的褒奖” 和 “我的背包”,只是展现形式和展示数据类型以及按钮的点击事宜是不一样的。

以是,如果第三次 H5 ,或者往后的 H5 都写一遍这样的东西意义不大。
以是,这样利用频率比较高的模块,就必须和业务方谈论。
我们可以把这样的模块定出一个基本的交互和原型,统一做成一个通用列表展示组件,这个组件必须支持通用样式的展示,也须要支持分外的样式展示,例如下面的情形

最左边的也便是正常的列表样式,右边的便是一些分外的卡片和笔墨样式,以是,一个组件须要考虑的东西有很多。

这里叫列表展示组件而不叫“我的奖品组件”的缘故原由是:我们只须要通过参数掌握它须要展示什么的内容、标题是什么、按钮名称是什么、点击之后的逻辑是什么,而不是只局限于我的奖品列表,它也可以用于其他数据的展示。
同时,这样的通用组件可以适用于各种 H5 。
组件拿来即用或者用于一些 H5 自动天生的平台,只要根据文档传参数就可以了。

通过 config 掌握详细的展示名字还有按钮id的标记区分事宜,data 初始化组件的列表,与业务区分开来这里就变成了一个很纯粹的列表展示组件,可以展示任意的数据,只要按照格式传参就行。
这里只是写一个很大略的 DEMO ,后面会提到入参和函数绑定。

方案公共组件

拿到设计稿之后找出通用的模块,再根据类似模块之间的差异定出一个通用的规则。
下面是本次大型 H5 的设计稿总览:

第一大类:分组赛,资格赛,弹窗,规则和投票等

第二大类:冲刺赛,总决赛,PK模块等

这次大型 H5 的分为了几个大阶段,分组赛,资格赛,冲刺赛和总决赛。
看上去是非常多内容的,以是须要找出相似的模块,再和业务侧沟通从这几个赛段来看,可以抽离成组件的是

TAB 切页组件进度条组件列表记录组件韶光轴组件排行榜组件倒计时组件投票组件侧边栏悬浮组件pk 模块组件我的信息展示组件顶部组件吸底组件按钮组件主播展示组件

这里就拿一部分的组件描述一下实现思路,全部写的话就太多了,而且有些地方逻辑实现上也是比较像的。

组件的通用参数组件传入参数

一个通用组件所须要的配置参数一样平常归纳为几种,最主要的是这个组件的所有须要利用的值,也便是这个初始化参数。
其次,是这个组件的一些样式配置或者是全局参数赞助利用,还有一些情形须要特定的属于这个组件的 key 。
当然,不是说样式和全局参等等是不主要的参数,而是根据业务的需求来定,可能样式的参数才是重点这个也是可能的,详细的还得从业务或者这个组件本身的性子考虑,只是在做组件的时候优先考虑功能的实现。
以下是我封装通用组件的一个习气,分别绑定的参数是 data, styleForm, commonStyle, global, componentKey。
以下是一个组件绑定参数和方法的例子:

<template> <head-section :data="headData" :global="global" :styleForm="headConfig" :commonStyle="headCommonStyle" :componentKey="headComponentKey" @methods="headMethods" /></template><script>export default {data(){return {// 全局配置global: {},// 顶部组件配置headData: {},headConfig: {},headCommonStyle: {},headComponentKey: {},}}}</script>data 组件初始化参数

data 是这个组件传入的初始化参数或者是渲染组件的所有数据,类型是 Object 。
组件可以用初始化参数通过 ajax 获取数据也可以通过 class 实行初始化逻辑或者是直接将数据绑定在这个 data 中。

<script>// 组件export default {props: { data: { type: Object, default: () => ({ // list:[], example // total: 10 example }), },}</script>styleForm 组件的配置参数

styleForm 这个是组件的配置信息,比如这个组件的一些背景、样式信息以及一些固定的数据不会发生变革的数据。
数据格式类型是 Object 。

<script>// 组件export default {props: { styleForm: { type: Object, default: () => ({ // styles: {}, example // bg: &#39;./images/xx.png' example }), },}</script>commonStyle 通用样式配置参数

commonForm 这个参数是通用的样式配置,比如,掌握这个组件的宽、高以及背景色等等。
这个我们在自己在独立开拓的 H5 的时候,会按照这样的参数格式配置。
目的是让组件更加通用,适用于不同的地方,比如一些 H5 的自动天生平台。
由于,在互联网大厂里 H5 的开拓如果是比较大略的页面,是不会单独用人力去开拓的,而是通过平台配置天生 H5。
我们须要做的便是供应各种各样的组件,让业务同学去配置利用。
以是,平台的配置因此每个功能模块划分,commonForm 可以接入他们的平台的接口数值,直接在平台上掌握这个组件的宽、高、是否居中等等的根本样式。

<script>// 组件export default {props: { commonStyle: { type: Object, default: () => ({ // width: 300, example // height: 20 example }), },}</script>global 全局属性

全局属性指的是这个项目的唯一标记,适用于项目中的任意一个地方。
比如说这个项目的 id ,他可能在做上报操作或者在要求接口的时候须要带上这个参数。
那么就由 global 这个参数统一吸收。
类型也是 Object 。

<script>// 组件export default {props: { global: { type: Object, default: () => ({ // page_id: 111, example }), },}</script>componentKey

componentKey 是组件的标记,紧张用于在做区分组件的时候,利用比如上报数据。
同时也可以用于一些非常特定的逻辑,供应临时的办理方法。
举个非常大略例子:业务方须要画 10 个圆且背景都是白色,溘然间提出要在第 9 个圆中某个位置加上一个玄色的点,其他不变。
这样既不合理也不通用还砍不掉的需求,临时的办理方法便是通过 key 写一个 if else ,之后再说。

<script>// 组件export default {props: { componentKey: { type: [String,Number], default: 1, // example },}</script>@methods 方法绑定

在组件内通过输出按钮 id 或者事宜类型,由上一层组件进行实行特定逻辑,这样的好处是通用的样式和 DOM 与 JavaScript 分离,不含有业务逻辑一下次也可以直接复用这个组件,不须要再去改。

组件

<template> <div class="head-section" style="padding: 0px 0px"> <div class="lottery-btn" @click="onClickBtn('lottery', 'normal')" ></div> <div class="nav-btn rule-btn" @click="onClickBtn('rule', 'page')" ></div> </div></template><script>export default { methods: { onClickBtn(id, type = 'page', eventParams = {}) { this.$emit('methods',{ id: type, value: eventParams }); }, },}</script>

父级组件

<script>export default { methods: { headMethods($Event) { const { id, value } = $Event; const page = (params) => { this.goPage(); }; const anchor = (params) => { this.goAnchor(); }; const clickEventMap = { 'page': page, 'anchor': anchor, }; // 区分不同事宜类型 传参 clickEventMap[id](value); }, goAnchor(params) { // ... }, goPage(params) { // ... } },}</script>页面顶部组件组件布局和实现:

组件封装思路

首先从功能上看,这个组件只能适用于独立开拓的 H5 ,它不适宜 H5 天生平台。
或者说这样的组件在 H5 天生平台完备没故意义。
由于,左边的 icon 和右边按钮列表,他们在 H5 天生平台里面,这些按钮都是靠利用者自己手动配置的地方。
例如: 左边的 icon 便是利用一个按钮组件拖拽进去,再加上一个跳转事宜。
右边的 btn-list 可以算作是 3 个独立按钮,也和上面一样用一个按钮组件拖拽进去,加上一个跳转事宜,再连续配置 3 次。

但是,这一次是独立开拓,以是只能按照可复用定制模版的思路来实现。
须要考虑的地方是:

头图片支持背景配置左边的按钮支持背景设置,文案设置和显隐设置。
右侧的按钮列表中支持单个按钮的背景设置,文案设置和显隐设置以及支持再新增加多个按钮的情形下可复用样式 。

如图:对应的每个模块利用一个 ID 作为区分,个中 btn-list 包含以 btn-x 为唯一的标记,内容便是掌握这个按钮的背景,显隐和文案。

之后再通过 headData 来渲染 btn-list ,数据格式为:

<script>export default { data() { return { headData:[ { id: 'btn-1', value: { url: '...' } }, { id: 'btn-2', value: { url: '...' } } .... ] } },}</script>

它的核心思想便是通过 ID 关联数据,通过 ID 关联配置。
这有点像是数据库里面的主键,可以根据这个主键可以查询或者关联查找其他的数据表。

细节实现要点

写一个通用的方法,在后面如果有新增的按钮,可以直接通过传参 (第几个按钮) 掌握按钮的位置。

@function head-nav-btn-top($number) { $top: 15; $boxHeight: 46; @if($number == 1){ @return 385; } @return 385 + (($top + $boxHeight) ($number - 1));}// .classtop: remit(head-nav-btn-top(1));top: remit(head-nav-btn-top(2));top: remit(head-nav-btn-top(3));

锚点跳转到参数指定位置

headMethods($Event){ const { id, value } = $Event; if (id === 'lottery-btn') { const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; window.scrollTo(scrollTop, this.$refs[value].offsetTop); }},倒计时组件组件布局和实现:

组件封装思路

倒计时组件在逻辑上是比较大略的,更多须要考虑倒计时的展示样式,由于在 H5 里面,每种设计的风格或者业务需求不一样,不可能一贯沿用一种样式的倒计时,比如这样

以是须要考虑的点是:

倒计时的多种展示样式和展示形式,比如是否须要拼接标题和展示天数或者秒数。
倒计时在结束的时候须要实行的下一步逻辑。
细节实现要点

在打算倒计时的方法上有两种,第一种是获取本地手机韶光再写一个 inteval 函数递减打算,第二种是利用 interval 每秒都向做事器进行韶光获取打算出剩余韶光。
这里我一样平常会选择后面那种,由于,首先本地韶光不一定是精确的,也有可能是人为的去修正了系统的韶光,其次,本地的 interval 延迟韶光不一定准确。
比如我们设定 1000 毫秒实行,但是由于部分手机本身的缘故原由,这里可能存在着偏差,设定的是 1000 毫秒,而在实际的实行中,它相称于 800 毫秒。
那么就会导致一个问题,本地的韶光越算,偏差越大,如果在页面中的韶光勾留较短那问题不大,但是如果在页面的勾留韶光很长,到了末了看上便是一个大大的 BUG 。
以是,每次都读取做事器韶光是比较靠谱的。

实现要点:

<template> <div class="countdown-section"> <div v-if="styleForm.type === 'normal'" :style="[{ 'background-image': `url(${styleForm.bgUrl})` }]" :class="['countdown-section-bg', `width-${styleForm.bgWidth}`]" > <div class="countdown-content"> <p class="time-front" v-text="styleForm.timeFront"></p> <p class="time" v-text="countTime"></p> <p class="time-end" v-text="styleFrom.timeEnd"></p> </div> </div> </div></template><script>export default { methods: { countdown() { ... this.timeStr = {} //data this.format = ['hours', 'minutes','seconds'] // props for(let i = 0 ;i < format.length; i++ ){ this.timeStr = this.time[format[i]]; } }, },}</script>

节点样式方面:让 countdown-content 的内容居中,倒计时前后可以配置任意文案,再给 clase="time" 加上一个宽度,这样的好处是避免了在数字变革的时,因倒计时数字切换发生的抖动而影响到了全体倒计时文案的抖动问题。
另一个是,在倒计时外层再包一层 v-if 样式,这个是来拓展倒计时多种样式的功能。

逻辑方面:传入一个韶光格式的配置项,比如是否须要展示天数或者秒数,利用一个循环指天命据更新。
末了的韶光由 computed 打算属性将 day, hours minutes , seconds 打算出来。
同时,倒计时为零的时候支持配置一个方法,例如,最常见的操作便是刷新当前页面或者是实行跳转。

进度条组件组件布局和实现

组件封装思路

进度条组件和倒计时组件一样,属于逻辑比较大略而比较看重样式上的一些配置。
进度条组件须要考虑的点是:

背景色支持渐变配置进度条每个节点上面和下面的文案与样式支持配置进度条的节点所有内容支持显隐细节实现要点

<template><div class="progress-content"> <div class="progress"> <div class="progress-line" :style="{ width: `${currentProgress}%`, backgroundImage: `linear-gradient( to right, ${styles.lineStyle.begin}, ${styles.lineStyle.end} )`}"></div> <div class="progress-state"> <div v-for="(item, index) in styles.list" :key="`${index}buttom`" :style="[{ 'background-image': `url(${+index <= +current ? styles.dot.high: styles.dot.normal})` }]" class="state" > <div v-if="item.topText" :style="styles.top[index]" :class="['top']" v-text="item.topText"></div> <div v-if="item.bottomText" :style="styles.bottom[index]" class="buttom" v-text="item.bottomText"></div> </div> </div> </div></div></template><script>export default { methods: { },}</script>

首先,通过 props 进来的 styles参数, 获取到这个进度条的颜色,为了进度条能有更多的颜色配置,便是用渐变色来配置,只要传入一个开始和一个结束的色值。

节点的的样式和文案全部通过数组渲染,来达到通用配置的目,以下是我截取大略单纯的配置数据

const progress = { top: [ { color: '#f5ddff', }, { color: '#d6a5ea', },.... ], bottom: [ { color: '#d6a5ea', }, { color: '#d6a5ea', }, .... ], dot: { high: '', normal: '', }, list: [ { topText: 'Switch', bottomText: '0', hidden: false }, { topText: '2部', bottomText: '10000', hidden: false },... ], lineStyle:{ begin:'rgba(255, 166, 248, 1)', end:'rgba(255, 58, 210, 1)' }};吸底组件组件布局和实现:

组件封装思路

吸底部组件和顶部组件一样,它不适用于 H5 自动天生平台。
吸底组件和顶部组件它们更像是一个容器,在这个容器里面配置其他的组件,以是这里还是做成一个可复用的定制模版。
须要考虑的一些点:

最左边部分的头像和和昵称可以写去世固定下来同时须要配置默认打底状态数据。
中间部分的票数做成可配置的情形可以展示 1 行或者 2 行以及支持显隐。
最右边部分的按钮同样支持可配置 1 个或者 2 个以及支持显隐。
可配置背景色细节实现要点

他们的数据格式是:

<script>export default { data() { return { // 数据源绑定 suspensionData: { userSection: { name: 'xxxx', url: '....png', }, textSection: [ { 'text': `已贡献助力票数: 600` }, { 'text': `剩余助力票数: 400` } ], }, // 数据配置样式设定 suspensionConfig: { bg: '....png', btn: [ { 'id': 'get', 'url': '....png', 'text': '获取助力票' }, { 'id': 'exchange', 'url': '....png', 'text': '兑换助力票' } ] }, }, },}</script>

中间的笔墨展示和右边的按钮利用 Array 的形式渲染

<template>... <div v-if="btn && btn.length > 0" :class="['item-right, `length-${btn.length}`']" > ... </div></template><style lang="scss" scoped>... .btn{...&.length-2{justify-content: space-between;}&.length-1{justify-content: space-evenly;}}</style>

在样式上线配置好 length-x 情形下是居中还是均分的样式。
中间的笔墨也是同样的方法,只是这里就多了一些细节的考虑,比如:字体容器的溢出处理和行间距的一些设定。

头像和昵称按理也可以做一些设定,但是这里根据实际的需求来说没有必要,以是这里就直接固定下来。

投票组件组件布局和实现

组件封装思路

这个 H5 的投票功能相对大略,只有一个增加/减少和最大值。

在做这个组件之前,我实在更想把它做成这样的形式。
如图:

它可展示图片,还可以展示选择票的类型,同时下面还可以配置拓展按钮也可以绑定实行事宜,看上去非常的好。
但是后来想了一下,还是以为这样投票组件的逻辑会有点冗余,既然是一个投票组件该当不就有其他的东西。

以是我也在原来的根本上结合这个组件多加了一投票的类型选择。
便是这样:

这样看上去逻辑大略,而且也确实多了是一个实用的功能。
以是,这个组件须要考虑的点是:

增加/减少和最大值的打算类型选择同时可以支持拓展以及默认隐蔽利用。
细节实现要点

<template>...<!-- 票数编辑区域 --><div class="ticket-section"> <div class="ticket-edit"> <input class="ticket-text" v-model="ticketInfo['count']"/> <div :class="['ticket-add']" @click="ticketAdd()" > <p class="add">+</p> </div> <div :class="['ticket-min']" @click="ticketMins"> <p class="min">-</p> </div> </div> <div :class="['ticket-max', 'allow']"> <p class="max" @click="ticketAdd(true)">MAX</p> </div> </div> <!-- 类型选择区域 --> <div class="ticket-type-section"> <div v-if="typeList.length > 0" class="ticket-type-content"> <div v-for="(item,index) in typeList" :key="`type${index}`" class="ticket-type-item"> <div :class="['box',item.active? 'active': '']"></div> <div v-text="item.text"></div> </div> </div></div>...</template>

首先用一个数组渲染类型列表,编辑区域票数区域比较主要的的便是做好数字上的校验和统一管理考验失落败的提示文案。

const tipsMap = { error: '亲!
剩余助力票不敷,请重新输入!
', success: '助力成功!
', errorNum: '必须是一个数值,把稳不能有空格', errorMax: '亲,剩余助票不敷,请重新输入', errorZero: '亲,剩余助票不敷,请去获取哦!
',};const validCount = (num) => {this.$set(this, 'showTips', false); const regExp = /^\+?[1-9][0-9]$/g; if (+this.ticketInfo.count === 0) { return false } if (!regExp.test(this.ticketInfo.count)) { this.toast(tipsMap['errorNum']) return false;}; if (+this.ticketInfo.count > +this.ticketInfo.left){ this.toast(tipsMap['errorMax']) return false; }; // 考验通过 return true; }},validCount(1000);
排行榜组件组件布局和实现

组件封装思路

排行组件是这一个活动逻辑最繁芜的一个,他除了须要支持到这个活动展示的列表数据,也须要支持到往后其他 H5 的数据展示,也便是支持拓展。
比如:在这个排行榜中,第一列是一个头像列表类型,第二列是一个笔墨类型,第三列也是一个头像类型,第四列是一个按钮类型。
那么,在组件初始化的时候通过 config 配置定义好每一列的类型和样式。
如图:

const rankConfig = { init: [ { type: 'headList', key: 'head', name: '超能', tips: 'live', style: { width: '25%', color: '#ffffff', background: '#c69494', } }, { type: 'text', key: 'score', name: '总助力值', style: { width: '25%', color: '#ffffff', background: '#e53de7', } },.... ]};

这一段是部分配置

type 是数据类型,比如 纯笔墨,头像或者按钮。
key 是对应实际数据的 key,通过这个 key 让这一列展示这个字段的内容。
name 是这一列的标题style 是这一列的详细样式配置参数。

往后无论是须要怎么样的一个排行榜,先查找文档看看是不是都有这样类型的样式,没有的话拓展,有的话只须要传入配置参数之后,再传入详细的数据就可以跑起一个排行榜的组件。

从功能上看他须要支持:

支持数据分页支持数据展示和拓展展示的数据类型点击事宜通用配置细节实现要点

标题部分紧张代码:

<template>...<div v-for="(item, index) in styles.init" :style="item.style" :class="['column-item', 'column-type']" :key="`${index}column`"> <div class="item-title"> <p class="title-text" v-text="item.name"></p> <slot class="title-tips" :name="`sub-${item.key}`"></slot> </div></div></template>

利用配置项循环列出列表的标题,里面有个 icon 的提示图标,利用插槽的办法插入,这里须要用 sub-${item.key} 作为一个区分,须要显示 tips icon 的标题才展示。
这里只能用 key 作为区分不能用 type,缘故原由是一个列表里面是有可能有相同的 type 列的。

列表的渲染,这里须要各种类型的展示再抽离成一个小组件,比如将 text ,headList 等等抽离成一个小组件,须要的时候再引用。
这样做的好处出逻辑分开随意马虎掩护,小组件随意马虎拓展,排行榜的代码也不会过多,如图:

他的核心代码如下:

<template>...<divclass="column"v-for="(item, index) in info.list":key="`${index}rankList`"> <div v-for="(styleItem, styleIndex) in styles.init" :key="`${styleIndex}rankConfig`" :class="'column-item'" > <HeadList v-if="styleItem.type === 'headList'" @methods="onClickEvent(item.key, item)" ><HeadList> <Text v-if="styleItem.type === 'text'"></Text> <ListBtn v-if="styleItem.type === 'btn'"></ListBtn> </div></div></template>

第一层循环遍历所有列表数据,第二层循环遍历配置表,根据类型渲染详细内容,之后每一个块的内容都通过小组件形式引入。