表单组件详解 - 输入框、选择器、开关按钮
📝 表单是用户与应用交互的重要方式,今天我们来学习各种表单组件
1. input - 输入框
input
是最常用的表单组件:
基本用法
vue
<template>
<view class="form-demo">
<!-- 基础输入框 -->
<input
v-model="username"
placeholder="请输入用户名"
class="input-item"
/>
<!-- 密码输入框 -->
<input
v-model="password"
type="password"
placeholder="请输入密码"
class="input-item"
/>
<!-- 数字输入框 -->
<input
v-model="phone"
type="number"
placeholder="请输入手机号"
maxlength="11"
class="input-item"
/>
<!-- 带事件的输入框 -->
<input
v-model="email"
type="text"
placeholder="请输入邮箱"
@input="onEmailInput"
@blur="onEmailBlur"
@focus="onEmailFocus"
class="input-item"
/>
</view>
</template>
<script>
export default {
data() {
return {
username: '',
password: '',
phone: '',
email: ''
}
},
methods: {
onEmailInput(e) {
console.log('邮箱输入中:', e.detail.value)
},
onEmailBlur(e) {
console.log('邮箱失去焦点:', e.detail.value)
this.validateEmail(e.detail.value)
},
onEmailFocus(e) {
console.log('邮箱获得焦点')
},
validateEmail(email) {
const emailReg = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (email && !emailReg.test(email)) {
uni.showToast({
title: '邮箱格式不正确',
icon: 'error'
})
}
}
}
}
</script>
<style>
.form-demo {
padding: 20px;
}
.input-item {
border: 1px solid #ddd;
border-radius: 8px;
padding: 12px 15px;
margin-bottom: 15px;
font-size: 16px;
}
</style>
2. picker - 选择器
picker
用于从预设选项中选择:
普通选择器
vue
<template>
<view class="picker-demo">
<!-- 单列选择器 -->
<picker
:range="cities"
:value="cityIndex"
@change="onCityChange"
>
<view class="picker-item">
<text>选择城市:\{\{ cities[cityIndex] \}\}</text>
<text class="arrow">></text>
</view>
</picker>
<!-- 多列选择器 -->
<picker
mode="multiSelector"
:range="regions"
:value="regionIndex"
@change="onRegionChange"
>
<view class="picker-item">
<text>选择地区:\{\{ selectedRegion \}\}</text>
<text class="arrow">></text>
</view>
</picker>
</view>
</template>
<script>
export default {
data() {
return {
cities: ['北京', '上海', '广州', '深圳', '杭州'],
cityIndex: 0,
regions: [
['广东省', '浙江省', '江苏省'],
['广州市', '深圳市', '珠海市'],
['天河区', '越秀区', '海珠区']
],
regionIndex: [0, 0, 0]
}
},
computed: {
selectedRegion() {
const [provinceIndex, cityIndex, districtIndex] = this.regionIndex
return `\${this.regions[0][provinceIndex]} \${this.regions[1][cityIndex]} \${this.regions[2][districtIndex]}`
}
},
methods: {
onCityChange(e) {
this.cityIndex = e.detail.value
console.log('选择的城市:', this.cities[this.cityIndex])
},
onRegionChange(e) {
this.regionIndex = e.detail.value
console.log('选择的地区:', this.selectedRegion)
}
}
}
</script>
时间和日期选择器
vue
<template>
<view class="time-picker-demo">
<!-- 日期选择器 -->
<picker
mode="date"
:value="selectedDate"
start="2020-01-01"
end="2030-12-31"
@change="onDateChange"
>
<view class="picker-item">
<text>选择日期:\{\{ selectedDate \}\}</text>
<text class="arrow">></text>
</view>
</picker>
<!-- 时间选择器 -->
<picker
mode="time"
:value="selectedTime"
@change="onTimeChange"
>
<view class="picker-item">
<text>选择时间:\{\{ selectedTime \}\}</text>
<text class="arrow">></text>
</view>
</picker>
</view>
</template>
<script>
export default {
data() {
return {
selectedDate: '2024-01-01',
selectedTime: '12:00'
}
},
methods: {
onDateChange(e) {
this.selectedDate = e.detail.value
console.log('选择的日期:', this.selectedDate)
},
onTimeChange(e) {
this.selectedTime = e.detail.value
console.log('选择的时间:', this.selectedTime)
}
}
}
</script>
3. switch - 开关
switch
用于开关状态的切换:
vue
<template>
<view class="switch-demo">
<view class="setting-item">
<text>消息推送</text>
<switch
:checked="settings.notification"
@change="onNotificationChange"
color="#007aff"
/>
</view>
<view class="setting-item">
<text>夜间模式</text>
<switch
:checked="settings.darkMode"
@change="onDarkModeChange"
color="#34c759"
/>
</view>
<view class="setting-item">
<text>自动播放</text>
<switch
:checked="settings.autoPlay"
@change="onAutoPlayChange"
color="#ff9500"
/>
</view>
</view>
</template>
<script>
export default {
data() {
return {
settings: {
notification: true,
darkMode: false,
autoPlay: true
}
}
},
methods: {
onNotificationChange(e) {
this.settings.notification = e.detail.value
console.log('消息推送:', this.settings.notification)
// 保存设置到本地
this.saveSettings()
},
onDarkModeChange(e) {
this.settings.darkMode = e.detail.value
console.log('夜间模式:', this.settings.darkMode)
this.saveSettings()
},
onAutoPlayChange(e) {
this.settings.autoPlay = e.detail.value
console.log('自动播放:', this.settings.autoPlay)
this.saveSettings()
},
saveSettings() {
uni.setStorageSync('userSettings', this.settings)
}
}
}
</script>
<style>
.switch-demo {
padding: 20px;
}
.setting-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 0;
border-bottom: 1px solid #f0f0f0;
}
</style>
4. slider - 滑块
slider
用于选择数值范围:
vue
<template>
<view class="slider-demo">
<view class="slider-item">
<text>音量:\{\{ volume \}\}%</text>
<slider
:value="volume"
min="0"
max="100"
@change="onVolumeChange"
activeColor="#007aff"
backgroundColor="#e9e9e9"
block-color="#007aff"
block-size="20"
/>
</view>
<view class="slider-item">
<text>亮度:\{\{ brightness \}\}%</text>
<slider
:value="brightness"
min="10"
max="100"
step="10"
@change="onBrightnessChange"
activeColor="#34c759"
/>
</view>
</view>
</template>
<script>
export default {
data() {
return {
volume: 50,
brightness: 80
}
},
methods: {
onVolumeChange(e) {
this.volume = e.detail.value
console.log('音量调整为:', this.volume)
},
onBrightnessChange(e) {
this.brightness = e.detail.value
console.log('亮度调整为:', this.brightness)
}
}
}
</script>
<style>
.slider-demo {
padding: 20px;
}
.slider-item {
margin-bottom: 30px;
}
.slider-item text {
display: block;
margin-bottom: 10px;
font-size: 16px;
}
</style>
5. 完整表单案例:用户注册
让我们做一个完整的注册表单:
vue
<template>
<view class="register-form">
<view class="form-title">用户注册</view>
<!-- 用户名 -->
<view class="form-group">
<text class="label">用户名</text>
<input
v-model="formData.username"
placeholder="请输入用户名"
class="form-input"
@blur="validateUsername"
/>
<text v-if="errors.username" class="error-text">\{\{ errors.username \}\}</text>
</view>
<!-- 密码 -->
<view class="form-group">
<text class="label">密码</text>
<input
v-model="formData.password"
type="password"
placeholder="请输入密码"
class="form-input"
@blur="validatePassword"
/>
<text v-if="errors.password" class="error-text">\{\{ errors.password \}\}</text>
</view>
<!-- 手机号 -->
<view class="form-group">
<text class="label">手机号</text>
<input
v-model="formData.phone"
type="number"
placeholder="请输入手机号"
maxlength="11"
class="form-input"
@blur="validatePhone"
/>
<text v-if="errors.phone" class="error-text">\{\{ errors.phone \}\}</text>
</view>
<!-- 性别选择 -->
<view class="form-group">
<text class="label">性别</text>
<picker
:range="genderOptions"
:value="genderIndex"
@change="onGenderChange"
>
<view class="picker-input">
<text>\{\{ genderOptions[genderIndex] \}\}</text>
<text class="arrow">></text>
</view>
</picker>
</view>
<!-- 生日选择 -->
<view class="form-group">
<text class="label">生日</text>
<picker
mode="date"
:value="formData.birthday"
end="2010-12-31"
@change="onBirthdayChange"
>
<view class="picker-input">
<text>\{\{ formData.birthday || '请选择生日' \}\}</text>
<text class="arrow">></text>
</view>
</picker>
</view>
<!-- 同意协议 -->
<view class="form-group">
<view class="agreement">
<switch
:checked="formData.agreeTerms"
@change="onAgreeChange"
color="#007aff"
/>
<text class="agreement-text">我已阅读并同意</text>
<text class="link-text" @click="showTerms">《用户协议》</text>
</view>
</view>
<!-- 提交按钮 -->
<button
class="submit-btn"
:disabled="!canSubmit"
@click="handleSubmit"
:loading="isSubmitting"
>
\{\{ isSubmitting ? '注册中...' : '立即注册' \}\}
</button>
</view>
</template>
<script>
export default {
data() {
return {
formData: {
username: '',
password: '',
phone: '',
gender: '',
birthday: '',
agreeTerms: false
},
errors: {},
genderOptions: ['男', '女', '保密'],
genderIndex: 0,
isSubmitting: false
}
},
computed: {
canSubmit() {
return this.formData.username &&
this.formData.password &&
this.formData.phone &&
this.formData.agreeTerms &&
Object.keys(this.errors).length === 0
}
},
methods: {
validateUsername() {
if (!this.formData.username) {
this.$set(this.errors, 'username', '请输入用户名')
} else if (this.formData.username.length < 3) {
this.$set(this.errors, 'username', '用户名至少3个字符')
} else {
this.$delete(this.errors, 'username')
}
},
validatePassword() {
if (!this.formData.password) {
this.$set(this.errors, 'password', '请输入密码')
} else if (this.formData.password.length < 6) {
this.$set(this.errors, 'password', '密码至少6个字符')
} else {
this.$delete(this.errors, 'password')
}
},
validatePhone() {
const phoneReg = /^1[3-9]\d{9}$/
if (!this.formData.phone) {
this.$set(this.errors, 'phone', '请输入手机号')
} else if (!phoneReg.test(this.formData.phone)) {
this.$set(this.errors, 'phone', '手机号格式不正确')
} else {
this.$delete(this.errors, 'phone')
}
},
onGenderChange(e) {
this.genderIndex = e.detail.value
this.formData.gender = this.genderOptions[this.genderIndex]
},
onBirthdayChange(e) {
this.formData.birthday = e.detail.value
},
onAgreeChange(e) {
this.formData.agreeTerms = e.detail.value
},
showTerms() {
uni.navigateTo({
url: '/pages/terms/terms'
})
},
async handleSubmit() {
// 验证所有字段
this.validateUsername()
this.validatePassword()
this.validatePhone()
if (!this.canSubmit) {
uni.showToast({
title: '请完善表单信息',
icon: 'error'
})
return
}
this.isSubmitting = true
try {
const res = await uni.request({
url: '/api/register',
method: 'POST',
data: this.formData
})
uni.showToast({
title: '注册成功',
icon: 'success'
})
// 跳转到登录页面
setTimeout(() => {
uni.redirectTo({
url: '/pages/login/login'
})
}, 1500)
} catch (err) {
console.error('注册失败:', err)
uni.showToast({
title: '注册失败,请重试',
icon: 'error'
})
} finally {
this.isSubmitting = false
}
}
}
}
</script>
<style>
.register-form {
padding: 20px;
background-color: #f8f8f8;
min-height: 100vh;
}
.form-title {
font-size: 24px;
font-weight: bold;
text-align: center;
margin-bottom: 30px;
color: #333;
}
.form-group {
margin-bottom: 20px;
}
.label {
display: block;
font-size: 16px;
color: #333;
margin-bottom: 8px;
}
.form-input {
width: 100%;
padding: 12px 15px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 16px;
background-color: white;
}
.picker-input {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 15px;
border: 1px solid #ddd;
border-radius: 8px;
background-color: white;
}
.arrow {
color: #999;
}
.error-text {
color: #ff4757;
font-size: 14px;
margin-top: 5px;
display: block;
}
.agreement {
display: flex;
align-items: center;
gap: 10px;
}
.agreement-text {
font-size: 14px;
color: #666;
}
.link-text {
font-size: 14px;
color: #007aff;
}
.submit-btn {
width: 100%;
background-color: #007aff;
color: white;
border: none;
border-radius: 8px;
padding: 15px;
font-size: 18px;
margin-top: 20px;
}
.submit-btn[disabled] {
background-color: #ccc;
}
</style>
小结
今天我们学习了:
- ✅
input
- 各种类型的输入框 - ✅
picker
- 选择器(普通、多列、时间、日期) - ✅
switch
- 开关组件 - ✅
slider
- 滑块组件 - ✅ 完整的注册表单案例
表单开发要点:
- 数据双向绑定用
v-model
- 表单验证要及时反馈
- 用户体验要友好
- 数据提交要有加载状态
下一篇预告
下一篇我们将学习《布局组件使用 - 让页面排版更美观》,学习如何用各种布局组件构建美观的页面结构。
表单是用户输入的桥梁,做好表单体验,用户才会愿意与你的应用互动!