第14次 · 表单控件:输入框、下拉、单选、多选
第14次 · 表单控件:输入框、下拉、单选、多选
第1/7页 · 先知道今天要做什么
第 6 课你学过 v-model:它让数据和界面双向同步。今天要做的,是把 v-model 接到 Element Plus 的各类输入控件上,并且在选控件时问自己一个问题:这个数据最合适的输入方式是什么?
同样是“选择”这件事,就可能用到六种控件:下拉框、单选、多选、开关、滑块、评分。选错了,用户就会难受;选对了,用户会觉得“这个系统挺顺手”。控件选型是前端最容易体现专业度的地方。
今天的任务是把八种常用输入控件全部过一遍,并记住一句话:数据是什么类型,就选什么控件。
今天的场景
做一个“学员信息录入卡”。八个字段,八种控件,右侧实时显示录入结果。你要能说出每个字段为什么用这个控件。
下课时拿出这三样结果
| 做到什么 | 在哪里看 | 完成的证据 |
|---|---|---|
| 会用八种输入控件 | 录入卡 | 八种控件都能改到数据,右侧快照同步变化。 |
| 会做多选与可搜索下拉 | 报名方向、班级 | 多选返回数组;filterable 时能输入关键字过滤;multiple 时能折叠标签。 |
| 能说清控件选型依据 | 讲解与自查 | 每个字段都能说出“为什么不用另一种控件”。 |
你只需要带着这些基础来
第 6 课的 v-model 与绑定类型(字符串 / 数组 / 布尔 / 数字),第 13 课的按钮。
本课时 45 分钟怎么上
| 时间 | 老师带着做 | 学生留下什么 |
|---|---|---|
| 0—6 分钟 | 给出六个字段的数据类型,让学生先自己猜该用什么控件。 | 一张“字段 → 控件”的猜答案。 |
| 6—24 分钟 | 讲输入框家族:普通、数字、多行、密码、带前缀后缀、字数限制。 | 会按数据类型选输入框。 |
| 24—38 分钟 | 讲选择家族:下拉(单选/多选/可搜索/分组)、单选、多选、开关、滑块。 | 会写四种选择控件。 |
| 38—43 分钟 | 粘贴本课 App.vue,逐项改值看快照。 |
八种控件全部跑通。 |
| 43—45 分钟 | 抽查:问“报名方向为什么不能用单选”。 | 全员提交。 |
从哪里开始
复制第 13 课的工程,改名 lesson-vue-14。不需要新增依赖。
今天先掌握到这里
第 15 课讲表格(展示数据),第 16 课讲反馈组件(消息提示)。表单校验从第 17 课才开始——本课只做录入,不做校验。
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档 · 组件依据 Element Plus 2.8 官方文档
第2/7页 · 把新词说清楚
控件选型对照表(本课最重要的一张表)
| 数据特征 | 该用的控件 | 绑定的类型 |
|---|---|---|
| 任意短文本(姓名、学号) | el-input |
字符串 |
| 数字,且有范围 | el-input-number |
数字 |
| 长文本(备注、说明) | el-input type="textarea" |
字符串 |
| 密码 | el-input type="password" show-password |
字符串 |
| 从固定几项里选一个(少于 5 项) | el-radio-group |
字符串/数字 |
| 从固定几项里选一个(多于 5 项) | el-select |
字符串/数字 |
| 从固定几项里选多个 | el-checkbox-group 或 el-select multiple |
数组 |
| 是 / 否 | el-switch 或 el-radio-group |
布尔 |
| 一个范围内的数值(比例、评分) | el-slider |
数字 |
| 星级评价(1—5) | el-rate |
数字 |
一条实操建议:选项少于 5 个用单选按钮,多于 5 个用下拉框。 单选按钮把所有选项都摊开,用户一眼看到全部;下拉框节省空间,但要点击才能看到。
输入框家族的常用属性
| 属性 | 作用 | 什么时候用 |
|---|---|---|
placeholder |
空值时显示的提示文字 | 基本都要写,说明该填什么 |
clearable |
显示一键清空按钮 | 可选的输入 |
maxlength + show-word-limit |
限制长度并显示计数 | 备注、简介 |
prefix-icon / suffix-icon |
前后缀图标 | 搜索框加放大镜 |
disabled / readonly |
禁用 / 只读 | 有条件不可编辑时 |
type="textarea" + :rows |
多行文本 | 长文本 |
show-password |
密码可见开关 | 密码输入 |
disabled 与 readonly 的区别:disabled 是“灰掉、不可点、不提交”;readonly 是“可以选中复制,但不能改”。需要用户复制内容时用 readonly。
下拉框的四个常用能力
| 写法 | 效果 |
|---|---|
filterable |
可输入关键字过滤(选项多时必备) |
multiple |
多选(值变成数组) |
collapse-tags |
多选时把标签折叠成 +N,避免撑开 |
clearable |
允许清空 |
el-option-group |
给选项分组显示 |
<el-select v-model="dirs" multiple filterable collapse-tags placeholder="可多选" style="width: 100%">
<el-option-group label="前端方向">
<el-option label="Vue" value="vue" />
<el-option label="React" value="react" />
</el-option-group>
<el-option-group label="后端方向">
<el-option label="Java" value="java" />
<el-option label="Go" value="go" />
</el-option-group>
</el-select>
单选 / 多选的两个细节
el-radio的value是新写法(老版本叫label,容易和显示文字混在一起,现在已经分开)。推荐一律写value;el-checkbox-group的v-model必须是数组,这是最常见的错误来源。
只看这几行:类型匹配
const form = reactive({
name: '', // 字符串 → el-input
age: 18, // 数字 → el-input-number
tags: [], // 数组 → el-checkbox-group / el-select multiple
enabled: true // 布尔 → el-switch
})
先口头预测:如果把 tags 从 [] 改成 ''(空字符串),会发生什么?
多选框会报错或行为异常。 el-checkbox-group 要求 v-model 是一个数组,它需要往里面 push / 移除元素。给字符串它就没法工作。这就是“绑定类型必须匹配控件”的最直接例子。
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档 · 组件依据 Element Plus 2.8 官方文档
第3/7页 · 先看一个小例子
完整代码:App.vue(学员信息录入卡)
<script setup>
import { ref, reactive, computed } from 'vue'
import {
ElConfigProvider, ElCard, ElRow, ElCol, ElForm, ElFormItem, ElInput,
ElInputNumber, ElSelect, ElOption, ElOptionGroup, ElRadioGroup, ElRadio,
ElCheckboxGroup, ElCheckbox, ElSwitch, ElSlider, ElRate, ElButton,
ElDivider, ElDescriptions, ElDescriptionsItem, ElAlert, ElSpace, ElTag,
ElMessage
} from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import 'element-plus/dist/index.css'
const form = reactive({
name: '张三', // 字符串 → el-input
sno: '2026001', // 字符串 → el-input
age: 18, // 数字 → el-input-number
gender: '男', // 单选 → el-radio-group
cls: '', // 单选 → el-select
directions: [], // 多选 → el-select multiple
skills: ['vue'], // 多选 → el-checkbox-group
enabled: true, // 布尔 → el-switch
progress: 60, // 数字 → el-slider
rating: 4, // 数字 → el-rate
password: '', // 字符串 → el-input show-password
remark: '' // 长文本 → textarea
})
const skillOptions = ['vue', 'react', 'java', 'python', 'sql']
const dirOptions = [
{ label: '前端方向', items: [{ label: 'Vue 开发', value: 'vue' }, { label: 'React 开发', value: 'react' }] },
{ label: '后端方向', items: [{ label: 'Java 后端', value: 'java' }, { label: 'Go 后端', value: 'go' }] },
{ label: '数据方向', items: [{ label: '数据分析', value: 'data' }, { label: '数据库运维', value: 'dba' }] }
]
// 实时快照:检验每个控件到底写了什么类型进去
const snapshot = computed(() => ({
name: form.name,
类型_name: typeof form.name,
age: form.age,
类型_age: typeof form.age,
directions: form.directions,
类型_directions: Array.isArray(form.directions) ? 'array' : typeof form.directions,
skills: form.skills,
enabled: form.enabled,
类型_enabled: typeof form.enabled,
progress: form.progress,
rating: form.rating
}))
const remarkLen = computed(() => form.remark.length)
function submit() {
ElMessage.success('已收集:「' + form.name + '」,方向 ' + form.directions.length + ' 个')
console.log('提交快照:', JSON.parse(JSON.stringify(form)))
}
function reset() {
Object.assign(form, {
name: '', sno: '', age: 18, gender: '男', cls: '', directions: [],
skills: [], enabled: true, progress: 50, rating: 0, password: '', remark: ''
})
ElMessage.info('已重置')
}
</script>
<template>
<el-config-provider :locale="zhCn">
<div class="page">
<h1 class="title">学员信息录入</h1>
<el-row :gutter="16">
<el-col :xs="24" :md="14">
<el-card>
<template #header>八种控件(每个都改一下试试)</template>
<el-form label-width="96px" @submit.prevent>
<el-divider content-position="left">文本类</el-divider>
<el-form-item label="姓名">
<el-input v-model.trim="form.name" placeholder="请输入姓名" clearable />
</el-form-item>
<el-form-item label="学号">
<el-input v-model.trim="form.sno" placeholder="只读示例:可以选中复制" readonly />
</el-form-item>
<el-form-item label="密码">
<el-input v-model="form.password" type="password" show-password placeholder="试试右侧的小眼睛" />
</el-form-item>
<el-form-item label="年龄">
<el-input-number v-model="form.age" :min="10" :max="60" @change="v => ElMessage.info('年龄变成数字:' + v + '(' + typeof v + ')')" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" :rows="3" maxlength="100" show-word-limit placeholder="最多 100 字" />
</el-form-item>
<el-divider content-position="left">选择类</el-divider>
<el-form-item label="性别">
<!-- 选项少于 5 个:用单选按钮,一眼看全 -->
<el-radio-group v-model="form.gender">
<el-radio value="男">男</el-radio>
<el-radio value="女">女</el-radio>
<el-radio value="保密">保密</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="班级">
<!-- 单选 + 可搜索 -->
<el-select v-model="form.cls" filterable clearable placeholder="可输入关键字过滤" style="width: 100%">
<el-option v-for="i in 6" :key="i" :label="'计算机' + i + '班'" :value="'计算机' + i + '班'" />
</el-select>
</el-form-item>
<el-form-item label="报名方向">
<!-- 多选 + 分组 + 标签折叠:值一定是数组 -->
<el-select v-model="form.directions" multiple filterable collapse-tags placeholder="可多选" style="width: 100%">
<el-option-group v-for="g in dirOptions" :key="g.label" :label="g.label">
<el-option v-for="it in g.items" :key="it.value" :label="it.label" :value="it.value" />
</el-option-group>
</el-select>
</el-form-item>
<el-form-item label="已掌握技能">
<!-- 多选框组:值必须是数组 -->
<el-checkbox-group v-model="form.skills">
<el-checkbox v-for="s in skillOptions" :key="s" :value="s">{{ s }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="是否启用">
<el-switch v-model="form.enabled" active-text="启用" inactive-text="停用" />
</el-form-item>
<el-form-item label="学习进度">
<el-slider v-model="form.progress" show-input style="width: 100%" />
</el-form-item>
<el-form-item label="课程评分">
<el-rate v-model="form.rating" show-score allow-half />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">提交</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</el-col>
<el-col :xs="24" :md="10">
<el-card class="mb">
<template #header>实时类型快照(这是本课的关键)</template>
<pre class="json">{{ JSON.stringify(snapshot, null, 2) }}</pre>
<el-alert
title="重点看:name / age / directions / skills / enabled 分别是什么类型。控件不同,写进数据的类型就不同。"
type="info" :closable="false" show-icon
/>
</el-card>
<el-card>
<template #header>字段速览</template>
<el-descriptions :column="1" border size="small">
<el-descriptions-item label="姓名">{{ form.name || '—' }}</el-descriptions-item>
<el-descriptions-item label="年龄">{{ form.age }}({{ typeof form.age }})</el-descriptions-item>
<el-descriptions-item label="班级">{{ form.cls || '未选' }}</el-descriptions-item>
<el-descriptions-item label="报名方向">
<el-tag v-for="d in form.directions" :key="d" size="small" style="margin-right: 4px">{{ d }}</el-tag>
<span v-if="!form.directions.length">未选</span>
</el-descriptions-item>
<el-descriptions-item label="技能">{{ form.skills.join('、') || '未选' }}</el-descriptions-item>
<el-descriptions-item label="启用">{{ form.enabled ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item label="备注字数">{{ remarkLen }} / 100</el-descriptions-item>
</el-descriptions>
</el-card>
</el-col>
</el-row>
</div>
</el-config-provider>
</template>
<style>
html, body, #app {
margin: 0; padding: 0; max-width: none; width: auto; height: auto;
display: block; place-items: initial; text-align: left; background: #f5f7fa;
}
</style>
<style scoped>
.page { max-width: 1150px; margin: 28px auto; padding: 0 16px; }
.title { font-size: 22px; color: #303133; margin: 0 0 18px; }
.mb { margin-bottom: 16px; }
.json {
background: #1f2430; color: #a9dc76; padding: 12px; border-radius: 6px;
font-size: 12px; line-height: 1.7; margin: 0 0 12px; max-height: 420px; overflow: auto;
}
</style>
预期看见什么
- 左边一张长表单,用两条分割线分成“文本类”和“选择类”。
- 右边一张卡实时显示各字段的类型快照,另一张卡是字段速览。
- 在姓名框输入,快照里
name是字符串;改年龄,age是 数字;勾选两个技能,skills是 数组;切换开关,enabled是 布尔。 - “报名方向”下拉:输入“vue”能过滤;选三项后显示成
+2折叠标签。 - 备注框输入时右下角字数实时增加。
- 点“提交”,Console 里打印完整快照。
先猜一猜,读完代码再回答
我把“学号”设成了 readonly 而不是 disabled,为什么?
<el-input v-model.trim="form.sno" readonly />
因为学号是用户需要看到、需要复制的东西。 disabled 会让输入框灰掉且无法选中文字,用户想复制作业号都做不到;readonly 允许选中与复制,只是不让改。这就是“要不要让用户复制”这个问题的正确答案。 反过来,如果这个字段是“系统自动生成、完全不该被人碰”(比如数据库主键),那就用 disabled。
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档 · 组件依据 Element Plus 2.8 官方文档
第4/7页 · 一步一步做出来
第1步:从最简单的输入框开始
<el-input v-model="name" placeholder="请输入姓名" clearable />
预期:能输入、能一键清空。
第2步:加字数限制与计数
<el-input v-model="remark" type="textarea" :rows="3" maxlength="100" show-word-limit />
预期:输入到 100 字无法再输入,右下角显示 100/100。
第3步:数字输入与类型验证
<el-input-number v-model="age" :min="10" :max="60" @change="v => console.log(typeof v)" />
预期:Console 打印 number。再换成普通 el-input 不加 .number,预期打印 string——这就是两种控件的本质区别。
第4步:三种选择控件
单选(选项少)、下拉(选项多)、多选(要选多个) 各写一个。预期:多选控件的值在快照里是数组;把 v-model 的初始值从 [] 改成 '',预期:报错或异常,验证“类型必须匹配”。
第5步:多选下拉的四个能力
依次加上 multiple、filterable、collapse-tags、clearable,每加一个观察一次变化。预期:能多选、能搜索、标签会折叠、能清空。
第6步:滑块与评分
<el-slider v-model="progress" show-input />
<el-rate v-model="rating" show-score allow-half />
预期:滑块带数字输入框且双向联动;评分可以打半星。
第7步:整段替换为本课完整版
把第 3 页的完整 App.vue 覆盖 src/App.vue,逐项验证八种控件。
本课文件速查
| 位置 | 本次作用 | 绑定类型 |
|---|---|---|
form.name |
普通文本输入 | 字符串 |
form.age |
数字输入框 | 数字 |
form.directions |
多选下拉(分组 + 折叠) | 数组 |
form.skills |
多选框组 | 数组 |
form.enabled |
开关 | 布尔 |
form.progress / form.rating |
滑块 / 评分 | 数字 |
snapshot |
实时显示每个字段的类型 | — |
完成修改后检查
| 操作或输入 | 预期观察 |
|---|---|
| 改年龄 | 快照里 类型_age 是 number。 |
| 勾两个技能 | skills 是长度为 2 的数组。 |
| 下拉选三项 | 显示成折叠标签 +2。 |
| 输入超过 100 字 | 输入被截住,计数显示 100/100。 |
| 点“重置” | 所有控件回到初始状态。 |
| 在“学号”框里选中文字 | 可以选中复制(因为是 readonly)。 |
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档 · 组件依据 Element Plus 2.8 官方文档
第5/7页 · 换成自己的代码
你的任务
- 做一张自己的录入表单,至少 10 个字段,必须覆盖:字符串、数字、数组、布尔四种类型。
- 刻意做一次错误实验:把某个多选控件的初值改成非数组,记录现象。
- 加一个“控件选型说明”:在表单每个字段旁边加一行小字,写“为什么用这个控件”(例如“选项只有 3 个,用单选按钮一眼看全”)。
- 加一个联动禁用:当“是否启用”关闭时,把“学习进度”和“课程评分”设为禁用(
:disabled="!form.enabled"),并说明理由。 - 做一个字段速览表:用
el-descriptions把当前数据完整展示出来,包括数组用join拼接。
按这张操作单完成
| 步骤 | 改哪里 | 运行后应该看到什么 |
|---|---|---|
| 1 十字段表单 | 模板 | 四种类型的字段都有。 |
| 2 错误实验 | 多选初值 | 记录报错或异常现象。 |
| 3 选型说明 | 字段旁小字 | 每个字段一句理由。 |
| 4 联动禁用 | :disabled |
关闭开关后两个控件变灰。 |
| 5 速览表 | el-descriptions |
当前数据完整可读。 |
最低完成标准:先把这三项做实
| 检查项 | 通过条件 |
|---|---|
| 类型正确 | 多选是数组、数字控件给数字、开关给布尔,能在快照里验证。 |
| 控件选型合理 | 选项 ≤5 个用单选按钮,>5 个用下拉,多选一律用数组。 |
| 都改得动 | 每个控件都能真正改到数据(不是只显示)。 |
在本地写练习记录
- 画出“字段 → 控件 → 数据类型”的对照表。
- 记录多选初值写错时的报错原文。
- 写下
readonly与disabled的选择理由(你的表单里哪几个用哪个)。
最后交什么
提交 src/App.vue 与 练习记录.md。
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档 · 组件依据 Element Plus 2.8 官方文档
第6/7页 · 出错时从哪里查
把现象和检查动作对上
| 看到什么现象 / 报错 | 先检查哪里 | 修好后怎样确认 |
|---|---|---|
| 多选控件选不动 / 报错 | v-model 绑的不是数组。 |
改成 [] 后正常。 |
| 数字框拿到的是字符串 | 用了 el-input 而没加 .number。 |
换 el-input-number 或在 v-model 后加 .number。 |
el-radio 不显示文字 |
把显示文字写到了 label 上又同时用了 value。 |
显示文字放标签中间,值放 value。 |
el-select 选项不显示 |
el-option 忘了写 label,或 value 与数据对不上。 |
补上后正常。 |
| 下拉选项很多但没搜索 | 忘了 filterable。 |
加上后可搜索。 |
| 多选标签把布局撑开 | 忘了 collapse-tags。 |
加上后折叠成 +N。 |
readonly 输入框还能被改 |
写成了 :readonly="'false'"(字符串恒为真)或写错属性名。 |
用 readonly 或 :readonly="bool值"。 |
| 输入框里汉字输入时计数不对 | 用了 .lazy 导致更新滞后。 |
去掉 .lazy 后计数实时。 |
排错口诀
多选必是数组,开关必是布尔,数字用 number 控件; 选项五个以内用单选,多了用下拉; 要给用户复制就用 readonly,彻底不让碰才用 disabled; 下拉难用先看三件套:filterable、clearable、collapse-tags。
不要用这些办法“修复”
- 不要为了让多选控件“能用”而把数组改成字符串。数据一旦类型错了,后面做统计、提交接口都会陆续出问题。
- 不要给整个表单的每个控件都加
clearable。像“姓名”这种必填项,清空按钮只是给用户添乱。 - 不要用
el-input加一堆正则去模拟数字框。el-input-number已经处理好了上下限、步进和输入校验。
需要老师帮忙时交这些
出问题那个控件的完整标签、对应数据的定义(初值是什么)、控制台报错原文、你期望的数据类型与实际得到的类型。
老师巡视时只问三句话
- 这个字段为什么用这种控件?(能说出选型依据)
- 它绑的数据是什么类型?怎么验证的?(能指出快照里的类型)
- 如果要选 20 个方向,你还会用现在这个控件吗?(能说出改用多选下拉 + 搜索)
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档 · 组件依据 Element Plus 2.8 官方文档
第7/7页 · 用自己的话讲一遍
回到第 3 页:你刚才猜对了吗?
为什么学号用 readonly 而不是 disabled?
参考解释:学号是需要用户“看到并复制”的信息,disabled 会让文本无法选中;readonly 保持可选中、可复制,只是不可编辑。是否禁用要看用户对这个字段还有没有别的需求(复制、查看),而不只是“能不能改”。
回答本课的两个问题
| 问题 | 参考解释 |
|---|---|
| 单选按钮与下拉框怎么选? | 看选项数量与使用频率。选项少于 5 个、或需要用户一眼看全时用单选按钮;选项多、需要搜索时用下拉框。 |
| 为什么多选一定是数组? | 因为“多选”的结果天然可能是零个、一个或多个;只有数组能同时表示这三种情况。 |
五个必须能答上来的问题
| 考考自己 | 可以这样通俗回答 |
|---|---|
el-input-number 和 el-input 的区别? |
前者给的是数字,自带上下限与步进;后者给的是字符串。 |
| 下拉框三个必备属性是什么? | filterable(可搜索)、clearable(可清空)、多选时加 collapse-tags。 |
| 是 / 否该用什么? | 用 el-switch;如果两者不是“开关式”的语义(如“男/女/保密”),就用 el-radio-group。 |
| 长文本用什么? | el-input type="textarea",按需加 maxlength 与 show-word-limit。 |
| 如何一眼看出控件写进去的是什么类型? | 页面上放一个快照区,用 typeof / Array.isArray 打印出来。 |
一分钟现场演示指南
- 演示类型:改年龄、勾多选、切开关,指着快照说“三处类型完全不同”。
- 演示选型:把班级下拉临时改成单选按钮,指出“6 个选项摊开后太占地方,所以用下拉”。
- 演示 readonly / disabled:现场选中文字复制,说明差别。
- 收尾一句:“控件选型不是为了好看,是为了让用户少犯错。”
交作业前最后检查
src/App.vue与练习记录.md已保存。- 四种数据类型(字符串 / 数字 / 数组 / 布尔)都有对应控件。
- 多选控件的初值是数组。
- 每个控件都能真正改到数据。
- Console 无红色报错。
课后再做一个小对照
把“报名方向”从多选下拉改成多选框组(el-checkbox-group),再改回下拉。写下两种方式的优劣:什么时候摊开更好(选项少、需要强调),什么时候收起更好(选项多、节省空间)。
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档 · 组件依据 Element Plus 2.8 官方文档