# 第15次课 讲稿

> 课程：小程序开发
> 主题：小程序进阶与uni-app开发
> 教材：《微信小程序开发实战（第2版）》第7-8章
> 时长：3学时（理论 1 + 实践 2）
> 重点：Vant Weapp、自定义组件、uni-app跨平台
> 难点：自定义组件通信、uni-app与小程序差异

---

## 一、开场（0:00）

这是本学期最后一次授课，内容是第7、8两章：小程序进阶和 uni-app 跨平台。

今天的主题可以概括成一句话：**怎么少写代码**。

三个层次：第一层，别人写好的组件直接用——组件库（Vant Weapp）；第二层，自己写一次到处用——自定义组件；第三层，一套代码跑多个平台——uni-app。

最后我想留点时间做个总结讨论。学了一学期，回到第一次课那个问题：什么时候该用小程序，什么时候不该。经过十五次课的实践，你们现在的答案应该比开学时具体得多了。

---

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

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

- 掌握Vant Weapp组件库的使用
- 理解自定义组件的开发与通信
- 了解uni-app跨平台开发框架
- 能够使用uni-app创建跨平台项目
- 案例：案例7-1 自定义标签栏、案例7-2 电影列表、案例7-3 待办事项、案例8 uni-app 短视频

本次课 3 学时的安排是：**理论讲解 1 学时，上机实践 2 学时**——组件库与 uni-app 讲解 1 学时，跨平台改造实操 2 学时。

---

## 三、知识点讲解

### 幻灯片 3：Vant Weapp组件库

**【8:00 讲解】**

- npm安装: npm i @vant/weapp -S --production
- 构建npm: 工具 → 构建npm
- 在page.json中usingComponents引入组件
- 常用组件: Button/Cell/Dialog/Toast/Tab/SwipeCell
- 主题定制: 修改CSS变量覆盖默认样式

### 幻灯片 4：自定义组件

**【13:00 讲解】**

- 组件创建: 右键新建Component(4个文件)
- 组件通信: properties(父→子) + triggerEvent(子→父)
- 数据监听: observers 监听properties/data变化
- 组件生命周期: created/attached/ready/detached
- 插槽slot: 匿名插槽/具名插槽

### 幻灯片 5：uni-app入门

**【18:00 讲解】**

- uni-app: 使用Vue.js开发跨平台应用
- 一套代码 → 微信/支付宝/H5/App多端运行
- HBuilderX编辑器: 官方推荐开发工具
- 语法: 与小程序类似, 但使用Vue模板语法
- 条件编译: #ifdef MP-WEIXIN ... #endif

### 幻灯片 6：案例7-1/7-2/7-3 与 案例8 uni-app 短视频

**【23:00 讲解】**

**案例7-1 自定义标签栏**

custom-tab-bar 目录 + tabBar.custom:true 接管底部导航

**案例7-2 电影列表**

自定义组件封装电影卡片，properties 传入、triggerEvent 回传

**案例7-3 待办事项**

组件内 observers 监听数据变化，slot 支持外部内容定制

**案例8 uni-app 短视频**

用 Vue 语法一套代码同时编译到微信小程序与 H5

- 对比总结：原生开发与 uni-app 的适用场景、性能差异与迁移成本

---

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

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

### 组件库

像宜家的成品家具。自己打一个柜子能完全合心意，但要花三天；买成品半小时装好，只是颜色可能不完全是你想的。组件库就是成品家具——先用起来，实在不合适再定制。

### properties 与 triggerEvent

组件像一台带遥控器的电器。properties 是遥控器上的按钮（父传子，我告诉你该显示什么）；triggerEvent 是电器上的指示灯（子传父，我告诉你我这边发生了什么）。数据向下流，事件向上冒，这是组件通信的铁律。

### slot 插槽

像相框留的空位。相框（组件）定好了边框样式和大小，中间放谁的照片由使用者决定。这让一个组件能适应多种内容。

### uni-app 条件编译

像一份菜谱写了两个版本的注释："如果你用燃气灶，中火五分钟；如果你用电磁炉，180度四分钟。" 主体流程共用，差异部分用 #ifdef 分开，编译时按目标平台各取所需。

---

## 五、代码演示环节

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

### 演示 1：Vant Weapp组件库

**文件：** `vant-setup.js`　**预期效果：** Vant Weapp 常用组件

