menu.tsx 4.5 KB

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