开发预览 更新于 2026-05-10

Dropdown 下拉菜单

下拉菜单 —— Popover + 列表 + 键盘导航。支持分组标题、分隔线、危险操作语义。

基础用法

items 数组式配置,按顺序渲染菜单项;divider: true 渲染分隔线,header: true 渲染分组标题。键盘 ↑↓ 切换、Enter / 空格 选中、Esc 关闭、Home / End 跳到首/末项。

背景 视口
src/App.vue
<script setup lang="ts">
import { CfButton, CfDropdown } from '@chufix-design/vue';
import type { DropdownItem } from '@chufix-design/vue';

const items: DropdownItem[] = [
  { key: 'profile', label: '个人资料' },
  { key: 'settings', label: '设置' },
  { divider: true, label: '' },
  { header: true, label: '操作' },
  { key: 'rename', label: '重命名' },
  { key: 'archive', label: '归档', disabled: true },
  { key: 'delete', label: '删除', tone: 'danger' },
];

function onSelect(item: DropdownItem) {
  console.log('selected:', item.key);
}
</script>
<template>
  <CfDropdown :items="items" placement="bottom" @select="onSelect">
    <CfButton variant="secondary">操作菜单 ▾</CfButton>
  </CfDropdown>
</template>
<script setup>
import { CfButton, CfDropdown } from '@chufix-design/vue';

const items= [
  { key: 'profile', label: '个人资料' },
  { key: 'settings', label: '设置' },
  { divider: true, label: '' },
  { header: true, label: '操作' },
  { key: 'rename', label: '重命名' },
  { key: 'archive', label: '归档', disabled: true },
  { key: 'delete', label: '删除', tone: 'danger' },
];

function onSelect(item) {
  console.log('selected:', item.key);
}
</script>
<template>
  <CfDropdown :items="items" placement="bottom" @select="onSelect">
    <CfButton variant="secondary">操作菜单 ▾</CfButton>
  </CfDropdown>
</template>
import { CfButton, CfDropdown } from '@chufix-design/react';
import type { DropdownItem } from '@chufix-design/react';

const items: DropdownItem[] = [
{ key: 'profile', label: '个人资料' },
{ key: 'settings', label: '设置' },
{ divider: true, label: '' },
{ header: true, label: '操作' },
{ key: 'rename', label: '重命名' },
{ key: 'archive', label: '归档', disabled: true },
{ key: 'delete', label: '删除', tone: 'danger' },
];

export default function Demo() {
return (
  <CfDropdown
    items={items}
    placement="bottom"
    onSelect={(item) => console.log('selected:', item.key)}
  >
    <CfButton variant="secondary">操作菜单 ▾</CfButton>
  </CfDropdown>
);
}
import { CfButton, CfDropdown } from '@chufix-design/react';

const items= [
{ key: 'profile', label: '个人资料' },
{ key: 'settings', label: '设置' },
{ divider: true, label: '' },
{ header: true, label: '操作' },
{ key: 'rename', label: '重命名' },
{ key: 'archive', label: '归档', disabled: true },
{ key: 'delete', label: '删除', tone: 'danger' },
];

export default function Demo() {
return (
  <CfDropdown
    items={items}
    placement="bottom"
    onSelect={(item) => console.log('selected:', item.key)}
  >
    <CfButton variant="secondary">操作菜单 ▾</CfButton>
  </CfDropdown>
);
}

Placement 方向

placement 决定菜单相对触发元素的位置:top / bottom(默认)/ left / right。空间不足时浮层会自动翻转到对侧。

背景 视口
src/App.vue
<script setup lang="ts">
import { CfButton, CfDropdown } from '@chufix-design/vue';
import type { DropdownItem } from '@chufix-design/vue';

const items: DropdownItem[] = [
  { key: 'rename',  label: '重命名' },
  { key: 'archive', label: '归档' },
  { divider: true, label: '' },
  { key: 'delete',  label: '删除', tone: 'danger' },
];
</script>
<template>
  <div class="adm-grid">
    <CfDropdown :items="items" placement="top">
      <CfButton variant="secondary">placement = top</CfButton>
    </CfDropdown>
    <CfDropdown :items="items" placement="bottom">
      <CfButton variant="secondary">placement = bottom</CfButton>
    </CfDropdown>
    <CfDropdown :items="items" placement="left">
      <CfButton variant="secondary">placement = left</CfButton>
    </CfDropdown>
    <CfDropdown :items="items" placement="right">
      <CfButton variant="secondary">placement = right</CfButton>
    </CfDropdown>
  </div>