```js
// 1. 安装 Vant Weapp
// npm i @vant/weapp -S --production

// 2. 构建 npm
// 工具 → 构建 npm → 生成 miniprogram_npm

// 3. app.json 配置 usingComponents
{
usingComponents: {
"van-button": "@vant/weapp/button/index",
"van-cell": "@vant/weapp/cell/index",
"van-field": "@vant/weapp/field/index",
"van-tab": "@vant/weapp/tab/index",
"van-tabs": "@vant/weapp/tabs/index",
"van-toast": "@vant/weapp/toast/index"
}
}

<!-- 4. 使用组件 -->
<van-button type="primary"
size="small">主要按钮</van-button>

<van-button type="warning">警告按钮</van-button>

<!-- Cell 单元格 -->
<van-cell title="用户名" value="张三" />

<!-- Field 输入框 -->
<van-field label="手机号"
placeholder="请输入手机号" type="number" />

<!-- Toast 轻提示 -->
import Toast from '@vant/weapp/toast/toast'
Toast.success('操作成功')

// ⚠️ 使用注意
// 1. 安装后必须点「构建 npm」
// 2. usingComponents 路径要精确到 index
// 3. project.config.json 设置 packNpmManually
// 4. app.wxss 中可覆盖 CSS 变量定制主题
```

**讲解口径：**

Vant Weapp 是轻量小程序 UI 组件库。使用步骤：**npm install @vant/weapp** 安装，在开发者工具中点 **构建 npm** 生成 miniprogram_npm 目录，在 app.json 的 **usingComponents** 中注册组件，路径要精确到 index。常用组件：**van-button** 按钮、**van-cell** 单元格、**van-field** 输入框、**van-tabs** 标签页、**Toast** 轻提示。可在 app.wxss 中覆盖 CSS 变量定制主题色。

### 演示 2：自定义组件

**文件：** `custom-component.js`　**预期效果：** 自定义组件结构

```js
// 自定义组件：评分卡片 rate-card
// components/rate-card/rate-card.js
Component({
properties: {
title: { type: String, value: '' },
score: { type: Number, value: 0 },
image: { type: String, value: '' }
},

data: { innerScore: 0 },

observers: {
score: function(val) {
this.setData({ innerScore: val })
}
},

methods: {
onRate(e) {
const score = e.detail
this.triggerEvent('change', { score })
}
}
})

// components/rate-card/rate-card.wxml
<view class="rate-card">
<image src="{{image}}" />
<text>{{title}}</text>
<view class="stars">
<slot></slot>
</view>
</view>

// 使用自定义组件
{
usingComponents: {
"rate-card": "/components/rate-card/rate-card"
}
}

<rate-card title="菜品评分" score="{{4}}"
bind:change="onScoreChange">
<view>⭐⭐⭐⭐</view>
</rate-card>

// ⚠️ 组件关键点
// properties: 外部传入的数据（类似 props）
// data: 组件内部数据
// observers: 数据监听器（类似 watch）
// triggerEvent: 向父组件发送事件
// slot: 插槽，允许父组件传入内容
```

**讲解口径：**

自定义组件用 **Component** 构造器创建。**properties** 定义外部传入属性，类似 Vue 的 props，支持类型校验和默认值；**data** 是组件内部数据；**observers** 数据监听器，当 properties 或 data 变化时触发，类似 Vue 的 watch；**triggerEvent** 向父组件发送自定义事件，父组件用 **bind:事件名** 接收；**slot** 插槽允许父组件传入自定义内容到组件内部。

### 演示 3：uni-app入门

**文件：** `uniapp-intro.vue`　**预期效果：** uni-app 跨平台开发

```html
<!-- uni-app 基于 Vue.js 跨平台框架 -->
<!-- 一套代码 → 微信/支付宝/H5/App -->

<!-- pages/index/index.vue -->
<template>
<view class="container">
<text>{{title}}</text>
<button @click="handleClick">点击</button>
<!-- 条件编译：仅微信小程序 -->
<!-- #ifdef MP-WEIXIN -->
<button open-type="getUserInfo">微信登录</button>
<!-- #endif -->
<!-- 条件编译：仅H5 -->
<!-- #ifdef H5 -->
<a href="/login">网页登录</a>
<!-- #endif -->
</view>
</template>

<script>
export default {
data() { return { title: 'Hello uni-app' } },
methods: {
handleClick() {
uni.showToast({ title: '点击了' })
}
}
}
</script>

// 条件编译（JS中）
// #ifdef MP-WEIXIN
wx.getSystemInfoSync()
// #endif

// 条件编译（样式）
/* #ifdef H5 */
.container { max-width: 750rpx; }
/* #endif */

// ⚠️ uni-app vs 原生对比
// wx.xxx → uni.xxx（API兼容层）
// .wxml → .vue template
// .wxss → .vue style（支持scss）
// Page() → export default {}
// rpx单位通用，750rpx = 屏幕宽度
```

**讲解口径：**

uni-app 是基于 **Vue.js** 的跨平台框架，一套代码可编译到微信小程序、支付宝小程序、H5 和 App。**条件编译**是核心特性：用 **#ifdef MP-WEIXIN** 包裹仅微信小程序的代码，**#ifdef H5** 包裹仅网页的代码，编译时自动剔除其他平台代码。API 层面 **wx.xxx 替换为 uni.xxx**，兼容多端。开发工具用 HBuilderX，支持 .vue 单文件组件和 scss 预处理。

