ERPTurbo_Admin/packages/app-operation/src/components/SearchMenu/index.tsx
shenyifei b4ce6e45dc refactor(order): 重构发货单功能模块
- 将 ShipOrderList 组件从 Delivery 模块迁移至 Order 模块
- 移除 Delivery 模块的导出并在 Order 模块中新增 OrderShipList 和相关组件
- 更新发货单列表的列配置,添加订单、经销商、公司等关联字段
- 修改发货单列表的字段映射,将经销商名称和车次号格式从短横线改为竖线分隔
- 在 BizPage 组件中添加 convertValue 属性支持数据转换
- 更新左侧菜单组件的依赖数组以包含 menuData
- 调整发货单列表的国际化配置,更新字段标题和状态枚举值
- 新增 OrderShip 页面路由文件
- 移除项目根目录的 AGENTS.md 和 project.md 文件
- 从组件索引中移除 Delivery 模块并添加 SearchMenu 组件
2026-01-08 16:15:03 +08:00

268 lines
6.4 KiB
TypeScript

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<string, FrequentMenuItem> => {
try {
const data = localStorage.getItem(STORAGE_KEY);
return data ? JSON.parse(data) : {};
} catch {
return {};
}
};
/** 保存常用菜单到本地 */
const saveFrequentMenus = (menus: Record<string, FrequentMenuItem>) => {
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<SearchMenuProps> = ({ 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: (
<div
className={styles.searchItem}
onClick={() => handleMenuClick(item.path!, item.name)}
>
<span className={styles.menuName}>{item.name}</span>
<span className={styles.menuPath}>{item.path}</span>
</div>
),
}));
}
// 如果没有搜索关键词,显示常用菜单
if (frequentMenus.length > 0) {
return [
{
value: 'frequent-header',
label: (
<div className={styles.sectionHeader}>
<StarOutlined className={styles.sectionIcon} />
<span></span>
</div>
),
disabled: true,
},
...frequentMenus.map((item) => ({
value: item.path,
label: (
<div
className={styles.searchItem}
onClick={() => handleMenuClick(item.path!, item.name)}
>
<div className={styles.menuRow}>
<span className={styles.menuName}>{item.name}</span>
<span className={styles.frequency}>{item.frequency} </span>
</div>
<span className={styles.menuPath}>{item.path}</span>
</div>
),
})),
];
}
// 没有常用菜单时显示提示
return [
{
value: 'empty-header',
label: (
<div className={styles.sectionHeader}>
<ClockCircleOutlined className={styles.sectionIcon} />
<span></span>
</div>
),
disabled: true,
},
{
value: 'empty-tip',
label: (
<div className={styles.emptyTip}>使</div>
),
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 (
<div className={styles.searchMenuContainer}>
<AutoComplete
open={open}
onDropdownVisibleChange={handleDropdownOpen}
onSearch={handleSearch}
onSelect={handleSelect}
options={options}
dropdownClassName={styles.dropdown}
dropdownMatchSelectWidth={320}
backfill
>
<Input
placeholder="搜索菜单..."
prefix={<SearchOutlined className={styles.searchIcon} />}
allowClear
className={styles.searchInput}
onClick={() => setOpen(true)}
/>
</AutoComplete>
</div>
);
};
export default SearchMenu;