在我们仿 ElementPlus 组件库的旅程中,已经成功地探索并实现了Tooltip 组件。现在让我们把目光聚焦在另一个在用户界面交互中扮演重要角色的组件 ------Dropdown 组件。
一、什么是 Dropdown 组件
Dropdown 组件,即下拉菜单组件,以其紧凑的布局和丰富的操作选项,广泛应用于各类用户界面中。无论是在管理后台、数据表格还是复杂的导航栏中,Dropdown 组件都能帮助用户便捷地进行操作选择,是构建功能强大且交互性出色的应用不可或缺的部分。
二、实现 Dropdown 组件
(一)组件目录
目录
components
├── Dropdown
├── Dropdown.vue
├── types.ts
├── style.css
├── Dropdown.tsx
├── Common
├── RenderVnode.ts
(二)实现 Dropdown 组件基本功能
- 基于
Tooltip
组件构建Dropdown
组件。 - 组件接收
menuOptions
属性,用于定义下拉菜单的选项,每个选项可以包含显示内容、唯一标识、是否禁用和是否需要分隔线等信息。 - 组件支持通过触发元素(父组件传递的内容)来显示和隐藏下拉菜单,并且可以配置显示和隐藏的延迟时间、位置等。
- 当用户点击菜单项时,如果菜单项未被禁用,组件会触发
select
事件并传递当前菜单项。 - 同时,组件还暴露了
show
和hide
方法,方便外部调用以手动控制下拉菜单的显示和隐藏。
types.ts
import type { VNode } from 'vue'
import type { TooltipProps } from '../Tooltip/types'
export interface DropdownProps extends TooltipProps {
menuOptions: MenuOption[]
}
export interface MenuOption {
label: string | VNode
key: string | number
disabled?: boolean
divided?: boolean
}
export interface DropdownEmits {
(e:'visible-change', value: boolean) : void;
(e:'select', value: MenuOption) : void;
}
export interface DropdownInstance {
show: () => void;
hide: () => void;
}
RenderVnode.ts
import { defineComponent } from 'vue'
const RenderVnode = defineComponent({
props: {
vNode: {
type: [String, Object],
required: true
}
},
setup(props) {
return () => props.vNode
}
})
export default RenderVnode
Dropdown.vue
<template>
<div class="yl-dropdown">
<Tooltip
:trigger="trigger"
:placement="placement"
:popper-options="popperOptions"
:open-delay="openDelay"
:close-delay="closeDelay"
:manual="manual"
@visible-change="visibleChange"
ref="tooltipRef"
>
<slot></slot>
<template #content>
<ul class="yl-dropdown__menu">
<template v-for="item in menuOptions" :key="item.key">
<li v-if="item.divided" role="separator" class="divided-placeholder"></li>
<li
class="yl-dropdown__item"
@click="itemClick(item)"
:class="{ 'is-disabled': item.disabled, 'is-divided': item.divided }"
:id="`dropdown-item-${item.key}`"
>
<RenderVnode :vNode="item.label" />
</li>
</template>
</ul>
</template>
</Tooltip>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { Ref } from 'vue'
import type { DropdownProps, DropdownInstance, DropdownEmits, MenuOption } from './types'
import Tooltip from '../Tooltip/Tooltip.vue'
import RenderVnode from '../Common/RenderVnode'
import type { TooltipInstance } from '../Tooltip/types'
const props = defineProps<DropdownProps>()
const emits = defineEmits<DropdownEmits>()
const tooltipRef = ref<TooltipInstance | null>(null)
const visibleChange = (e: boolean) => {
emits('visible-change', e)
}
const itemClick = (e: MenuOption) => {
if (e.disabled) {
return
}
emits('select', e)
}
defineExpose<DropdownInstance>({
show: () => tooltipRef.value?.show(),
hide: () => tooltipRef.value?.hide(),
})
</script>
App.vue
import { ref, onMounted, h } from 'vue'
import type { MenuOption } from './components/Dropdown/types'
import Dropdown from './components/Dropdown/Dropdown.vue'
(三)实现 Dropdown 组件点击关闭功能
- 为
Dropdown
组件添加点击菜单项后关闭下拉菜单的功能。 - 通过在
DropdownProps
接口中新增hideAfterClick
属性,用户可以灵活控制点击菜单项后下拉菜单的行为。 - 当
hideAfterClick
为true
(默认值)时,用户点击下拉菜单中的非禁用菜单项后,下拉菜单会自动关闭;当hideAfterClick
为false
时,点击菜单项后下拉菜单不会关闭,保持打开状态。 - 同时,点击菜单项时会触发
select
事件,方便父组件处理用户的选择操作。
types.ts
export interface DropdownProps extends TooltipProps {
menuOptions: MenuOption[]
hideAfterClick?: boolean
}
Dropdown.vue
const props = withDefaults(defineProps<DropdownProps>(), { hideAfterClick: true })
const itemClick = (e: MenuOption) => {
if (e.disabled) {
return
}
emits('select', e)
if (props.hideAfterClick) {
tooltipRef.value?.hide()
}
}
(四)为 Dropdown 组件添加样式
style.css
.yl-dropdown .yl-dropdown__menu {
--yl-dropdown-menuItem-hover-fill: var(--yl-color-primary-light-9);
--yl-dropdown-menuItem-hover-color: var(--yl-color-primary);
--yl-dropdown-menuItem-disabled-color: var(--yl-border-color-lighter);
--yl-dropdown-menuItem-divided-color: var(--yl-border-color-lighter);
}
.yl-dropdown {
display: inline-block;
.yl-tooltip {
--yl-popover-padding: 5px 0;
}
}
.yl-dropdown__menu {
list-style-type: none;
margin: 0;
padding: 0;
.yl-dropdown__item {
display: flex;
align-items: center;
white-space: nowrap;
list-style: none;
line-height: 22px;
padding: 5px 16px;
margin: 0;
font-size: var(--yl-font-size-base);
color: var(--yl-text-color-regular);
cursor: pointer;
outline: none;
&:hover {
background-color: var(--yl-dropdown-menuItem-hover-fill);
color: var(--yl-dropdown-menuItem-hover-color);
}
&.is-disabled {
color: var(--yl-dropdown-menuItem-disabled-color);
cursor: not-allowed;
background-image: none;
}
}
.divided-placeholder {
margin: 6px 0;
border-top: 1px solid var(--yl-dropdown-menuItem-divided-color);
}
}
style/index.css
@import '../components/Dropdown/style.css';