/** * Admin 后台 - 键盘快捷键: Cmd+K 命令面板 * * 提供全局快捷键: * Cmd/Ctrl+K — 打开命令面板(页面跳转) * / — 聚焦搜索(日志页) * r — 刷新当前页面数据(通用) */ import { useEffect, useState, useCallback } from 'react' import { useNavigate } from 'react-router-dom' interface CommandItem { id: string label: string group: string action: () => void } export function useCommandPalette(pages: Array<{ to: string; label: string; group: string }>) { const [open, setOpen] = useState(false) const [query, setQuery] = useState('') const navigate = useNavigate() const items: CommandItem[] = pages.map(p => ({ id: p.to, label: p.label, group: p.group, action: () => { navigate(p.to); setOpen(false) }, })) const filtered = query ? items.filter(i => i.label.toLowerCase().includes(query.toLowerCase()) || i.id.includes(query.toLowerCase())) : items useEffect(() => { const handler = (e: KeyboardEvent) => { // Cmd/Ctrl+K → 命令面板 if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault() setOpen(o => !o) setQuery('') } // Escape → 关闭 if (e.key === 'Escape' && open) { setOpen(false) setQuery('') } } document.addEventListener('keydown', handler) return () => document.removeEventListener('keydown', handler) }, [open]) return { open, setOpen, query, setQuery, items: filtered } } /** 命令面板弹窗 */ export function CommandPalette({ open, query, setQuery, items, onClose, }: { open: boolean query: string setQuery: (q: string) => void items: CommandItem[] onClose: () => void }) { const [selected, setSelected] = useState(0) // 重置选中项当列表变化 useEffect(() => { setSelected(0) }, [items.length]) useEffect(() => { if (!open) return const handler = (e: KeyboardEvent) => { if (e.key === 'ArrowDown') { e.preventDefault(); setSelected(s => Math.min(s + 1, items.length - 1)) } if (e.key === 'ArrowUp') { e.preventDefault(); setSelected(s => Math.max(s - 1, 0)) } if (e.key === 'Enter' && items[selected]) { items[selected].action(); onClose() } } document.addEventListener('keydown', handler) return () => document.removeEventListener('keydown', handler) }, [open, items, selected, onClose]) if (!open) return null // 按分组聚合 const grouped: Record = {} for (const item of items) { grouped[item.group] = grouped[item.group] || [] grouped[item.group].push(item) } return (
e.stopPropagation()} > {/* 搜索框 */}
setQuery(e.target.value)} placeholder="输入页面名或路径…" autoFocus className="flex-1 bg-transparent text-sm outline-none placeholder:text-ink-400" /> ESC
{/* 结果列表 */}
{items.length === 0 ? (

无匹配页面

) : ( Object.entries(grouped).map(([group, groupItems]) => (

{group}

{groupItems.map((item, i) => ( ))}
)) )}
{/* 底部提示 */}
↑↓ 导航 ↵ 跳转 ESC 关闭
) }