应用入口与页面导航
杰理 OTA 微信小程序(JLOTA)的应用启动入口与页面导航体系:包含 app.json 全局注册、自定义 tabBar 切换机制、页面层级关系以及蓝牙定位权限等全局配置。
Purpose and Scope
本页面向开发者完整说明 JLOTA 小程序的应用入口与页面导航机制,覆盖:
miniprogram/app.json中的页面注册(pages)、自定义 tabBar(tabBar)、窗口样式(window)、蓝牙定位权限(permission)等全局入口配置;custom-tab-bar自定义导航栏组件的实现与切换逻辑(wx.switchTab);- 三大一级页面(连接 pageConnect、升级 pageUpdate、设置 pageSetting)与两个二级页面(pageBLEDataSet、pageCustomCmd)之间的导航层级关系。
与页面导航相关的蓝牙连接流程、OTA 升级状态机、BLE 数据设置 / 自定义指令的具体业务逻辑属于对应页面的能力,请参见仓库中 pages/pageConnect、pages/pageUpdate、pages/pageSetting 相关实现;底层协议库(lib/jl_lib/jl_ota_2.1.1.js、jl_rcsp_ota_2.1.1.js、jl_auth_2.0.0.js)属于 SDK 层,不在本页展开。
概述
JLOTA 是运行在微信小程序平台上的 OTA(Over-The-Air)升级工具,核心链路为「蓝牙连接设备 → 获取固件 → 执行升级」。整个应用以 3 个 tab 页 + 2 个设置二级页 组织:
- 连接页(pageConnect):应用首屏,负责扫描并连接蓝牙设备,是 OTA 链路的数据源头;
- 升级页(pageUpdate):展示固件升级进度与状态,依赖连接页建立的设备链路;
- 设置页(pageSetting):入口页,可继续下钻到 BLE 数据设置(pageBLEDataSet) 与 自定义指令(pageCustomCmd) 两个二级页面。
导航体系采用自定义 tabBar(tabBar.custom: true),由 custom-tab-bar 组件接管三个一级页面的切换,使 UI 与业务解耦、切换逻辑可控。入口配置集中声明在 app.json,微信小程序框架在启动时按 pages 数组顺序加载首屏页面,并按 tabBar.list 渲染底部导航。
架构
flowchart TD
subgraph sg_Entry["入口层 (app.json)"]
AppJson["app.json<br/>页面注册 / tabBar / window / permission"]
Sitemap["sitemap.json<br/>搜索索引"]
end
subgraph sg_TabBar["导航层 (custom-tab-bar)"]
TabBar["custom-tab-bar/index.js<br/>Component 自定义 tabBar"]
SwitchTab["switchTab()<br/>wx.switchTab"]
end
subgraph sg_Pages["页面层 (pages)"]
PageConnect["pages/pageConnect<br/>连接 (tab 0)"]
PageUpdate["pages/pageUpdate<br/>升级 (tab 1)"]
PageSetting["pages/pageSetting<br/>设置 (tab 2)"]
PageBLEDataSet["pages/pageSetting/pageBLEDataSet<br/>BLE 数据设置 (二级页)"]
PageCustomCmd["pages/pageSetting/pageCustomCmd<br/>自定义指令 (二级页)"]
end
subgraph sg_Lib["支撑层 (lib / components)"]
OtaLib["lib/jl_lib/jl_ota_2.1.1.js"]
RcspLib["lib/jl_lib/jl_rcsp_ota_2.1.1.js"]
AuthLib["lib/jl_lib/jl_auth_2.0.0.js"]
Util["lib/util.js"]
Components["components/<br/>otaProgressView / timesSelectView / waittingView"]
end
AppJson -->|"声明 pages["0"] 为首屏"| PageConnect
AppJson -->|"tabBar.custom: true"| TabBar
TabBar -->|"switchTab 切换"| PageConnect
TabBar -->|"switchTab 切换"| PageUpdate
TabBar -->|"switchTab 切换"| PageSetting
PageSetting -->|"navigateTo 下钻"| PageBLEDataSet
PageSetting -->|"navigateTo 下钻"| PageCustomCmd
PageConnect --> OtaLib
PageUpdate --> OtaLib
PageUpdate --> RcspLib
PageConnect --> AuthLib
PageConnect --> Util
PageUpdate --> Components
PageSetting --> Components
架构说明:
- 入口层:
app.json是微信小程序框架的启动契约,声明了全部 5 个页面、自定义 tabBar 的三项配置、全局窗口样式与蓝牙定位权限;sitemap.json控制页面是否可被微信索引。 - 导航层:
custom-tab-bar是一个全局自定义组件,微信框架会在tabBar.custom: true时自动渲染它;其switchTab方法通过wx.switchTab完成三个一级页面之间的切换,并同步维护高亮索引selected。 - 页面层:三个 tab 页构成应用主框架;两个二级页从设置页
navigateTo进入,属于下钻型页面(不在 tabBar 中)。 - 支撑层:连接/升级页依赖
lib/jl_lib下的认证与 OTA 协议库,页面内复用了components下的进度视图、时间选择、等待视图等 UI 组件——它们不参与导航,但为页面能力提供支撑。
核心流程
sequenceDiagram
participant F as 微信框架 (WeChat Runtime)
participant A as app.json
participant T as custom-tab-bar
participant P0 as pageConnect (连接)
participant P1 as pageUpdate (升级)
participant P2 as pageSetting (设置)
F->>A: 启动时读取全局配置
A-->>F: pages / tabBar / window / permission
F->>P0: 加载 pages[0] 作为首屏
F->>T: tabBar.custom=true,实例化自定义导航
T-->>F: 渲染 3 个 tab 项 (list + selected=0)
Note over P0,P1: 用户点击 tab 项
T->>T: switchTab(e) 读取 dataset.path
T->>F: wx.switchTab(url=../../pages/...)
F->>P1: 切换并缓存页面栈 (仅保留 tab 页)
T->>T: setData(selected: index) 更新高亮
Note over P2: 用户从设置页下钻
P2->>F: wx.navigateTo(二级页)
F->>P2: 压栈打开 pageBLEDataSet / pageCustomCmd
流程要点:
- 小程序冷启动时,微信框架解析
app.json,按pages数组顺序注册页面,并将pages[0](pageConnect)作为默认首页渲染。 - 由于
tabBar.custom为true,框架不再使用内置 tabBar,而是寻找custom-tab-bar目录下的自定义组件进行渲染。 - 用户点击 tab 项触发
switchTab:读取dataset.path拼出页面路径后调用wx.switchTab。switchTab会关闭所有非 tab 页面并把页面栈收敛到目标 tab 页,保证 tab 间不累积页面栈。 - 切换成功后组件通过
setData({ selected: index })更新选中态,配合selectedColor(#398BFF)高亮当前 tab。 - 设置页内的二级页(pageBLEDataSet、pageCustomCmd)不在 tabBar 中,使用普通压栈导航(
navigateTo),可通过返回手势/按钮回到设置页。
应用入口:app.json 全局注册
miniprogram/app.json 是整个小程序的入口契约,微信框架启动时首先解析该文件。其完整内容如下:
{
"pages": [
"pages/pageConnect/pageConnect",
"pages/pageUpdate/pageUpdate",
"pages/pageSetting/pageSetting",
"pages/pageSetting/pageBLEDataSet/pageBLEDataSet",
"pages/pageSetting/pageCustomCmd/pageCustomCmd"
],
"tabBar": {
"custom": true,
"color": "#808080",
"selectedColor": "#398BFF",
"list": [
{
"pagePath": "pages/pageConnect/pageConnect",
"text": "连接",
"iconPath": "/images/tab_icon_bt_nol.png",
"selectedIconPath": "/images/tab_icon_bt_sel.png"
},
{
"pagePath": "pages/pageUpdate/pageUpdate",
"text": "升级",
"iconPath": "/images/tab_icon_update_nol.png",
"selectedIconPath": "/images/tab_icon_update_sel.png"
},
{
"pagePath": "pages/pageSetting/pageSetting",
"text": "设置",
"iconPath": "/images/tab_icon_settle_nol.png",
"selectedIconPath": "/images/tab_icon_settle_sel.png"
}
]
},
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "杰理OTA升级",
"navigationBarTextStyle": "black"
},
"permission": {
"scope.userLocation": {
"desc": "你的位置信息将用于发现蓝牙设备"
}
},
"style": "v2",
"sitemapLocation": "sitemap.json"
}
Source: app.json
页面注册(pages)
pages 数组声明了应用的全部 5 个页面,数组第一项即冷启动默认首页:
| 顺序 | 页面路径 | 角色 | 是否在 tabBar |
|---|---|---|---|
| 0 | pages/pageConnect/pageConnect | 蓝牙连接(首屏) | ✅ tab 0「连接」 |
| 1 | pages/pageUpdate/pageUpdate | 固件升级 | ✅ tab 1「升级」 |
| 2 | pages/pageSetting/pageSetting | 设置入口 | ✅ tab 2「设置」 |
| 3 | pages/pageSetting/pageBLEDataSet/pageBLEDataSet | BLE 数据设置(二级页) | ❌ |
| 4 | pages/pageSetting/pageCustomCmd/pageCustomCmd | 自定义指令(二级页) | ❌ |
设计意图:将「连接 → 升级」这两个 OTA 强相关流程放在最前两个 tab,符合用户使用动线——先连设备、再升级;设置页收纳低频操作并继续下钻两个二级页,避免 tabBar 拥挤。
自定义 tabBar 开关(tabBar.custom)
tabBar.custom: true 是关键开关:它指示微信框架不渲染内置 tabBar,而是查找根目录下的 custom-tab-bar 自定义组件来接管导航 UI。与此同时,color / selectedColor 仍作为「未选中 / 选中」文字颜色基线(与组件内 data 保持一致),list 中每项声明了 pagePath(页面路径)、text(文案)与 iconPath / selectedIconPath(未选中/选中图标)。
全局窗口与权限
window.navigationBarTitleText: "杰理OTA升级"为所有页面提供默认导航栏标题;navigationBarBackgroundColor: "#fff"配navigationBarTextStyle: "black"为白底黑字。permission.scope.userLocation声明了地理位置授权用途:「你的位置信息将用于发现蓝牙设备」。这是因为 iOS 上扫描 BLE 外设依赖系统定位权限,此声明是小程序蓝牙发现能力的合规前置条件。style: "v2"启用新版组件样式;sitemapLocation: "sitemap.json"指向搜索索引配置。
自定义 tabBar 导航实现
custom-tab-bar/index.js 是导航层核心,它是一个 Component 定义的自定义组件,逻辑如下:
Component({
data: {
selected: 0,
enable:true,
"color": "#808080",
"selectedColor": "#398BFF",
list:[{
"pagePath": "pages/pageConnect/pageConnect",
"text": "连接",
"iconPath": "/images/tab_icon_bt_nol.png",
"selectedIconPath": "/images/tab_icon_bt_sel.png"
},
{
"pagePath": "pages/pageUpdate/pageUpdate",
"text": "升级",
"iconPath": "/images/tab_icon_update_nol.png",
"selectedIconPath": "/images/tab_icon_update_sel.png"
},
{
"pagePath": "pages/pageSetting/pageSetting",
"text": "设置",
"iconPath": "/images/tab_icon_settle_nol.png",
"selectedIconPath": "/images/tab_icon_settle_sel.png"
}]
},
attached() {
},
methods: {
switchTab(e) {
if(!this.data.enable)return
const data = e.currentTarget.dataset
const url = data.path
wx.switchTab({url:"../../"+url})
this.setData({
selected: data.index
})
}
}
})
Source: custom-tab-bar/index.js
关键实现细节
- data 自包含:
list、color、selectedColor在组件内重复声明了与app.json一致的数据。原因是自定义 tabBar 的data会直接用于渲染,微信官方推荐在组件内维护list副本(也可在attached中从app.json读取)。这里选择静态冗余,保证渲染数据不依赖运行时读取。 - switchTab(e):从
e.currentTarget.dataset取path与index(由 wxml 侧绑定,如data-path="pages/pageUpdate/pageUpdate" data-index="1")。先用enable标志做开关闸门(if(!this.data.enable)return,可用于升级中锁定导航),再以wx.switchTab({url: "../../" + url})完成页面切换——前缀../../是因为组件位于custom-tab-bar/目录,页面路径需回退两级到miniprogram/根再拼接。 - 选中态同步:切换后
setData({ selected: data.index })更新高亮索引,由 wxml 配合selectedColor渲染选中样式。 - attached 为空:生命周期钩子留空,说明组件不依赖异步初始化,启动即就绪。
为什么要用自定义 tabBar
- 可控的导航闸门:
enable标志可在升级进行中等场景下拦截 tab 切换,内置 tabBar 无法做到; - UI 自由度:图标、文案、高亮样式完全由组件 wxml/wxss 控制,与品牌风格一致;
- 与业务状态联动:可在
switchTab前后插入业务逻辑(校验、统计、重置状态),将导航收敛为可编程行为。
页面层级与导航关系
三个 tab 页与两个二级页的导航关系如下:
flowchart LR
subgraph sg_Tabs["tab 一级页 (switchTab)"]
PC["pageConnect 连接"]
PU["pageUpdate 升级"]
PS["pageSetting 设置"]
end
subgraph sg_Sub["二级页 (navigateTo)"]
BD["pageBLEDataSet BLE数据设置"]
CC["pageCustomCmd 自定义指令"]
end
PC <--> PU
PC <--> PS
PU <--> PS
PS -->|"下钻"| BD
PS -->|"下钻"| CC
BD -.->|"返回"| PS
CC -.->|"返回"| PS
- 一级页之间:仅通过
custom-tab-bar的wx.switchTab切换,页面栈中同时只保留 tab 页,切换时非 tab 页面全部销毁——这是微信框架对switchTab的固有语义,天然避免内存泄漏。 - 一级页 → 二级页:设置页通过
wx.navigateTo压栈打开pageBLEDataSet/pageCustomCmd;二级页不注册进 tabBar,因此拥有独立导航栏(可返回),与 tab 切换互不干扰。 - 业务依赖:pageUpdate 依赖 pageConnect 建立的 BLE 连接链路(连接数据保存在全局/缓存中),因此把「连接」放在 tab 0 作为动线起点;pageBLEDataSet / pageCustomCmd 为开发者调试与协议测试提供入口,属低频工具页。
配置选项
app.json 全局配置
| 配置项 | 类型 | 默认值(本仓库实际值) | 说明 |
|---|---|---|---|
pages | string[] | 5 个页面路径 | 全部页面注册,首项为启动首页 |
tabBar.custom | boolean | true | 启用自定义 tabBar(custom-tab-bar 组件接管渲染) |
tabBar.color | string | "#808080" | 未选中 tab 文字颜色 |
tabBar.selectedColor | string | "#398BFF" | 选中 tab 文字颜色(品牌蓝) |
tabBar.list[].pagePath | string | 见上表 | tab 对应的页面路径(必须已在 pages 注册) |
tabBar.list[].text | string | 连接 / 升级 / 设置 | tab 文案 |
tabBar.list[].iconPath | string | /images/tab_icon_*_nol.png | 未选中图标 |
tabBar.list[].selectedIconPath | string | /images/tab_icon_*_sel.png | 选中图标 |
window.navigationBarTitleText | string | "杰理OTA升级" | 全局默认导航栏标题 |
window.navigationBarBackgroundColor | string | "#fff" | 导航栏背景色 |
window.navigationBarTextStyle | string | "black" | 导航栏文字颜色 |
window.backgroundTextStyle | string | "light" | 下拉背景样式 |
permission.scope.userLocation.desc | string | "你的位置信息将用于发现蓝牙设备" | 定位权限用途说明(BLE 扫描前置条件) |
style | string | "v2" | 新版组件样式 |
sitemapLocation | string | "sitemap.json" | 搜索索引配置位置 |
custom-tab-bar 组件 data
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
selected | number | 0 | 当前高亮 tab 索引(与 list 下标对应) |
enable | boolean | true | 导航闸门;为 false 时 switchTab 直接返回,可阻止切换 |
color | string | "#808080" | 未选中颜色(与 app.json 保持一致) |
selectedColor | string | "#398BFF" | 选中颜色(与 app.json 保持一致) |
list | object[] | 3 个 tab 项 | 每项含 pagePath/text/iconPath/selectedIconPath |
注意:
tabBar.list与组件data.list是两处独立的数据源,修改 tab 项时必须同步更新app.json与custom-tab-bar/index.js(及其 wxml/wxss 的图标资源),否则会出现「框架注册页与渲染项不一致」的问题。
失败模式、边界情况与并发
wx.switchTab路径前缀错误:switchTab要求 url 必须以/开头或为合法相对路径。组件中拼接"../../" + url(如../../pages/pageUpdate/pageUpdate),若移动custom-tab-bar目录位置或修改页面路径,前缀需同步调整,否则wx.switchTab直接失败并抛出fail回调。- tab 页必须注册且路径一致:
tabBar.list[].pagePath引用的页面若未出现在pages数组中,或大小写/路径不一致,框架启动即报错;switchTab目标也必须是 tab 页,否则调用无效。 - 升级中的导航竞态:
enable标志为导航提供了互斥闸门。升级进行中(pageUpdate 处于升级状态机)若业务将enable置为false,用户点击其他 tab 会被静默忽略(return),避免升级流程被页面切换打断导致状态丢失。这是典型的「UI 事件与后台任务互斥」设计。 - 自定义 tabBar 高亮不同步:由于
selected由组件内部维护,若用户通过其他入口(如wx.reLaunch)直接跳转 tab 页,组件selected不会自动跟随,可能出现高亮与实际页面不符。解决方向是在页面onShow中向组件同步selected(本仓库attached为空、未做同步,属于已知简化点)。 - iOS 定位权限缺失:
permission.scope.userLocation仅完成声明,若用户在系统设置中拒绝定位授权,iOS 上 BLE 扫描将无法发现外设——这是连接页(tab 0)的硬前置条件,失败需在页面层给出引导。 switchTab的页面栈语义:switchTab会关闭所有非 tab 页面并保留目标 tab 页,因此不能用它打开 pageBLEDataSet / pageCustomCmd 这类二级页;二级页必须走navigateTo。若误用会得到「fail: can not switch to non-tabBar page」错误。
性能与运维注意点
- tab 页常驻内存:三个 tab 页在首次打开后常驻页面栈,升级流程(pageUpdate)中若有高频 UI 刷新(如 OTA 进度条),注意避免在非活动 tab 页中做无效渲染;组件
otaProgressView等被设计为可复用视图,正是为降低页面复杂度。 - 静态冗余的成本:
custom-tab-bar内重复维护list/颜色配置是「以少量重复换取渲染确定性」,但新增 tab 或改图标时需三处联动(app.json、组件 js、组件 wxml/wxss),建议在代码注释中标注同步点。 - 首屏策略:
pages[0](pageConnect)为冷启动首页,蓝牙扫描初始化较重,若首屏白屏时间过长,可考虑在 pageConnect 的onLoad中延迟初始化扫描、优先渲染 UI。
扩展点
- 导航闸门(enable):
switchTab已内置enable拦截点,可在升级状态机运行期间由 pageUpdate 通过全局事件或getApp()访问组件实例并置enable=false,实现「升级中禁止切页」。 - tab 项扩展:在
app.json的tabBar.list与组件data.list中同步追加项即可扩展 tab(上限 5 个),并补充对应页面与图标资源。 - 选中态同步:如需让
selected跟随任意入口跳转,可在每个 tab 页onShow中调用this.getTabBar().setData({ selected: N }),这是微信官方推荐的自定义 tabBar 同步模式,可作为本仓库的后续增强。 - 二级页扩展:设置页下钻模式(navigateTo)可继续扩展更多调试页(如日志页、设备信息页),只需在
pages数组中注册新路径并保持pageSetting作为唯一入口。
相关链接
- app.json(应用入口配置)
- custom-tab-bar/index.js(自定义导航实现)
- custom-tab-bar/index.json
- sitemap.json(搜索索引)
- pageConnect 页面配置
- pageUpdate 页面配置
- pageSetting 页面配置
- pageBLEDataSet 页面配置
- pageCustomCmd 页面配置
- project.config.json(项目/开发者工具配置)
相关能力页面:蓝牙连接流程(pageConnect)、OTA 升级状态机(pageUpdate)、BLE 数据设置与自定义指令(pageSetting 二级页)的深入实现请查看对应页面源码。