menu.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. import React from 'react';
  2. import { useNavigate } from 'react-router';
  3. import type { MenuProps } from 'antd';
  4. import {
  5. UserOutlined,
  6. DashboardOutlined,
  7. TeamOutlined,
  8. SettingOutlined,
  9. FileOutlined,
  10. MessageOutlined,
  11. InfoCircleOutlined,
  12. BarChartOutlined,
  13. LineChartOutlined,
  14. EnvironmentOutlined,
  15. MoonOutlined,
  16. SunOutlined,
  17. FileTextOutlined
  18. } from '@ant-design/icons';
  19. import { useTheme } from './hooks_sys.tsx';
  20. export interface MenuItem {
  21. key: string;
  22. label: string;
  23. icon?: React.ReactNode;
  24. children?: MenuItem[];
  25. path?: string;
  26. permission?: string;
  27. }
  28. /**
  29. * 菜单搜索 Hook
  30. * 封装菜单搜索相关逻辑
  31. */
  32. export const useMenuSearch = (menuItems: MenuItem[]) => {
  33. const [searchText, setSearchText] = React.useState('');
  34. // 过滤菜单项
  35. const filteredMenuItems = React.useMemo(() => {
  36. if (!searchText) return menuItems;
  37. const filterItems = (items: MenuItem[]): MenuItem[] => {
  38. return items
  39. .map(item => {
  40. // 克隆对象避免修改原数据
  41. const newItem = { ...item };
  42. if (newItem.children) {
  43. newItem.children = filterItems(newItem.children);
  44. }
  45. return newItem;
  46. })
  47. .filter(item => {
  48. // 保留匹配项或其子项匹配的项
  49. const match = item.label.toLowerCase().includes(searchText.toLowerCase());
  50. if (match) return true;
  51. if (item.children?.length) return true;
  52. return false;
  53. });
  54. };
  55. return filterItems(menuItems);
  56. }, [menuItems, searchText]);
  57. // 清除搜索
  58. const clearSearch = () => {
  59. setSearchText('');
  60. };
  61. return {
  62. searchText,
  63. setSearchText,
  64. filteredMenuItems,
  65. clearSearch
  66. };
  67. };
  68. export const useMenu = () => {
  69. const { isDark, toggleTheme } = useTheme();
  70. const navigate = useNavigate();
  71. const [collapsed, setCollapsed] = React.useState(false);
  72. const [openKeys, setOpenKeys] = React.useState<string[]>([]);
  73. // 基础菜单项配置
  74. const menuItems: MenuItem[] = [
  75. {
  76. key: 'dashboard',
  77. label: '控制台',
  78. icon: <DashboardOutlined />,
  79. path: '/admin/dashboard'
  80. },
  81. {
  82. key: 'stock-training',
  83. label: '股票训练',
  84. icon: <LineChartOutlined />,
  85. children: [
  86. {
  87. key: 'date-notes',
  88. label: '日期笔记',
  89. path: '/admin/date-notes',
  90. permission: 'content:manage'
  91. },
  92. {
  93. key: 'classroom-data',
  94. label: '教室数据',
  95. path: '/admin/classroom-data',
  96. permission: 'data:view'
  97. },
  98. {
  99. key: 'submission-records',
  100. label: '提交记录',
  101. path: '/admin/submission-records',
  102. permission: 'data:view'
  103. },
  104. {
  105. key: 'xunlian-codes',
  106. label: '训练代码',
  107. path: '/admin/xunlian-codes',
  108. permission: 'content:manage'
  109. }
  110. ]
  111. },
  112. {
  113. key: 'users',
  114. label: '用户管理',
  115. icon: <TeamOutlined />,
  116. path: '/admin/users',
  117. permission: 'user:manage'
  118. },
  119. {
  120. key: 'settings',
  121. label: '系统设置',
  122. icon: <SettingOutlined />,
  123. children: [
  124. {
  125. key: 'theme-settings',
  126. label: '主题设置',
  127. path: '/admin/theme-settings',
  128. permission: 'system:settings'
  129. },
  130. {
  131. key: 'system-settings',
  132. label: '系统配置',
  133. path: '/admin/settings',
  134. permission: 'system:settings'
  135. }
  136. ]
  137. },
  138. {
  139. key: 'content',
  140. label: '内容管理',
  141. icon: <FileOutlined />,
  142. children: [
  143. {
  144. key: 'know-info',
  145. label: '知识库',
  146. path: '/admin/know-info',
  147. permission: 'content:manage'
  148. },
  149. {
  150. key: 'file-library',
  151. label: '文件库',
  152. path: '/admin/file-library',
  153. permission: 'content:manage'
  154. }
  155. ]
  156. },
  157. {
  158. key: 'messages',
  159. label: '消息中心',
  160. icon: <MessageOutlined />,
  161. path: '/admin/messages',
  162. permission: 'message:view'
  163. },
  164. {
  165. key: 'charts',
  166. label: '数据图表',
  167. icon: <BarChartOutlined />,
  168. path: '/admin/chart-dashboard',
  169. permission: 'chart:view'
  170. },
  171. {
  172. key: 'maps',
  173. label: '地图',
  174. icon: <EnvironmentOutlined />,
  175. path: '/admin/map-dashboard',
  176. permission: 'map:view'
  177. }
  178. ];
  179. // 用户菜单项
  180. const userMenuItems: MenuProps['items'] = [
  181. {
  182. key: 'profile',
  183. label: '个人资料',
  184. icon: <UserOutlined />
  185. },
  186. {
  187. key: 'theme',
  188. label: isDark ? '切换到亮色模式' : '切换到暗色模式',
  189. icon: isDark ? <SunOutlined /> : <MoonOutlined />,
  190. onClick: () => toggleTheme()
  191. },
  192. {
  193. key: 'logout',
  194. label: '退出登录',
  195. icon: <InfoCircleOutlined />,
  196. danger: true
  197. }
  198. ];
  199. // 处理菜单点击
  200. const handleMenuClick = (item: MenuItem) => {
  201. if (item.path) {
  202. navigate(item.path);
  203. }
  204. };
  205. // 处理菜单展开变化
  206. const onOpenChange = (keys: string[]) => {
  207. const latestOpenKey = keys.find(key => openKeys.indexOf(key) === -1);
  208. setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
  209. };
  210. return {
  211. menuItems,
  212. userMenuItems,
  213. openKeys,
  214. collapsed,
  215. setCollapsed,
  216. handleMenuClick,
  217. onOpenChange
  218. };
  219. };