一、組件使用場景及需求分析
表單多個固定單值的情況,我們不用再去
input框輸入值,直接在固定的值里面去選擇選擇以后父組件綁定的值對應改變,使得不需要發(fā)送表單前再進行賦值
選擇前后,列表都是不可見的
二、開始我們的codeing
首先我們需要的是一個有所有單值選項的list展示
然后是一個展示當前選擇的文字框
像這樣的:

這一步我們只需要父組件傳遞單值代碼,然后當前選中的一個值,沒有的話就默認為空。
vue:
<div class="fd-select-box">
<p v-text="scoped.selected&&scoped.selected.name?scoped.selected.name:'請選擇'"></p>
<span :class="fd-arrow icon iconfont"></span>
<ul class="fd-select-list">
<li v-for="(item,index) in list"
:key="index+'select'"
:class="{'active':scoped.selected&&item.code===scoped.selected.code}">
{{item.name}}</li>
</ul>
</div>
JS:
props: {
list: {
type: Array,
required: true,
},
selected: Object,
},
data() {
return {
scoped: {
// 當前選中的
selected: this.selected,
},
};
},
CSS:
.fd-select-box {
position: relative;
width: 200px;
padding-right: 40px;
padding-left: 10px;
height: 36px;
margin: 30px auto;
line-height: 36px;
border: 1px solid #41b883;
border-radius: 4px;
color: #000;
font-size: 14px;
text-align: left;
cursor: pointer;
box-sizing: border-box;
?
.fd-arrow {
position: absolute;
top: 0;
right: 0;
font-size: 30px;
transition: all 200ms;
?
&.fd-down {
transform: rotate(180deg);
}
}
?
.fd-select-list {
position: absolute;
width: 100%;
max-height: 200px;
overflow: auto;
list-style: none;
top: 36px;
left: 0;
background: #fff;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
z-index: 9;
?
li {
padding-left: 12px;
line-height: 30px;
cursor: pointer;
?
&:hover {
background: rgba(65, 191, 138, 0.2);
}
?
&.active {
background: rgba(65, 191, 138, 0.9);
color: #fff;
}
}
}
}
這樣,我們已經(jīng)把外層框架搭建好了。
接下來,解決子組件改變,父組件對應的值發(fā)生改變。
一般情況我們會想到子組件把當前選中的值通過this.$emit傳給父組件,然后父組件再在對應方法里面給對應的值賦值。今天我們用另外一種方法來解決這個問題,那就是v-model,相信我,用了它你會愛上它。
好了,我們看看官方文檔怎么說的:
一個組件上的
v-model默認會利用名為value的 prop 和名為input的事件,但是像單選框、復選框等類型的輸入控件可能會將valueattribute 用于不同的目的。model選項可以用來避免這樣的沖突:
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
我的理解就是提供了v-model;在自定義組件上model里的prop里的字段的值會直接賦給props里面對應字段,像之前我們給checked傳值是在父組件上通過:checked='false'這樣一種形式?,F(xiàn)在我們可以使用v-model='false'。來看具體在select框里面的表現(xiàn)吧。
export default {
name: 'fdSselect',
model: {
prop: 'selected',
event: 'changeValue',
},
props: {
list: {
type: Array,
required: true,
},
selected: Object,
},
data() {
return {
scoped: {
// 是否展示下面的列表
showFlag: false,
// 當前選中的
selected: this.selected,
},
};
},
methods: {
// 值改變后傳給父組件,因為組件定義了model,所以父組件相當于執(zhí)行了綁定的model值=emit出去的值
changeValue(item) {
this.scoped.selected = item;
this.scoped.showFlag = false;
this.$emit('changeValue', this.scoped.selected);
},
},
};
父組件調(diào)用:
<fd-select :list="selectList" v-model="selected"></fd-select>
上面的event是我們要emit出去的事件名。這一步相當于在父組件執(zhí)行了父組件的this.selected等于子組件的this.scoped.selected;所以其實你用組件的時候v-model="value"其實就是:value="value" @change="(val) => {value = val}";
現(xiàn)在看看我們實現(xiàn)的效果:

前兩個需求已經(jīng)實現(xiàn)了,最后一個需求是在交互上的優(yōu)化。
首先他要一開始的時候不展示,我們給一個控制下拉框顯隱的變量。showFlag默認值為false;點擊輸入框時展開下拉列表。然后選中選項后隱藏下拉列表。
注意我們的頁面結(jié)構(gòu),下拉列表是輸入框的子元素,所以點擊下拉列表元素的時候會涉及到事件冒泡,這個時候我們使用.stop修飾符來組織時間冒泡導致下拉列表一直不能隱藏。
vue:
<div class="fd-select-box" @click="changeShow">
<p v-text="scoped.selected&&scoped.selected.name?scoped.selected.name:'請選擇'"></p>
<span :class="['fd-arrow icon iconfont',{'fd-down':scoped.showFlag}]"></span>
<ul class="fd-select-list" v-show="scoped.showFlag">
<li v-for="(item,index) in list"
:key="index+'select'"
@click.stop="changeValue(item)"
:class="{'active':scoped.selected&&item.code===scoped.selected.code}">
{{item.name}}</li>
</ul>
</div>
JS:
// 值改變后傳給父組件,因為組件定義了model,所以父組件相當于執(zhí)行了綁定的model值=emit出去的值
changeValue(item) {
this.scoped.selected = item;
this.scoped.showFlag = false;
this.$emit('changeValue', this.scoped.selected);
},
// 改變下拉選項的顯隱
changeShow() {
this.scoped.showFlag = !this.scoped.showFlag;
},
繼續(xù)優(yōu)化,我們現(xiàn)在實現(xiàn)了組件列表的顯隱,但是只有操作當前組件時可以控制。那么我們點擊其他地方的時候,其實也是希望組件列表可以隱藏起來的。
實現(xiàn)這個的思路:綁定一個點擊事件在頁面上,只要點擊的元素不是當前組件,那么我們就可以隱藏當前組件的列表。這里我用到了自定義指令,具體實現(xiàn)如下:
clickOutside: {
bind(el, binding) {
function clickHandler(e) {
// 這里判斷點擊的元素是否是本身,是本身,則返回
if (el.contains(e.target)) {
return false;
}
// 判斷指令中是否綁定了函數(shù)
if (binding.expression) {
// 如果綁定了函數(shù) 則調(diào)用那個函數(shù),此處binding.value就是handleClose方法
binding.value(e);
}
return true;
}
// 給當前元素綁定個私有變量,方便在unbind中可以解除事件監(jiān)聽
el.vueClickOutside = clickHandler;
document.addEventListener('click', clickHandler);
},
unbind(el) {
// 解除事件監(jiān)聽
document.removeEventListener('click', el.vueClickOutside);
delete el.vueClickOutside;
},
},
最后實現(xiàn)效果如圖:

后期待優(yōu)化:實現(xiàn)可搜索的下拉框-->實現(xiàn)可以遠程搜索的下拉框
以上為個人編寫,希望能對大家的項目有所幫助,如有不當以及有更好的方法歡迎交流。