# 第5次课 讲稿

> 课程：小程序开发
> 主题：多页面导航与tabBar配置
> 教材：《微信小程序开发实战（第2版）》第2章
> 时长：3学时（理论 1 + 实践 2）
> 重点：页面导航、tabBar配置、页面传参
> 难点：页面栈管理、传参编码解码

---

## 一、开场（0:00）

到现在为止，我们做的都是单页面。但真实的小程序不可能只有一页——点一个商品要跳详情，点底部图标要换栏目。

今天解决两个问题：**页面之间怎么跳**，以及**跳的时候怎么带数据过去**。

我先抛个问题给大家：微信里那些底部有四个图标的小程序，切换图标的时候，页面是"新开"的还是"切换"的？

（等回答）

答案是切换，而且切 tab 的时候会把其他非 tab 页面全关掉。这就引出今天的第一个知识点：不同的跳转方式，对页面栈的处理是不一样的，用错了会出现"返回按钮消失"或者"返回了十几层还没到头"这类问题。

---

## 二、明确本课目标（约 5:00）

把目标写在白板上或直接翻到课件第 2 页，逐条念一遍：

- 掌握小程序页面导航的两种方式
- 理解tabBar的配置与使用
- 掌握页面间参数传递的方法
- 能够实现多页面应用
- 案例：案例2-3 婚礼邀请函

本次课 3 学时的安排是：**理论讲解 1 学时，上机实践 2 学时**——导航与传参讲解 1 学时，案例 2-3 婚礼邀请函实操 2 学时。

---

## 三、知识点讲解

### 幻灯片 3：页面导航方式

**【8:00 讲解】**

- 声明式导航: <navigator url="/pages/xxx">跳转</navigator>
- 编程式导航: wx.navigateTo / wx.redirectTo / wx.switchTab
**wx.navigateTo**

保留当前页, 可返回(最多10层)

**wx.redirectTo**

关闭当前页, 不可返回

**wx.switchTab**

跳转tabBar页面, 关闭其他所有页面


### 幻灯片 4：tabBar配置

**【13:00 讲解】**

- 在app.json中配置tabBar节点
- list数组: 最少2个, 最多5个tab
- 每个tab: pagePath / text / iconPath / selectedIconPath
- color / selectedColor / backgroundColor 样式配置
- position: bottom(默认) 或 top

### 幻灯片 5：页面传参

**【18:00 讲解】**

- 路径传参: /pages/detail?id=1&name=test
- 接收参数: onLoad(options) 中获取 options.id
- 编码: encodeURIComponent() 处理特殊字符
- 全局变量: getApp().globalData 共享数据
- 缓存传参: wx.setStorageSync / wx.getStorageSync

### 幻灯片 6：案例2-3 婚礼邀请函

**【23:00 讲解】**

- 使用tabBar实现首页/照片/故事/祝福4个页面
- 使用navigator实现页面跳转
- 使用路径传参传递照片/故事详情
- 自定义tabBar图标与样式
- 综合运用导航与传参完成婚礼邀请函

---

## 四、难点突破：用类比讲清楚

下面这几个点是学生最容易卡住的地方，抽象讲不通就换成类比。

### 页面栈

像一摞盘子。navigateTo 是往上再摞一个盘子，原来的还在下面，所以能返回；redirectTo 是把最上面那个换掉，摞的高度不变，所以返回不到原来那页；navigateBack 是把最上面的拿走。小程序规定这摞最多 10 个盘子。

### switchTab 的特殊性

tab 页面像商场的几个主力楼层，电梯直达。你在某个专柜逛（普通页面），一按电梯回主力楼层，专柜那条路径就断了——这就是 switchTab 会关闭所有非 tab 页面的原因。

### URL 传参

像寄快递时在包裹上贴便条。`/pages/detail?id=1` 就是"送到详情页，便条上写 id=1"。收件人（onLoad 的 options）撕下便条读内容。但便条能写的东西有限——传大段 JSON 会超长，也不能传对象。

---

## 五、代码演示环节

以下每一段都建议**现场敲**，不要直接贴完整代码。边敲边问"下一行该写什么"，让学生跟上节奏。

### 演示 1：页面导航方式

**文件：** `navigate.js`　**预期效果：** 导航方式对比

