仿 ElementPlus 组件库(五)—— Dropdown 组件实现

在我们仿 ElementPlus 组件库的旅程中,已经成功地探索并实现了Tooltip 组件。现在让我们把目光聚焦在另一个在用户界面交互中扮演重要角色的组件 ------Dropdown 组件。

Dropdown 组件,即下拉菜单组件,以其紧凑的布局和丰富的操作选项,广泛应用于各类用户界面中。无论是在管理后台、数据表格还是复杂的导航栏中,Dropdown 组件都能帮助用户便捷地进行操作选择,是构建功能强大且交互性出色的应用不可或缺的部分。

(一)组件目录

目录 复制代码
components
├── Dropdown
    ├── Dropdown.vue
    ├── types.ts
    ├── style.css   
    ├── Dropdown.tsx  
├── Common
    ├── RenderVnode.ts 
  • 基于 Tooltip 组件构建Dropdown 组件。
  • 组件接收 menuOptions 属性,用于定义下拉菜单的选项,每个选项可以包含显示内容、唯一标识、是否禁用和是否需要分隔线等信息。
  • 组件支持通过触发元素(父组件传递的内容)来显示和隐藏下拉菜单,并且可以配置显示和隐藏的延迟时间、位置等。
  • 当用户点击菜单项时,如果菜单项未被禁用,组件会触发 select 事件并传递当前菜单项。
  • 同时,组件还暴露了 showhide 方法,方便外部调用以手动控制下拉菜单的显示和隐藏。
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 组件添加点击菜单项后关闭下拉菜单的功能。
  • 通过在 DropdownProps 接口中新增 hideAfterClick 属性,用户可以灵活控制点击菜单项后下拉菜单的行为。
  • hideAfterClicktrue(默认值)时,用户点击下拉菜单中的非禁用菜单项后,下拉菜单会自动关闭;当 hideAfterClickfalse 时,点击菜单项后下拉菜单不会关闭,保持打开状态。
  • 同时,点击菜单项时会触发 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()
  }
}
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';
相关推荐
醉城夜风~36 分钟前
CSS元素显示模式(display)
前端·css
AI大模型-小华1 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子4 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅4 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni4 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学5 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端
赵庆明老师6 小时前
Vben精讲:21-详解web-antd:tsconfig.json
前端·json·vim
wc886 小时前
微软EDGE浏览器功能学习
前端·学习·edge
Csvn6 小时前
🎯 原生 `<dialog>` 元素:终于可以扔掉一半的自定义弹窗组件了?
前端