仿 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';
相关推荐
烂蜻蜓7 分钟前
巧用 VSCode 开启 Vue 开发之旅
ide·vue.js·vscode
失乐园15 分钟前
Web 通信的安全密码:HTTP/HTTPS 协议详解与最佳实践
前端·后端·面试
zzialx12315 分钟前
HarmonyOS:基于axios实现文件的下载以及下载进度的监听
前端·arkts
飘尘16 分钟前
一文搞懂什么是幻影依赖
前端·javascript·面试
the_one16 分钟前
跨域解决方案及优劣
前端·javascript
混血哲谈17 分钟前
如果我的项目是用ts写的,那么如何使用webpack的动态导入功能呢?
前端·webpack·node.js
the_one18 分钟前
同源策略与跨域解决
前端·javascript
1024小神18 分钟前
tauri2程序单例模式实现,二次点击桌面图标显示之前最小化的程序并聚焦
前端·javascript
前端花园19 分钟前
10分钟内手把手教你探索AI+blender自动建模
前端·node.js
xu__yanfeng20 分钟前
编写自己的第一个mcp插件
前端