Element Plus 组件库实现:5. Dropdown组件

发布于:2024-04-28 ⋅ 阅读:(20) ⋅ 点赞:(0)

前言

在数字时代的浪潮中,用户界面的设计与交互体验日益成为软件产品成功与否的关键因素。Dropdown组件,作为用户界面中的常见元素,承载着提供选项列表、优化用户选择流程的重要职责。它不仅简化了用户的操作,也提升了界面的整体美观度和使用便捷性。本文将简单介绍开发Dropdown组件的基本实现。

需求分析

  • 可以根据已有的Tooltip组件进行二次开发
  • 显示内容为菜单列表
  • 菜单中有多个选项,用户可自定义复杂选项
  • 使用语义化结构

确定方案

  • 属性
import type { VNode } from "vue";
import type { TooltipProps } from "../Tooltip/types";
// 需要在Tooltip组件的属性上进行继承和拓展
export interface DropdownProps extends TooltipProps {
  // 传入的菜单项,用一个数组保存
  menuOptions: MenuOption[];
  // 点击某一项之后关闭菜单展示
  afterClickItem?: boolean;
}
// 每一项列表具有的属性
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;
}
  • 组件

<template>
    <div class="yv-dropdown">
        <Tooltip>
        <!-- 触发区域 -->
            <slot></slot>
            <!-- 内容区域 -->
            <template #content>
                <ul>
                    <template >
                        <!-- 分割线 -->
                        <hr >
                        <!-- 列表 -->
                        <li>
                            <RenderVnode  />
                        </li>
                    </template>
                </ul>
            </template>
        </Tooltip>
    </div>
</template>

设计思路

  • 使用Tooltip组件进行二次开发
  • 菜单列表使用Vnode节点,用户可自定义
  • 列表之间可以添加分割线
  • 默认插槽为触发区域,触发区域设置为Tooltip中预留的content部分

代码实现

<script>
import { ref } from 'vue'
import Tooltip from '../Tooltip/Tooltip.vue';
import RenderVnode from '@/common/RenderVnode';

import type { DropdownProps, DropdownEmits, MenuOption, DropdownInstance } from './types';
import type { TooltipInstance } from '../Tooltip/types';
defineOptions({
    name: "YvDropdown"
})
// 通过ref属性拿到实例
const tooltipRef = ref<TooltipInstance | null>(null)
// 定义属性
const props = withDefaults(defineProps<DropdownProps>(), {
    afterClickItem: true
})
// 定义变量
const emits = defineEmits<DropdownEmits>()
// 切换菜单显示/隐藏
const visibleChange = (e: boolean) => {
    emits('visible-change', e)
}
// 选中菜单某一项
const itemClick = (e: MenuOption) => {
    if (e.disabled) {
        return
    }
    emits('select', e)
    if (props.afterClickItem) {
        tooltipRef.value?.hide()
    }
}
// 暴露出实例属性对应的方法
defineExpose<DropdownInstance>({
    // 通过闭包的形式,直接赋值会拿收不到对应的节点,因为是在setup函数中,此时实例还未被挂载
    show: () => tooltipRef.value?.show(),
    hide: () => tooltipRef.value?.hide()
})
</script>
<template>
    <div class="yv-dropdown">
    <!-- 其他Tooltip属性不再赘述,这里只列出用到的 -->
        <Tooltip  @visible-change="visibleChange" ref="tooltipRef">
            <slot></slot>
            <template #content>
                <ul class="yv-dropdown__menu">
                    <template v-for="item in menuOptions" :key="item.key">
                        <!-- 分割线 -->
                        <hr 
                        v-if="item.divided" 
                        role="separator" 
                        class="divided-placeholder"
                        >

                        <li 
                        class="yv-dropdown__item" 
                        @click="itemClick(item)" 
                        :class="{
                            'is-disabled': item.disabled,
                            'is-divided': item.divided 
                        }
                            " 
                            :id="`dropdown-item-${item.key}`">
                            <!-- 中介组件,用户可自定义 -->
                            <RenderVnode :v-node="item.label" />
                        </li>
                    </template>
                </ul>
            </template>
        </Tooltip>
    </div>
</template>

总结

本文简单介绍了Dropdown组件的基本实现,基本思路是根据已有的Tooltip组件进行二次开发,在Tooltip的基础上拓展属性,用户可以自定义进行菜单列表的内容,不止可以是文本,还可以是其他更复杂的节点显示。