</template>
<style scoped>
.adm-grid {
  display: grid;
  grid-template-columns: repeat(2, max-content);
  gap: 16px;
  padding: 40px 0;
  justify-content: center;
}
</style>
<script setup>
import { CfButton, CfDropdown } from '@chufix-design/vue';

const items= [
  { key: 'rename',  label: '重命名' },
  { key: 'archive', label: '归档' },
  { divider: true, label: '' },
  { key: 'delete',  label: '删除', tone: 'danger' },
];
</script>
<template>
  <div class="adm-grid">
    <CfDropdown :items="items" placement="top">
      <CfButton variant="secondary">placement = top</CfButton>
    </CfDropdown>
    <CfDropdown :items="items" placement="bottom">
      <CfButton variant="secondary">placement = bottom</CfButton>
    </CfDropdown>
    <CfDropdown :items="items" placement="left">
      <CfButton variant="secondary">placement = left</CfButton>
    </CfDropdown>
    <CfDropdown :items="items" placement="right">
      <CfButton variant="secondary">placement = right</CfButton>
    </CfDropdown>
  </div>
</template>
<style scoped>
.adm-grid {
  display: grid;
  grid-template-columns: repeat(2, max-content);
  gap: 16px;
  padding: 40px 0;
  justify-content: center;
}
</style>
import { CfButton, CfDropdown } from '@chufix-design/react';

export default function Demo() {
  const items: DropdownItem[] = [
    { key: 'rename',  label: '重命名' },
    { key: 'archive', label: '归档' },
    { divider: true, label: '' },
    { key: 'delete',  label: '删除', tone: 'danger' },
  ];
  return (
    <>
      <div className="adm-grid">
          <CfDropdown items={items} placement="top">
            <CfButton variant="secondary">placement = top</CfButton>
          </CfDropdown>
          <CfDropdown items={items} placement="bottom">
            <CfButton variant="secondary">placement = bottom</CfButton>
          </CfDropdown>
          <CfDropdown items={items} placement="left">
            <CfButton variant="secondary">placement = left</CfButton>
          </CfDropdown>
          <CfDropdown items={items} placement="right">
            <CfButton variant="secondary">placement = right</CfButton>
          </CfDropdown>
        </div>
    </>
  );
}
import { CfButton, CfDropdown } from '@chufix-design/react';

export default function Demo() {
  const items= [
    { key: 'rename',  label: '重命名' },
    { key: 'archive', label: '归档' },
    { divider: true, label: '' },
    { key: 'delete',  label: '删除', tone: 'danger' },
  ];
  return (
    <>
      <div className="adm-grid">
          <CfDropdown items={items} placement="top">
            <CfButton variant="secondary">placement = top</CfButton>
          </CfDropdown>
          <CfDropdown items={items} placement="bottom">
            <CfButton variant="secondary">placement = bottom</CfButton>
          </CfDropdown>
          <CfDropdown items={items} placement="left">
            <CfButton variant="secondary">placement = left</CfButton>
          </CfDropdown>
          <CfDropdown items={items} placement="right">
            <CfButton variant="secondary">placement = right</CfButton>
          </CfDropdown>
        </div>
    </>
  );
}

禁用状态

disabled 让 Dropdown 整体不可触发;通常同时给触发元素加 disabled 让视觉反馈一致。

背景 视口
src/App.vue
<script setup lang="ts">
import { CfButton, CfDropdown } from '@chufix-design/vue';
import type { DropdownItem } from '@chufix-design/vue';

const items: DropdownItem[] = [
  { key: 'one',   label: '选项一' },
  { key: 'two',   label: '选项二' },
  { key: 'three', label: '选项三' },
];
</script>
<template>
  <div class="demo-row">
    <CfDropdown :items="items">
      <CfButton variant="secondary">正常 ▾</CfButton>
    </CfDropdown>
    <CfDropdown :items="items" disabled>
      <CfButton variant="secondary" disabled>禁用 ▾</CfButton>
    </CfDropdown>
  </div>
