文本输入框是最常见的表单元素,包括单行文本、密码框、多行文本等。虽然使用
v-model很简单,但实际开发中有很多细节需要注意。
<input type="text" v-model="message">
<p>{{ message }}</p>
<script>
new Vue({
data: {
message: ''
}
})
</script>
HTML5 提供了多种输入类型,v-model 都能正常工作:
<!-- 普通文本 -->
<input type="text" v-model="text">
<!-- 密码框 -->
<input type="password" v-model="password">
<!-- 邮箱 -->
<input type="email" v-model="email">
<!-- 数字 -->
<input type="number" v-model.number="age">
<!-- 电话 -->
<input type="tel" v-model="phone">
<!-- URL -->
<input type="url" v-model="website">
<!-- 搜索框 -->
<input type="search" v-model="keyword">
type="number" 的陷阱
type="number" 的输入框,v-model 返回的仍然是字符串。需要使用 .number 修饰符:
<!-- ❌ 返回字符串 -->
<input type="number" v-model="age">
<!-- age: "123" -->
<!-- ✅ 返回数字 -->
<input type="number" v-model.number="age">
<!-- age: 123 -->
<!-- 占位符 -->
<input type="text" v-model="name" placeholder="请输入姓名">
<!-- 只读 -->
<input type="text" v-model="name" readonly>
<!-- 禁用 -->
<input type="text" v-model="name" disabled>
<!-- 最大长度 -->
<input type="text" v-model="name" maxlength="20">
<!-- 自动聚焦 -->
<input type="text" v-model="name" autofocus>
<textarea v-model="content"></textarea>
<script>
new Vue({
data: {
content: ''
}
})
</script>
在 textarea 中使用插值语法不会生效:
<!-- ❌ 错误:插值不会生效 -->
<textarea>{{ content }}</textarea>
<!-- ✅ 正确:使用 v-model -->
<textarea v-model="content"></textarea>
<!-- 使用 rows 和 cols -->
<textarea v-model="content" rows="5" cols="30"></textarea>
<!-- 使用 CSS -->
<textarea v-model="content" style="width: 100%; height: 200px;"></textarea>
<!-- 禁止调整大小 -->
<textarea v-model="content" style="resize: none;"></textarea>
<div class="textarea-wrapper">
<textarea
v-model="content"
maxlength="200"
placeholder="请输入内容..."
></textarea>
<span class="counter">{{ content.length }}/200</span>
</div>
<style>
.textarea-wrapper {
position: relative;
}
.counter {
position: absolute;
bottom: 10px;
right: 10px;
color: #999;
font-size: 12px;
}
</style>
<div class="form-group">
<input
type="text"
v-model="username"
@input="validateUsername"
placeholder="用户名"
>
<span v-if="errors.username" class="error">{{ errors.username }}</span>
</div>
<script>
new Vue({
data: {
username: '',
errors: {
username: ''
}
},
methods: {
validateUsername: function() {
if (!this.username) {
this.errors.username = '用户名不能为空'
} else if (this.username.length < 3) {
this.errors.username = '用户名至少3个字符'
} else if (this.username.length > 20) {
this.errors.username = '用户名最多20个字符'
} else {
this.errors.username = ''
}
}
}
})
</script>
<div class="form-group">
<input type="email" v-model="email" placeholder="邮箱">
<span v-if="email && !emailValid" class="error">邮箱格式不正确</span>
</div>
<script>
new Vue({
data: {
email: ''
},
computed: {
emailValid: function() {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return re.test(this.email)
}
}
})
</script>
<div class="form-group">
<input
type="text"
v-model="phone"
@blur="validatePhone"
placeholder="手机号"
>
<span v-if="errors.phone" class="error">{{ errors.phone }}</span>
</div>
<script>
new Vue({
data: {
phone: '',
errors: {
phone: ''
}
},
methods: {
validatePhone: function() {
const re = /^1[3-9]\d{9}$/
if (this.phone && !re.test(this.phone)) {
this.errors.phone = '手机号格式不正确'
} else {
this.errors.phone = ''
}
}
}
})
</script>
<input
type="search"
v-model="keyword"
@input="debounceSearch"
placeholder="搜索..."
>
<script>
new Vue({
data: {
keyword: '',
timer: null
},
methods: {
debounceSearch: function() {
clearTimeout(this.timer)
this.timer = setTimeout(() => {
this.search()
}, 300)
},
search: function() {
if (!this.keyword.trim()) return
console.log('搜索:', this.keyword)
// 调用搜索 API
}
}
})
</script>
<input type="search" v-model="keyword" placeholder="搜索...">
<script>
new Vue({
data: {
keyword: ''
},
watch: {
keyword: function(newVal) {
this.debouncedSearch(newVal)
}
},
created: function() {
this.debouncedSearch = this.debounce(this.search, 300)
},
methods: {
debounce: function(fn, delay) {
let timer = null
return function(...args) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
},
search: function(keyword) {
if (!keyword.trim()) return
console.log('搜索:', keyword)
}
}
})
</script>
<div class="autocomplete">
<input
type="text"
v-model="query"
@input="fetchSuggestions"
@keydown.down="selectNext"
@keydown.up="selectPrev"
@keydown.enter="selectCurrent"
placeholder="搜索..."
>
<ul v-if="suggestions.length > 0" class="suggestions">
<li
v-for="(item, index) in suggestions"
:key="item.id"
:class="{ active: index === selectedIndex }"
@click="selectSuggestion(item)"
>
{{ item.name }}
</li>
</ul>
</div>
<script>
new Vue({
data: {
query: '',
suggestions: [],
selectedIndex: 0
},
methods: {
fetchSuggestions: function() {
if (!this.query.trim()) {
this.suggestions = []
return
}
// 模拟 API 调用
this.suggestions = [
{ id: 1, name: this.query + ' 建议一' },
{ id: 2, name: this.query + ' 建议二' },
{ id: 3, name: this.query + ' 建议三' }
]
this.selectedIndex = 0
},
selectNext: function() {
if (this.selectedIndex < this.suggestions.length - 1) {
this.selectedIndex++
}
},
selectPrev: function() {
if (this.selectedIndex > 0) {
this.selectedIndex--
}
},
selectCurrent: function() {
if (this.suggestions.length > 0) {
this.selectSuggestion(this.suggestions[this.selectedIndex])
}
},
selectSuggestion: function(item) {
this.query = item.name
this.suggestions = []
}
}
})
</script>
<div class="password-input">
<input
:type="showPassword ? 'text' : 'password'"
v-model="password"
placeholder="密码"
>
<button @click="showPassword = !showPassword">
{{ showPassword ? '隐藏' : '显示' }}
</button>
</div>
<script>
new Vue({
data: {
password: '',
showPassword: false
}
})
</script>
<input
type="text"
v-model="formattedPhone"
@input="formatPhone"
placeholder="手机号"
>
<script>
new Vue({
data: {
phone: '',
formattedPhone: ''
},
methods: {
formatPhone: function() {
// 只保留数字
let value = this.formattedPhone.replace(/\D/g, '')
// 限制长度
value = value.substring(0, 11)
// 格式化:138 1234 5678
if (value.length > 7) {
this.formattedPhone = value.substring(0, 3) + ' ' +
value.substring(3, 7) + ' ' +
value.substring(7)
} else if (value.length > 3) {
this.formattedPhone = value.substring(0, 3) + ' ' + value.substring(3)
} else {
this.formattedPhone = value
}
// 保存纯数字
this.phone = value
}
}
})
</script>
<input
type="text"
v-model="formattedAmount"
@input="formatAmount"
placeholder="金额"
>
<p>实际金额:{{ amount }}</p>
<script>
new Vue({
data: {
amount: 0,
formattedAmount: ''
},
methods: {
formatAmount: function() {
// 只保留数字和小数点
let value = this.formattedAmount.replace(/[^\d.]/g, '')
// 只保留第一个小数点
const parts = value.split('.')
if (parts.length > 2) {
value = parts[0] + '.' + parts.slice(1).join('')
}
// 限制小数位数
if (parts[1] && parts[1].length > 2) {
value = parts[0] + '.' + parts[1].substring(0, 2)
}
this.formattedAmount = value
this.amount = parseFloat(value) || 0
}
}
})
</script>
文本输入框处理要点:
typetype="number" 配合 .number 修饰符