### 演示 4：案例7-1自定义标签栏+案例7-3待办事项+案例8短视频

**文件：** `cases.js`　**预期效果：** 三个案例效果

```js
// 案例7-1：自定义标签栏 custom-tabbar
// components/custom-tabbar/custom-tabbar.js
Component({
data: {
list: [
{ pagePath: '/pages/index/index', text: '首页', icon: 'home' },
{ pagePath: '/pages/menu/menu', text: '菜单', icon: 'menu' },
{ pagePath: '/pages/mine/mine', text: '我的', icon: 'user' }
],
active: 0
},

methods: {
switchTab(e) {
const { index, path } = e.currentTarget.dataset
this.setData({ active: index })
wx.switchTab({ url: path })
}
}
})

// 案例7-3：待办事项（uni-app）todo.vue
<template>
<view class="todo-app">
<view class="input-bar">
<input v-model="newTodo" placeholder="输入待办" />
<button @click="addTodo">添加</button>
</view>
<view v-for="(item, i) in todos" :key="i"
class="todo-item {{item.done ? 'done' : ''}}">
<checkbox :checked="item.done" @click="toggle(i)" />
<text>{{item.text}}</text>
<text class="del" @click="remove(i)">删除</text>
</view>
</view>
</template>

<script>
export default {
data() { return { newTodo: '', todos: [] } },
methods: {
addTodo() {
if (!this.newTodo.trim()) return
this.todos.push({ text: this.newTodo, done: false })
this.newTodo = ''
},
toggle(i) { this.todos[i].done = !this.todos[i].done },
remove(i) { this.todos.splice(i, 1) }
}
}
</script>

// 案例8：短视频列表（核心：video组件）
// <video src="{{item.url}}" autoplay loop />
// 全屏竖屏滑动：swiper + video 组件
// 监听 swiper change 暂停/播放视频
```

**讲解口径：**

案例7-1自定义标签栏：用 **Component** 创建 custom-tabbar 组件，list 定义标签项，switchTab 方法切换页面并更新 active 状态。案例7-3待办事项（uni-app）：用 **Vue.js** 语法，v-model 双向绑定输入，v-for 渲染列表，点击 checkbox 切换 done 状态，splice 删除项。案例8短视频：核心是 **swiper + video** 组件，竖屏全屏滑动，监听 swiper 的 change 事件控制视频暂停和播放。

---

## 六、常见错误与排查

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

| # | 现象 | 原因与解决 |
|:-:|------|------------|
| 1 | Vant Weapp 装了但组件不显示 | 漏了「工具 → 构建 npm」这一步，或者 page.json 里没在 usingComponents 声明。这两步都做了才生效。 |
| 2 | 组件里的样式不生效 | 组件默认样式隔离，外部的 class 影响不到组件内部。需要用 externalClasses 或设置 styleIsolation。 |
| 3 | 组件 properties 里的对象改了不更新 | 直接修改对象的某个字段不会触发子组件更新，要整体替换或用 observers 监听。 |
| 4 | 在组件里用 this.setData 改 properties | 不该这么做。properties 属于父组件的数据，子组件要改应该 triggerEvent 通知父组件去改。 |
| 5 | uni-app 代码在小程序端报错 | 用了浏览器专属 API（window、document）。跨端代码要避开平台专属能力，或者用条件编译隔离。 |

---

## 七、上机实践指引

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

- **【案例7-1】自定义标签栏** — 含 知识储备、案例实现 共 2 个工程
- **【案例7-2】电影列表** — 含 知识储备、案例实现 共 2 个工程
- **【案例7-3】待办事项** — 含 知识储备、案例实现 共 2 个工程
- **【案例8】uni-app项目 短视频** — 含 案例实现、服务器端 共 2 个工程

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

---

## 八、本课小结

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

- Vant Weapp组件库
- 自定义组件
- uni-app入门
- 案例7-1自定义标签栏+案例7-3待办事项+案例8短视频

一学期的内容到这里就全部讲完了。我们从"小程序是什么"开始，做到了一个完整的点餐系统，最后还看到了跨平台的路。

回顾一下你们这学期的产出：20 个教材案例、一个综合项目、15 次课的三档作业。这些东西整理一下，就是一份不错的作品集。

下次课是期末考试，理论 40 分 + 实操 60 分。理论部分覆盖全部十五次课，实操是做一个小型完整项目。复习的重点我放在第16次课的页面上了，回去按那个范围过一遍。

（留 10 分钟做技术选型的总结讨论）

---

## 九、布置作业

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

### 【基础】自定义tabBar

使用Vant Weapp实现自定义tabBar，替换原生tabBar，添加自定义图标

### 【进阶】uni-app待办事项

使用uni-app和Vue.js开发待办事项应用，体验响应式数据绑定和组件化开发

### 【挑战】跨平台短视频

使用uni-app开发短视频应用，分别编译到微信小程序和H5端运行

---

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