命令面板
规则
- 使用
cmdk 库——组合式、无障碍、内置快速模糊搜索
- 触发方式:
Cmd+K(Mac)/ Ctrl+K(Windows/Linux)——注册全局键盘监听器
- 分组操作:导航(页面)、命令(操作)、最近(最近使用)——显示分组标题
- 键盘导航:方向键移动,回车选择,Escape 关闭——全部由 cmdk 处理
- 模糊搜索:cmdk 自动过滤——匹配高亮,基于分数的排序
- 最近项目:将最近选择的 5-10 项存储在 localStorage 中——输入框为空时在顶部显示
- 动态操作:从应用的不同部分注册操作——上下文感知(不同页面显示不同操作)
- 加载状态:异步搜索结果(例如 API 搜索)——在结果区域显示加载指示器
模式
import { Command } from "cmdk";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
function CommandPalette() {
const [open, setOpen] = useState(false);
const router = useRouter();
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen((prev) => !prev);
}
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, []);
return (
<Command.Dialog open={open} onOpenChange={setOpen} label="命令面板">
<Command.Input placeholder="搜索或输入命令..." />
<Command.List>
<Command.Empty>未找到结果。</Command.Empty>
<Command.Group heading="最近">
{recentItems.map((item) => (
<Command.Item key={item.id} onSelect={() => { router.push(item.href); setOpen(false); }}>
{item.label}
</Command.Item>
))}
</Command.Group>
<Command.Group heading="导航">
<Command.Item onSelect={() => { router.push("/dashboard"); setOpen(false); }}>
仪表盘
</Command.Item>
<Command.Item onSelect={() => { router.push("/settings"); setOpen(false); }}>
设置
</Command.Item>
</Command.Group>
<Command.Group heading="命令">
<Command.Item onSelect={() => { toggleTheme(); setOpen(false); }}>
切换深色模式
</Command.Item>
<Command.Item onSelect={() => { logout(); setOpen(false); }}>
退出登录
</Command.Item>
</Command.Group>
</Command.List>
</Command.Dialog>
);
}
避免
- 自行构建模糊搜索——cmdk 内置处理过滤、评分和排序
- 忘记在
Cmd+K 上调用 e.preventDefault()——否则浏览器默认的搜索/地址栏会打开
- 缺少 Escape 键处理——cmdk 通过
onOpenChange 处理,但需确保焦点返回到上一个元素
- 仅支持静态操作——应从页面上下文动态注册操作(例如,只在文章页面显示“编辑此文章”);
- 没有空状态——搜索无匹配时始终显示“未找到结果”。