فهرست منبع

web_app.tsx 模块化拆分路由、菜单、布局

yourname 6 ماه پیش
والد
کامیت
945171cbc9
6فایلهای تغییر یافته به همراه559 افزوده شده و 523 حذف شده
  1. 45 0
      client/admin/components/ErrorPage.tsx
  2. 222 0
      client/admin/layouts/MainLayout.tsx
  3. 199 0
      client/admin/menu.tsx
  4. 85 0
      client/admin/routes.tsx
  5. 7 523
      client/admin/web_app.tsx
  6. 1 0
      client/share/types.ts

+ 45 - 0
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 (
+    <div className="flex flex-col items-center justify-center min-h-screen p-4"
+      style={{ color: isDark ? '#fff' : 'inherit' }}
+    >
+      <div className="max-w-3xl w-full">
+        <h1 className="text-2xl font-bold mb-4">发生错误</h1>
+        <Alert 
+          type="error"
+          message={error?.message || '未知错误'}
+          description={
+            error?.stack ? (
+              <pre className="text-xs overflow-auto p-2 bg-gray-100 dark:bg-gray-800 rounded">
+                {error.stack}
+              </pre>
+            ) : null
+          }
+          className="mb-4"
+        />
+        <div className="flex gap-4">
+          <Button 
+            type="primary" 
+            onClick={() => window.location.reload()}
+          >
+            重新加载
+          </Button>
+          <Button 
+            onClick={() => window.location.href = '/admin'}
+          >
+            返回首页
+          </Button>
+        </div>
+      </div>
+    </div>
+  );
+};

+ 222 - 0
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 (
+    <Layout style={{ minHeight: '100vh' }}>
+      <Sider 
+        trigger={null} 
+        collapsible 
+        collapsed={collapsed}
+        width={240}
+        className="custom-sider"
+        style={{
+          overflow: 'auto',
+          height: '100vh',
+          position: 'fixed',
+          left: 0,
+          top: 0,
+          bottom: 0,
+          zIndex: 100,
+        }}
+      >
+        <div className="p-4">
+          <Typography.Title level={2} className="text-xl font-bold truncate">
+            {collapsed ? '应用' : appName}
+          </Typography.Title>
+          
+          {/* 菜单搜索框 */}
+          {!collapsed && (
+            <div className="mb-4">
+              <Input.Search
+                placeholder="搜索菜单..."
+                allowClear
+                value={searchText}
+                onChange={(e) => setSearchText(e.target.value)}
+              />
+            </div>
+          )}
+        </div>
+        
+        {/* 菜单列表 */}
+        <Menu
+          theme={isDark ? 'dark' : 'light'}
+          mode="inline"
+          items={filteredMenuItems}
+          openKeys={openKeys}
+          selectedKeys={[selectedKey]}
+          onOpenChange={onOpenChange}
+          onClick={({ key }) => handleMenuClick(key)}
+          inlineCollapsed={collapsed}
+        />
+      </Sider>
+      
+      <Layout style={{ marginLeft: collapsed ? 80 : 240, transition: 'margin-left 0.2s' }}>
+        <Header className="p-0 flex justify-between items-center" 
+          style={{ 
+            position: 'sticky', 
+            top: 0, 
+            zIndex: 99, 
+            boxShadow: '0 1px 4px rgba(0,21,41,0.08)',
+          }}
+        >
+          <Button
+            type="text"
+            icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
+            onClick={() => setCollapsed(!collapsed)}
+            className="w-16 h-16"
+          />
+          
+          <Space size="middle" className="mr-4">
+            <Badge count={5} offset={[0, 5]}>
+              <Button 
+                type="text" 
+                icon={<BellOutlined />}
+              />
+            </Badge>
+            
+            <Dropdown menu={{ items: userMenuItems }}>
+              <Space className="cursor-pointer">
+                <Avatar 
+                  src={user?.avatar || 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?q=80&w=40&auto=format&fit=crop'}
+                  icon={!user?.avatar && !navigator.onLine && <UserOutlined />}
+                />
+                <span>
+                  {user?.nickname || user?.username}
+                </span>
+              </Space>
+            </Dropdown>
+          </Space>
+        </Header>
+        
+        <Content className="m-6" style={{ overflow: 'initial' }}>
+          <div className="site-layout-content p-6 rounded-lg">
+            <Outlet />
+          </div>
+          
+          {/* 回到顶部按钮 */}
+          {showBackTop && (
+            <Button
+              type="primary"
+              shape="circle"
+              icon={<VerticalAlignTopOutlined />}
+              size="large"
+              onClick={scrollToTop}
+              style={{
+                position: 'fixed',
+                right: 30,
+                bottom: 30,
+                zIndex: 1000,
+                boxShadow: '0 3px 6px rgba(0,0,0,0.16)',
+              }}
+            />
+          )}
+        </Content>
+      </Layout>
+    </Layout>
+  );
+};

