# 第8次课 讲稿

> 课程：小程序开发
> 主题：表单组件与数据提交
> 教材：《微信小程序开发实战（第2版）》第3章
> 时长：3学时（理论 1 + 实践 2）
> 重点：表单组件、表单验证、数据提交
> 难点：表单验证逻辑、数据格式处理

---

## 一、开场（0:00）

注册、登录、下单、问卷——这些功能有一个共同点：都要让用户填东西。今天讲表单。

表单这块知识点不难，但**琐碎**。input、textarea、radio、checkbox、picker、switch，一个一个都有自己的脾气。我不打算把每个组件的属性念一遍，那是文档的事。

今天真正要讲清楚的是三件事：**怎么把用户填的东西收上来、怎么判断填得对不对、怎么把它发给服务器**。收集、校验、提交，这条链路走通了，换什么组件都是查文档的事。

---

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

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

- 掌握常用表单组件的用法
- 理解表单验证的实现方式
- 掌握表单数据的收集与提交
- 能够实现完整的表单交互
- 案例：案例3-4 调查问卷

本次课 3 学时的安排是：**理论讲解 1 学时，上机实践 2 学时**——表单组件讲解 1 学时，案例 3-4 调查问卷实操 2 学时。

---

## 三、知识点讲解

### 幻灯片 3：常用表单组件

**【8:00 讲解】**

**input**

文本输入框(单行)

**textarea**

多行文本输入

**radio**

单选框(radio-group包裹)

**checkbox**

复选框(checkbox-group包裹)

**picker**

选择器(日期/时间/省市区/自定义)

**switch**

开关选择器


### 幻灯片 4：表单数据收集

**【13:00 讲解】**

- form组件: bindsubmit / bindreset
- button formType="submit" 触发提交
- e.detail.value 获取所有表单数据
- 需要给每个表单项设置name属性
- input事件实时获取: bindinput + e.detail.value

### 幻灯片 5：表单验证

**【18:00 讲解】**

- 非空验证: 检查必填项是否为空
- 格式验证: 手机号/邮箱等正则匹配
- 长度验证: 最小/最大字符数限制
- 自定义验证: 根据业务规则校验
- 提示方式: wx.showToast / wx.showModal

### 幻灯片 6：案例3-4 调查问卷

**【23:00 讲解】**

- 使用input收集姓名和联系方式
- 使用radio实现单选题
- 使用checkbox实现多选题
- 使用textarea收集开放性回答
- 表单验证+数据提交完成调查问卷

---

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

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

### form 组件与 name 属性

form 像一个信封，提交时它会把里面所有带 name 的表单项装进去。没写 name 的组件，相当于没贴标签的纸条，装进信封了也不知道是什么，所以取不到。

### 表单校验的顺序

像过安检：先看有没有票（非空），再看票是不是真的（格式），最后看能不能上这班车（业务规则）。顺序反了会让用户很困惑——一个空输入框弹"手机号格式不对"，体验就很差。

### 受控输入

小程序的 input 更像"半受控"：你设 value 它就显示，用户输入它自己也会变。要做到真正可控（比如限制只能输数字），必须在 bindinput 里过滤后再 setData 回去。

---

## 五、代码演示环节

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

### 演示 1：常用表单组件

**文件：** `form-components.wxml`　**预期效果：** 表单组件一览

```html
<!-- input 输入框 -->
<input type="text" placeholder="请输入姓名"
bindinput="onInput" value="{{name}}" />

<!-- textarea 多行输入 -->
<textarea placeholder="请输入备注" maxlength="200" />

<!-- radio 单选 -->
<radio-group bindchange="onRadioChange">
<label><radio value="male" checked="{{true}}"/>男</label>
<label><radio value="female"/>女</label>
</radio-group>

<!-- checkbox 多选 -->
<checkbox-group bindchange="onCheckChange">
<label><checkbox value="read"/>阅读</label>
<label><checkbox value="sport"/>运动</label>
<label><checkbox value="music"/>音乐</label>
</checkbox-group>

<!-- picker 选择器 -->
<picker mode="selector" range="{{cities}}"
bindchange="onCityChange">
当前选择：{{cities[index]}}
</picker>

<!-- switch 开关 -->
<switch checked="{{isAgree}}" bindchange="onSwitch" />
```

**讲解口径：**

小程序常用表单组件：**input** 单行输入框，支持 text/number/idcard 等类型；**textarea** 多行输入，可设 maxlength 限制字数；**radio-group** 单选组，同一组内只能选一个；**checkbox-group** 多选组，可同时选多个；**picker** 选择器，支持 selector/date/region 等模式；**switch** 开关，常用于协议勾选等场景。

### 演示 2：表单数据收集

**文件：** `form-submit.wxml`　**预期效果：** 表单提交效果

```html
<!-- 使用 form 统一收集数据 -->
<form bindsubmit="onSubmit">
<!-- 每个表单项必须有 name 属性 -->
<view class="form-item">
<text>姓名：</text>
<input name="username" placeholder="请输入" />
</view>

<view class="form-item">
<text>性别：</text>
<radio-group name="gender">
<label><radio value="male"/>男</label>
<label><radio value="female"/>女</label>
</radio-group>
</view>

<view class="form-item">
<text>爱好：</text>
<checkbox-group name="hobbies">
<label><checkbox value="read"/>阅读</label>
<label><checkbox value="sport"/>运动</label>
</checkbox-group>
</view>

<!-- form-type="submit" 触发表单提交 -->
<button form-type="submit">提交</button>
<button form-type="reset">重置</button>
</form>

// JS 中获取表单数据
onSubmit(e) {
// e.detail.value 包含所有 name 属性的值
const formData = e.detail.value
console.log(formData)
// { username: '张三', gender: 'male', hobbies: ['read'] }
}
```

