diff --git a/client/admin/components/ErrorPage.tsx b/client/admin/components/ErrorPage.tsx new file mode 100644 index 0000000..fe2ff51 --- /dev/null +++ b/client/admin/components/ErrorPage.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { useRouteError } from 'react-router'; +import { Alert, Button } from 'antd'; +import { useTheme } from '../hooks_sys.tsx'; + +export const ErrorPage = () => { + const { isDark } = useTheme(); + const error = useRouteError() as any; + const errorMessage = error?.statusText || error?.message || '未知错误'; + + return ( +
+
+

发生错误

+ + {error.stack} + + ) : null + } + className="mb-4" + /> +
+ + +
+
+
+ ); +}; \ No newline at end of file diff --git a/client/admin/layouts/MainLayout.tsx b/client/admin/layouts/MainLayout.tsx new file mode 100644 index 0000000..4a831c1 --- /dev/null +++ b/client/admin/layouts/MainLayout.tsx @@ -0,0 +1,222 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import { + Outlet, + useLocation, +} from 'react-router'; +import { + Layout, Button, Space, Badge, Avatar, Dropdown, Typography, Input, Menu, +} from 'antd'; +import { + MenuFoldOutlined, + MenuUnfoldOutlined, + BellOutlined, + VerticalAlignTopOutlined, + UserOutlined +} from '@ant-design/icons'; +import { useAuth, useTheme } from '../hooks_sys.tsx'; +import { useMenu, useMenuSearch, type MenuItem } from '../menu.tsx'; + +const { Header, Sider, Content } = Layout; + +/** + * 主布局组件 + * 包含侧边栏、顶部导航和内容区域 + */ +export const MainLayout = () => { + const { user } = useAuth(); + const { isDark } = useTheme(); + const [showBackTop, setShowBackTop] = useState(false); + const location = useLocation(); + + // 使用菜单hook + const { + menuItems, + userMenuItems, + openKeys, + collapsed, + setCollapsed, + handleMenuClick: handleRawMenuClick, + onOpenChange + } = useMenu(); + + // 处理菜单点击 + const handleMenuClick = (key: string) => { + const item = findMenuItem(menuItems, key); + if (item && 'label' in item) { + handleRawMenuClick(item); + } + }; + + // 查找菜单项 + const findMenuItem = (items: MenuItem[], key: string): MenuItem | null => { + for (const item of items) { + if (!item) continue; + if (item.key === key) return item; + if (item.children) { + const found = findMenuItem(item.children, key); + if (found) return found; + } + } + return null; + }; + + // 使用菜单搜索hook + const { + searchText, + setSearchText, + filteredMenuItems + } = useMenuSearch(menuItems); + + // 获取当前选中的菜单项 + const selectedKey = useMemo(() => { + const findSelectedKey = (items: MenuItem[]): string | null => { + for (const item of items) { + if (!item) continue; + if (item.path === location.pathname) return item.key || null; + if (item.children) { + const childKey = findSelectedKey(item.children); + if (childKey) return childKey; + } + } + return null; + }; + + return findSelectedKey(menuItems) || ''; + }, [location.pathname, menuItems]); + + // 检测滚动位置,控制回到顶部按钮显示 + useEffect(() => { + const handleScroll = () => { + setShowBackTop(window.pageYOffset > 300); + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + // 回到顶部 + const scrollToTop = () => { + window.scrollTo({ + top: 0, + behavior: 'smooth' + }); + }; + + + // 应用名称 - 从CONFIG中获取或使用默认值 + const appName = window.CONFIG?.APP_NAME || '应用Starter'; + + return ( + + +
+ + {collapsed ? '应用' : appName} + + + {/* 菜单搜索框 */} + {!collapsed && ( +
+ setSearchText(e.target.value)} + /> +
+ )} +
+ + {/* 菜单列表 */} + handleMenuClick(key)} + inlineCollapsed={collapsed} + /> + + + +
+
+ + +
+ +
+ + {/* 回到顶部按钮 */} + {showBackTop && ( + - - - - - ); -}; - // 应用入口组件 const App = () => { - // 路由配置 - const router = createBrowserRouter([ - { - path: '/', - element: - }, - { - path: '/admin/login', - element: - }, - { - path: '/admin', - element: ( - - - - ), - children: [ - { - index: true, - element: - }, - { - path: 'dashboard', - element: , - errorElement: - }, - { - path: 'users', - element: , - errorElement: - }, - { - path: 'settings', - element: , - errorElement: - }, - { - path: 'theme-settings', - element: , - errorElement: - }, - { - path: 'chart-dashboard', - element: , - errorElement: - }, - { - path: 'map-dashboard', - element: , - errorElement: - }, - { - path: 'know-info', - element: , - errorElement: - }, - { - path: 'file-library', - element: , - errorElement: - }, - { - path: 'messages', - element: , - errorElement: - }, - ], - }, - ]); return }; @@ -560,4 +45,3 @@ root.render( ); - diff --git a/client/share/types.ts b/client/share/types.ts index 014432e..9223c99 100644 --- a/client/share/types.ts +++ b/client/share/types.ts @@ -33,6 +33,7 @@ export interface User { role: string; avatar?: string; password?: string; + permissions?: string[]; } export interface MenuItem {