二、组件设计
接下来是开发选择框组件,首先需要自定义一个点击外部使下拉菜单关闭的指令:
export default { bind(el, binding, vnode) { function documentHandler(e) { if (el.contains(e.target)) { return false; } if (binding.expression) { binding.value(e); } } el.__vueClickOutside__ = documentHandler; document.addEventListener('click', documentHandler); }, unbind(el, binding) { document.removeEventListener('click', el.__vueClickOutside__); delete el.__vueClickOutside__; } }
使用方法很简单,在需要该指令的组件中:
import clickoutside from '../directives/clickoutside'
然后申明该指令:
directives: {
clickoutside
}
使用的时候就像这样给他一个事件函数:
v-clickoutside="dosomething"
2)开发下拉框组件
首先考虑到下拉菜单的数据双向绑定,而且分为单选和多选两种情况,所以使用v-model来绑定数据,同时添加一个multiple的布尔类型数据:
<template> <div class="my-select"></div> </template> <script> import emitter from '../mixins/emitter' import clickoutside from '../directives/clickoutside' export default { name: 'mySelect', mixins: [emitter], directives: { clickoutside }, props: { value: { type: [String, Array] }, multiple: { type: Boolean, default: false } }, model: { prop: 'value', event: 'change' }, data() { return { focus: false, show: true } } } </script>
多选的情况下还需要引入一个tag组件:
<template> <span class="my-tag"> <slot></slot> <i class="fa fa-times-circle" v-show="closeable" @click="closeHandler"></i> </span> </template> <script> import emitter from "../mixins/emitter"; export default { name: "myTag", mixins: [emitter], props: { closeable: { type: Boolean, default: false }, closeHandler: { type: Function, default: () => () => {} } } }; </script> <style lang="scss" scoped> .my-tag { border: 1px solid black; border-radius: 2px; padding: 0 2px; > .fa-times-circle { cursor: pointer; &:hover { color: red; } } } </style>
它看起来像是这个样子的:
需要注意的是,如果下拉菜单有需要带默认值的情况,需要在数据加载的时候根据v-model的value值去查找对应的label文本,特别是单选的情况。这里有一个坑要注意一下,一般我们在做对应查找的时候为了方便会直接把值放到数组的对应位置,然而vue的双向数据绑定无法捕捉到数组索引位置发生的变化,因此需要先放到一个临时数组里,在遍历完成的时候再赋值:
init() { if (this.multiple) { let tmpLabel = []; this.$children.forEach(child => { if (child.$options.name === "myOption") { let index = this.value.indexOf(child.value); if (index !== -1) { tmpLabel[index] = child.myLabel; } } }); this.label = tmpLabel; } else { this.$children.forEach(child => { if ( child.$options.name === "myOption" && child.value === this.value ) { this.label = child.myLabel; } }); } }
3)组装下拉菜单
下拉框是整个组件中起到承上启下的作用,先上进行双向数据绑定,向下控制选项的状态,其核心代码如下:
this.$on("option-click", (selected, label, value) => { if (this.multiple) { if (selected) { this.broadcast("myOption", "cancel", value); let index = this.value.indexOf(value); if (index !== -1) { let tmpValue = this.value; tmpValue.splice(index, 1); this.$emit("change", tmpValue); this.label.splice(index, 1); } } else { this.broadcast("myOption", "select", value); this.$emit("change", [...this.value, value]); this.label.push(label); } } else { if (!selected) { if (this.value) { this.broadcast("myOption", "cancel", this.value); } this.broadcast("myOption", "select", value); this.$emit("change", value); this.label = label; this.show = false; } } });
最后演示一下功能,样式是随便写的emmmm...
源码下载(需要自己装包):https://files.cnblogs.com/files/viewts/dropdown.zip