**讲解口径：**

使用 **form** 组件的 **bindsubmit** 可以一次性收集所有表单数据。关键点是每个表单控件必须有 **name** 属性，提交时通过 **e.detail.value** 获取一个包含所有 name-value 的对象。radio-group 和 checkbox-group 的 name 放在 group 标签上，值分别是选中的 value 和 value 数组。按钮用 **form-type="submit"** 触发提交，form-type="reset" 触发重置。

### 演示 3：表单验证

**文件：** `validate.js`　**预期效果：** 表单验证效果

```js
// 表单验证工具函数
const validateForm = (rules, data) => {
for (let field in rules) {
const rule = rules[field]
const value = data[field]

// 必填验证
if (rule.required && !value) {
return { valid: false, msg: rule.message || `${field}不能为空` }
}

// 长度验证
if (rule.minLength && value.length < rule.minLength) {
return { valid: false, msg: `最少${rule.minLength}个字符` }
}

// 格式验证（正则）
if (rule.pattern && !rule.pattern.test(value)) {
return { valid: false, msg: rule.msg || '格式不正确' }
}
}
return { valid: true }
}

// 使用示例
onSubmit(e) {
const formData = e.detail.value
const rules = {
username: {
required: true, message: '请输入姓名'
minLength: 2
},
phone: {
required: true, message: '请输入手机号',
pattern: /^1[3-9]\d{9}$/, msg: '手机号格式错误'
}
}
const result = validateForm(rules, formData)
if (!result.valid) {
wx.showToast({ title: result.msg, icon: 'none' })
return
}
// 验证通过，提交数据
this.submitData(formData)
}
```

**讲解口径：**

表单验证是提交前必不可少的环节。常见验证类型：**required** 必填验证，检查值是否为空；**minLength** 长度验证，检查字符数；**pattern** 格式验证，用正则表达式校验手机号、邮箱等。验证不通过时，用 **wx.showToast** 提示用户错误信息，icon 设为 'none' 不显示图标。建议封装通用验证函数，通过规则配置对象来验证，提高代码复用性。

### 演示 4：案例3-4调查问卷

**文件：** `survey.wxml`　**预期效果：** 调查问卷页面

```html
<!-- 案例3-4：调查问卷 survey.wxml -->
<form bindsubmit="onSubmit">
<view class="form-item">
<text>姓名：</text>
<input name="name" placeholder="请输入姓名" />
</view>

<view class="form-item">
<text>性别：</text>
<radio-group name="gender">
<label><radio value="male"/>男</label>
<label><radio value="female"/>女</label>
</radio-group>
</view>

<view class="form-item">
<text>爱好：</text>
<checkbox-group name="hobbies">
<label><checkbox value="coding"/>编程</label>
<label><checkbox value="reading"/>阅读</label>
<label><checkbox value="gaming"/>游戏</label>
</checkbox-group>
</view>

<view class="form-item">
<text>建议：</text>
<textarea name="suggestion" placeholder="请输入建议" />
</view>

<button form-type="submit">提交问卷</button>
</form>

// survey.js
Page({
onSubmit(e) {
const { name, gender, hobbies, suggestion } = e.detail.value
if (!name || !gender) {
wx.showToast({ title: '请填写完整', icon: 'none' })
return
}
console.log('提交数据：', e.detail.value)
wx.showToast({ title: '提交成功' })
}
})
```

**讲解口径：**

案例3-4调查问卷综合运用了表单组件和数据提交。WXML 中用 **form** 包裹所有控件，每个控件设置 **name** 属性。JS 中 **onSubmit** 通过 **e.detail.value** 一次性获取所有表单数据，先做必填验证，不通过则 **wx.showToast** 提示，通过后提交数据并显示成功提示。这是表单开发的标准流程。

---

## 六、常见错误与排查

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

| # | 现象 | 原因与解决 |
|:-:|------|------------|
| 1 | form 提交拿不到某个字段 | 99% 是那个组件忘了写 name 属性。 |
| 2 | checkbox 取到的是数组 | checkbox-group 的 e.detail.value 是被选中项 value 组成的数组，不是布尔值。radio-group 才是单个字符串。 |
| 3 | picker 显示的是索引不是文字 | picker 的 e.detail.value 返回的是选中项的下标，要拿文字得自己去数组里取。 |
| 4 | wx.showToast 提示一闪而过 | toast 默认 1.5 秒，用于校验失败提示时字数别太多。较长的提示应该用 wx.showModal。 |
| 5 | 重复提交 | 按钮点两下就下了两单。提交前禁用按钮或加锁，这是上线前必查项。 |

---

## 七、上机实践指引

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

- **【案例3-4】调查问卷** — 含 知识储备、案例实现、服务器端 共 3 个工程

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

---

## 八、本课小结

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

- 常用表单组件
- 表单数据收集
- 表单验证
- 案例3-4调查问卷

今天的案例3-4 调查问卷，把六种表单组件用了个遍，是一次很好的"熟悉手感"的练习。

挑战任务是多步骤表单，它的难点不在组件，而在**状态怎么存**——上一步填的东西，切到下一步不能丢。想想应该放在 data 里还是 storage 里，为什么。

---

## 九、布置作业

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

### 【基础】完成调查问卷

按照案例3-4，完成调查问卷页面，包含各类表单组件与数据提交

### 【进阶】表单验证

增加表单验证逻辑：必填项检查、手机号格式验证、提交后清空表单

### 【挑战】多步骤表单

将调查问卷拆分为多个步骤页面，支持上一步/下一步导航，最终一次性提交

---

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