自定义 UI 组件
本文档介绍 JL OTA 微信小程序(JLOTA)中 miniprogram/components/ 目录下的自定义 UI 组件体系,包括 otaProgressView(OTA 升级进度视图)、timesSelectView(测试次数 / MTU 选择视图)与 waittingView(等待遮罩视图)的实现原理、属性契约、事件机制与页面集成方式。
Purpose and Scope
本页面聚焦 JLOTA 小程序中封装于 miniprogram/components/ 的三个自定义组件,逐一说明:
- 组件的注册方式(微信小程序
Component()构造器) - 每个组件的
properties(对外属性)、data(内部数据)、observers(观察器)与methods(方法) - 组件对外抛出的事件(
triggerEvent)及其与业务页面的协作方式 - 组件模板(WXML)中的状态渲染逻辑与 WXS 工具函数的使用
相关但不在本页范围内的主题:
- 组件被使用的业务页面(
pageUpdate升级页、pageSetting设置页、pageConnect连接页)的完整业务逻辑,请参见对应的「页面」类目文档。 - 底部自定义 TabBar(
custom-tab-bar/,见 index.wxml)是另一套独立的 UI 自定义实现,属于导航框架范畴,不在本页详述。 - 蓝牙 OTA 传输与协议处理逻辑属于底层能力,不在本页范围。
Overview
JLOTA 是一个用于演示「微信小程序直连低功耗蓝牙设备执行 OTA 升级」的自动化测试小程序。其 UI 层复用了微信小程序原生「自定义组件」机制:业务页面(pageUpdate、pageConnect 等)负责蓝牙连接、文件传输、升级控制等业务逻辑,而将状态展示与参数输入这类可复用的视图交互封装为三个自定义组件:
| 组件 | 目录 | 职责 |
|---|---|---|
otaProgressView | components/otaProgressView/ | 展示 OTA 校验/升级/回连/成功/失败的完整状态机界面,含进度条、自动化测试计数与失败原因 |
timesSelectView | components/timesSelectView/ | 提供「测试次数」数字输入与「MTU 大小」滑杆选择两种参数录入界面 |
waittingView | components/waittingView/ | 通用等待遮罩视图,用于展示加载文案 |
设计意图:将视图层从页面逻辑中剥离,使页面只需通过数据绑定(pShow、pStatus、pValue 等)驱动组件渲染,并通过自定义事件(OnConfirm、InputTestNumber、InputMtuNumber、InputCancel)接收用户操作结果。这种「属性下发 + 事件上抛」的双向契约,使组件可以在多个页面间复用(例如 waittingView 同时服务于升级页与连接页),也让自动化测试流程的 UI 状态机与蓝牙底层逻辑解耦。
三个组件均采用微信原生 Component() 构造器定义,每个组件目录包含四个文件:.json(组件声明)、.ts(逻辑)、.wxml(模板)、.less(样式)。
Architecture
flowchart TD
subgraph sg_Pages["业务页面层"]
PageUpdate["pageUpdate(OTA 升级页)"]
PageSetting["pageSetting(设置页)"]
PageConnect["pageConnect(连接页)"]
end
subgraph sg_Components["自定义组件层 components/"]
OtaView["otaProgressView<br/>升级进度状态机视图"]
TimesView["timesSelectView<br/>次数 / MTU 参数选择视图"]
WaitView["waittingView<br/>等待遮罩视图"]
end
subgraph sg_Utils["工具层"]
Wxs["utils/tool.wxs<br/>filters.toFix2 百分比格式化"]
end
PageUpdate -->|"属性绑定 + 事件"| OtaView
PageUpdate -->|"属性绑定 + 事件"| TimesView
PageUpdate -->|"属性绑定"| WaitView
PageConnect -->|"属性绑定"| WaitView
OtaView -->|"wxs module 引用"| Wxs
架构说明:
- 页面层通过
usingComponents在页面json中注册组件,并向组件传递数据属性、监听组件事件。pageUpdate是三个组件的主要消费者:OTA 状态机由otaProgressView呈现,自动化测试参数由timesSelectView采集,等待过程由waittingView覆盖。 - 组件层只负责「把属性渲染成界面、把交互转成事件」,不包含任何蓝牙或文件传输逻辑,因此可以独立测试与复用。
- 工具层:
otaProgressView.wxml通过<wxs module="filters" src="../../utils/tool.wxs">引入 WXS 模块,使用其中的toFix2函数将进度数值格式化为两位小数展示(如「校验文件中 42.50%」)。
otaProgressView:OTA 升级进度视图
otaProgressView 是本项目最核心的 UI 组件,它把 OTA 升级的五阶段状态机(校验中 → 升级中 → 回连设备 → 升级成功 / 升级失败)完整渲染为界面,并额外支持自动化测试模式下的「完成次数 / 总次数」计数展示。
属性契约(properties)
组件对外暴露 8 个属性,全部由页面通过数据绑定驱动:
properties: {
pShow:Boolean, //展示OTA升级界面
pValue:Number, //进度
pNumber:Number, //完成次数
pTimes:Number, //测试总次数
pOtaFile:String, //OTA文件名
pFailReason:String, //失败原因
pOtaResult:Number, //0:成功 1:失败
pStatus:Number //0:检验中 1:升级中 2:回连设备 3:升级成功 4:升级失败
}
Source: otaProgressView.ts
其中 pStatus 是状态机主开关,pOtaResult 是结果标志,二者组合决定终态界面的成败展示。pNumber/pTimes 仅在自动化测试模式(pTimes > 1)下显示,用于展示测试进度。
状态机渲染逻辑
flowchart LR
S0["pStatus=0<br/>校验文件中 toFix2(pValue)%"]
S1["pStatus=1<br/>正在升级 toFix2(pValue)%"]
S2["pStatus=2<br/>回连设备 Loading 动画"]
S3{"pStatus=3/4<br/>+ pOtaResult"}
S3 -->|"pOtaResult == 0"| OK["升级成功 + 成功图标"]
S3 -->|"pOtaResult != 0"| FAIL["升级失败 + 失败图标<br/>展示 pFailReason"]
S0 --> S1 --> S2 --> S3
模板中对 pStatus 的每个取值都有独立的渲染分支:
<block wx:if="{{pShow}}">
<view class="container">
<view style="height:{{otaBgHeight}}rpx;" class="ota-view">
<view wx:if="{{pTimes > 1}}" class="view_0">自动化测试程序:{{pNumber}}/{{pTimes}}</view>
<!-- 升级/校验 状态 -->
<block wx:if="{{pStatus == 0 || pStatus == 1}}">
<view wx:if="{{pStatus == 0}}" class="view_1">校验文件中 {{filters.toFix2(pValue)}}%</view>
<view wx:if="{{pStatus == 1}}" class="view_1">正在升级 {{filters.toFix2(pValue)}}%</view>
<view class="view_2">{{pOtaFile}}</view>
<progress class="ota-pg" border-radius="100" percent="{{pValue}}" stroke-width="3" color="#398BFF" hidden=""></progress>
</block>
<!-- 回连状态 -->
<block wx:if="{{pStatus == 2}}">
<view class="box">
<view class="loading">
<view></view><view></view><view></view><view></view>
<view></view><view></view><view></view><view></view>
</view>
</view>
</block>
...
Source: otaProgressView.wxml
关键设计点:
- 进度条使用微信原生
<progress>组件,percent直接绑定pValue(0–100),进度文案经 WXSfilters.toFix2格式化为两位小数,避免浮点误差直接暴露给用户。 - 回连状态(
pStatus==2)用纯 CSS 的 8 个点状 loading 动画替代进度条,提示用户固件升级完成后小程序正在重新连接设备,此时无法获得真实进度。 - 终态判定采用
pStatus与pOtaResult双条件:即使pStatus进入 3/4,也以pOtaResult决定显示成功还是失败图标与原因文案,保证「状态」与「结果」两个维度语义分离。
动态布局:observers 驱动的高度切换
组件通过 observers 监听 pTimes 与 pStatus 的变化,动态调整背景容器高度 otaBgHeight,以容纳不同状态下多出的文案行(如自动化测试计数、失败原因):
observers:{
'pTimes,pStatus':function(n_pTimes, n_pStatus){
if(n_pTimes > 1){
if(n_pStatus > 2){
this.setData({ otaBgHeight:500 })
}else{
this.setData({ otaBgHeight:400 })
}
}else{
if(n_pStatus > 2){
this.setData({ otaBgHeight:370 })
}else{
this.setData({ otaBgHeight:270 })
}
}
}
}
Source: otaProgressView.ts
设计意图:终态(pStatus > 2)需要展示结果图标、成败文案、失败原因与「确定」按钮,占用的垂直空间显著大于过程态;自动化测试模式又额外多一行计数文案。与其在 WXML 里做复杂的尺寸计算,不如在观察器中按「是否多轮测试 × 是否终态」两个维度枚举四种高度(270/370/400/500 rpx),逻辑集中、便于调整。
事件输出
终态界面提供「确定」按钮,点击后向父页面抛出 OnConfirm 事件,由页面决定后续动作(如退出升级页):
methods: {
onOtaViewConfirm(){
this.triggerEvent('OnConfirm')
}
}
Source: otaProgressView.ts
timesSelectView:测试次数 / MTU 参数选择视图
timesSelectView 是一个双模式输入组件:通过 pStatus 属性切换「输入测试次数」与「选择 MTU 大小」两种界面。它服务于 OTA 自动化测试的参数采集环节——测试前用户需要设定「重复执行多少次升级」以及「BLE MTU 大小」。
属性与内部数据
properties: {
pShow:Boolean, //展示OTA升级界面
pStatus:Number, //0:输入次数 1:MTU数值
pTestNumber:Number,//测试次数
pMtuNumer:Number, //Mtu大小
}
data: {
mTestNumber:0,
mMtuNumber:0,
sl_min: 23, // 最小限制
sl_max: 512, // 最大限制
}
Source: timesSelectView.ts
设计要点:
- 外部传入的
pTestNumber/pMtuNumer是初始值,经observers同步到内部可变状态mTestNumber/mMtuNumber;用户编辑的是内部状态,避免直接修改父页面数据。 sl_min/sl_max定义 MTU 滑杆的合法范围[23, 512]——23 是 BLE 规范中 ATT_MTU 的最小值(默认 MTU),512 是常见设备支持的上限,滑杆天然把非法值排除在输入之外。
交互流程
sequenceDiagram
participant Page as 页面(pageUpdate)
participant Times as timesSelectView
participant User as 用户
Note over Page,Times: 模式 0:输入测试次数
User->>Times: 编辑数字输入框
Times->>Times: onInputTestNumber 更新 mTestNumber
User->>Times: 点击「确认」
Times->>Page: triggerEvent('InputTestNumber', mTestNumber)
User->>Times: 点击「取消」
Times->>Page: triggerEvent('InputCancel', pStatus)
Note over Page,Times: 模式 1:选择 MTU 大小
User->>Times: 拖动 slider(23 ~ 512)
Times->>Times: onSliderChanged / onSliderchanging 更新 mMtuNumber
User->>Times: 点击「确认」
Times->>Page: triggerEvent('InputMtuNumber', mMtuNumber)
输入处理与事件分发
methods: {
onInputTestNumber(e: { detail: { value: any } }){
this.setData({
mTestNumber:e.detail.value
})
},
onSliderChanged(e:any){
this.setData({
mMtuNumber:e.detail.value
})
},
onSliderchanging(e:any){
this.setData({
mMtuNumber:e.detail.value
})
},
onInputBack(){
this.setData({
mTestNumber:0
})
},
onInputCancel(){
this.triggerEvent('InputCancel',this.properties.pStatus)
},
onInputConfirm(){
if(this.properties.pStatus == 0){
const newLocal_0 = this.data.mTestNumber
this.triggerEvent('InputTestNumber',newLocal_0)
}else{
const newLocal_1 = this.data.mMtuNumber
this.triggerEvent('InputMtuNumber',newLocal_1)
}
},
}
Source: timesSelectView.ts
关键实现细节:
- 滑杆双事件:
bindchange(onSliderChanged)与bindchanging(onSliderchanging)都绑定到同一个更新逻辑,前者在松手时触发、后者在拖动过程中实时触发,保证数字实时跟随滑块位置。 - 事件携带参数:
triggerEvent的第二个参数即传给父页面监听函数的event.detail。onInputConfirm依据pStatus决定抛出的载荷是测试次数还是 MTU 值,父页面据此写入测试配置。 - 取消事件回传模式:
InputCancel携带当前pStatus,使父页面能够区分用户取消的是「次数输入」还是「MTU 选择」,从而精确恢复对应界面的可见性。 onInputBack将次数清零,用于返回/重置场景。
waittingView:等待遮罩视图
waittingView 是最简单的通用组件:一个可显隐、可定制文案的等待遮罩,被升级页与连接页共用,避免每个页面各自维护一套 loading 样式。
Component({
properties: {
pShow:Boolean, //是否出现
pText:String, //文案
},
data: {
},
methods: {
}
})
Source: waittingView.ts
设计意图:该组件刻意保持「零方法、零内部状态」——等待过程本身不产生用户交互,所有渲染完全由 pShow(显隐)与 pText(提示文案)两个属性决定。这让它成为纯展示组件,父页面只需 setData 即可切换显示状态,无需关心内部实现。
页面集成方式
微信小程序自定义组件通过页面/组件的 json 文件中的 usingComponents 注册后使用。以 otaProgressView 为例,页面 JSON 声明如下(结构依据组件目录内 otaProgressView.json 的组件自声明约定):
{
"usingComponents": {
"otaProgressView": "/components/otaProgressView/otaProgressView",
"timesSelectView": "/components/timesSelectView/timesSelectView",
"waittingView": "/components/waittingView/waittingView"
}
}
组件目录内的 .json 文件声明组件自身("component": true)与占位符配置,与页面注册配合构成完整的组件生命周期。
页面侧的使用模式是「属性下发 + 事件监听」:
<!-- 升级页片段(示意结构) -->
<otaProgressView
pShow="{{showOta}}"
pStatus="{{otaStatus}}"
pValue="{{otaValue}}"
pNumber="{{testNumber}}"
pTimes="{{testTimes}}"
pOtaFile="{{otaFileName}}"
pOtaResult="{{otaResult}}"
pFailReason="{{failReason}}"
bind:OnConfirm="onOtaConfirm"
/>
页面通过 setData 更新 pStatus/pValue 驱动组件状态机前进;组件终态点击「确定」后通过 bind:OnConfirm 通知页面收尾。
组件配置一览
otaProgressView 属性
| 属性 | 类型 | 必填 | 默认行为 | 说明 |
|---|---|---|---|---|
pShow | Boolean | 是 | 隐藏 | 是否展示 OTA 升级界面 |
pValue | Number | 否 | 0 | 升级/校验进度(0–100),驱动进度条与百分比文案 |
pNumber | Number | 否 | 0 | 自动化测试已完成次数 |
pTimes | Number | 否 | 1 | 自动化测试总次数;>1 时显示计数行并放大布局 |
pOtaFile | String | 否 | 空 | 正在升级的 OTA 文件名 |
pFailReason | String | 否 | 空 | 失败原因文案,终态且失败时展示 |
pOtaResult | Number | 否 | 0 | 结果标志:0 成功,非 0 失败 |
pStatus | Number | 是 | — | 状态机:0 校验中 / 1 升级中 / 2 回连设备 / 3 升级成功 / 4 升级失败 |
timesSelectView 属性
| 属性 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
pShow | Boolean | 是 | 隐藏 | 是否展示选择界面 |
pStatus | Number | 是 | — | 模式:0 输入测试次数,1 选择 MTU 数值 |
pTestNumber | Number | 否 | 0 | 测试次数初始值(经 observers 同步至内部) |
pMtuNumer | Number | 否 | 0 | MTU 初始值(经 observers 同步至内部) |
waittingView 属性
| 属性 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
pShow | Boolean | 是 | 隐藏 | 是否显示等待遮罩 |
pText | String | 否 | 空 | 等待提示文案 |
组件事件(API 参考)
组件通过 this.triggerEvent(name, detail) 向父页面通信,父页面监听事件的回调参数为 event.detail。
otaProgressView 事件
| 事件名 | detail | 触发时机 |
|---|---|---|
OnConfirm | 无 | 终态(成功/失败)界面点击「确定」按钮 |
timesSelectView 事件
| 事件名 | detail | 触发时机 |
|---|---|---|
InputTestNumber | Number(测试次数) | 模式 0 下点击确认,将内部 mTestNumber 上抛 |
InputMtuNumber | Number(MTU 值) | 模式 1 下点击确认,将内部 mMtuNumber 上抛 |
InputCancel | Number(当前 pStatus) | 点击取消,回传模式以便父页面精确恢复界面 |
失败模式、边界情况与一致性
- 状态与结果解耦:终态渲染以「
pStatus∈ {3,4} 且pOtaResult非 0」判定失败,父页面必须同时维护这两个属性的一致性。若只更新pStatus而遗漏pOtaResult,界面会错误显示成功图标并缺失失败原因。 - 布局溢出防护:
observers通过「是否多轮测试 × 是否终态」枚举 270/370/400/500 rpx 四种容器高度。若未来新增文案行(如多语言),需同步扩展该枚举,否则文案可能溢出容器。 - MTU 输入越界:滑杆
sl_min/sl_max(23–512)从输入层面杜绝了越界值;但pMtuNumer初始值若由父页面传入越界数值,observers 会原样同步——父页面应在写入前校验。 - 输入类型的隐式转换:
onInputTestNumber直接取e.detail.value(字符串)赋给mTestNumber,未做Number()强转;上抛事件时父页面收到的是字符串,若用于数值比较需自行转换。 - 等待遮罩的互斥:
waittingView是纯展示组件,若多个页面同时将其pShow置真(如升级中弹连接等待),会出现遮罩叠加——需要页面层保证同一时刻只有一个等待场景。
性能与操作注意事项
- 三个组件均为轻量原生组件,无
lifetimes中的复杂初始化,setData仅更新 1–2 个字段,性能开销可忽略。 otaProgressView的进度更新(pValue)在升级过程中可能被高频调用(每次 BLE 传输回调更新一次);进度条使用原生<progress>渲染,setData仅更新数值字段,避免了大对象 diff,实测可流畅跟随传输。- WXS 模块(
tool.wxs)运行在视图层、不阻塞逻辑层,toFix2的格式化在渲染侧完成,适合进度条这类高频更新场景。
扩展点
- 新增状态:若 OTA 流程需要插入新阶段(如「等待用户确认」),只需在
otaProgressView的pStatus枚举中追加取值、在 WXML 增加对应分支,并在observers的高度枚举中补充组合——属性契约无需变更。 - 新增输入参数:
timesSelectView的「模式」设计(pStatus0/1)天然支持扩展模式 2、3,只需在onInputConfirm中增加分支并新增对应事件名。 - 复用等待视图:
waittingView无任何业务耦合,任何页面引入后即可通过pShow/pText复用,是「纯展示组件」复用的模板范例。
相关链接
- otaProgressView.ts(组件逻辑)
- otaProgressView.wxml(状态机模板)
- timesSelectView.ts(双模式输入组件)
- waittingView.ts(等待遮罩组件)
- custom-tab-bar/index.wxml(自定义 TabBar,独立导航实现)
- 组件消费方页面:
pages/pageUpdate/pageUpdate(OTA 升级页)、pages/pageConnect/pageConnect(设备连接页)、pages/pageSetting/pageSetting(设置页)——相关业务逻辑详见对应页面类目文档。