Dropdown 下拉選單
向下彈出的命令選單。觸發器可以是任意元素,選單項透過 command 標識,點選後以 command 事件拋出。按 WAI-ARIA menu 模式實作:方向鍵循環、Home/End 首尾、Enter/Space 觸發、Esc 關閉並歸還焦點。
基礎用法
click 觸發 + command 事件
最近指令:—
vue
<template>
<LhDropdown @command="onCommand">
<LhButton>{{ selectedLabel || '更多操作' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="new">新建</LhDropdownItem>
<LhDropdownItem command="edit">编辑</LhDropdownItem>
<LhDropdownItem command="delete">删除</LhDropdownItem>
</template>
</LhDropdown>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const lastCommand = ref('')
// 觸發器文字由使用者維護:command 事件只拋出命令值,標籤怎麼顯示交給外部決定
const labelMap: Record<string, string> = { new: '新建', edit: '编辑', delete: '删除' }
const selectedLabel = ref('')
function onCommand(command: string | number | undefined) {
lastCommand.value = String(command)
selectedLabel.value = labelMap[String(command)] ?? String(command)
}
</script>vue
<script>
export default {
data() {
return {
lastCommand: '',
// 觸發器文字由使用者維護:command 事件只拋出命令值,標籤怎麼顯示交給外部決定
labelMap: { new: '新建', edit: '编辑', delete: '删除' },
selectedLabel: '',
}
},
methods: {
onCommand(command) {
this.lastCommand = String(command)
this.selectedLabel = this.labelMap[String(command)] ?? String(command)
},
},
}
</script>
<template>
<LhDropdown @command="onCommand">
<LhButton>{{ selectedLabel || '更多操作' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="new">新建</LhDropdownItem>
<LhDropdownItem command="edit">编辑</LhDropdownItem>
<LhDropdownItem command="delete">删除</LhDropdownItem>
</template>
</LhDropdown>
</template>觸發方式
預設為 click,也可改為懸停或右鍵展開。
hover / contextmenu
vue
<script setup lang="ts">
import { ref } from 'vue'
const hoverLabel = ref('')
function onHoverCommand(command: string | number | undefined) {
hoverLabel.value = String(command)
}
const contextLabel = ref('')
function onContextCommand(command: string | number | undefined) {
contextLabel.value = String(command)
}
</script>
<template>
<!-- 懸停觸發 -->
<LhDropdown trigger="hover" @command="onHoverCommand">
<LhButton>{{ hoverLabel || '悬停触发' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="hover-1">选项一</LhDropdownItem>
<LhDropdownItem command="hover-2">选项二</LhDropdownItem>
</template>
</LhDropdown>
<!-- 右鍵觸發 -->
<LhDropdown trigger="contextmenu" @command="onContextCommand">
<LhButton>{{ contextLabel || '右键触发' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="ctx-1">刷新</LhDropdownItem>
<LhDropdownItem command="ctx-2">复制</LhDropdownItem>
</template>
</LhDropdown>
</template>vue
<script>
export default {
data() {
return { hoverLabel: '', contextLabel: '' }
},
methods: {
onHoverCommand(command) {
this.hoverLabel = String(command)
},
onContextCommand(command) {
this.contextLabel = String(command)
},
},
}
</script>
<template>
<!-- 懸停觸發 -->
<LhDropdown trigger="hover" @command="onHoverCommand">
<LhButton>{{ hoverLabel || '悬停触发' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="hover-1">选项一</LhDropdownItem>
<LhDropdownItem command="hover-2">选项二</LhDropdownItem>
</template>
</LhDropdown>
<!-- 右鍵觸發 -->
<LhDropdown trigger="contextmenu" @command="onContextCommand">
<LhButton>{{ contextLabel || '右键触发' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="ctx-1">刷新</LhDropdownItem>
<LhDropdownItem command="ctx-2">复制</LhDropdownItem>
</template>
</LhDropdown>
</template>
contextmenu觸發會阻止瀏覽器預設右鍵選單。
停用與分隔線
disabled / divided
vue
<script setup lang="ts">
import { ref } from 'vue'
const actionLabel = ref('')
function onActionCommand(command: string | number | undefined) {
actionLabel.value = String(command)
}
</script>
<template>
<!-- 帶停用項與分隔線的操作選單 -->
<LhDropdown @command="onActionCommand">
<LhButton>{{ actionLabel || '操作' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="export">导出</LhDropdownItem>
<LhDropdownItem command="import">导入</LhDropdownItem>
<LhDropdownItem divided command="audit">审计日志</LhDropdownItem>
<LhDropdownItem disabled command="purge">清空数据</LhDropdownItem>
</template>
</LhDropdown>
</template>vue
<script>
export default {
data() {
return { actionLabel: '' }
},
methods: {
onActionCommand(command) {
this.actionLabel = String(command)
},
},
}
</script>
<template>
<!-- 帶停用項與分隔線的操作選單 -->
<LhDropdown @command="onActionCommand">
<LhButton>{{ actionLabel || '操作' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="export">导出</LhDropdownItem>
<LhDropdownItem command="import">导入</LhDropdownItem>
<LhDropdownItem divided command="audit">审计日志</LhDropdownItem>
<LhDropdownItem disabled command="purge">清空数据</LhDropdownItem>
</template>
</LhDropdown>
</template>停用項仍可被聚焦(
aria-disabled="true"),螢幕閱讀器能讀出該項;但方向鍵會跳過它,點選與 Enter/Space 均不觸發command。
位置與最大高度
placement 與 maxHeight
vue
<script setup lang="ts">
import { ref } from 'vue'
const itemLabel = ref('')
function onItemCommand(command: string | number | undefined) {
itemLabel.value = `第 ${command} 项`
}
</script>
<template>
<!-- 底部右對齊並限制最大高度 -->
<LhDropdown placement="bottom-end" :max-height="160" @command="onItemCommand">
<LhButton>{{ itemLabel || '底部右对齐' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem v-for="i in 12" :key="i" :command="i">第 {{ i }} 项</LhDropdownItem>
</template>
</LhDropdown>
</template>vue
<script>
export default {
data() {
return { itemLabel: '' }
},
methods: {
onItemCommand(command) {
this.itemLabel = `第 ${command} 项`
},
},
}
</script>
<template>
<!-- 底部右對齊並限制最大高度 -->
<LhDropdown placement="bottom-end" :max-height="160" @command="onItemCommand">
<LhButton>{{ itemLabel || '底部右对齐' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem v-for="i in 12" :key="i" :command="i">第 {{ i }} 项</LhDropdownItem>
</template>
</LhDropdown>
</template>受控顯隱與點選行為
v-model:visible 手動控制顯隱;hide-on-click 控制選中後是否自動收起;popper-class 給彈出層加自訂類別;teleport-target 指定掛載容器。
受控顯隱
vue
<script setup lang="ts">
import { ref } from 'vue'
const menuVisible = ref(false)
</script>
<template>
<!-- 受控顯隱:選中後不收起 -->
<LhDropdown
v-model:visible="menuVisible"
:hide-on-click="false"
popper-class="demo-dropdown-popper"
>
<LhButton>{{ menuVisible ? '收起' : '展开' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="copy">复制(选中后不收起)</LhDropdownItem>
<LhDropdownItem command="paste">粘贴</LhDropdownItem>
</template>
</LhDropdown>
</template>vue
<script>
export default {
data() {
return { menuVisible: false }
},
}
</script>
<template>
<!-- 受控顯隱:選中後不收起 -->
<LhDropdown
v-model:visible="menuVisible"
:hide-on-click="false"
popper-class="demo-dropdown-popper"
>
<LhButton>{{ menuVisible ? '收起' : '展开' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="copy">复制(选中后不收起)</LhDropdownItem>
<LhDropdownItem command="paste">粘贴</LhDropdownItem>
</template>
</LhDropdown>
</template>掛載到指定容器
Teleport 容器(#dropdown-zone)
vue
<script setup lang="ts">
</script>
<template>
<!-- 掛載到指定容器 -->
<LhDropdown teleport-target="#dropdown-zone">
<LhButton>更多 ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="a">选项</LhDropdownItem>
</template>
</LhDropdown>
</template>vue
<script>
export default {}
</script>
<template>
<!-- 掛載到指定容器 -->
<LhDropdown teleport-target="#dropdown-zone">
<LhButton>更多 ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="a">选项</LhDropdownItem>
</template>
</LhDropdown>
</template>Dropdown API
Props
| 屬性 | 說明 | 類型 | 預設值 |
|---|---|---|---|
| trigger | 觸發方式 | "hover" | "click" | "contextmenu" | 'click' |
| placement | 展示位置(12 種,同 Tooltip) | "top" | "top-start" | "top-end" | "bottom" | "bottom-start" | "bottom-end" | "left" | "left-start" | "left-end" | "right" | "right-start" | "right-end" | 'bottom-start' |
| visible | 顯隱狀態(v-model:visible) | boolean | — |
| disabled | 是否停用觸發器 | boolean | — |
| hideOnClick | 點選選單項後是否自動關閉 | boolean | true |
| teleportTarget | Teleport 目標(選擇器或元素) | string | HTMLElement | 'body' |
| popperClass | 彈出層自訂類別名 | string | — |
| maxHeight | 選單最大高度(數字為 px) | string | number | — |
| themeVars | 元件級主題變數 | { [x: string]: string; } | — |
| bgPattern | 元件級背景花紋,覆寫 ConfigProvider 設定 | string | — |
| sx | sx 快速版面配置屬性(作用於觸發器根元素) | LhSxProps | — |
Events
| 事件 | 說明 | 參數 |
|---|---|---|
| command | 點選選單項時觸發 | (command: string | number | undefined) |
| click | 選單項(LhDropdownItem)被點選時觸發 | (command: string | number | undefined) |
| visible-change | 顯隱變化 | (value: boolean) |
| update:visible | 顯隱變化(v-model) | (value: boolean) |
Slots
| 插槽 | 說明 |
|---|---|
| default | 觸發器內容 |
| dropdown | 選單內容(LhDropdownItem 列表) |
Methods
| 方法 | 說明 |
|---|---|
| show | 程式化展開 |
| hide | 程式化收起 |
| toggle | 程式化切換 |
DropdownItem API
Props
| 屬性 | 說明 | 類型 | 預設值 |
|---|---|---|---|
| command | 命令標識,點選時隨 command 事件拋出 | string | number | — |
| disabled | 是否停用 | boolean | false |
| divided | 是否在上方顯示分隔線 | boolean | false |
無障礙
- 觸發器帶
aria-haspopup="menu"與aria-expanded;選單為role="menu",項為role="menuitem" - 鍵盤遵循 APG menu 模式:↑/↓ 循環(跳過停用項)、Home/End 跳首尾、Enter/Space 觸發
command並關閉、Esc 關閉並歸還焦點 - 停用項用
aria-disabled而非移出焦點樹,方向鍵與 Enter 鍵均會跳過它 - 選單展開時主軸溢出會自動翻轉方向,交叉軸溢出會夾回視區
類型定義
本元件對外匯出的 TypeScript 類型結構如下(自動提取自 packages/components/dropdown/src/types.ts 與 @lh-ui/share/core/* 原始碼,點選各條目展開檢視完整定義;屬性說明見上方 Props 表):
LhDropdownProps
ts
/**
* Dropdown 元件 Props
*/
export interface LhDropdownProps {
/** 觸發方式 */
trigger?: LhDropdownTrigger
/** 展示位置 */
placement?: LhDropdownPlacement
/** 手動控制的顯示狀態(v-model:visible) */
visible?: boolean
/** 是否停用 */
disabled?: boolean
/** 點選選單項後是否自動關閉 */
hideOnClick?: boolean
/** Teleport 目標(選擇器或元素,預設 body) */
teleportTarget?: string | HTMLElement
/** 彈出層自訂類別名 */
popperClass?: string
/** 選單最大高度 */
maxHeight?: string | number
/** 元件級主題變數,覆寫 ConfigProvider 設定 */
themeVars?: Record<string, string>
/** 元件級背景花紋,覆寫 ConfigProvider 設定 */
bgPattern?: string
/** sx 快速版面配置屬性(作用於觸發器根元素) */
sx?: LhSxProps
}LhDropdownItemProps
ts
/**
* DropdownItem 元件 Props
*/
export interface LhDropdownItemProps {
/** 命令標識,點選時隨 command 事件拋出 */
command?: string | number
/** 是否停用(保留可聚焦性以進入無障礙樹) */
disabled?: boolean
/** 是否顯示分隔線 */
divided?: boolean
}LhDropdownTrigger (來自 @lh-ui/share/core/dropdown (logic.ts))
ts
/** 觸發方式 */
export type LhDropdownTrigger = 'hover' | 'click' | 'contextmenu'