Skip to content

下拉菜单 DropDown

通过下拉框展示的小型菜单。

基础使用

通过 options 属性设置菜单内容。选项中字符串值或者对象值的 index 字段会作为选项唯一标识,建议确保它们没有重复。

通过 select 事件获取触发的选项。

options 根据子元素的 hrefroute 字段决定把选项渲染 <a> 标签或者 Vue Router 的 RouterLink 组件。

WARNING

hrefroute 将直接渲染到 <a> 标签中,如果传递类似 javascript:alert(1) 这样的值或恶意 URL,可能会导致 XSS 或开放重定向漏洞。

<template>
	<px-space>
		<px-drop-down :options="options1" @select="selectHandler">
			<px-button>String Option</px-button>
		</px-drop-down>
		<px-drop-down :options="options2" @select="selectHandler">
			<px-button>Object Option</px-button>
		</px-drop-down>
	</px-space>
</template>
<script setup lang="ts">
import type { DropDownListOption } from '@pixelium/web-vue'

// On-demand import
// import type { DropDownListOption } from '@pixelium/web-vue/es';

import { ref } from 'vue'

const options1 = ref(['Action 1', 'Action 2', 'Action 3'])
const options2 = ref([
	{ label: 'Home', index: 'Home', href: '/pixelium-design/' },
	{
		label: 'Github',
		index: 'Github',
		target: '_blank',
		href: 'https://github.com/shika-works/pixelium-design'
	},
	{ label: 'Action 1', index: 'Action 1' },
	{ label: 'Action 2', index: 'Action 2', disabled: true },
	{ label: 'Action 3', index: 'Action 3' }
])

const selectHandler = (index: string, option: string | DropDownListOption) => {
	$message.info(
		`Selected index: ${index}, option: ${
			typeof option === 'string' ? option : JSON.stringify(option)
		}`
	)
}
</script>

弹出位置

DropDown 弹出框提供 9 种展示位置。

placement 属性决定弹出的位置。该属性值格式为:[方向]-[对齐位置],分别是'top''right''bottom''left''top-start''top-end''right-start''right-end''bottom-start''bottom-end''left-start''left-end',有四个展示方向,和三种对齐方式,默认的 placementtop

<template>
	<div class="box">
		<div class="left">
			<px-drop-down
				v-for="item in ['left-start', 'left', 'left-end']"
				:key="item"
				:placement="item"
				:options="getOptions(item)"
			>
				<px-button>{{ getLabel(item) }}</px-button>
			</px-drop-down>
		</div>
		<div class="center">
			<div class="top">
				<px-drop-down
					v-for="item in ['top-start', 'top', 'top-end']"
					:key="item"
					:placement="item"
					:options="getOptions(item)"
				>
					<px-button>{{ getLabel(item) }}</px-button>
				</px-drop-down>
			</div>
			<div class="bottom">
				<px-drop-down
					v-for="item in ['bottom-start', 'bottom', 'bottom-end']"
					:key="item"
					:placement="item"
					:options="getOptions(item)"
				>
					<px-button>{{ getLabel(item) }}</px-button>
				</px-drop-down>
			</div>
		</div>
		<div class="right">
			<px-drop-down
				v-for="item in ['right-start', 'right', 'right-end']"
				:key="item"
				:placement="item"
				:options="getOptions(item)"
			>
				<px-button>{{ getLabel(item) }}</px-button>
			</px-drop-down>
		</div>
	</div>
</template>

<script setup lang="ts">
const getLabel = (placement: string) => {
	return placement.replace(/-/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase())
}

const getOptions = (placement: string) => {
	let contentArr: string[] = []
	if (placement.includes('-')) {
		const [pos, sub] = placement.split('-')
		contentArr = [capitalize(pos), capitalize(sub), 'Popover']
	} else {
		contentArr = [capitalize(placement), 'Popover']
	}

	return contentArr
}

const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1)
</script>

<style lang="css" scoped>
.box {
	display: flex;
	width: 600px;
	height: 350px;
	margin-left: 100px;
	margin-top: 64px;
	margin-bottom: 64px;
}
.left,
.right {
	flex-shrink: 0;
	display: flex;
	flex-direction: column;
	justify-content: space-around;
}
.left {
	align-items: flex-start;
}
.right {
	align-items: flex-end;
}
.center {
	flex: 1;
	display: flex;
	flex-direction: column;
}
.top,
.bottom {
	display: flex;
	justify-content: space-around;
}
.top {
	flex: 1;
}
</style>

菜单分组

选项是可以分组的。

<template>
	<px-space>
		<px-drop-down :options="options1">
			<px-button>DropDown Group</px-button>
		</px-drop-down>
		<px-drop-down :options="options2">
			<px-button>DropDown Divider</px-button>
		</px-drop-down>
	</px-space>