+ 199 - 0
client/admin/menu.tsx

@@ -0,0 +1,199 @@
+import React from 'react';
+import { useNavigate } from 'react-router';
+import type { MenuProps } from 'antd';
+import {
+  UserOutlined,
+  DashboardOutlined,
+  TeamOutlined,
+  SettingOutlined,
+  FileOutlined,
+  MessageOutlined,
+  InfoCircleOutlined,
+  BarChartOutlined,
+  EnvironmentOutlined,
+  MoonOutlined,
+  SunOutlined
+} from '@ant-design/icons';
+import { useTheme } from './hooks_sys.tsx';
+
+export interface MenuItem {
+  key: string;
+  label: string;
+  icon?: React.ReactNode;
+  children?: MenuItem[];
+  path?: string;
+  permission?: string;
+}
+
+/**
+ * 菜单搜索 Hook
+ * 封装菜单搜索相关逻辑
+ */
+export const useMenuSearch = (menuItems: MenuItem[]) => {
+  const [searchText, setSearchText] = React.useState('');
+
+  // 过滤菜单项
+  const filteredMenuItems = React.useMemo(() => {
+    if (!searchText) return menuItems;
+    
+    const filterItems = (items: MenuItem[]): MenuItem[] => {
+      return items
+        .map(item => {
+          // 克隆对象避免修改原数据
+          const newItem = { ...item };
+          if (newItem.children) {
+            newItem.children = filterItems(newItem.children);
+          }
+          return newItem;
+        })
+        .filter(item => {
+          // 保留匹配项或其子项匹配的项
+          const match = item.label.toLowerCase().includes(searchText.toLowerCase());
+          if (match) return true;
+          if (item.children?.length) return true;
+          return false;
+        });
+    };
+    
+    return filterItems(menuItems);
+  }, [menuItems, searchText]);
+
+  // 清除搜索
+  const clearSearch = () => {
+    setSearchText('');
+  };
+
+  return {
+    searchText,
+    setSearchText,
+    filteredMenuItems,
+    clearSearch
+  };
+};
+
+export const useMenu = () => {
+  const { isDark, toggleTheme } = useTheme();
+  const navigate = useNavigate();
+  const [collapsed, setCollapsed] = React.useState(false);
+  const [openKeys, setOpenKeys] = React.useState<string[]>([]);
+
+  // 基础菜单项配置
+  const menuItems: MenuItem[] = [
+    {
+      key: 'dashboard',
+      label: '控制台',
+      icon: <DashboardOutlined />,
+      path: '/admin/dashboard'
+    },
+    {
+      key: 'users',
+      label: '用户管理',
+      icon: <TeamOutlined />,
+      path: '/admin/users',
+      permission: 'user:manage'
+    },
+    {
+      key: 'settings',
+      label: '系统设置',
+      icon: <SettingOutlined />,
+      children: [
+        {
+          key: 'theme-settings',
+          label: '主题设置',
+          path: '/admin/theme-settings',
+          permission: 'system:settings'
+        },
+        {
+          key: 'system-settings',
+          label: '系统配置',
+          path: '/admin/settings',
+          permission: 'system:settings'
+        }
+      ]
+    },
+    {
+      key: 'content',
+      label: '内容管理',
+      icon: <FileOutlined />,
+      children: [
+        {
+          key: 'know-info',
+          label: '知识库',
+          path: '/admin/know-info',
+          permission: 'content:manage'
+        },
+        {
+          key: 'file-library',
+          label: '文件库',
+          path: '/admin/file-library',
+          permission: 'content:manage'
+        }
+      ]
+    },
+    {
+      key: 'messages',
+      label: '消息中心',
+      icon: <MessageOutlined />,
+      path: '/admin/messages',
+      permission: 'message:view'
+    },
+    {
+      key: 'charts',
+      label: '数据图表',
+      icon: <BarChartOutlined />,
+      path: '/admin/chart-dashboard',
+      permission: 'chart:view'
+    },
+    {
+      key: 'maps',
+      label: '地图',
+      icon: <EnvironmentOutlined />,
+      path: '/admin/map-dashboard',
+      permission: 'map:view'
+    }
+  ];
+
+  // 用户菜单项
+  const userMenuItems: MenuProps['items'] = [
+    {
+      key: 'profile',
+      label: '个人资料',
+      icon: <UserOutlined />
+    },
+    {
+      key: 'theme',
+      label: isDark ? '切换到亮色模式' : '切换到暗色模式',
+      icon: isDark ? <SunOutlined /> : <MoonOutlined />,
+      onClick: () => toggleTheme()
+    },
+    {
+      key: 'logout',
+      label: '退出登录',
+      icon: <InfoCircleOutlined />,
+      danger: true
+    }
+  ];
+
+  // 处理菜单点击
+  const handleMenuClick = (item: MenuItem) => {
+    if (item.path) {
+      navigate(item.path);
+    }
+  };
+
+  // 处理菜单展开变化
+  const onOpenChange = (keys: string[]) => {
+    const latestOpenKey = keys.find(key => openKeys.indexOf(key) === -1);
+    setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
+  };
+
+  return {
+    menuItems,
+    userMenuItems,
+    openKeys,
+    collapsed,
+    setCollapsed,
+    handleMenuClick,
+    onOpenChange
+  };
+};