```js
// 方式一：声明式导航 navigator
<!-- 保留当前页，跳转到非tabBar页 -->
<navigator url="/pages/detail/detail?id=1">
查看详情
</navigator>

<!-- 关闭当前页，重定向 -->
<navigator url="/pages/login/login" open-type="redirect">
去登录
</navigator>

<!-- 跳转到 tabBar 页 -->
<navigator url="/pages/index/index" open-type="switchTab">
回首页
</navigator>

// 方式二：编程式导航 wx API
// 保留当前页，可返回（最多10层）
wx.navigateTo({
url: '/pages/detail/detail?id=1'
})

// 关闭当前页，不可返回
wx.redirectTo({
url: '/pages/login/login'
})

// 跳转到 tabBar 页（关闭所有非tabBar页）
wx.switchTab({
url: '/pages/index/index'
})

// 返回上一页（默认delta=1）
wx.navigateBack({ delta: 1 })

// ⚠️ navigateTo 最多10层页面栈
// tabBar 页只能用 switchTab 跳转
```

**讲解口径：**

页面导航分**声明式**（navigator组件）和**编程式**（wx API）两种。核心区别：**navigateTo**保留当前页可返回，最多10层；**redirectTo**关闭当前页不可返回；**switchTab**跳转tabBar页，会关闭所有非tabBar页。注意**tabBar页面只能用switchTab跳转**，不能用navigateTo。

### 演示 2：tabBar配置详解

**文件：** `app.json`　**预期效果：** tabBar效果预览

```json
{
"pages": [
"pages/home/home",
"pages/message/message",
"pages/contact/contact"
],

"tabBar": {
"color": "#999",
"selectedColor": "#07c160",
"backgroundColor": "#fff",
"borderStyle": "black",
"position": "bottom",

"list": [
{
"pagePath": "pages/home/home",
"text": "首页",
"iconPath": "images/home.png",
"selectedIconPath": "images/home-active.png"
},
{
"pagePath": "pages/message/message",
"text": "消息",
"iconPath": "images/msg.png",
"selectedIconPath": "images/msg-active.png"
},
{
"pagePath": "pages/contact/contact",
"text": "联系",
"iconPath": "images/contact.png",
"selectedIconPath": "images/contact-active.png"
}
]
}
}

// ⚠️ tabBar 配置规则
list 最少 2 个，最多 5 个
pagePath 必须在 pages 中已声明
图标仅支持本地图片，建议 81×81px
tabBar 页面不会触发 onUnload
```

**讲解口径：**

tabBar在app.json中配置，**list数组最少2项最多5项**。每项必须包含pagePath、text、iconPath和selectedIconPath。全局样式属性：**color**未选中文字颜色、**selectedColor**选中文字颜色、**backgroundColor**背景色。注意**pagePath必须在pages数组中已声明**，图标只支持本地图片建议81×81px，tabBar页面不会触发onUnload。

### 演示 3：页面传参三种方式

**文件：** `params.js`　**预期效果：** 传参方式对比

```js
// 方式一：URL 参数传递

// 发送方：URL 拼接参数
wx.navigateTo({
url: '/pages/detail/detail?id=1&name=张三'
})

// 接收方：onLoad 的 options 参数
Page({
onLoad(options) {
console.log(options.id)    // "1"
console.log(options.name)  // "张三"
}
})

// 方式二：globalData 全局数据

// app.js 中定义
App({
globalData: { token: '', userInfo: null }
})

// 页面中读取
const app = getApp()
console.log(app.globalData.userInfo)

// 方式三：本地缓存 Storage

// 存储数据
wx.setStorageSync('token', 'abc123')

// 读取数据
const token = wx.getStorageSync('token')

// 清除数据
wx.removeStorageSync('token')

// ⚠️ 三种方式对比
URL参数：简单页面间传值，仅onLoad可接收
globalData：全局共享数据，小程序运行期间有效
Storage：持久化存储，关闭小程序后仍存在
```

**讲解口径：**

页面传参有三种方式：**URL参数**最简单，通过navigateTo的url拼接参数，在目标页**onLoad(options)**接收，但仅限onLoad；**globalData**在App中定义全局数据，任意页面通过**getApp()**读取，小程序运行期间有效；**Storage**本地缓存，**关闭小程序后仍存在**，适合存储登录态等持久化数据。

### 演示 4：案例2-3 婚礼邀请函

**文件：** `wedding.wxml`　**预期效果：** 婚礼邀请函效果

