import { ClockCircleOutlined, SearchOutlined, StarOutlined, } from '@ant-design/icons'; import { MenuDataItem } from '@ant-design/pro-components'; import { history } from '@umijs/max'; import type { AutoCompleteProps } from 'antd'; import { AutoComplete, Input } from 'antd'; import React, { useMemo, useState } from 'react'; import useSearchMenuStyle from './style.style'; interface SearchMenuProps { menuData?: MenuDataItem[]; } interface FrequentMenuItem extends MenuDataItem { frequency: number; lastUsed: number; } /** 本地存储键 */ const STORAGE_KEY = 'erp_frequent_menus'; /** 保留常用菜单数量 */ const MAX_FREQUENT_MENUS = 8; /** 最低使用频率阈值 */ const MIN_FREQUENCY = 1; /** * 递归扁平化菜单数据,生成搜索列表 */ const flattenMenuData = (menuData: MenuDataItem[]): MenuDataItem[] => { const result: MenuDataItem[] = []; menuData.forEach((item) => { if (item.hideInMenu) return; const currentPath = item.path; if (item.name) { result.push({ ...item, path: currentPath, }); } if (item.children && item.children.length > 0) { result.push(...flattenMenuData(item.children)); } }); return result; }; /** 从本地获取常用菜单 */ const getFrequentMenus = (): Record => { try { const data = localStorage.getItem(STORAGE_KEY); return data ? JSON.parse(data) : {}; } catch { return {}; } }; /** 保存常用菜单到本地 */ const saveFrequentMenus = (menus: Record) => { localStorage.setItem(STORAGE_KEY, JSON.stringify(menus)); }; /** * 记录菜单使用 */ const recordMenuUsage = (path: string, name?: string) => { if (!path) return; const frequentMenus = getFrequentMenus(); const now = Date.now(); if (frequentMenus[path]) { frequentMenus[path].frequency += 1; frequentMenus[path].lastUsed = now; } else { frequentMenus[path] = { path, name: name || '', frequency: 1, lastUsed: now, }; } saveFrequentMenus(frequentMenus); }; const SearchMenu: React.FC = ({ menuData = [] }) => { const { styles } = useSearchMenuStyle(); const [open, setOpen] = useState(false); const [searchValue, setSearchValue] = useState(''); // 扁平化菜单数据用于搜索 const flatMenus = useMemo(() => flattenMenuData(menuData), [menuData]); // 根据搜索关键词过滤菜单 const searchResults = useMemo(() => { if (!searchValue.trim()) return []; const keyword = searchValue.toLowerCase(); return flatMenus.filter( (item) => item.name?.toLowerCase().includes(keyword) || item.path?.toLowerCase().includes(keyword), ); }, [flatMenus, searchValue]); // 获取常用菜单(按频率和使用时间排序) const frequentMenus = useMemo(() => { const frequentData = getFrequentMenus(); // 将常用菜单与扁平化菜单合并,获取完整信息 const mergedMenus = flatMenus .map((item) => { const freq = frequentData[item.path!]; return freq ? { ...item, frequency: freq.frequency, lastUsed: freq.lastUsed, } : { ...item, frequency: 0, lastUsed: 0 }; }) .filter((item) => item.frequency >= MIN_FREQUENCY); // 按频率降序,使用时间降序排序 return mergedMenus .sort((a, b) => { if (b.frequency !== a.frequency) { return b.frequency - a.frequency; } return b.lastUsed - a.lastUsed; }) .slice(0, MAX_FREQUENT_MENUS); }, [flatMenus]); // 处理菜单点击 const handleMenuClick = (path: string, name?: string) => { if (path) { recordMenuUsage(path, name); history.push(path); setOpen(false); setSearchValue(''); } }; // 生成 AutoComplete 的 options const options: AutoCompleteProps['options'] = useMemo(() => { // 如果有搜索关键词,显示搜索结果 if (searchValue.trim()) { return searchResults.map((item) => ({ value: item.path, label: (
handleMenuClick(item.path!, item.name)} > {item.name} {item.path}
), })); } // 如果没有搜索关键词,显示常用菜单 if (frequentMenus.length > 0) { return [ { value: 'frequent-header', label: (
常用菜单
), disabled: true, }, ...frequentMenus.map((item) => ({ value: item.path, label: (
handleMenuClick(item.path!, item.name)} >
{item.name} {item.frequency} 次
{item.path}
), })), ]; } // 没有常用菜单时显示提示 return [ { value: 'empty-header', label: (
暂无常用菜单
), disabled: true, }, { value: 'empty-tip', label: (
点击菜单后会自动记录使用频率
), disabled: true, }, ]; }, [searchResults, searchValue, frequentMenus, styles]); // 处理搜索值变化 const handleSearch: AutoCompleteProps['onSearch'] = (value) => { setSearchValue(value); }; // 处理下拉框显示状态变化 const handleDropdownOpen = (open: boolean) => { setOpen(open); if (!open) { setSearchValue(''); } }; // 处理选中选项 const handleSelect: AutoCompleteProps['onSelect'] = (value) => { const selectedMenu = flatMenus.find((item) => item.path === value); handleMenuClick(value as string, selectedMenu?.name); }; return (
} allowClear className={styles.searchInput} onClick={() => setOpen(true)} />
); }; export default SearchMenu;