+ 85 - 0
client/admin/routes.tsx

@@ -0,0 +1,85 @@
+import React from 'react';
+import { createBrowserRouter, Navigate } from 'react-router';
+import { ProtectedRoute } from './components_protected_route.tsx';
+import { MainLayout } from './layouts/MainLayout.tsx';
+import { ErrorPage } from './components/ErrorPage.tsx';
+import { DashboardPage } from './pages_dashboard.tsx';
+import { UsersPage } from './pages_users.tsx';
+import { FileLibraryPage } from './pages_file_library.tsx';
+import { KnowInfoPage } from './pages_know_info.tsx';
+import { MessagesPage } from './pages_messages.tsx';
+import { SettingsPage } from './pages_settings.tsx';
+import { ThemeSettingsPage } from './pages_theme_settings.tsx';
+import { ChartDashboardPage } from './pages_chart.tsx';
+import { LoginMapPage } from './pages_map.tsx';
+import { LoginPage } from './pages_login_reg.tsx';
+
+export const router = createBrowserRouter([
+  {
+    path: '/',
+    element: <Navigate to="/admin" replace />
+  },
+  {
+    path: '/admin/login',
+    element: <LoginPage />
+  },
+  {
+    path: '/admin',
+    element: (
+      <ProtectedRoute>
+        <MainLayout />
+      </ProtectedRoute>
+    ),
+    children: [
+      {
+        index: true,
+        element: <Navigate to="/admin/dashboard" />
+      },
+      {
+        path: 'dashboard',
+        element: <DashboardPage />,
+        errorElement: <ErrorPage />
+      },
+      {
+        path: 'users',
+        element: <UsersPage />,
+        errorElement: <ErrorPage />
+      },
+      {
+        path: 'settings',
+        element: <SettingsPage />,
+        errorElement: <ErrorPage />
+      },
+      {
+        path: 'theme-settings',
+        element: <ThemeSettingsPage />,
+        errorElement: <ErrorPage />
+      },
+      {
+        path: 'chart-dashboard',
+        element: <ChartDashboardPage />,
+        errorElement: <ErrorPage />
+      },
+      {
+        path: 'map-dashboard',
+        element: <LoginMapPage />,
+        errorElement: <ErrorPage />
+      },
+      {
+        path: 'know-info',
+        element: <KnowInfoPage />,
+        errorElement: <ErrorPage />
+      },
+      {
+        path: 'file-library',
+        element: <FileLibraryPage />,
+        errorElement: <ErrorPage />
+      },
+      {
+        path: 'messages',
+        element: <MessagesPage />,
+        errorElement: <ErrorPage />
+      },
+    ],
+  },
+]);