</template>
<script setup>
import { CfButton, CfDropdown } from '@chufix-design/vue';

const items= [
  { key: 'one',   label: '选项一' },
  { key: 'two',   label: '选项二' },
  { key: 'three', label: '选项三' },
];
</script>
<template>
  <div class="demo-row">
    <CfDropdown :items="items">
      <CfButton variant="secondary">正常 ▾</CfButton>
    </CfDropdown>
    <CfDropdown :items="items" disabled>
      <CfButton variant="secondary" disabled>禁用 ▾</CfButton>
    </CfDropdown>
  </div>
</template>
import { CfButton, CfDropdown } from '@chufix-design/react';

export default function Demo() {
  const items: DropdownItem[] = [
    { key: 'one',   label: '选项一' },
    { key: 'two',   label: '选项二' },
    { key: 'three', label: '选项三' },
  ];
  return (
    <>
      <div className="demo-row">
          <CfDropdown items={items}>
            <CfButton variant="secondary">正常 ▾</CfButton>
          </CfDropdown>
          <CfDropdown items={items} disabled>
            <CfButton variant="secondary" disabled>禁用 ▾</CfButton>
          </CfDropdown>
        </div>
    </>
  );
}
import { CfButton, CfDropdown } from '@chufix-design/react';

export default function Demo() {
  const items= [
    { key: 'one',   label: '选项一' },
    { key: 'two',   label: '选项二' },
    { key: 'three', label: '选项三' },
  ];
  return (
    <>
      <div className="demo-row">
          <CfDropdown items={items}>
            <CfButton variant="secondary">正常 ▾</CfButton>
          </CfDropdown>
          <CfDropdown items={items} disabled>
            <CfButton variant="secondary" disabled>禁用 ▾</CfButton>
          </CfDropdown>
        </div>
    </>
  );
}

受控模式

通过 v-model:open / open + onOpenChange 让父组件接管展开状态。 常用于由外部事件触发(如键盘快捷键、右键菜单、引导提示)。

背景 视口
src/App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { CfButton, CfDropdown } from '@chufix-design/vue';
import type { DropdownItem } from '@chufix-design/vue';

const open = ref<boolean>(false);
const items: DropdownItem[] = [
  { key: 'duplicate', label: '复制' },
  { key: 'move',      label: '移动' },
  { key: 'delete',    label: '删除', tone: 'danger' },
];
</script>
<template>
  <div class="demo-row">
    <CfButton variant="tertiary" @click="open = !open">
      {{ open ? '从外部关闭' : '从外部打开' }}
    </CfButton>
    <CfDropdown v-model:open="open" :items="items">
      <CfButton variant="secondary">受控触发 ▾</CfButton>
    </CfDropdown>
  </div>
</template>
<script setup>
import { ref } from 'vue';
import { CfButton, CfDropdown } from '@chufix-design/vue';

const open = ref<boolean>(false);
const items= [
  { key: 'duplicate', label: '复制' },
  { key: 'move',      label: '移动' },
  { key: 'delete',    label: '删除', tone: 'danger' },
];
</script>
<template>
  <div class="demo-row">
    <CfButton variant="tertiary" @click="open = !open">
      {{ open ? '从外部关闭' : '从外部打开' }}
    </CfButton>
    <CfDropdown v-model:open="open" :items="items">
      <CfButton variant="secondary">受控触发 ▾</CfButton>
    </CfDropdown>
  </div>
</template>
import { useState } from 'react';

export default function Demo() {
const [open, setOpen] = useState(false);
return (
  <>
    <CfButton onClick={() => setOpen(!open)}>{open ? '关闭' : '打开'}</CfButton>
    <CfDropdown open={open} onOpenChange={setOpen} items={items}>
      <CfButton variant="secondary">受控触发 ▾</CfButton>
    </CfDropdown>
  </>
);
}
import { useState } from 'react';

