概述:Dropdown 下拉菜单与 Select 的区别在于它是包含了一部分操作,所以它的 Option 下拉项要对其进行支持。
publish:2019-03-31
自从我写了关于中后台系统组件的第一篇文章以来,已经三个月了。由于业务场景的较为简单,所以它没有其他组件库的功能那么强大,但是整体结构较为类似,代码结构清晰,容易扩展。
将 Dropdown 分为两个模块,父组件 Dropdown,子组件 Dropdown-option。其中Dropdown 负责控制整体的显示,Drop-option 负责下拉菜单的每一个选项。
Dropdown - 组件结构
├── Dropdown
├── Dropdown-option
Dropdown
父组件主要负责的是选中项的显示以及下拉菜单状态(开、合)的控制。
具体的代码如下
<template>
<div
:class="['dropdown', { 'is-hover': trigger === 'hover' && isOpen }]"
tabindex="0"
@click.stop="isOpen = !isOpen"
@blur="trigger !== 'hover' && (isOpen = false)"
>
<div :class="['dropdown__label']">
<slot name="label">
<span class="c-color-success">{{ placeholder }}</span>
</slot>
<fat-icon name="expand_more" class="c-color-success" />
</div>
<div class="dropdown__menu" v-if="trigger === 'hover' || isOpen">
<slot name="menu"></slot>
</div>
</div>
</template>
<script>
export default {
name: "dropdown",
provide() {
return {
Dropdown: this
};
},
props: {
placeholder: { type: String, default: "下拉菜单" },
trigger: { type: String, default: "hover" },
selectValue: { type: [String, Number] },
optionKey: { type: String, default: "value" }
},
data() {
return {
isOpen: this.trigger === "hover",
selectItem: {}
};
},
model: {
prop: "selectValue",
event: "select"
}
};
复制代码
首先对处理下拉菜单开关状态的控制,依据 trigger
也就是触发方式的不同,可以分为两类 hover
|| click
。
- 当
trigger = 'hover'
时,对最外层的div
添加is-hover
的类名,它主要是负责添加:hover
伪类来显示下拉菜单
&.is-hover {
&:hover {
.dropdown__menu {
display: block;
}
}
.dropdown__menu {
display: none;
}
}
复制代码
同时依据 trigger
初始化 isOpen
状态为 true
。
- 当
trigger = 'click'
时,利用isOpen
的状态来控制下拉菜单的开、合。主要是依据事件来触发,利用@click.stop="isOpen = !isOpen"
,来完成下来菜单的展开操作。之后,对最外层的div
添加tabindex="0"
属性使得它能够触发失焦事件blur
,同时添加@blur="trigger !== 'hover' && (isOpen = false)"
,意味着当它失效的时候,自动关闭下拉菜单。
以上完成了 Dropdown 对下拉菜单控制的功能,利用 provide
,完成它与 Dropdown-option 的通讯,传递 selectValue
、selectItem
、optionKey
。
Dropdown-option
Dropdown-option 是下拉菜单的每个选项,其基本结构
<template>
<div
:class="[
'dorpdown-option',
{ 'is-disabled': disabled },
{ 'is-selected': isSelected }
]"
@mousedown="handleClick"
>
<slot>
{{ label }}
</slot>
</div>
</template>
复制代码
主要是利用默认插槽,和 label
属性来构建每一项,并且包含着两种状态,是否 disabled 或 selected。disabled 状态是依据 props: disabled
来改变的,而 selected 则是由 computed
来完成的
<script>
export default {
inject: {
Dropdown: { default: "Dropdown" }
},
computed: {
isSelected() {
const {
Dropdown: { optionKey, selectValue }
} = this;
const key = this[optionKey] || this.$attrs[optionKey];
return key === selectValue;
}
},
...
};
</script>
复制代码
首先利用 inject
将父组件 Dropdown 注入,这样可以通过 this.Dropdown
来访问它的状态、属性。
然后在 isSelected()
中获取 selectValue
,与当前 Dropdown-option 的 key
值进行比对,查看是否为选中项。
为了要引入 optionKey,是因为在实际的业务中,有的场景会以 label 作为去区分项,有的则是以 value,故引入,方便自定义。
每个 Dropdown-option 具备选中功能,但是从 @mousedown="handleClick"
可以看出,利用 mousedown 来代替 click 事件
由于我们利用 Dropdown 的 blur 事件来控制下拉列表的展开与关闭,此时如果利用 click 事件,则会在 blur 之后触发,所以无法选中。故采用 mousedown 来完成该功能。
methods: {
handleSelect(key) {
let {
Dropdown: { multiple, trigger },
value,
label
} = this;
this.Dropdown.$emit("change", key);
this.Dropdown.$emit("select", key);
if (trigger !== "hover") {
this.Dropdown.isOpen = false;
}
},
handleClick() {
let {
Dropdown: { optionKey },
disabled
} = this;
const key = this[optionKey] || this.$attrs[optionKey];
if (!disabled) {
this.$slots.default[0].elm.click && this.$slots.default[0].elm.click();
key && this.handleSelect(key);
}
}
}
复制代码
这一份部分的逻辑就比较简单了,只有一处需要解释下
this.$slots.default[0].elm.click && this.$slots.default[0].elm.click();
复制代码
由于我们利用 mousedown 来代替原来的 click 事件,但我们利用 slot
插槽来完成下拉菜单的开发时,就无法触发 slot
的点击时间,所以利用上述代码来手动触发。
由于 Dropdown 组件中,使用了 v-model
来完成数据的双向绑定
model: {
prop: "selectValue",
event: "select"
}
复制代码
所以在 Dropdown-option 中则需要利用 this.Dropdown.$emit("select", key);
来完成双向绑定。
总结
代码地址:Dropdown Github
实例:Fat-UI lib