+ 7 - 523
client/admin/web_app.tsx

@@ -1,76 +1,16 @@
-import React, { useState, useEffect} from 'react';
+import React from 'react';
 import { createRoot } from 'react-dom/client';
-import { 
-  createBrowserRouter,
-  RouterProvider,
-  Outlet,
-  useNavigate,
-  useLocation,
-  Navigate,
-  useParams,
-  useRouteError
-} from 'react-router';
-import { 
-  Layout, Menu, Button, Table, Space,
-  Form, Input, Select, message, Modal,
-  Card, Spin, Row, Col, Breadcrumb, Avatar,
-  Dropdown, ConfigProvider, theme, Typography,
-  Switch, Badge, Image, Upload, Divider, Descriptions,
-  Popconfirm, Tag, Statistic, DatePicker, Radio, Progress, Tabs, List, Alert, Collapse, Empty, Drawer
-} from 'antd';
-import {
-  MenuFoldOutlined,
-  MenuUnfoldOutlined,
-  UserOutlined,
-  DashboardOutlined,
-  TeamOutlined,
-  SettingOutlined,
-  LogoutOutlined,
-  BellOutlined,
-  BookOutlined,
-  FileOutlined,
-  PieChartOutlined,
-  VerticalAlignTopOutlined,
-  CloseOutlined,
-  SearchOutlined
-} from '@ant-design/icons';   
-import { 
-  QueryClient,
-  QueryClientProvider,
-} from '@tanstack/react-query';
+import { RouterProvider } from 'react-router';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
 import dayjs from 'dayjs';
 import weekday from 'dayjs/plugin/weekday';
 import localeData from 'dayjs/plugin/localeData';
 import 'dayjs/locale/zh-cn';
-import type { 
-  GlobalConfig
-} from '../share/types.ts';
 
-
-import {
-  AuthProvider,
-  useAuth,
-  ThemeProvider,
-  useTheme,
-} from './hooks_sys.tsx';
-
-import {
-  DashboardPage
-} from './pages_dashboard.tsx';
-import {
-  UsersPage
-} from './pages_users.tsx';
-import {
-  FileLibraryPage
-} from './pages_file_library.tsx';
-import { KnowInfoPage } from './pages_know_info.tsx';
-import { MessagesPage } from './pages_messages.tsx';
-import {SettingsPage } from './pages_settings.tsx';
-import {ThemeSettingsPage} from './pages_theme_settings.tsx'
-import { ChartDashboardPage } from './pages_chart.tsx';
-import { LoginMapPage } from './pages_map.tsx';
-import { LoginPage } from './pages_login_reg.tsx';
-import { ProtectedRoute } from './components_protected_route.tsx';
+import { AuthProvider } from './hooks_sys.tsx';
+import { ThemeProvider } from './hooks_sys.tsx';
+import { router } from './routes.tsx';
+import type { GlobalConfig } from '../share/types.ts';
 
 // 配置 dayjs 插件
 dayjs.extend(weekday);
@@ -79,12 +19,9 @@ dayjs.extend(localeData);
 // 设置 dayjs 语言
 dayjs.locale('zh-cn');
 
-const { Header, Sider, Content } = Layout;
-
 // 创建QueryClient实例
 const queryClient = new QueryClient();
 