export default function Demo() {
const [open, setOpen] = useState(false);
return (
  <>
    <CfButton onClick={() => setOpen(!open)}>{open ? '关闭' : '打开'}</CfButton>
    <CfDropdown open={open} onOpenChange={setOpen} items={items}>
      <CfButton variant="secondary">受控触发 ▾</CfButton>
    </CfDropdown>
  </>
);
}

带图标的菜单项

DropdownItem.icon 接收一段文本(emoji / 符号),会被原样渲染在 label 左侧。 适合简洁标识;如果需要绘制矢量图标,建议改用嵌套组件方案,把整个菜单项写成自定义 trigger + content 的形式。

背景 视口
src/App.vue
<script setup lang="ts">
import { CfButton, CfDropdown } from '@chufix-design/vue';
import type { DropdownItem } from '@chufix-design/vue';

const items: DropdownItem[] = [
  { key: 'edit',     label: '编辑',     icon: '✏️' },
  { key: 'copy',     label: '复制',     icon: '📄' },
  { key: 'download', label: '下载',     icon: '⬇️' },
  { divider: true, label: '' },
  { key: 'delete',   label: '删除',     icon: '🗑', tone: 'danger' },
];
</script>
<template>
  <CfDropdown :items="items">
    <CfButton variant="secondary">带图标的菜单 ▾</CfButton>
  </CfDropdown>
</template>
<script setup>
import { CfButton, CfDropdown } from '@chufix-design/vue';

const items= [
  { key: 'edit',     label: '编辑',     icon: '✏️' },
  { key: 'copy',     label: '复制',     icon: '📄' },
  { key: 'download', label: '下载',     icon: '⬇️' },
  { divider: true, label: '' },
  { key: 'delete',   label: '删除',     icon: '🗑', tone: 'danger' },
];
</script>
<template>
  <CfDropdown :items="items">
    <CfButton variant="secondary">带图标的菜单 ▾</CfButton>
  </CfDropdown>
</template>
import { CfButton, CfDropdown } from '@chufix-design/react';

export default function Demo() {
  const items: DropdownItem[] = [
    { key: 'edit',     label: '编辑',     icon: '✏️' },
    { key: 'copy',     label: '复制',     icon: '📄' },
    { key: 'download', label: '下载',     icon: '⬇️' },
    { divider: true, label: '' },
    { key: 'delete',   label: '删除',     icon: '🗑', tone: 'danger' },
  ];
  return (
    <>
      <CfDropdown items={items}>
          <CfButton variant="secondary">带图标的菜单 ▾</CfButton>
        </CfDropdown>
    </>
  );
}
import { CfButton, CfDropdown } from '@chufix-design/react';

export default function Demo() {
  const items= [
    { key: 'edit',     label: '编辑',     icon: '✏️' },
    { key: 'copy',     label: '复制',     icon: '📄' },
    { key: 'download', label: '下载',     icon: '⬇️' },
    { divider: true, label: '' },
    { key: 'delete',   label: '删除',     icon: '🗑', tone: 'danger' },
  ];
  return (
    <>
      <CfDropdown items={items}>
          <CfButton variant="secondary">带图标的菜单 ▾</CfButton>
        </CfDropdown>
    </>
  );
}

API · Props

属性类型默认值说明
itemsDropdownItem[][]菜单项配置数组
placement'top' | 'bottom' | 'left' | 'right''bottom'首选位置
offsetnumber6与触发元素的间距(px)
closeOnSelectbooleantrue选中后关闭
disabledbooleanfalse禁用触发
widthnumber | string固定宽度,省略则自适应
openboolean受控时使用

API · DropdownItem

属性类型说明
keystring唯一标识(建议提供,便于 select 事件分流)
labelstring显示文本
iconstring可选前置图标(emoji 或符号)
disabledboolean禁用项
dividerboolean渲染为分隔线
headerboolean渲染为分组标题(不可点)
tone'default' | 'danger'语义色,danger 用于不可逆操作

API · Events

事件参数说明
select (Vue) / onSelect (React)(item, index)选中某一项时触发
update:open (Vue) / onOpenChange (React)(open: boolean)展开状态变化

反馈与讨论

Dropdown 下拉菜单 的讨论

0
0 / 600
正在加载评论...