Vue.js 表单处理
v-model 双向绑定
v-model 是 Vue 提供的表单输入绑定指令,它可以在表单元素和数据之间创建双向绑定。
文本输入
vue
<script setup>
import { ref } from 'vue'
const message = ref('')
</script>
<template>
<div>
<input v-model="message" placeholder="请输入内容" />
<p>输入的内容:{{ message }}</p>
</div>
</template>多行文本
vue
<script setup>
import { ref } from 'vue'
const description = ref('')
</script>
<template>
<div>
<textarea v-model="description" placeholder="请输入描述"></textarea>
<p>字符数:{{ description.length }}</p>
<pre>{{ description }}</pre>
</div>
</template>复选框
单个复选框
vue
<script setup>
import { ref } from 'vue'
const agreed = ref(false)
</script>
<template>
<div>
<label>
<input type="checkbox" v-model="agreed" />
我同意服务条款
</label>
<p>{{ agreed ? '已同意' : '未同意' }}</p>
</div>
</template>多个复选框
vue
<script setup>
import { ref } from 'vue'
const hobbies = ref([])
</script>
<template>
<div>
<label><input type="checkbox" v-model="hobbies" value="reading" /> 阅读</label>
<label><input type="checkbox" v-model="hobbies" value="sports" /> 运动</label>
<label><input type="checkbox" v-model="hobbies" value="music" /> 音乐</label>
<label><input type="checkbox" v-model="hobbies" value="travel" /> 旅游</label>
<p>选中的爱好:{{ hobbies.join(', ') }}</p>
</div>
</template>单选按钮
vue
<script setup>
import { ref } from 'vue'
const gender = ref('')
</script>
<template>
<div>
<label><input type="radio" v-model="gender" value="male" /> 男</label>
<label><input type="radio" v-model="gender" value="female" /> 女</label>
<label><input type="radio" v-model="gender" value="other" /> 其他</label>
<p>选择的性别:{{ gender }}</p>
</div>
</template>下拉选择
单选
vue
<script setup>
import { ref } from 'vue'
const selected = ref('')
</script>
<template>
<div>
<select v-model="selected">
<option disabled value="">请选择</option>
<option value="apple">苹果</option>
<option value="banana">香蕉</option>
<option value="orange">橙子</option>
</select>
<p>选择的水果:{{ selected }}</p>
</div>
</template>多选
vue
<script setup>
import { ref } from 'vue'
const selected = ref([])
</script>
<template>
<div>
<select v-model="selected" multiple>
<option value="apple">苹果</option>
<option value="banana">香蕉</option>
<option value="orange">橙子</option>
<option value="grape">葡萄</option>
</select>
<p>选择的水果:{{ selected.join(', ') }}</p>
</div>
</template>v-model 修饰符
.lazy
默认情况下,v-model 在每次 input 事件后更新数据。使用 .lazy 修饰符可以改为在 change 事件后更新:
vue
<script setup>
import { ref } from 'vue'
const message = ref('')
</script>
<template>
<div>
<input v-model.lazy="message" placeholder="失去焦点后更新" />
<p>{{ message }}</p>
</div>
</template>.number
自动将用户输入转换为数字:
vue
<script setup>
import { ref } from 'vue'
const age = ref(0)
</script>
<template>
<div>
<input v-model.number="age" type="number" />
<p>年龄:{{ age }} (类型:{{ typeof age }})</p>
</div>
</template>.trim
自动去除首尾空格:
vue
<script setup>
import { ref } from 'vue'
const username = ref('')
</script>
<template>
<div>
<input v-model.trim="username" placeholder="自动去除空格" />
<p>用户名:'{{ username }}'</p>
</div>
</template>实战示例:用户注册表单
vue
<script setup>
import { ref, computed } from 'vue'
const form = ref({
username: '',
email: '',
password: '',
confirmPassword: '',
gender: '',
hobbies: [],
country: '',
agreed: false
})
const errors = ref({})
const isFormValid = computed(() => {
return Object.keys(errors.value).length === 0 &&
form.value.username &&
form.value.email &&
form.value.password &&
form.value.confirmPassword &&
form.value.agreed
})
function validateUsername() {
if (!form.value.username) {
errors.value.username = '用户名不能为空'
} else if (form.value.username.length < 3) {
errors.value.username = '用户名至少3个字符'
} else {
delete errors.value.username
}
}
function validateEmail() {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!form.value.email) {
errors.value.email = '邮箱不能为空'
} else if (!emailRegex.test(form.value.email)) {
errors.value.email = '邮箱格式不正确'
} else {
delete errors.value.email
}
}
function validatePassword() {
if (!form.value.password) {
errors.value.password = '密码不能为空'
} else if (form.value.password.length < 6) {
errors.value.password = '密码至少6个字符'
} else {
delete errors.value.password
}
validateConfirmPassword()
}
function validateConfirmPassword() {
if (!form.value.confirmPassword) {
errors.value.confirmPassword = '请确认密码'
} else if (form.value.password !== form.value.confirmPassword) {
errors.value.confirmPassword = '两次密码不一致'
} else {
delete errors.value.confirmPassword
}
}
function handleSubmit() {
validateUsername()
validateEmail()
validatePassword()
validateConfirmPassword()
if (isFormValid.value) {
console.log('表单数据:', form.value)
alert('注册成功!')
}
}
</script>
<template>
<form @submit.prevent="handleSubmit" class="register-form">
<h2>用户注册</h2>
<!-- 用户名 -->
<div class="form-group">
<label>用户名 *</label>
<input
v-model.trim="form.username"
@blur="validateUsername"
placeholder="请输入用户名"
/>
<span v-if="errors.username" class="error">{{ errors.username }}</span>
</div>
<!-- 邮箱 -->
<div class="form-group">
<label>邮箱 *</label>
<input
v-model.trim="form.email"
@blur="validateEmail"
type="email"
placeholder="请输入邮箱"
/>
<span v-if="errors.email" class="error">{{ errors.email }}</span>
</div>
<!-- 密码 -->
<div class="form-group">
<label>密码 *</label>
<input
v-model="form.password"
@blur="validatePassword"
type="password"
placeholder="请输入密码"
/>
<span v-if="errors.password" class="error">{{ errors.password }}</span>
</div>
<!-- 确认密码 -->
<div class="form-group">
<label>确认密码 *</label>
<input
v-model="form.confirmPassword"
@blur="validateConfirmPassword"
type="password"
placeholder="请再次输入密码"
/>
<span v-if="errors.confirmPassword" class="error">{{ errors.confirmPassword }}</span>
</div>
<!-- 性别 -->
<div class="form-group">
<label>性别</label>
<div class="radio-group">
<label><input type="radio" v-model="form.gender" value="male" /> 男</label>
<label><input type="radio" v-model="form.gender" value="female" /> 女</label>
<label><input type="radio" v-model="form.gender" value="other" /> 其他</label>
</div>
</div>
<!-- 爱好 -->
<div class="form-group">
<label>爱好</label>
<div class="checkbox-group">
<label><input type="checkbox" v-model="form.hobbies" value="reading" /> 阅读</label>
<label><input type="checkbox" v-model="form.hobbies" value="sports" /> 运动</label>
<label><input type="checkbox" v-model="form.hobbies" value="music" /> 音乐</label>
<label><input type="checkbox" v-model="form.hobbies" value="travel" /> 旅游</label>
</div>
</div>
<!-- 国家 -->
<div class="form-group">
<label>国家</label>
<select v-model="form.country">
<option value="">请选择</option>
<option value="china">中国</option>
<option value="usa">美国</option>
<option value="japan">日本</option>
<option value="korea">韩国</option>
</select>
</div>
<!-- 同意条款 -->
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" v-model="form.agreed" />
我已阅读并同意服务条款 *
</label>
</div>
<!-- 提交按钮 -->
<button type="submit" :disabled="!isFormValid">注册</button>
</form>
</template>
<style scoped>
.register-form {
max-width: 500px;
margin: 20px auto;
padding: 30px;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #fff;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: bold;
color: #333;
}
.form-group input[type="text"],
.form-group input[type="email"],
.form-group input[type="password"],
.form-group select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.form-group input:focus,
.form-group select:focus {
outline: none;
border-color: #42b983;
}
.error {
display: block;
color: #f56c6c;
font-size: 12px;
margin-top: 5px;
}
.radio-group,
.checkbox-group {
display: flex;
gap: 15px;
flex-wrap: wrap;
}
.radio-group label,
.checkbox-group label {
font-weight: normal;
display: flex;
align-items: center;
gap: 5px;
}
.checkbox-label {
font-weight: normal !important;
display: flex;
align-items: center;
gap: 8px;
}
button {
width: 100%;
padding: 12px;
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover:not(:disabled) {
background-color: #35a372;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
</style>实战示例:动态表单
vue
<script setup>
import { ref } from 'vue'
const formFields = ref([
{ id: 1, label: '姓名', value: '', type: 'text' }
])
let nextId = 2
function addField() {
formFields.value.push({
id: nextId++,
label: `字段 ${nextId - 1}`,
value: '',
type: 'text'
})
}
function removeField(id) {
formFields.value = formFields.value.filter(field => field.id !== id)
}
function handleSubmit() {
const data = formFields.value.reduce((acc, field) => {
acc[field.label] = field.value
return acc
}, {})
console.log('表单数据:', data)
alert(JSON.stringify(data, null, 2))
}
</script>
<template>
<div class="dynamic-form">
<h2>动态表单</h2>
<div v-for="field in formFields" :key="field.id" class="field-row">
<input v-model="field.label" placeholder="字段名称" class="field-label" />
<input v-model="field.value" :placeholder="`请输入${field.label}`" class="field-value" />
<button @click="removeField(field.id)" class="remove-btn">删除</button>
</div>
<div class="actions">
<button @click="addField" class="add-btn">添加字段</button>
<button @click="handleSubmit" class="submit-btn">提交</button>
</div>
</div>
</template>
<style scoped>
.dynamic-form {
max-width: 800px;
margin: 20px auto;
padding: 20px;
}
.field-row {
display: flex;
gap: 10px;
margin-bottom: 10px;
}
.field-label {
width: 150px;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.field-value {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.remove-btn {
padding: 8px 16px;
background-color: #f56c6c;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.actions {
display: flex;
gap: 10px;
margin-top: 20px;
}
.add-btn,
.submit-btn {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
color: white;
}
.add-btn {
background-color: #409eff;
}
.submit-btn {
background-color: #42b983;
}
</style>总结
v-model实现表单元素的双向绑定- 支持文本、复选框、单选按钮、下拉选择等多种表单元素
- 使用修饰符
.lazy、.number、.trim优化输入处理 - 结合验证逻辑创建完整的表单系统
- 可以创建动态表单满足复杂需求
下一步
接下来学习 组件通信,了解父子组件之间如何传递数据。