下拉选择框是表单中常用的选择控件,适合选项较多时使用。Vue 的
v-model让下拉框的数据绑定变得简单,但仍有一些细节需要注意。
<div id="app">
<select v-model="selected">
<option value="">请选择</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="guangzhou">广州</option>
<option value="shenzhen">深圳</option>
</select>
<p>已选择:{{ selected }}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
selected: ''
}
})
</script>
使用 v-for 动态生成选项:
<div id="app">
<select v-model="selected">
<option value="">请选择城市</option>
<option
v-for="city in cities"
:key="city.value"
:value="city.value"
>
{{ city.label }}
</option>
</select>
<p>已选择:{{ selected }}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
selected: '',
cities: [
{ value: 'beijing', label: '北京' },
{ value: 'shanghai', label: '上海' },
{ value: 'guangzhou', label: '广州' },
{ value: 'shenzhen', label: '深圳' }
]
}
})
</script>
使用 v-bind 绑定完整的对象:
<div id="app">
<select v-model="selectedCity">
<option value="">请选择</option>
<option
v-for="city in cities"
:key="city.id"
:value="city"
>
{{ city.name }} - {{ city.province }}
</option>
</select>
<div v-if="selectedCity">
<h3>{{ selectedCity.name }}</h3>
<p>省份:{{ selectedCity.province }}</p>
<p>人口:{{ selectedCity.population }}</p>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
selectedCity: null,
cities: [
{ id: 1, name: '北京', province: '北京', population: '2171万' },
{ id: 2, name: '上海', province: '上海', population: '2487万' },
{ id: 3, name: '广州', province: '广东', population: '1867万' }
]
}
})
</script>
添加 multiple 属性实现多选:
<div id="app">
<select v-model="selected" multiple>
<option value="apple">苹果</option>
<option value="banana">香蕉</option>
<option value="orange">橙子</option>
<option value="grape">葡萄</option>
</select>
<p>已选择:{{ selected }}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
selected: [] // 多选时必须是数组
}
})
</script>
注意
多选下拉框需要按住 Ctrl(Windows)或 Command(Mac)才能选择多个选项,用户体验不如复选框好。实际开发中,多个复选框或标签选择器更常用。
使用 <optgroup> 对选项进行分组:
<div id="app">
<select v-model="selected">
<option value="">请选择城市</option>
<optgroup label="华北地区">
<option value="beijing">北京</option>
<option value="tianjin">天津</option>
</optgroup>
<optgroup label="华东地区">
<option value="shanghai">上海</option>
<option value="hangzhou">杭州</option>
<option value="nanjing">南京</option>
</optgroup>
<optgroup label="华南地区">
<option value="guangzhou">广州</option>
<option value="shenzhen">深圳</option>
</optgroup>
</select>
<p>已选择:{{ selected }}</p>
</div>
<div id="app">
<select v-model="selected">
<option value="">请选择</option>
<optgroup
v-for="group in groups"
:key="group.label"
:label="group.label"
>
<option
v-for="item in group.options"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</option>
</optgroup>
</select>
</div>
<script>
new Vue({
el: '#app',
data: {
selected: '',
groups: [
{
label: '华北地区',
options: [
{ value: 'beijing', label: '北京' },
{ value: 'tianjin', label: '天津' }
]
},
{
label: '华东地区',
options: [
{ value: 'shanghai', label: '上海' },
{ value: 'hangzhou', label: '杭州' }
]
}
]
}
})
</script>
省市区三级联动是典型的级联选择场景:
<div id="app">
<select v-model="province" @change="onProvinceChange">
<option value="">请选择省份</option>
<option
v-for="p in provinces"
:key="p.code"
:value="p.code"
>
{{ p.name }}
</option>
</select>
<select v-model="city" @change="onCityChange" :disabled="!province">
<option value="">请选择城市</option>
<option
v-for="c in cities"
:key="c.code"
:value="c.code"
>
{{ c.name }}
</option>
</select>
<select v-model="district" :disabled="!city">
<option value="">请选择区县</option>
<option
v-for="d in districts"
:key="d.code"
:value="d.code"
>
{{ d.name }}
</option>
</select>
<p>已选择:{{ fullAddress }}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
province: '',
city: '',
district: '',
provinces: [
{ code: '11', name: '北京市' },
{ code: '31', name: '上海市' },
{ code: '44', name: '广东省' }
],
allCities: {
'11': [{ code: '1101', name: '北京市' }],
'31': [{ code: '3101', name: '上海市' }],
'44': [
{ code: '4401', name: '广州市' },
{ code: '4403', name: '深圳市' }
]
},
allDistricts: {
'1101': [
{ code: '110101', name: '东城区' },
{ code: '110102', name: '西城区' }
],
'3101': [
{ code: '310101', name: '黄浦区' },
{ code: '310104', name: '徐汇区' }
],
'4401': [
{ code: '440103', name: '荔湾区' },
{ code: '440104', name: '越秀区' }
],
'4403': [
{ code: '440303', name: '罗湖区' },
{ code: '440304', name: '福田区' }
]
}
},
computed: {
cities: function() {
return this.province ? this.allCities[this.province] || [] : []
},
districts: function() {
return this.city ? this.allDistricts[this.city] || [] : []
},
fullAddress: function() {
if (!this.province) return ''
const p = this.provinces.find(item => item.code === this.province)
const c = this.cities.find(item => item.code === this.city)
const d = this.districts.find(item => item.code === this.district)
return [p && p.name, c && c.name, d && d.name].filter(Boolean).join(' ')
}
},
methods: {
onProvinceChange: function() {
this.city = ''
this.district = ''
},
onCityChange: function() {
this.district = ''
}
}
})
</script>
结合搜索框实现可搜索的下拉框:
<div id="app">
<div class="search-select">
<input
type="text"
v-model="keyword"
@focus="showOptions = true"
placeholder="搜索城市..."
>
<ul v-if="showOptions && filteredOptions.length > 0" class="options">
<li
v-for="option in filteredOptions"
:key="option.value"
@click="selectOption(option)"
>
{{ option.label }}
</li>
</ul>
<p v-if="showOptions && filteredOptions.length === 0">无匹配结果</p>
</div>
<p>已选择:{{ selected }}</p>
</div>
<style>
.search-select {
position: relative;
}
.options {
position: absolute;
top: 100%;
left: 0;
right: 0;
border: 1px solid #ddd;
max-height: 200px;
overflow-y: auto;
background: white;
list-style: none;
padding: 0;
margin: 0;
}
.options li {
padding: 8px 12px;
cursor: pointer;
}
.options li:hover {
background: #f5f5f5;
}
</style>
<script>
new Vue({
el: '#app',
data: {
keyword: '',
selected: '',
showOptions: false,
options: [
{ value: 'beijing', label: '北京' },
{ value: 'shanghai', label: '上海' },
{ value: 'guangzhou', label: '广州' },
{ value: 'shenzhen', label: '深圳' },
{ value: 'hangzhou', label: '杭州' },
{ value: 'nanjing', label: '南京' }
]
},
computed: {
filteredOptions: function() {
if (!this.keyword) return this.options
return this.options.filter(option =>
option.label.includes(this.keyword) ||
option.value.includes(this.keyword)
)
}
},
methods: {
selectOption: function(option) {
this.selected = option.value
this.keyword = option.label
this.showOptions = false
}
},
mounted: function() {
document.addEventListener('click', (e) => {
if (!this.$el.contains(e.target)) {
this.showOptions = false
}
})
}
})
</script>
原生 <select> 样式难以自定义,实际开发中常用自定义组件:
<div id="app">
<custom-select
v-model="selected"
:options="options"
placeholder="请选择城市"
></custom-select>
<p>已选择:{{ selected }}</p>
</div>
<script>
Vue.component('custom-select', {
props: ['value', 'options', 'placeholder'],
template: `
<div class="custom-select" @click="toggle">
<div class="select-input">
<span v-if="selectedOption">{{ selectedOption.label }}</span>
<span v-else class="placeholder">{{ placeholder }}</span>
<span class="arrow" :class="{ open: isOpen }">▼</span>
</div>
<ul v-if="isOpen" class="select-options">
<li
v-for="option in options"
:key="option.value"
:class="{ selected: option.value === value }"
@click.stop="select(option)"
>
{{ option.label }}
</li>
</ul>
</div>
`,
data: function() {
return {
isOpen: false
}
},
computed: {
selectedOption: function() {
return this.options.find(opt => opt.value === this.value)
}
},
methods: {
toggle: function() {
this.isOpen = !this.isOpen
},
select: function(option) {
this.$emit('input', option.value)
this.isOpen = false
}
},
mounted: function() {
document.addEventListener('click', (e) => {
if (!this.$el.contains(e.target)) {
this.isOpen = false
}
})
}
})
new Vue({
el: '#app',
data: {
selected: '',
options: [
{ value: 'beijing', label: '北京' },
{ value: 'shanghai', label: '上海' },
{ value: 'guangzhou', label: '广州' },
{ value: 'shenzhen', label: '深圳' }
]
}
})
</script>
<style>
.custom-select {
position: relative;
width: 200px;
}
.select-input {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
display: flex;
justify-content: space-between;
}
.placeholder {
color: #999;
}
.arrow {
font-size: 12px;
transition: transform 0.3s;
}
.arrow.open {
transform: rotate(180deg);
}
.select-options {
position: absolute;
top: 100%;
left: 0;
right: 0;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
list-style: none;
padding: 0;
margin: 4px 0 0 0;
max-height: 200px;
overflow-y: auto;
}
.select-options li {
padding: 8px 12px;
cursor: pointer;
}
.select-options li:hover {
background: #f5f5f5;
}
.select-options li.selected {
background: #42b983;
color: white;
}
</style>
下拉选择框处理要点:
multiple 属性,绑定数组v-for 生成选项:value 绑定完整对象