组件库Dropdown 下拉菜单
BreezliDropdown 下拉菜单
Dropdown.vue
Script 部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <script setup lang="ts"> import { ref, computed, provide } from 'vue' import { omit, isNil } from 'lodash-es' import type { DropdownProps, DropdownEmits, DropdownContext } from './types' import { useId, useDisabledStyle } from '@veyra/hooks' import { DROPDOWN_CTX_KEY } from './constants' import VrButton from '../Button/index.vue' import VrButtonGroup from '../ButtonGroup/index.vue' import DropdownItem from './DropdownItem.vue' import Tooltip from '../Tooltip/Tooltip.vue'
defineOptions({ name: 'VrDropdown', inheritAttrs: false, })
const props = withDefaults(defineProps<DropdownProps>(), { hideOnClick: true, items: () => [] as DropdownItemProps[], })
const emits = defineEmits<DropdownEmits>() const slots = defineSlots()
const tooltipRef = ref<TooltipInstance>() const triggerRef = ref<typeof VrButton>()
const tooltipProps = computed(() => omit(props, ['items', 'hideAfterClick', 'size', 'type', 'splitButton']) )
function handleItemClick(item: DropdownItemProps) { props.hideOnClick && tooltipRef.value?.hide() if (!isNil(item.command)) emits('command', item.command) }
useDisabledStyle()
defineExpose<DropdownInstance>({ open: () => tooltipRef.value?.show(), close: () => tooltipRef.value?.hide(), })
provide(DROPDOWN_CTX_KEY, { handleItemClick, size: computed(() => props.size), }) </script>
|
关键点解释
组件结构
- 继承自
Tooltip
,通过 tooltipProps
传递配置,支持弹出位置、触发方式等。
- 支持
splitButton
模式:组合主按钮和下拉按钮(如 VrButtonGroup
)。
- 提供
open
和 close
方法,通过 defineExpose
暴露给外部调用。
Props & 默认值
items
:下拉项数组,每个项需包含 command
、label
、disabled
、divided
等属性。
splitButton
:启用分体按钮模式(主按钮 + 下拉按钮)。
hideOnClick
:点击下拉项后自动关闭菜单。
事件处理
command
:当点击有 command
的项时触发,传递 command
值。
visible-change
:弹出层显示/隐藏时触发,传递布尔值。
提供上下文(Provide/Inject)
- 通过
DROPDOWN_CTX_KEY
向子组件 DropdownItem
提供 handleItemClick
和 size
。
size
用于子项样式适配。
Template 部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <template> <div class="vr-dropdown" :id="`dropdown-${useId().value}`" :class="{ 'is-disabled': props.disabled }"> <tooltip ref="tooltipRef" v-bind="tooltipProps" :virtual-triggering="splitButton" :virtual-ref="triggerRef" @visible-change="$emit('visible-change', $event)" > <!-- 触发器 --> <vr-button-group v-if="splitButton" :type="type" :size="size" :disabled="disabled" > <vr-button @click="$emit('click', $event)"> <slot name="default"></slot> </vr-button> <vr-button ref="triggerRef" icon="angle-down" /> </vr-button-group> <!-- 单一触发器 --> <slot v-else name="default"></slot>
<!-- 下拉内容 --> <template #content> <ul class="vr-dropdown__menu"> <slot name="dropdown"> <template v-for="item in items" :key="item.command ?? useId().value"> <dropdown-item v-bind="item" /> </template> </slot> </ul> </template> </tooltip> </div> </template>
|
关键点解释
触发器模式
- 分体按钮模式(splitButton):
- 使用
VrButtonGroup
包含主按钮和下拉箭头按钮。
- 主按钮点击触发
click
事件,下拉箭头按钮控制弹出层。
- 普通模式:
- 直接使用插槽内容作为触发器(如自定义按钮或图标)。
下拉内容渲染
- 通过
#content
插槽渲染下拉菜单,优先使用 slot="dropdown"
的自定义内容。
- 默认遍历
items
数组,渲染 DropdownItem
组件。
样式与禁用状态
is-disabled
类控制禁用状态样式。
- 分体按钮模式下,按钮组的最后一个按钮(箭头)样式通过
:deep
进行 CSS 覆盖。
DropdownItem.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <script setup lang="ts"> import { inject, computed } from 'vue' import { useId } from '@veyra/hooks' import { DROPDOWN_CTX_KEY } from './constants' import type { DropdownItemProps } from './types'
defineOptions({ name: 'VrDropdownItem', })
const props = withDefaults(defineProps<DropdownItemProps>(), { command: useId().value, divided: false, disabled: false, })
const ctx = inject(DROPDOWN_CTX_KEY) const size = computed(() => ctx?.size.value)
function handleClick() { if (props.disabled) return ctx?.handleItemClick(props) } </script>
<template> <li v-if="divided" role="separator" class="divided-placeholder"></li> <li :id="`dropdown-item-${command ?? useId().value}`" :class="{ 'vr-dropdown__item': true, [`vr-dropdown__item--${size}`]: size, 'is-disabled': disabled, 'is-divided': divided, }" @click="handleClick" > <slot>{{ label }}</slot> </li> </template>
|
关键点解释
项的渲染
divided
属性:当为 true
时渲染分隔符(role="separator"
的 li
元素)。
command
:唯一标识符,用于触发 command
事件。
样式适配
- 根据父组件的
size
动态添加类名(如 vr-dropdown__item--large
)。
is-disabled
和 is-divided
控制禁用和分隔符样式。
事件触发
- 点击非禁用项时,调用父组件的
handleItemClick
方法,并传递自身 props
。
辅助文件
constants.ts
1 2 3 4
| import type { InjectionKey } from "vue" import type { DropdownContext } from "./types"
export const DROPDOWN_CTX_KEY: InjectionKey<DropdownContext> = Symbol("dropdownContext")
|
- 定义注入键
DROPDOWN_CTX_KEY
,用于在子组件中注入上下文。
types.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| export interface DropdownItemProps { command?: string | number label?: string | VNode disabled?: boolean divided?: boolean }
export interface DropdownProps extends TooltipProps { type?: ButtonType size?: ButtonSize items?: DropdownItemProps[] hideOnClick?: boolean splitButton?: boolean }
export interface DropdownEmits { (e: "visible-change", value: boolean): void (e: "command", value: DropdownCommand): void (e: "click", value: MouseEvent): void }
|
- DropdownProps:继承
TooltipProps
,支持 placement
、trigger
等属性。
- DropdownItemProps:定义下拉项属性,如
command
、disabled
、divided
。
使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <template> <vr-dropdown v-model:visible="visible" :items="items" split-button type="primary" @command="handleCommand" @visible-change="handleVisibleChange" > <template #default> <vr-button>自定义触发器</vr-button> </template> <template #dropdown> <ul class="custom-menu"> <vr-dropdown-item command="custom1">自定义项1</vr-dropdown-item> </ul> </template> </vr-dropdown> </template>
<script setup> import { ref } from 'vue'
const items = ref([ { label: '选项1', command: 'cmd1' }, { label: '选项2', command: 'cmd2', divided: true }, ])
const handleCommand = (command: string) => { console.log('Command:', command) } </script>
|
核心机制总结
- 弹出层集成:基于
Tooltip
实现,支持位置、触发方式等配置。
- 分体按钮模式:通过
splitButton
展示主按钮和下拉按钮组合。
- 动态项渲染:通过
items
数组或 slot="dropdown"
自定义下拉内容。
- 事件交互:
command
:点击带 command
的项时触发。
visible-change
:弹出层状态变化时触发。
- 样式适配:通过
size
和 type
继承按钮样式,支持分隔符和禁用状态。
通过 provide/inject
实现父子组件通信,确保下拉项点击能触发父组件的事件。