```html
<!-- 案例2-3：婚礼邀请函 -->
<!-- app.json tabBar 配置 -->
{
"pages": [
"pages/home/home",
"pages/photo/photo",
"pages/map/map"
],
"tabBar": {
"color": "#999",
"selectedColor": "#e91e63",
"list": [
{ "pagePath": "pages/home/home", "text": "邀请" },
{ "pagePath": "pages/photo/photo", "text": "相册" },
{ "pagePath": "pages/map/map", "text": "导航" }
]
}
}

<!-- home.wxml 邀请页 -->
<view class="invite">
<image src="{{coverImg}}" mode="aspectFill" />
<view class="title">{{groom}} & {{bride}}</view>
<view class="date">{{date}}</view>

<!-- 带参数跳转到相册详情 -->
<navigator
url="/pages/photo/photo?albumId={{albumId}}"
open-type="switchTab">
查看相册
</navigator>

<!-- 跳转到地图导航 -->
<navigator
url="/pages/map/map"
open-type="switchTab">
宴会导航
</navigator>
</view>

// home.js 数据与传参
Page({
data: {
coverImg: "/images/cover.jpg",
groom: "小明",
bride: "小红",
date: "2025年10月1日",
albumId: "wedding2025"
}
})
```

**讲解口径：**

案例2-3婚礼邀请函综合运用了本课知识点：**tabBar配置**实现邀请、相册、导航三个底部标签页；**navigator声明式导航**配合open-type="switchTab"跳转tabBar页；**URL参数传递**将albumId传到相册页；**{{}}数据绑定**显示新郎新娘姓名和婚期。这是一个典型的多页面小程序结构。

---

## 六、常见错误与排查

上机环节学生一定会遇到下面这些问题，提前讲一遍能省掉大量重复答疑。

| # | 现象 | 原因与解决 |
|:-:|------|------------|
| 1 | navigateTo 跳 tab 页面失败 | tabBar 里配置的页面只能用 switchTab 跳，用 navigateTo 会静默失败。这是最高频的报错之一。 |
| 2 | 传中文参数乱码 | 中文和 & = 这类字符必须用 encodeURIComponent 编码，接收端再 decodeURIComponent。不编码时参数会被截断。 |
| 3 | 页面栈溢出 | 列表→详情→相关推荐→详情……这样连环跳很容易超过 10 层。链路深的场景应该用 redirectTo 或 navigateBack 控制层数。 |
| 4 | tabBar 图标不显示 | iconPath 必须是本地路径，不支持网络图片，也不支持字体图标；尺寸建议 81×81 px。 |

---

## 七、上机实践指引

本次课对应教材案例 1 个，源码已放在站点 `code/session05/` 下：

- **【案例2-3】婚礼邀请函** — 含 知识储备、案例实现、服务器端 共 3 个工程

建议顺序：先带学生跑一遍「知识储备」里的最小示例，确认每个知识点都能单独跑通，再进「案例实现」做完整案例。直接上完整案例，出错了不知道错在哪。

---

## 八、本课小结

回到课件小结页，逐条回顾：

- 页面导航方式
- tabBar配置详解
- 页面传参三种方式
- 案例2-3 婚礼邀请函

今天把导航和传参理清楚了，你就具备了做多页面应用的能力。

案例2-3 婚礼邀请函是本章的收尾，它把前三次课的数据绑定、Flex 布局、页面导航全用上了。做完这个案例，第2章就算过关了。挑战任务里让大家故意跳到第 11 层看报错，别嫌麻烦，亲眼见过报错以后就不会再踩。

---

## 九、布置作业

三档任务，基础必做，进阶建议做，挑战选做。

### 【基础】完成婚礼邀请函项目

按照案例2-3，完成包含首页、照片墙、婚礼信息三个tabBar页面的项目

### 【进阶】扩展婚礼邀请函

增加宾客祝福页面（非tabBar页），通过 navigateTo 跳转，使用 navigateBack 返回

### 【挑战】页面栈与传参调试

连续 navigateTo 跳转至第 11 层观察报错，改用 redirectTo 对比页面栈变化；传递含中文与 & 的参数，验证 encodeURIComponent 的必要性

---

> 配套资源：课件 `slides-v2/session05.pptx`　课堂视频 `videos-output/session05.mp4`　扩展阅读 `docs/第5次课_扩展阅读.md`