</template>
<script setup lang="ts">
import { ref } from 'vue'

const options1 = ref([
	'Action 1',
	'Action 2',
	'Action 3',
	{
		label: 'Action More',
		index: 'Action More',
		children: ['Action More 1', 'Action More 2', 'Action More 3'],
		type: 'group'
	}
])
const options2 = ref([
	'Action 1',
	'Action 2',
	'Action 3',
	{
		label: 'Action More 1',
		index: 'Action More 1',
		divider: true
	},
	'Action More 2',
	'Action More 3'
])
</script>

分割按钮

结合 ButtonGroup 和 Button 组件,这里可以使得图标按钮触发弹出。

<template>
	<px-space>
		<px-drop-down :options="options">
			<px-button
				>DropDown <IconChevronDown style="margin-left: 8px"></IconChevronDown
			></px-button>
		</px-drop-down>
		<px-button-group>
			<px-button>DropDown</px-button>

			<px-drop-down :options="options">
				<px-button shape="square"><IconChevronDown /></px-button>
			</px-drop-down>
		</px-button-group>
	</px-space>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { IconChevronDown } from '@pixelium/web-vue/icon-hn/es'

const options = ref(['Action 1', 'Action 2', 'Action 3'])
</script>

受控模式

传入 visible 属性进入受控模式;不传或传 undefined 则为非受控模式,可使用 defaultVisible 指定初始显示。组件会触发 update:visible 事件以配合 v-model

<template>
	<px-drop-down :options="options" v-model:visible="visible">
		<px-button variant="text">Controlled {{ visible }}</px-button>
	</px-drop-down>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const visible = ref(false)
const options = ref(['Action 1', 'Action 2', 'Action 3'])
</script>

禁用状态

传入 disabled 设置禁用状态,此时触发方式将失效,弹出层不会被打开。

<template>
	<px-drop-down :options="options" disabled>
		<px-button theme="info">Disabled</px-button>
	</px-drop-down>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const options = ref(['Action 1', 'Action 2', 'Action 3'])
</script>

API

属性类型可选默认值描述版本
optionsDropDownOption | DropDownGroupOption下拉菜单内容。0.1.0
visibleboolean | null是否显示(受控模式,支持 v-model)。0.1.0
defaultVisibleboolean | null非受控模式下默认的显示状态。0.1.0
placement'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end''top'弹出位置。0.1.0
trigger'hover' | 'click''hover'触发方式。0.1.0
disabledbooleanboolean是否禁用。0.1.0
offsetnumber8弹出偏移距离(px)。0.1.0
variant'dark' | 'light''light'组件样式变体。0.1.0
arrowbooleantrue是否展示箭头。0.1.0
rootHTMLElement | string'body'挂载元素。0.1.0
zIndexnumber弹出层 z-index0.1.0
destroyOnHidebooleanfalse隐藏时是否销毁内容。0.1.0
popoverPropsOmit<PopoverProps, 'visible' | 'content' | 'defaultVisible'> & EmitEvent<PopoverEvents>透传给内部 Popover 的属性。0.1.0
dividerPropsRestAttrs透传 DOM 属性到分割线元素。0.1.0
事件参数描述版本
update:visiblevalue: boolean更新 visible 的回调。0.1.0
closeevent: MouseEvent弹出层关闭时触发。0.1.0
openevent: MouseEvent弹出层打开时触发。0.1.0
selectindex: string | number | symbol, option: DropDownOption | string, event: MouseEvent选择菜单时触发的回调。0.1.0
插槽参数描述版本
default触发元素插槽。0.1.0
option选项自定义渲染。0.1.0
group-label0.1.0
属性类型可选默认值描述版本
open() => void打开确认弹出框。0.1.0
close() => void关闭确认弹出框。0.1.0

RestAttrs

ts
import type { StyleValue } from 'vue'

export type VueClassValue = string | Record<string, any> | VueClassValue[]
export type VueStyleValue = StyleValue

export type RestAttrs = {
	style?: VueStyleValue | null
	class?: VueClassValue | null
	[x: string]: any
}

EmitEvent

ts
export type EmitEvent<T extends Record<string, any>> = {
	[K in keyof T as `on${Capitalize<K & string>}`]?: (...args: T[K]) => void
}
ts
export interface DropDownOption extends NavigationOption {
	divider?: boolean
	disabled?: boolean
	href?: string
	route?: string | object
	target?: string
}

export interface DropDownGroupOption extends NavigationOption {
	children: (DropDownOption | string)[]
	type: 'group'
}

export interface NavigationOption {
	index: string | number | symbol
	label?: string
}