-
 // 声明全局配置对象类型
 declare global {
   interface Window {
@@ -92,460 +29,8 @@ declare global {
   }
 }
 
-
-// 主布局组件
-const MainLayout = () => {
-  const [collapsed, setCollapsed] = useState(false);
-  const { user, logout } = useAuth();
-  const { isDark, toggleTheme } = useTheme();
-  const navigate = useNavigate();
-  const location = useLocation();
-  const [openKeys, setOpenKeys] = useState<string[]>([]);
-  const [showBackTop, setShowBackTop] = useState(false);
-  const [searchText, setSearchText] = useState('');
-  const [filteredMenuItems, setFilteredMenuItems] = useState<any[]>([]);
-  
-  // 检测滚动位置,控制回到顶部按钮显示
-  useEffect(() => {
-    const handleScroll = () => {
-      setShowBackTop(window.pageYOffset > 300);
-    };
-    
-    window.addEventListener('scroll', handleScroll);
-    return () => window.removeEventListener('scroll', handleScroll);
-  }, []);
-  
-  // 回到顶部
-  const scrollToTop = () => {
-    window.scrollTo({
-      top: 0,
-      behavior: 'smooth'
-    });
-  };
-  
-  // 菜单项配置
-  const menuItems = [
-    {
-      key: '/dashboard',
-      icon: <DashboardOutlined />,
-      label: '仪表盘',
-    },
-    {
-      key: '/analysis',
-      icon: <PieChartOutlined />,
-      label: '数据分析',
-      children: [
-        {
-          key: '/chart-dashboard',
-          label: '图表统计',
-        },
-        {
-          key: '/map-dashboard',
-          label: '地图概览',
-        },
-      ],
-    },
-    {
-      key: '/files',
-      icon: <FileOutlined />,
-      label: '文件管理',
-      children: [
-        {
-          key: '/file-library',
-          label: '文件库',
-        },
-      ],
-    },
-    {
-      key: '/know-info',
-      icon: <BookOutlined />,
-      label: '知识库',
-    },
-    {
-      key: '/users',
-      icon: <TeamOutlined />,
-      label: '用户管理',
-    },
-    {
-      key: '/messages',
-      icon: <BellOutlined />,
-      label: '消息管理',
-    },
-    {
-      key: '/settings_group',
-      icon: <SettingOutlined />,
-      label: '系统设置',
-      children: [
-        {
-          key: '/theme-settings',
-          label: '主题设置',
-        },
-        {
-          key: '/settings',
-          label: '基本设置',
-        },
-      ],
-    },
-  ];
-  
-  // 初始化filteredMenuItems
-  useEffect(() => {
-    setFilteredMenuItems(menuItems);
-  }, []);
-
-  // 搜索菜单项
-  const handleSearch = (value: string) => {
-    setSearchText(value);
-    
-    if (!value.trim()) {
-      setFilteredMenuItems(menuItems);
-      return;
-    }
-    
-    // 搜索功能 - 过滤菜单项
-    const filtered = menuItems.reduce((acc: any[], item) => {
-      // 检查主菜单项是否匹配
-      const mainItemMatch = item.label.toString().toLowerCase().includes(value.toLowerCase());
-      
-      // 如果有子菜单,检查子菜单中是否有匹配项
-      if (item.children) {
-        const matchedChildren = item.children.filter(child => 
-          child.label.toString().toLowerCase().includes(value.toLowerCase())
-        );
-        
-        if (matchedChildren.length > 0) {
-          // 如果有匹配的子菜单,创建包含匹配子菜单的副本
-          acc.push({
-            ...item,
-            children: matchedChildren
-          });
-          return acc;
-        }
-      }
-      
-      // 如果主菜单项匹配,添加整个项
-      if (mainItemMatch) {
-        acc.push(item);
-      }
-      
-      return acc;
-    }, []);
-    
-    setFilteredMenuItems(filtered);
-  };
-  
-  // 清除搜索
-  const clearSearch = () => {
-    setSearchText('');
-    setFilteredMenuItems(menuItems);
-  };
-
-  const handleMenuClick = ({ key }: { key: string }) => {
-    navigate(`/admin${key}`);
-    // 如果有搜索文本,清除搜索
-    if (searchText) {
-      clearSearch();
-    }
-  };
-  
-  // 处理登出
-  const handleLogout = async () => {
-    await logout();
-    navigate('/admin/login');
-  };
-  
-  // 处理菜单展开/收起
-  const onOpenChange = (keys: string[]) => {
-    // 当侧边栏折叠时不保存openKeys状态
-    if (!collapsed) {
-      setOpenKeys(keys);
-    }
-  };
-  
-  // 当侧边栏折叠状态改变时,控制菜单打开状态
-  useEffect(() => {
-    if (collapsed) {
-      setOpenKeys([]);
-    } else {
-      // 找到当前路径所属的父菜单
-      const currentPath = location.pathname.replace('/admin', '');
-      const parentKeys = menuItems
-        .filter(item => item.children && item.children.some(child => child.key === currentPath))
-        .map(item => item.key);
-      
-      // 仅展开当前所在的菜单组
-      if (parentKeys.length > 0) {
-        setOpenKeys(parentKeys);
-      } else {
-        // 初始时可以根据需要设置要打开的菜单组
-        setOpenKeys([]);
-      }
-    }
-  }, [collapsed, location.pathname]);
-  
-  // 用户下拉菜单项
-  const userMenuItems = [
-    {
-      key: 'profile',
-      label: '个人信息',
-      icon: <UserOutlined />
-    },
-    {
-      key: 'theme',
-      label: isDark ? '切换到亮色模式' : '切换到暗色模式',
-      icon: <SettingOutlined />,
-      onClick: toggleTheme
-    },
-    {
-      key: 'logout',
-      label: '退出登录',
-      icon: <LogoutOutlined />,
-      onClick: handleLogout
-    }
-  ];
-  
-  // 应用名称 - 从CONFIG中获取或使用默认值
-  const appName = window.CONFIG?.APP_NAME || '应用Starter';
-  
-  return (
-    <Layout style={{ minHeight: '100vh' }}>
-      <Sider 
-        trigger={null} 
-        collapsible 
-        collapsed={collapsed}
-        width={240}
-        className="custom-sider"
-        style={{
-          overflow: 'auto',
-          height: '100vh',
-          position: 'fixed',
-          left: 0,
-          top: 0,
-          bottom: 0,
-          zIndex: 100,
-        }}
-      >
-        <div className="p-4">
-          <Typography.Title level={2} className="text-xl font-bold truncate">
-            {collapsed ? '应用' : appName}
-          </Typography.Title>
-        </div>
-        
-        {/* 搜索框 - 仅在展开状态下显示 */}
-        {!collapsed && (
-          <div style={{ padding: '0 16px 16px' }}>
-            <Input 
-              placeholder="搜索菜单..." 
-              value={searchText}
-              onChange={(e) => handleSearch(e.target.value)}
-              suffix={
-                searchText ? 
-                <Button 
-                  type="text" 
-                  size="small" 
-                  icon={<CloseOutlined />} 
-                  onClick={clearSearch}
-                /> : 
-                <SearchOutlined />
-              }
-            />
-          </div>
-        )}
-        
-        <Menu
-          theme={isDark ? "light" : "light"}
-          mode="inline"
-          selectedKeys={[location.pathname.replace('/admin', '')]}
-          openKeys={openKeys}
-          onOpenChange={onOpenChange}
-          items={filteredMenuItems}
-          onClick={handleMenuClick}
-        />
-      </Sider>
-      
-      <Layout style={{ marginLeft: collapsed ? 80 : 240, transition: 'margin-left 0.2s' }}>
-        <Header className="p-0 flex justify-between items-center" 
-          style={{ 
-            position: 'sticky', 
-            top: 0, 
-            zIndex: 99, 
-            boxShadow: '0 1px 4px rgba(0,21,41,0.08)',
-          }}
-        >
-          <Button
-            type="text"
-            icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
-            onClick={() => setCollapsed(!collapsed)}
-            className="w-16 h-16"
-          />
-          
-          <Space size="middle" className="mr-4">
-            <Badge count={5} offset={[0, 5]}>
-              <Button 
-                type="text" 
-                icon={<BellOutlined />}
-              />
-            </Badge>
-            
-            <Dropdown menu={{ items: userMenuItems }}>
-              <Space className="cursor-pointer">
-                <Avatar 
-                  src={user?.avatar || 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?q=80&w=40&auto=format&fit=crop'}
-                  icon={!user?.avatar && !navigator.onLine && <UserOutlined />}
-                />
-                <span>
-                  {user?.nickname || user?.username}
-                </span>
-              </Space>
-            </Dropdown>
-          </Space>
-        </Header>
-        
-        <Content className="m-6" style={{ overflow: 'initial' }}>
-          <div className="site-layout-content p-6 rounded-lg">
-            <Outlet />
-          </div>
-          
-          {/* 回到顶部按钮 */}
-          {showBackTop && (
-            <Button
-              type="primary"
-              shape="circle"
-              icon={<VerticalAlignTopOutlined />}
-              size="large"
-              onClick={scrollToTop}
-              style={{
-                position: 'fixed',
-                right: 30,
-                bottom: 30,
-                zIndex: 1000,
-                boxShadow: '0 3px 6px rgba(0,0,0,0.16)',
-              }}
-            />
-          )}
-        </Content>
-      </Layout>
-    </Layout>
-  );
-};
-
-
-
-// 错误页面组件
-const ErrorPage = () => {
-  const { isDark } = useTheme();
-  const error = useRouteError() as any;
-  const errorMessage = error?.statusText || error?.message || '未知错误';
-  
-  
-  return (
-    <div className="flex flex-col items-center justify-center min-h-screen p-4"
-      style={{ color: isDark ? '#fff' : 'inherit' }}
-    >
-      <div className="max-w-3xl w-full">
-        <h1 className="text-2xl font-bold mb-4">发生错误</h1>
-        <Alert 
-          type="error"
-          message={error?.message || '未知错误'}
-          description={
-            error?.stack ? (
-              <pre className="text-xs overflow-auto p-2 bg-gray-100 dark:bg-gray-800 rounded">
-                {error.stack}
-              </pre>
-            ) : null
-          }
-          className="mb-4"
-        />
-        <div className="flex gap-4">
-          <Button 
-            type="primary" 
-            onClick={() => window.location.reload()}
-          >
-            重新加载
-          </Button>
-          <Button 
-            onClick={() => window.location.href = '/admin'}
-          >
-            返回首页
-          </Button>
-        </div>
-      </div>
-    </div>
-  );
-};
-
 // 应用入口组件
 const App = () => {
-  // 路由配置
-  const router = createBrowserRouter([
-    {
-      path: '/',
-      element: <Navigate to="/admin" replace />
-    },
-    {
-      path: '/admin/login',
-      element: <LoginPage />
-    },
-    {
-      path: '/admin',
-      element: (
-        <ProtectedRoute>
-          <MainLayout />
-        </ProtectedRoute>
-      ),
-      children: [
-        {
-          index: true,
-          element: <Navigate to="/admin/dashboard" />
-        },
-        {
-          path: 'dashboard',
-          element: <DashboardPage />,
-          errorElement: <ErrorPage />
-        },
-        {
-          path: 'users',
-          element: <UsersPage />,
-          errorElement: <ErrorPage />
-        },
-        {
-          path: 'settings',
-          element: <SettingsPage />,
-          errorElement: <ErrorPage />
-        },
-        {
-          path: 'theme-settings',
-          element: <ThemeSettingsPage />,
-          errorElement: <ErrorPage />
-        },
-        {
-          path: 'chart-dashboard',
-          element: <ChartDashboardPage />,
-          errorElement: <ErrorPage />
-        },
-        {
-          path: 'map-dashboard',
-          element: <LoginMapPage />,
-          errorElement: <ErrorPage />
-        },
-        {
-          path: 'know-info',
-          element: <KnowInfoPage />,
-          errorElement: <ErrorPage />
-        },
-        {
-          path: 'file-library',
-          element: <FileLibraryPage />,
-          errorElement: <ErrorPage />
-        },
-        {
-          path: 'messages',
-          element: <MessagesPage />,
-          errorElement: <ErrorPage />
-        },
-      ],
-    },
-  ]);
   return <RouterProvider router={router} />
 };
 
@@ -560,4 +45,3 @@ root.render(
     </ThemeProvider>
   </QueryClientProvider>
 );
-

+ 1 - 0
client/share/types.ts

@@ -33,6 +33,7 @@ export interface User {
   role: string;
   avatar?: string;
   password?: string;
+  permissions?: string[];
 }
 
 export interface MenuItem {