一、组件使用场景及需求分析
表单多个固定单值的情况,我们不用再去
input
框输入值,直接在固定的值里面去选择选择以后父组件绑定的值对应改变,使得不需要发送表单前再进行赋值
选择前后,列表都是不可见的
二、开始我们的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;
}
}
}
}
这样,我们已经把外层框架搭建好了。
接下来,解决子组件改变,父组件对应的值发生改变。
一般情况我们会想到子组件把当前选中的值通过this.$emit
传给父组件,然后父组件再在对应方法里面给对应的值赋值。今天我们用另外一种方法来解决这个问题,那就是v-model
,相信我,用了它你会爱上它。
好了,我们看看官方文档怎么说的:
一个组件上的
v-model
默认会利用名为value
的 prop 和名为input
的事件,但是像单选框、复选框等类型的输入控件可能会将value
attribute 用于不同的目的。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'
这样一种形式。现在我们可以使用v-model='false'
。来看具体在select
框里面的表现吧。
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,所以父组件相当于执行了绑定的model值=emit出去的值
changeValue(item) {
this.scoped.selected = item;
this.scoped.showFlag = false;
this.$emit('changeValue', this.scoped.selected);
},
},
};
父组件调用:
<fd-select :list="selectList" v-model="selected"></fd-select>
上面的event
是我们要emit
出去的事件名。这一步相当于在父组件执行了父组件的this.selected
等于子组件的this.scoped.selected
;所以其实你用组件的时候v-model="value"
其实就是:value="value" @change="(val) => {value = val}"
;
现在看看我们实现的效果:
前两个需求已经实现了,最后一个需求是在交互上的优化。
首先他要一开始的时候不展示,我们给一个控制下拉框显隐的变量。showFlag
默认值为false
;点击输入框时展开下拉列表。然后选中选项后隐藏下拉列表。
注意我们的页面结构,下拉列表是输入框的子元素,所以点击下拉列表元素的时候会涉及到事件冒泡,这个时候我们使用.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,所以父组件相当于执行了绑定的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;
},
继续优化,我们现在实现了组件列表的显隐,但是只有操作当前组件时可以控制。那么我们点击其他地方的时候,其实也是希望组件列表可以隐藏起来的。
实现这个的思路:绑定一个点击事件在页面上,只要点击的元素不是当前组件,那么我们就可以隐藏当前组件的列表。这里我用到了自定义指令,具体实现如下:
clickOutside: {
bind(el, binding) {
function clickHandler(e) {
// 这里判断点击的元素是否是本身,是本身,则返回
if (el.contains(e.target)) {
return false;
}
// 判断指令中是否绑定了函数
if (binding.expression) {
// 如果绑定了函数 则调用那个函数,此处binding.value就是handleClose方法
binding.value(e);
}
return true;
}
// 给当前元素绑定个私有变量,方便在unbind中可以解除事件监听
el.vueClickOutside = clickHandler;
document.addEventListener('click', clickHandler);
},
unbind(el) {
// 解除事件监听
document.removeEventListener('click', el.vueClickOutside);
delete el.vueClickOutside;
},
},
最后实现效果如图:
后期待优化:实现可搜索的下拉框-->实现可以远程搜索的下拉框
以上为个人编写,希望能对大家的项目有所帮助,如有不当以及有更好的方法欢迎交流。