Browse Source

🗑️ refactor(admin): 移除管理员后台完整功能模块

- 删除 admin 目录下所有页面、组件、路由和工具文件
- 移除用户管理、文件管理、仪表盘等功能
- 清理认证相关的组件和上下文
- 移除不再使用的依赖和导入

🗑️ refactor(home): 移除用户端完整功能模块

- 删除 home 目录下所有页面、组件、路由和工具文件
- 移除用户登录、注册、会员中心等功能
- 清理认证相关的组件和上下文
- 移除不再使用的依赖和导入
yourname 3 months ago
parent
commit
ee05805adc

+ 0 - 43
src/client/admin/components/ErrorPage.tsx

@@ -1,43 +0,0 @@
-import React from 'react';
-import { useRouteError, useNavigate } from 'react-router';
-import { Alert, Button } from 'antd';
-
-export const ErrorPage = () => {
-  const navigate = useNavigate();
-  const error = useRouteError() as any;
-  const errorMessage = error?.statusText || error?.message || '未知错误';
-  
-  return (
-    <div className="flex flex-col items-center justify-center flex-grow p-4"
-    >
-      <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={() => navigate(0)}
-          >
-            重新加载
-          </Button>
-          <Button 
-            onClick={() => navigate('/admin')}
-          >
-            返回首页
-          </Button>
-        </div>
-      </div>
-    </div>
-  );
-};

+ 0 - 260
src/client/admin/components/MinioUploader.tsx

@@ -1,260 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import { Upload, Progress, message, Tag, Space, Typography, Button } from 'antd';
-import { UploadOutlined, CloseOutlined, CheckCircleOutlined, SyncOutlined } from '@ant-design/icons';
-import { App } from 'antd';
-import type { UploadFile, UploadProps } from 'antd';
-import type { RcFile } from 'rc-upload/lib/interface';
-import type { UploadFileStatus } from 'antd/es/upload/interface';
-import type { UploadRequestOption } from 'rc-upload/lib/interface';
-import { uploadMinIOWithPolicy, MinioProgressEvent } from '@/client/utils/minio';
-
-interface MinioUploaderProps {
-  /** 上传路径 */
-  uploadPath: string;
-  /** 允许的文件类型,如['image/*', '.pdf'] */
-  accept?: string;
-  /** 最大文件大小(MB) */
-  maxSize?: number;
-  /** 是否允许多文件上传 */
-  multiple?: boolean;
-  /** 上传成功回调 */
-  onUploadSuccess?: (fileKey: string, fileUrl: string, file: File) => void;
-  /** 上传失败回调 */
-  onUploadError?: (error: Error, file: File) => void;
-  /** 自定义上传按钮文本 */
-  buttonText?: string;
-  /** 自定义提示文本 */
-  tipText?: string;
-}
-
-
-const MinioUploader: React.FC<MinioUploaderProps> = ({
-  uploadPath = '/',
-  accept,
-  maxSize = 500, // 默认最大500MB
-  multiple = false,
-  onUploadSuccess,
-  onUploadError,
-  buttonText = '点击或拖拽上传文件',
-  tipText = '支持单文件或多文件上传,单个文件大小不超过500MB'
-}) => {
-  const { message: antdMessage } = App.useApp();
-  const [fileList, setFileList] = useState<UploadFile[]>([]);
-  const [uploadingFiles, setUploadingFiles] = useState<Set<string>>(new Set());
-
-  // 处理上传进度
-  const handleProgress = useCallback((uid: string, event: MinioProgressEvent) => {
-    setFileList(prev => 
-      prev.map(item => {
-        if (item.uid === uid) {
-          return {
-            ...item,
-            status: event.stage === 'error' ? ('error' as UploadFileStatus) : ('uploading' as UploadFileStatus),
-            percent: event.progress,
-            error: event.stage === 'error' ? event.message : undefined
-          };
-        }
-        return item;
-      })
-    );
-  }, []);
-
-  // 处理上传成功
-  const handleComplete = useCallback((uid: string, result: { fileKey: string; fileUrl: string }, file: File) => {
-    setFileList(prev => 
-      prev.map(item => {
-        if (item.uid === uid) {
-          return {
-            ...item,
-            status: 'success' as UploadFileStatus,
-            percent: 100,
-            response: { fileKey: result.fileKey },
-            url: result.fileUrl,
-          };
-        }
-        return item;
-      })
-    );
-    
-    setUploadingFiles(prev => {
-      const newSet = new Set(prev);
-      newSet.delete(uid);
-      return newSet;
-    });
-    
-    antdMessage.success(`文件 "${file.name}" 上传成功`);
-    onUploadSuccess?.(result.fileKey, result.fileUrl, file);
-  }, [antdMessage, onUploadSuccess]);
-
-  // 处理上传失败
-  const handleError = useCallback((uid: string, error: Error, file: File) => {
-    setFileList(prev => 
-      prev.map(item => {
-        if (item.uid === uid) {
-          return {
-            ...item,
-            status: 'error' as UploadFileStatus,
-            percent: 0,
-            error: error.message || '上传失败'
-          };
-        }
-        return item;
-      })
-    );
-    
-    setUploadingFiles(prev => {
-      const newSet = new Set(prev);
-      newSet.delete(uid);
-      return newSet;
-    });
-    
-    antdMessage.error(`文件 "${file.name}" 上传失败: ${error.message}`);
-    onUploadError?.(error, file);
-  }, [antdMessage, onUploadError]);
-
-  // 自定义上传逻辑
-  const customRequest = async (options: UploadRequestOption) => {
-    const { file, onSuccess, onError } = options;
-    const rcFile = file as RcFile;
-    const uid = rcFile.uid;
-    
-    // 添加到文件列表
-    setFileList(prev => [
-      ...prev.filter(item => item.uid !== uid),
-      {
-        uid,
-        name: rcFile.name,
-        size: rcFile.size,
-        type: rcFile.type,
-        lastModified: rcFile.lastModified,
-        lastModifiedDate: new Date(rcFile.lastModified),
-        status: 'uploading' as UploadFileStatus,
-        percent: 0,
-      }
-    ]);
-    
-    // 添加到上传中集合
-    setUploadingFiles(prev => new Set(prev).add(uid));
-    
-    try {
-      // 调用minio上传方法
-      const result = await uploadMinIOWithPolicy(
-        uploadPath,
-        options.file as unknown as File,
-        rcFile.name,
-        {
-          onProgress: (event) => handleProgress(uid, event),
-          signal: new AbortController().signal
-        }
-      );
-      
-      handleComplete(uid, result, rcFile as unknown as File);
-      onSuccess?.({}, rcFile);
-    } catch (error) {
-      handleError(uid, error instanceof Error ? error : new Error('未知错误'), rcFile as unknown as File);
-      onError?.(error instanceof Error ? error : new Error('未知错误'));
-    }
-  };
-
-  // 处理文件移除
-  const handleRemove = (uid: string) => {
-    setFileList(prev => prev.filter(item => item.uid !== uid));
-  };
-
-  // 验证文件大小
-  const beforeUpload = (file: File) => {
-    const fileSizeMB = file.size / (1024 * 1024);
-    if (fileSizeMB > maxSize!) {
-      message.error(`文件 "${file.name}" 大小超过 ${maxSize}MB 限制`);
-      return false;
-    }
-    return true;
-  };
-
-  // 渲染上传状态
-  const renderUploadStatus = (item: UploadFile) => {
-    switch (item.status) {
-      case 'uploading':
-        return (
-          <Space>
-            <SyncOutlined spin size={12} />
-            <span>{item.percent}%</span>
-          </Space>
-        );
-      case 'done':
-        return (
-          <Space>
-            <CheckCircleOutlined style={{ color: '#52c41a' }} size={12} />
-            <Tag color="success">上传成功</Tag>
-          </Space>
-        );
-      case 'error':
-        return (
-          <Space>
-            <CloseOutlined style={{ color: '#ff4d4f' }} size={12} />
-            <Tag color="error">{item.error || '上传失败'}</Tag>
-          </Space>
-        );
-      default:
-        return null;
-    }
-  };
-
-  return (
-    <div className="minio-uploader">
-      <Upload.Dragger
-        name="files"
-        accept={accept}
-        multiple={multiple}
-        customRequest={customRequest}
-        beforeUpload={beforeUpload}
-        showUploadList={false}
-        disabled={uploadingFiles.size > 0 && !multiple}
-      >
-        <div className="flex flex-col items-center justify-center p-6 border-2 border-dashed border-gray-300 rounded-md transition-all hover:border-primary">
-          <UploadOutlined style={{ fontSize: 24, color: '#1890ff' }} />
-          <Typography.Text className="mt-2">{buttonText}</Typography.Text>
-          <Typography.Text type="secondary" className="mt-1">
-            {tipText}
-          </Typography.Text>
-        </div>
-      </Upload.Dragger>
-
-      {/* 上传进度列表 */}
-      {fileList.length > 0 && (
-        <div className="mt-4 space-y-3">
-          {fileList.map(item => (
-            <div key={item.uid} className="flex items-center p-3 border rounded-md">
-              <div className="flex-1 min-w-0">
-                <div className="flex justify-between items-center mb-1">
-                  <Typography.Text ellipsis className="max-w-xs">
-                    {item.name}
-                  </Typography.Text>
-                  <div className="flex items-center space-x-2">
-                    {renderUploadStatus(item)}
-                    <Button
-                      type="text"
-                      size="small"
-                      icon={<CloseOutlined />}
-                      onClick={() => handleRemove(item.uid)}
-                      disabled={item.status === 'uploading'}
-                    />
-                  </div>
-                </div>
-                {item.status === 'uploading' && (
-                  <Progress 
-                    percent={item.percent}
-                    size="small" 
-                    status={item.percent === 100 ? 'success' : undefined}
-                  />
-                )}
-              </div>
-            </div>
-          ))}
-        </div>
-      )}
-    </div>
-  );
-};
-
-export default MinioUploader;

+ 0 - 26
src/client/admin/components/NotFoundPage.tsx

@@ -1,26 +0,0 @@
-import React from 'react';
-import { useNavigate } from 'react-router';
-import { Button } from 'antd';
-
-export const NotFoundPage = () => {
-  const navigate = useNavigate();
-  
-  return (
-    <div className="flex flex-col items-center justify-center flex-grow p-4">
-      <div className="max-w-3xl w-full">
-        <h1 className="text-2xl font-bold mb-4">404 - 页面未找到</h1>
-        <p className="mb-6 text-gray-600 dark:text-gray-300">
-          您访问的页面不存在或已被移除
-        </p>
-        <div className="flex gap-4">
-          <Button 
-            type="primary" 
-            onClick={() => navigate('/admin')}
-          >
-            返回首页
-          </Button>
-        </div>
-      </div>
-    </div>
-  );
-};

+ 0 - 37
src/client/admin/components/ProtectedRoute.tsx

@@ -1,37 +0,0 @@
-import React, { useEffect } from 'react';
-import { 
-  useNavigate,
-} from 'react-router';
-import { useAuth } from '../hooks/AuthProvider';
-
-
-
-
-
-export const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
-  const { isAuthenticated, isLoading } = useAuth();
-  const navigate = useNavigate();
-  
-  useEffect(() => {
-    // 只有在加载完成且未认证时才重定向
-    if (!isLoading && !isAuthenticated) {
-      navigate('/admin/login', { replace: true });
-    }
-  }, [isAuthenticated, isLoading, navigate]);
-  
-  // 显示加载状态,直到认证检查完成
-  if (isLoading) {
-    return (
-      <div className="flex justify-center items-center h-screen">
-        <div className="loader ease-linear rounded-full border-4 border-t-4 border-gray-200 h-12 w-12"></div>
-      </div>
-    );
-  }
-  
-  // 如果未认证且不再加载中,不显示任何内容(等待重定向)
-  if (!isAuthenticated) {
-    return null;
-  }
-  
-  return children;
-};

+ 0 - 140
src/client/admin/hooks/AuthProvider.tsx

@@ -1,140 +0,0 @@
-import React, { useState, useEffect, createContext, useContext } from 'react';
-
-import {
-  useQuery,
-  useQueryClient,
-} from '@tanstack/react-query';
-import axios from 'axios';
-import 'dayjs/locale/zh-cn';
-import type {
-  AuthContextType
-} from '@/share/types';
-import { authClient } from '@/client/api';
-import type { InferResponseType, InferRequestType } from 'hono/client';
-
-type User = InferResponseType<typeof authClient.me.$get, 200>;
-
-
-// 创建认证上下文
-const AuthContext = createContext<AuthContextType<User> | null>(null);
-
-// 认证提供器组件
-export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
-  const [user, setUser] = useState<User | null>(null);
-  const [token, setToken] = useState<string | null>(localStorage.getItem('token'));
-  const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
-  const queryClient = useQueryClient();
-
-  // 声明handleLogout函数
-  const handleLogout = async () => {
-    try {
-      // 如果已登录,调用登出API
-      if (token) {
-        await authClient.logout.$post();
-      }
-    } catch (error) {
-      console.error('登出请求失败:', error);
-    } finally {
-      // 清除本地状态
-      setToken(null);
-      setUser(null);
-      setIsAuthenticated(false);
-      localStorage.removeItem('token');
-      // 清除Authorization头
-      delete axios.defaults.headers.common['Authorization'];
-      console.log('登出时已删除全局Authorization头');
-      // 清除所有查询缓存
-      queryClient.clear();
-    }
-  };
-
-  // 使用useQuery检查登录状态
-  const { isLoading } = useQuery({
-    queryKey: ['auth', 'status', token],
-    queryFn: async () => {
-      if (!token) {
-        setIsAuthenticated(false);
-        setUser(null);
-        return null;
-      }
-
-      try {
-        // 设置全局默认请求头
-        axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
-        // 使用API验证当前用户
-        const res = await authClient.me.$get();
-        if (res.status !== 200) {
-          const result = await res.json();
-          throw new Error(result.message)
-        }
-        const currentUser = await res.json();
-        setUser(currentUser);
-        setIsAuthenticated(true);
-        return { isValid: true, user: currentUser };
-      } catch (error) {
-        return { isValid: false };
-      }
-    },
-    enabled: !!token,
-    refetchOnWindowFocus: false,
-    retry: false
-  });
-
-  const handleLogin = async (username: string, password: string, latitude?: number, longitude?: number): Promise<void> => {
-    try {
-      // 使用AuthAPI登录
-      const response = await authClient.login.$post({
-        json: {
-          username,
-          password
-        }
-      })
-      if (response.status !== 200) {
-        const result = await response.json()
-        throw new Error(result.message);
-      }
-
-      const result = await response.json()
-
-      // 保存token和用户信息
-      const { token: newToken, user: newUser } = result;
-
-      // 设置全局默认请求头
-      axios.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
-
-      // 保存状态
-      setToken(newToken);
-      setUser(newUser);
-      setIsAuthenticated(true);
-      localStorage.setItem('token', newToken);
-
-    } catch (error) {
-      console.error('登录失败:', error);
-      throw error;
-    }
-  };
-
-  return (
-    <AuthContext.Provider
-      value={{
-        user,
-        token,
-        login: handleLogin,
-        logout: handleLogout,
-        isAuthenticated,
-        isLoading
-      }}
-    >
-      {children}
-    </AuthContext.Provider>
-  );
-};
-
-// 使用上下文的钩子
-export const useAuth = () => {
-  const context = useContext(AuthContext);
-  if (!context) {
-    throw new Error('useAuth必须在AuthProvider内部使用');
-  }
-  return context;
-};

+ 0 - 60
src/client/admin/index.tsx

@@ -1,60 +0,0 @@
-import { createRoot } from 'react-dom/client'
-import { RouterProvider } from 'react-router';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { App as AntdApp , ConfigProvider} from 'antd'
-import dayjs from 'dayjs';
-import weekday from 'dayjs/plugin/weekday';
-import localeData from 'dayjs/plugin/localeData';
-import 'dayjs/locale/zh-cn';
-import zhCN from 'antd/locale/zh_CN';
-
-import { AuthProvider } from './hooks/AuthProvider';
-import { router } from './routes';
-
-// 配置 dayjs 插件
-dayjs.extend(weekday);
-dayjs.extend(localeData);
-
-// 设置 dayjs 语言
-dayjs.locale('zh-cn');
-
-// 创建QueryClient实例
-const queryClient = new QueryClient();
-
-// 应用入口组件
-const App = () => {
-  return (
-    <QueryClientProvider client={queryClient}>
-      <ConfigProvider locale={zhCN} theme={{
-        token: {
-          colorPrimary: '#1890ff',
-          borderRadius: 4,
-          colorBgContainer: '#f5f5f5',
-        },
-        components: {
-          Button: {
-            borderRadius: 4,
-          },
-          Card: {
-            borderRadius: 6,
-            boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
-          }
-        }
-      }}>
-        <AntdApp>
-          <AuthProvider>
-            <RouterProvider router={router} />
-          </AuthProvider>
-        </AntdApp>
-      </ConfigProvider>
-    </QueryClientProvider>
-  )
-};
-
-const rootElement = document.getElementById('root')
-if (rootElement) {
-  const root = createRoot(rootElement)
-  root.render(
-    <App />
-  )
-}

+ 0 - 228
src/client/admin/layouts/MainLayout.tsx

@@ -1,228 +0,0 @@
-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 } from '../hooks/AuthProvider';
-import { useMenu, useMenuSearch, type MenuItem } from '../menu';
-import { getGlobalConfig } from '@/client/utils/utils';
-
-const { Header, Sider, Content } = Layout;
-
-/**
- * 主布局组件
- * 包含侧边栏、顶部导航和内容区域
- */
-export const MainLayout = () => {
-  const { user } = useAuth();
-  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 = getGlobalConfig('APP_NAME') || '应用Starter';
-  
-  return (
-    <Layout style={{ minHeight: '100vh' }}>
-      <Sider
-        trigger={null}
-        collapsible
-        collapsed={collapsed}
-        width={240}
-        className="custom-sider"
-        theme='light'
-        style={{
-          overflow: 'auto',
-          height: '100vh',
-          position: 'fixed',
-          left: 0,
-          top: 0,
-          bottom: 0,
-          zIndex: 100,
-          transition: 'all 0.2s ease',
-          boxShadow: '2px 0 8px 0 rgba(29, 35, 41, 0.05)',
-          background: 'linear-gradient(180deg, #001529 0%, #003a6c 100%)',
-        }}
-      >
-        <div className="p-4">
-          <Typography.Title level={2} className="text-xl font-bold truncate">
-            <span className="text-white">{collapsed ? '应用' : appName}</span>
-          </Typography.Title>
-          
-          {/* 菜单搜索框 */}
-          {!collapsed && (
-            <div className="mb-4">
-              <Input.Search
-                placeholder="搜索菜单..."
-                allowClear
-                value={searchText}
-                onChange={(e) => setSearchText(e.target.value)}
-              />
-            </div>
-          )}
-        </div>
-        
-        {/* 菜单列表 */}
-        <Menu
-          theme='dark'
-          mode="inline"
-          items={filteredMenuItems}
-          openKeys={openKeys}
-          selectedKeys={[selectedKey]}
-          onOpenChange={onOpenChange}
-          onClick={({ key }) => handleMenuClick(key)}
-          inlineCollapsed={collapsed}
-          style={{
-            backgroundColor: 'transparent',
-            borderRight: 'none'
-          }}
-        />
-      </Sider>
-      
-      <Layout style={{ marginLeft: collapsed ? 80 : 240, transition: 'margin-left 0.2s' }}>
-        <div className="sticky top-0 z-50 bg-white shadow-sm transition-all duration-200 h-16 flex items-center justify-between pl-2"
-          style={{
-            boxShadow: '0 1px 8px rgba(0,21,41,0.12)',
-            borderBottom: '1px solid #f0f0f0'
-          }}
-        >
-          <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>
-        </div>
-        
-        <Content className="m-6" style={{ overflow: 'initial', transition: 'all 0.2s ease' }}>
-          <div className="site-layout-content p-6 rounded-lg bg-white shadow-sm transition-all duration-300 hover:shadow-md">
-            <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>
-  );
-};

+ 0 - 136
src/client/admin/menu.tsx

@@ -1,136 +0,0 @@
-import React from 'react';
-import { useNavigate } from 'react-router';
-import { useAuth } from './hooks/AuthProvider';
-import type { MenuProps } from 'antd';
-import {
-  UserOutlined,
-  DashboardOutlined,
-  TeamOutlined,
-  InfoCircleOutlined,
-  FileTextOutlined,
-} from '@ant-design/icons';
-
-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 navigate = useNavigate();
-  const { logout: handleLogout } = useAuth();
-  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: 'files',
-      label: '文件管理',
-      icon: <FileTextOutlined />,
-      path: '/admin/files',
-      permission: 'file:manage'
-    },
-  ];
-
-  // 用户菜单项
-  const userMenuItems: MenuProps['items'] = [
-    {
-      key: 'profile',
-      label: '个人资料',
-      icon: <UserOutlined />
-    },
-    {
-      key: 'logout',
-      label: '退出登录',
-      icon: <InfoCircleOutlined />,
-      danger: true,
-      onClick: () => handleLogout()
-    }
-  ];
-
-  // 处理菜单点击
-  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
-  };
-};

+ 0 - 75
src/client/admin/pages/Dashboard.tsx

@@ -1,75 +0,0 @@
-import React from 'react';
-import {
-  Card, Row, Col, Typography, Statistic, Space
-} from 'antd';
-import {
-  UserOutlined, BellOutlined, EyeOutlined
-} from '@ant-design/icons';
-
-const { Title } = Typography;
-
-// 仪表盘页面
-export const DashboardPage = () => {
-  return (
-    <div>
-      <div className="mb-6 flex justify-between items-center">
-        <Title level={2}>仪表盘</Title>
-      </div>
-      <Row gutter={[16, 16]}>
-        <Col xs={24} sm={12} lg={8}>
-          <Card className="shadow-sm transition-all duration-300 hover:shadow-md">
-            <div className="flex items-center justify-between mb-2">
-              <Typography.Title level={5}>活跃用户</Typography.Title>
-              <UserOutlined style={{ fontSize: 24, color: '#1890ff' }} />
-            </div>
-            <Statistic
-              value={112893}
-              loading={false}
-              valueStyle={{ fontSize: 28 }}
-              prefix={<span style={{ color: '#52c41a' }}>↑</span>}
-              suffix="人"
-            />
-            <div style={{ marginTop: 8, fontSize: 12, color: '#8c8c8c' }}>
-              较昨日增长 12.5%
-            </div>
-          </Card>
-        </Col>
-        <Col xs={24} sm={12} lg={8}>
-          <Card className="shadow-sm transition-all duration-300 hover:shadow-md">
-            <div className="flex items-center justify-between mb-2">
-              <Typography.Title level={5}>系统消息</Typography.Title>
-              <BellOutlined style={{ fontSize: 24, color: '#faad14' }} />
-            </div>
-            <Statistic
-              value={93}
-              loading={false}
-              valueStyle={{ fontSize: 28 }}
-              prefix={<span style={{ color: '#faad14' }}>●</span>}
-              suffix="条"
-            />
-            <div style={{ marginTop: 8, fontSize: 12, color: '#8c8c8c' }}>
-              其中 5 条未读
-            </div>
-          </Card>
-        </Col>
-        <Col xs={24} sm={12} lg={8}>
-          <Card className="shadow-sm transition-all duration-300 hover:shadow-md">
-            <div className="flex items-center justify-between mb-2">
-              <Typography.Title level={5}>在线用户</Typography.Title>
-              <EyeOutlined style={{ fontSize: 24, color: '#722ed1' }} />
-            </div>
-            <Statistic
-              value={1128}
-              loading={false}
-              valueStyle={{ fontSize: 28 }}
-              suffix="人"
-            />
-            <div style={{ marginTop: 8, fontSize: 12, color: '#8c8c8c' }}>
-              当前在线率 32.1%
-            </div>
-          </Card>
-        </Col>
-      </Row>
-    </div>
-  );
-};

+ 0 - 423
src/client/admin/pages/Files.tsx

@@ -1,423 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { Table, Button, Space, Input, Modal, Form, Select, DatePicker, Upload, Popconfirm, Image } from 'antd';
-import { App } from 'antd';
-import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, UploadOutlined, DownloadOutlined, EyeOutlined } from '@ant-design/icons';
-import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
-import { fileClient } from '@/client/api';
-import type { InferResponseType, InferRequestType } from 'hono/client';
-import dayjs from 'dayjs';
-import { uploadMinIOWithPolicy } from '@/client/utils/minio';
-
-// 定义类型
-type FileItem = InferResponseType<typeof fileClient.$get, 200>['data'][0];
-type FileListResponse = InferResponseType<typeof fileClient.$get, 200>;
-type UpdateFileRequest = InferRequestType<typeof fileClient[':id']['$put']>['json'];
-
-export const FilesPage: React.FC = () => {
-  const { message } = App.useApp();
-  const [form] = Form.useForm();
-  const [modalVisible, setModalVisible] = useState(false);
-  const [editingKey, setEditingKey] = useState<number | null>(null);
-  const [searchText, setSearchText] = useState('');
-  const [pagination, setPagination] = useState({
-    current: 1,
-    pageSize: 10,
-    total: 0,
-  });
-
-  const queryClient = useQueryClient();
-  
-  
-  // 获取文件列表数据
-  const fetchFiles = async ({ page, pageSize }: { page: number; pageSize: number }): Promise<FileListResponse> => {
-    const response = await fileClient.$get({ query: { page, pageSize, keyword: searchText } });
-    if (!response.ok) throw new Error('Failed to fetch files');
-    return await response.json() as FileListResponse;
-  };
-
-  // 获取文件下载URL
-  const getFileUrl = async (fileId: number) => {
-    try {
-      const response = await fileClient[':id']['url'].$get({ param: { id: fileId } });
-      if (!response.ok) throw new Error('获取文件URL失败');
-      const data = await response.json();
-      return data.url;
-    } catch (error) {
-      message.error('获取文件URL失败');
-      return null;
-    }
-  };
-
-  // 获取文件下载URL
-  const getFileDownloadUrl = async (fileId: number) => {
-    try {
-      const response = await fileClient[':id']['download'].$get({ param: { id: fileId } });
-      if (!response.ok) throw new Error('获取文件下载URL失败');
-      const data = await response.json();
-      return data;
-    } catch (error) {
-      message.error('获取文件下载URL失败');
-      return null;
-    }
-  };
-
-  // 处理文件下载
-  const handleDownload = async (record: FileItem) => {
-    const result = await getFileDownloadUrl(record.id);
-    if (result?.url) {
-      const a = document.createElement('a');
-      a.href = result.url;
-      a.download = result.filename || record.name;
-      document.body.appendChild(a);
-      a.click();
-      document.body.removeChild(a);
-    }
-  };
-
-  // 处理文件预览
-  const handlePreview = async (record: FileItem) => {
-    const url = await getFileUrl(record.id);
-    if (url) {
-      if (record.type.startsWith('image/')) {
-        window.open(url, '_blank');
-      } else if (record.type.startsWith('video/')) {
-        window.open(url, '_blank');
-      } else {
-        message.warning('该文件类型不支持预览');
-      }
-    }
-  };
-
-  // 检查是否为可预览的文件类型
-  const isPreviewable = (fileType: string) => {
-    return fileType.startsWith('image/') || fileType.startsWith('video/');
-  };
-  
-  const { data, isLoading: loading, error: filesError } = useQuery({
-    queryKey: ['files', pagination.current, pagination.pageSize, searchText],
-    queryFn: () => fetchFiles({ page: pagination.current, pageSize: pagination.pageSize }),
-  });
-
-  // 错误处理
-  if (filesError) {
-    message.error(`获取文件列表失败: ${filesError instanceof Error ? filesError.message : '未知错误'}`);
-  }
-  
-  // 从API响应获取分页数据
-  const tablePagination = data?.pagination || pagination;
-  
-  // 搜索
-  const handleSearch = () => {
-    setPagination({ ...pagination, current: 1 });
-  };
-  
-  // 分页变化
-  const handleTableChange = (newPagination: any) => {
-    setPagination(newPagination);
-  };
-  
-  // 显示编辑弹窗
-  const showModal = (record: FileItem) => {
-    setModalVisible(true);
-    setEditingKey(record.id);
-    form.setFieldsValue({
-      name: record.name,
-      description: record.description,
-      type: record.type,
-      size: record.size,
-    });
-  };
-
-  // 关闭弹窗
-  const handleCancel = () => {
-    setModalVisible(false);
-    form.resetFields();
-  };
-  
-  // 更新文件记录
-  const updateFile = useMutation({
-    mutationFn: ({ id, data }: { id: number; data: UpdateFileRequest }) =>
-      fileClient[':id'].$put({ param: { id }, json: data }),
-    onSuccess: () => {
-      message.success('文件记录更新成功');
-      queryClient.invalidateQueries({ queryKey: ['files'] });
-      setModalVisible(false);
-    },
-    onError: (error: Error) => {
-      message.error(`操作失败: ${error instanceof Error ? error.message : '未知错误'}`);
-    }
-  });
-  
-  // 删除文件记录
-  const deleteFile = useMutation({
-    mutationFn: (id: number) => fileClient[':id'].$delete({ param: { id } }),
-    onSuccess: () => {
-      message.success('文件记录删除成功');
-      queryClient.invalidateQueries({ queryKey: ['files'] });
-    },
-    onError: (error: Error) => {
-      message.error(`删除失败: ${error instanceof Error ? error.message : '未知错误'}`);
-    }
-  });
-  
-  // 直接上传文件
-  const handleDirectUpload = async () => {
-    const input = document.createElement('input');
-    input.type = 'file';
-    input.multiple = false;
-    
-    input.onchange = async (e) => {
-      const file = (e.target as HTMLInputElement).files?.[0];
-      if (!file) return;
-      
-      try {
-        message.loading('正在上传文件...');
-        await uploadMinIOWithPolicy('/files', file, file.name);
-        message.success('文件上传成功');
-        queryClient.invalidateQueries({ queryKey: ['files'] });
-      } catch (error) {
-        message.error(`上传失败: ${error instanceof Error ? error.message : '未知错误'}`);
-      }
-    };
-    
-    input.click();
-  };
-  
-  // 提交表单(仅用于编辑已上传文件)
-  const handleSubmit = async () => {
-    try {
-      const values = await form.validateFields();
-      
-      const payload = {
-        name: values.name,
-        description: values.description,
-      };
-      
-      if (editingKey) {
-        await updateFile.mutateAsync({ id: editingKey, data: payload });
-      }
-    } catch (error) {
-      message.error('表单验证失败,请检查输入');
-    }
-  };
-  
-  // 表格列定义
-  const columns = [
-    {
-      title: '文件ID',
-      dataIndex: 'id',
-      key: 'id',
-      width: 80,
-      align: 'center' as const,
-    },
-    {
-      title: '文件名称',
-      dataIndex: 'name',
-      key: 'name',
-      width: 300,
-      ellipsis: true,
-      render: (name: string, record: FileItem) => (
-        <div className="flex items-center">
-          <span className="flex-1">{name}</span>
-        </div>
-      ),
-    },
-    {
-      title: '文件类型',
-      dataIndex: 'type',
-      key: 'type',
-      width: 120,
-      render: (type: string) => (
-        <span className="inline-block px-2 py-1 text-xs bg-blue-50 text-blue-700 rounded-full">
-          {type}
-        </span>
-      ),
-    },
-    {
-      title: '文件大小',
-      dataIndex: 'size',
-      key: 'size',
-      width: 120,
-      render: (size: number) => (
-        <span className="text-sm">
-          {size ? `${(size / 1024).toFixed(2)} KB` : '-'}
-        </span>
-      ),
-    },
-    {
-      title: '上传时间',
-      dataIndex: 'uploadTime',
-      key: 'uploadTime',
-      width: 180,
-      render: (time: string) => (
-        <span className="text-sm text-gray-600">
-          {time ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : '-'}
-        </span>
-      ),
-    },
-    {
-      title: '上传用户',
-      dataIndex: 'uploadUser',
-      key: 'uploadUser',
-      width: 120,
-      render: (uploadUser?: { username: string; nickname?: string }) => (
-        <span className="text-sm">
-          {uploadUser ? (uploadUser.nickname || uploadUser.username) : '-'}
-        </span>
-      ),
-    },
-    {
-      title: '操作',
-      key: 'action',
-      width: 200,
-      fixed: 'right' as const,
-      render: (_: any, record: FileItem) => (
-        <Space size="small">
-          <Button
-            type="text"
-            icon={<EyeOutlined />}
-            onClick={() => handlePreview(record)}
-            className="text-green-600 hover:text-green-800 hover:bg-green-50"
-            disabled={!isPreviewable(record.type)}
-            title={isPreviewable(record.type) ? '预览文件' : '该文件类型不支持预览'}
-          />
-          <Button
-            type="text"
-            icon={<DownloadOutlined />}
-            onClick={() => handleDownload(record)}
-            className="text-blue-600 hover:text-blue-800 hover:bg-blue-50"
-            title="下载文件"
-          />
-          <Button
-            type="text"
-            icon={<EditOutlined />}
-            onClick={() => showModal(record)}
-            className="text-purple-600 hover:text-purple-800 hover:bg-purple-50"
-            title="编辑文件信息"
-          />
-          <Popconfirm
-            title="确认删除"
-            description={`确定要删除文件"${record.name}"吗?此操作不可恢复。`}
-            onConfirm={() => deleteFile.mutate(record.id)}
-            okText="确认"
-            cancelText="取消"
-            okButtonProps={{ danger: true }}
-          >
-            <Button
-              type="text"
-              danger
-              icon={<DeleteOutlined />}
-              className="hover:bg-red-50"
-              title="删除文件"
-            >
-              删除
-            </Button>
-          </Popconfirm>
-        </Space>
-      ),
-    },
-  ];
-  
-  return (
-    <div className="p-6">
-      <div className="mb-6 flex justify-between items-center">
-        <h2 className="text-2xl font-bold text-gray-900">文件管理</h2>
-        <Button
-          type="primary"
-          icon={<UploadOutlined />}
-          onClick={handleDirectUpload}
-          className="h-10 flex items-center"
-        >
-          上传文件
-        </Button>
-      </div>
-      
-      <div className="mb-6">
-        <div className="flex items-center gap-4">
-          <Input
-            placeholder="搜索文件名称或类型"
-            prefix={<SearchOutlined />}
-            value={searchText}
-            onChange={(e) => setSearchText(e.target.value)}
-            onPressEnter={handleSearch}
-            className="w-80 h-10"
-            allowClear
-          />
-          <Button
-            type="default"
-            onClick={handleSearch}
-            className="h-10"
-          >
-            搜索
-          </Button>
-        </div>
-      </div>
-      
-      <div className="bg-white rounded-lg shadow-sm transition-all duration-300 hover:shadow-md">
-        <Table
-          columns={columns}
-          dataSource={data?.data || []}
-          rowKey="id"
-          loading={loading}
-          pagination={{
-            ...tablePagination,
-            showSizeChanger: true,
-            showQuickJumper: true,
-            showTotal: (total, range) =>
-              `显示 ${range[0]}-${range[1]} 条,共 ${total} 条`,
-          }}
-          onChange={handleTableChange}
-          bordered={false}
-          scroll={{ x: 'max-content' }}
-          className="[&_.ant-table]:!rounded-lg [&_.ant-table-thead>tr>th]:!bg-gray-50 [&_.ant-table-thead>tr>th]:!font-semibold [&_.ant-table-thead>tr>th]:!text-gray-700 [&_.ant-table-thead>tr>th]:!border-b-2 [&_.ant-table-thead>tr>th]:!border-gray-200"
-          rowClassName={(record, index) => index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}
-        />
-      </div>
-      
-      <Modal
-        title="编辑文件信息"
-        open={modalVisible}
-        onCancel={handleCancel}
-        footer={[
-          <Button key="cancel" onClick={handleCancel}>
-            取消
-          </Button>,
-          <Button
-            key="submit"
-            type="primary"
-            onClick={handleSubmit}
-            loading={updateFile.isPending}
-          >
-            确定
-          </Button>,
-        ]}
-        width={600}
-        centered
-        destroyOnClose
-        maskClosable={false}
-      >
-        <Form form={form} layout="vertical">
-          <Form.Item name="name" label="文件名称">
-            <Input className="h-10" />
-          </Form.Item>
-          
-          <Form.Item name="description" label="文件描述">
-            <Input.TextArea
-              rows={4}
-              placeholder="请输入文件描述"
-              className="rounded-md"
-            />
-          </Form.Item>
-          
-          <Form.Item name="type" label="文件类型" hidden>
-            <Input />
-          </Form.Item>
-          
-          <Form.Item name="size" label="文件大小" hidden>
-            <Input />
-          </Form.Item>
-        </Form>
-      </Modal>
-    </div>
-  );
-};

+ 0 - 129
src/client/admin/pages/Login.tsx

@@ -1,129 +0,0 @@
-import React, { useState } from 'react';
-import {
-  Form,
-  Input,
-  Button,
-  Card,
-  App,
-} from 'antd';
-import {
-  UserOutlined,
-  LockOutlined,
-  EyeOutlined,
-  EyeInvisibleOutlined
-} from '@ant-design/icons';
-import { useNavigate } from 'react-router';
-import {
-  useAuth,
-} from '../hooks/AuthProvider';
-
-
-// 登录页面
-export const LoginPage = () => {
-  const { message } = App.useApp();
-  const { login } = useAuth();
-  const [form] = Form.useForm();
-  const [loading, setLoading] = useState(false);
-  const navigate = useNavigate();
-  
-  const handleSubmit = async (values: { username: string; password: string }) => {
-    try {
-      setLoading(true);
-      
-      // 获取地理位置
-      let latitude: number | undefined;
-      let longitude: number | undefined;
-      
-      try {
-        if (navigator.geolocation) {
-          const position = await new Promise<GeolocationPosition>((resolve, reject) => {
-            navigator.geolocation.getCurrentPosition(resolve, reject);
-          });
-          latitude = position.coords.latitude;
-          longitude = position.coords.longitude;
-        }
-      } catch (geoError) {
-        console.warn('获取地理位置失败:', geoError);
-      }
-      
-      await login(values.username, values.password, latitude, longitude);
-      // 登录成功后跳转到管理后台首页
-      navigate('/admin/dashboard');
-    } catch (error: any) {
-      message.error(error instanceof Error ? error.message : '登录失败');
-    } finally {
-      setLoading(false);
-    }
-  };
-  
-  return (
-    <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100 py-12 px-4 sm:px-6 lg:px-8">
-      <div className="max-w-md w-full space-y-8">
-        <div className="text-center">
-          <div className="w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-4">
-            <UserOutlined style={{ fontSize: 32, color: '#1890ff' }} />
-          </div>
-          <h2 className="mt-2 text-center text-3xl font-extrabold text-gray-900">
-            管理后台登录
-          </h2>
-          <p className="mt-2 text-gray-500">请输入您的账号和密码</p>
-        </div>
-        
-        <Card className="shadow-lg border-none transition-all duration-300 hover:shadow-xl">
-          <Form
-            form={form}
-            name="login"
-            onFinish={handleSubmit}
-            autoComplete="off"
-            layout="vertical"
-          >
-            <Form.Item
-              name="username"
-              rules={[{ required: true, message: '请输入用户名' }]}
-              label="用户名"
-            >
-              <Input
-                prefix={<UserOutlined className="text-primary" />}
-                placeholder="请输入用户名"
-                size="large"
-                className="transition-all duration-200 focus:border-primary focus:ring-1 focus:ring-primary"
-              />
-            </Form.Item>
-            
-            <Form.Item
-              name="password"
-              rules={[{ required: true, message: '请输入密码' }]}
-              label="密码"
-            >
-              <Input.Password
-                prefix={<LockOutlined className="text-primary" />}
-                placeholder="请输入密码"
-                size="large"
-                iconRender={(visible) => (visible ? <EyeOutlined /> : <EyeInvisibleOutlined />)}
-                className="transition-all duration-200 focus:border-primary focus:ring-1 focus:ring-primary"
-              />
-            </Form.Item>
-            
-            <Form.Item>
-              <Button
-                type="primary"
-                htmlType="submit"
-                size="large"
-                block
-                loading={loading}
-                className="h-12 text-lg transition-all duration-200 hover:shadow-lg"
-              >
-                登录
-              </Button>
-            </Form.Item>
-          </Form>
-          
-          <div className="mt-6 text-center text-gray-500 text-sm">
-            <p>测试账号: <span className="font-medium">admin</span> / <span className="font-medium">admin123</span></p>
-            <p className="mt-1">© {new Date().getFullYear()} 管理系统. 保留所有权利.</p>
-          </div>
-        </Card>
-      </div>
-    </div>
-  );
-};

+ 0 - 342
src/client/admin/pages/Users.tsx

@@ -1,342 +0,0 @@
-import React, { useState } from 'react';
-import {
-  Button, Table, Space, Form, Input, Select, Modal, Card, Typography, Tag, Popconfirm,
-  App
-} from 'antd';
-import { useQuery } from '@tanstack/react-query';
-import dayjs from 'dayjs';
-import { roleClient, userClient } from '@/client/api';
-import type { InferResponseType, InferRequestType } from 'hono/client';
-
-type UserListResponse = InferResponseType<typeof userClient.$get, 200>;
-type RoleListResponse = InferResponseType<typeof roleClient.$get, 200>;
-type CreateRoleRequest = InferRequestType<typeof roleClient.$post>['json'];
-type UserDetailResponse = InferResponseType<typeof userClient[':id']['$get'], 200>;
-type CreateUserRequest = InferRequestType<typeof userClient.$post>['json'];
-type UpdateUserRequest = InferRequestType<typeof userClient[':id']['$put']>['json'];
-
-const { Title } = Typography;
-
-// 用户管理页面
-export const UsersPage = () => {
-  const { message } = App.useApp();
-  const [searchParams, setSearchParams] = useState({
-    page: 1,
-    limit: 10,
-    search: ''
-  });
-  const [modalVisible, setModalVisible] = useState(false);
-  const [modalTitle, setModalTitle] = useState('');
-  const [editingUser, setEditingUser] = useState<any>(null);
-  const [form] = Form.useForm();
-
-  const { data: usersData, isLoading, refetch } = useQuery({
-    queryKey: ['users', searchParams],
-    queryFn: async () => {
-      const res = await userClient.$get({
-        query: {
-          page: searchParams.page,
-          pageSize: searchParams.limit,
-          keyword: searchParams.search
-        }
-      });
-      if (res.status !== 200) {
-        throw new Error('获取用户列表失败');
-      }
-      return await res.json();
-    }
-  });
-
-  const users = usersData?.data || [];
-  const pagination = {
-    current: searchParams.page,
-    pageSize: searchParams.limit,
-    total: usersData?.pagination?.total || 0
-  };
-
-  // 处理搜索
-  const handleSearch = (values: any) => {
-    setSearchParams(prev => ({
-      ...prev,
-      search: values.search || '',
-      page: 1
-    }));
-  };
-
-  // 处理分页变化
-  const handleTableChange = (newPagination: any) => {
-    setSearchParams(prev => ({
-      ...prev,
-      page: newPagination.current,
-      limit: newPagination.pageSize
-    }));
-  };
-
-  // 打开创建用户模态框
-  const showCreateModal = () => {
-    setModalTitle('创建用户');
-    setEditingUser(null);
-    form.resetFields();
-    setModalVisible(true);
-  };
-
-  // 打开编辑用户模态框
-  const showEditModal = (user: any) => {
-    setModalTitle('编辑用户');
-    setEditingUser(user);
-    form.setFieldsValue(user);
-    setModalVisible(true);
-  };
-
-  // 处理模态框确认
-  const handleModalOk = async () => {
-    try {
-      const values = await form.validateFields();
-      
-      if (editingUser) {
-        // 编辑用户
-        const res = await userClient[':id']['$put']({
-          param: { id: editingUser.id },
-          json: values
-        });
-        if (res.status !== 200) {
-          throw new Error('更新用户失败');
-        }
-        message.success('用户更新成功');
-      } else {
-        // 创建用户
-        const res = await userClient.$post({
-          json: values
-        });
-        if (res.status !== 201) {
-          throw new Error('创建用户失败');
-        }
-        message.success('用户创建成功');
-      }
-      
-      setModalVisible(false);
-      form.resetFields();
-      refetch(); // 刷新用户列表
-    } catch (error) {
-      console.error('表单提交失败:', error);
-      message.error('操作失败,请重试');
-    }
-  };
-
-  // 处理删除用户
-  const handleDelete = async (id: number) => {
-    try {
-      const res = await userClient[':id']['$delete']({
-        param: { id }
-      });
-      if (res.status !== 204) {
-        throw new Error('删除用户失败');
-      }
-      message.success('用户删除成功');
-      refetch(); // 刷新用户列表
-    } catch (error) {
-      console.error('删除用户失败:', error);
-      message.error('删除失败,请重试');
-    }
-  };
-  
-  const columns = [
-    {
-      title: '用户名',
-      dataIndex: 'username',
-      key: 'username',
-    },
-    {
-      title: '昵称',
-      dataIndex: 'nickname',
-      key: 'nickname',
-    },
-    {
-      title: '邮箱',
-      dataIndex: 'email',
-      key: 'email',
-    },
-    {
-      title: '真实姓名',
-      dataIndex: 'name',
-      key: 'name',
-    },
-    {
-      title: '角色',
-      dataIndex: 'role',
-      key: 'role',
-      render: (role: string) => (
-        <Tag color={role === 'admin' ? 'red' : 'blue'}>
-          {role === 'admin' ? '管理员' : '普通用户'}
-        </Tag>
-      ),
-    },
-    {
-      title: '创建时间',
-      dataIndex: 'created_at',
-      key: 'created_at',
-      render: (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm:ss'),
-    },
-    {
-      title: '操作',
-      key: 'action',
-      render: (_: any, record: any) => (
-        <Space size="middle">
-          <Button type="link" onClick={() => showEditModal(record)}>
-            编辑
-          </Button>
-          <Popconfirm
-            title="确定要删除此用户吗?"
-            onConfirm={() => handleDelete(record.id)}
-            okText="确定"
-            cancelText="取消"
-          >
-            <Button type="link" danger>
-              删除
-            </Button>
-          </Popconfirm>
-        </Space>
-      ),
-    },
-  ];
-  
-  return (
-    <div>
-      <div className="mb-6 flex justify-between items-center">
-        <Title level={2}>用户管理</Title>
-      </div>
-      <Card className="shadow-md transition-all duration-300 hover:shadow-lg">
-        <Form layout="inline" onFinish={handleSearch} style={{ marginBottom: 16, padding: '16px 0' }}>
-          <Form.Item name="search" label="搜索">
-            <Input placeholder="用户名/昵称/邮箱" allowClear />
-          </Form.Item>
-          <Form.Item>
-            <Space>
-              <Button type="primary" htmlType="submit">
-                搜索
-              </Button>
-              <Button type="primary" onClick={showCreateModal}>
-                创建用户
-              </Button>
-            </Space>
-          </Form.Item>
-        </Form>
-
-        <Table
-          columns={columns}
-          dataSource={users}
-          loading={isLoading}
-          rowKey="id"
-          pagination={{
-            ...pagination,
-            showSizeChanger: true,
-            showQuickJumper: true,
-            showTotal: (total) => `共 ${total} 条记录`
-          }}
-          onChange={handleTableChange}
-          bordered
-          scroll={{ x: 'max-content' }}
-          rowClassName={(record, index) => index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}
-        />
-      </Card>
-
-      {/* 创建/编辑用户模态框 */}
-      <Modal
-        title={modalTitle}
-        open={modalVisible}
-        onOk={handleModalOk}
-        onCancel={() => {
-          setModalVisible(false);
-          form.resetFields();
-        }}
-        width={600}
-        centered
-        destroyOnClose
-        maskClosable={false}
-      >
-        <Form
-          form={form}
-          layout="vertical"
-          labelCol={{ span: 5 }}
-          wrapperCol={{ span: 19 }}
-        >
-          <Form.Item
-            name="username"
-            label="用户名"
-            required
-            rules={[
-              { required: true, message: '请输入用户名' },
-              { min: 3, message: '用户名至少3个字符' }
-            ]}
-          >
-            <Input placeholder="请输入用户名" />
-          </Form.Item>
-
-          <Form.Item
-            name="nickname"
-            label="昵称"
-            rules={[{ required: false, message: '请输入昵称' }]}
-          >
-            <Input placeholder="请输入昵称" />
-          </Form.Item>
-
-          <Form.Item
-            name="email"
-            label="邮箱"
-            rules={[
-              { required: false, message: '请输入邮箱' },
-              { type: 'email', message: '请输入有效的邮箱地址' }
-            ]}
-          >
-            <Input placeholder="请输入邮箱" />
-          </Form.Item>
-
-          <Form.Item
-            name="phone"
-            label="手机号"
-            rules={[
-              { required: false, message: '请输入手机号' },
-              { pattern: /^1[3-9]\d{9}$/, message: '请输入有效的手机号' }
-            ]}
-          >
-            <Input placeholder="请输入手机号" />
-          </Form.Item>
-
-          <Form.Item
-            name="name"
-            label="真实姓名"
-            rules={[{ required: false, message: '请输入真实姓名' }]}
-          >
-            <Input placeholder="请输入真实姓名" />
-          </Form.Item>
-
-          {!editingUser && (
-            <Form.Item
-              name="password"
-              label="密码"
-              required
-              rules={[
-                { required: true, message: '请输入密码' },
-                { min: 6, message: '密码至少6个字符' }
-              ]}
-            >
-              <Input.Password placeholder="请输入密码" />
-            </Form.Item>
-          )}
-
-          <Form.Item
-            name="isDisabled"
-            label="状态"
-            required
-            rules={[{ required: true, message: '请选择状态' }]}
-          >
-            <Select placeholder="请选择状态">
-              <Select.Option value={0}>启用</Select.Option>
-              <Select.Option value={1}>禁用</Select.Option>
-            </Select>
-          </Form.Item>
-        </Form>
-      </Modal>
-    </div>
-  );
-};

+ 0 - 60
src/client/admin/routes.tsx

@@ -1,60 +0,0 @@
-import React from 'react';
-import { createBrowserRouter, Navigate } from 'react-router';
-import { ProtectedRoute } from './components/ProtectedRoute';
-import { MainLayout } from './layouts/MainLayout';
-import { ErrorPage } from './components/ErrorPage';
-import { NotFoundPage } from './components/NotFoundPage';
-import { DashboardPage } from './pages/Dashboard';
-import { UsersPage } from './pages/Users';
-import { LoginPage } from './pages/Login';
-import { FilesPage } from './pages/Files';
-
-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: 'files',
-        element: <FilesPage />,
-        errorElement: <ErrorPage />
-      },
-      {
-        path: '*',
-        element: <NotFoundPage />,
-        errorElement: <ErrorPage />
-      },
-    ],
-  },
-  {
-    path: '*',
-    element: <NotFoundPage />,
-    errorElement: <ErrorPage />
-  },
-]);

+ 0 - 49
src/client/home/components/ErrorPage.tsx

@@ -1,49 +0,0 @@
-import React from 'react';
-import { useRouteError, useNavigate } from 'react-router';
-
-export const ErrorPage = () => {
-  const navigate = useNavigate();
-  const error = useRouteError() as any;
-  const errorMessage = error?.statusText || error?.message || '未知错误';
-  
-  return (
-    <div className="flex flex-col items-center justify-center min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 p-4">
-      <div className="w-full max-w-md bg-white dark:bg-gray-800 rounded-xl shadow-lg overflow-hidden transition-all duration-300 hover:shadow-xl">
-        <div className="bg-red-50 dark:bg-red-900/30 px-6 py-4 border-b border-red-100 dark:border-red-800">
-          <h1 className="text-2xl font-bold text-red-600 dark:text-red-400">发生错误</h1>
-        </div>
-        <div className="p-6">
-          <div className="flex items-start mb-4">
-            <div className="flex-shrink-0 bg-red-100 dark:bg-red-900/50 p-3 rounded-full">
-              <svg className="w-8 h-8 text-red-500 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
-                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
-              </svg>
-            </div>
-            <div className="ml-4">
-              <h3 className="text-lg font-medium text-gray-900 dark:text-white">{error?.message || '未知错误'}</h3>
-              {error?.stack && (
-                <pre className="mt-2 text-xs text-gray-600 dark:text-gray-300 bg-gray-50 dark:bg-gray-700 p-3 rounded overflow-x-auto max-h-40">
-                  {error.stack}
-                </pre>
-              )}
-            </div>
-          </div>
-          <div className="flex gap-4">
-            <button
-              onClick={() => navigate(0)}
-              className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-colors duration-200"
-            >
-              重新加载
-            </button>
-            <button
-              onClick={() => navigate('/')}
-              className="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 shadow-sm text-sm font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-colors duration-200"
-            >
-              返回首页
-            </button>
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-};

+ 0 - 35
src/client/home/components/NotFoundPage.tsx

@@ -1,35 +0,0 @@
-import React from 'react';
-import { useNavigate } from 'react-router';
-
-export const NotFoundPage = () => {
-  const navigate = useNavigate();
-  
-  return (
-    <div className="flex flex-col items-center justify-center min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 p-4">
-      <div className="w-full max-w-md bg-white dark:bg-gray-800 rounded-xl shadow-lg overflow-hidden transition-all duration-300 hover:shadow-xl">
-        <div className="bg-blue-50 dark:bg-blue-900/30 px-6 py-4 border-b border-blue-100 dark:border-blue-800">
-          <h1 className="text-2xl font-bold text-blue-600 dark:text-blue-400">404 - 页面未找到</h1>
-        </div>
-        <div className="p-6">
-          <div className="flex items-start mb-6">
-            <div className="flex-shrink-0 bg-blue-100 dark:bg-blue-900/50 p-3 rounded-full">
-              <svg className="w-10 h-10 text-blue-500 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
-                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
-              </svg>
-            </div>
-            <div className="ml-4">
-              <p className="text-lg text-gray-700 dark:text-gray-300">您访问的页面不存在或已被移除</p>
-              <p className="mt-2 text-gray-500 dark:text-gray-400">请检查URL是否正确或返回首页</p>
-            </div>
-          </div>
-          <button
-            onClick={() => navigate('/')}
-            className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200 w-full"
-          >
-            返回首页
-          </button>
-        </div>
-      </div>
-    </div>
-  );
-};

+ 0 - 37
src/client/home/components/ProtectedRoute.tsx

@@ -1,37 +0,0 @@
-import React, { useEffect } from 'react';
-import { 
-  useNavigate,
-} from 'react-router';
-import { useAuth } from '../hooks/AuthProvider';
-
-
-
-
-
-export const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
-  const { isAuthenticated, isLoading } = useAuth();
-  const navigate = useNavigate();
-  
-  useEffect(() => {
-    // 只有在加载完成且未认证时才重定向
-    if (!isLoading && !isAuthenticated) {
-      navigate('/login', { replace: true });
-    }
-  }, [isAuthenticated, isLoading, navigate]);
-  
-  // 显示加载状态,直到认证检查完成
-  if (isLoading) {
-    return (
-      <div className="flex justify-center items-center h-screen">
-        <div className="loader ease-linear rounded-full border-4 border-t-4 border-gray-200 h-12 w-12"></div>
-      </div>
-    );
-  }
-  
-  // 如果未认证且不再加载中,不显示任何内容(等待重定向)
-  if (!isAuthenticated) {
-    return null;
-  }
-  
-  return children;
-};

+ 0 - 140
src/client/home/hooks/AuthProvider.tsx

@@ -1,140 +0,0 @@
-import React, { useState, useEffect, createContext, useContext } from 'react';
-
-import {
-  useQuery,
-  useQueryClient,
-} from '@tanstack/react-query';
-import axios from 'axios';
-import 'dayjs/locale/zh-cn';
-import type {
-  AuthContextType
-} from '@/share/types';
-import { authClient } from '@/client/api';
-import type { InferResponseType, InferRequestType } from 'hono/client';
-
-export type User = InferResponseType<typeof authClient.me.$get, 200>;
-
-
-// 创建认证上下文
-const AuthContext = createContext<AuthContextType<User> | null>(null);
-
-// 认证提供器组件
-export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
-  const [user, setUser] = useState<User | null>(null);
-  const [token, setToken] = useState<string | null>(localStorage.getItem('token'));
-  const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
-  const queryClient = useQueryClient();
-
-  // 声明handleLogout函数
-  const handleLogout = async () => {
-    try {
-      // 如果已登录,调用登出API
-      if (token) {
-        await authClient.logout.$post();
-      }
-    } catch (error) {
-      console.error('登出请求失败:', error);
-    } finally {
-      // 清除本地状态
-      setToken(null);
-      setUser(null);
-      setIsAuthenticated(false);
-      localStorage.removeItem('token');
-      // 清除Authorization头
-      delete axios.defaults.headers.common['Authorization'];
-      console.log('登出时已删除全局Authorization头');
-      // 清除所有查询缓存
-      queryClient.clear();
-    }
-  };
-
-  // 使用useQuery检查登录状态
-  const { isFetching: isLoading } = useQuery({
-    queryKey: ['auth', 'status', token],
-    queryFn: async () => {
-      if (!token) {
-        setIsAuthenticated(false);
-        setUser(null);
-        return null;
-      }
-
-      try {
-        // 设置全局默认请求头
-        axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
-        // 使用API验证当前用户
-        const res = await authClient.me.$get();
-        if (res.status !== 200) {
-          const result = await res.json();
-          throw new Error(result.message)
-        }
-        const currentUser = await res.json();
-        setUser(currentUser);
-        setIsAuthenticated(true);
-        return { isValid: true, user: currentUser };
-      } catch (error) {
-        return { isValid: false };
-      }
-    },
-    enabled: !!token,
-    refetchOnWindowFocus: false,
-    retry: false
-  });
-
-  const handleLogin = async (username: string, password: string, latitude?: number, longitude?: number): Promise<void> => {
-    try {
-      // 使用AuthAPI登录
-      const response = await authClient.login.$post({
-        json: {
-          username,
-          password
-        }
-      })
-      if (response.status !== 200) {
-        const result = await response.json()
-        throw new Error(result.message);
-      }
-
-      const result = await response.json()
-
-      // 保存token和用户信息
-      const { token: newToken, user: newUser } = result;
-
-      // 设置全局默认请求头
-      axios.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
-
-      // 保存状态
-      setToken(newToken);
-      setUser(newUser);
-      setIsAuthenticated(true);
-      localStorage.setItem('token', newToken);
-
-    } catch (error) {
-      console.error('登录失败:', error);
-      throw error;
-    }
-  };
-
-  return (
-    <AuthContext.Provider
-      value={{
-        user,
-        token,
-        login: handleLogin,
-        logout: handleLogout,
-        isAuthenticated,
-        isLoading
-      }}
-    >
-      {children}
-    </AuthContext.Provider>
-  );
-};
-
-// 使用上下文的钩子
-export const useAuth = () => {
-  const context = useContext(AuthContext);
-  if (!context) {
-    throw new Error('useAuth必须在AuthProvider内部使用');
-  }
-  return context;
-};

+ 0 - 28
src/client/home/index.tsx

@@ -1,28 +0,0 @@
-import { createRoot } from 'react-dom/client'
-import { getGlobalConfig } from '../utils/utils'
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
-import { AuthProvider } from './hooks/AuthProvider'
-import { RouterProvider } from 'react-router-dom'
-import { router } from './routes'
-
-// 创建QueryClient实例
-const queryClient = new QueryClient();
-
-// 应用入口组件
-const App = () => {
-  return (
-    <QueryClientProvider client={queryClient}>
-      <AuthProvider>
-        <RouterProvider router={router} />
-      </AuthProvider>
-    </QueryClientProvider>
-  )
-};
-
-const rootElement = document.getElementById('root')
-if (rootElement) {
-  const root = createRoot(rootElement)
-  root.render(
-    <App />
-  )
-}

+ 0 - 14
src/client/home/layouts/MainLayout.tsx

@@ -1,14 +0,0 @@
-import React, { useState, useEffect, useMemo } from 'react';
-import {
-  Outlet
-} from 'react-router';
-
-/**
- * 主布局组件
- * 包含侧边栏、顶部导航和内容区域
- */
-export const MainLayout = () => {
-  return (
-    <Outlet />
-  );
-};

+ 0 - 221
src/client/home/pages/HomePage.tsx

@@ -1,221 +0,0 @@
-import React from 'react';
-import { useAuth } from '@/client/home/hooks/AuthProvider';
-import { useNavigate } from 'react-router-dom';
-
-const HomePage: React.FC = () => {
-  const { user } = useAuth();
-  const navigate = useNavigate();
-
-  return (
-    <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50 flex flex-col">
-      {/* 顶部导航 */}
-      <header className="bg-white shadow-sm border-b border-gray-100 fixed w-full z-10">
-        <div className="container mx-auto px-4 py-4 flex justify-between items-center">
-          <div className="flex items-center space-x-2">
-            <div className="w-8 h-8 bg-gradient-to-r from-blue-600 to-purple-600 rounded-lg flex items-center justify-center">
-              <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
-                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
-              </svg>
-            </div>
-            <h1 className="text-xl font-bold text-gray-900">云存储平台</h1>
-          </div>
-          
-          {user ? (
-            <div className="flex items-center space-x-4">
-              <a
-                href="/admin/files"
-                className="text-sm text-gray-600 hover:text-blue-600 transition-colors"
-              >
-                文件管理
-              </a>
-              <div className="flex items-center cursor-pointer hover:bg-gray-50 rounded-lg px-3 py-2 transition-colors" onClick={() => navigate(`/member`)}>
-                <div className="w-8 h-8 rounded-full bg-gradient-to-r from-blue-500 to-purple-500 flex items-center justify-center mr-2">
-                  <span className="text-white text-sm font-medium">{user.username.charAt(0).toUpperCase()}</span>
-                </div>
-                <span className="hidden md:inline text-sm text-gray-700">{user.username}</span>
-              </div>
-            </div>
-          ) : (
-            <div className="flex space-x-3">
-              <button 
-                onClick={() => navigate('/login')}
-                className="px-4 py-2 rounded-lg text-sm text-blue-600 hover:bg-blue-50 transition-colors"
-              >
-                登录
-              </button>
-              <button 
-                onClick={() => navigate('/register')}
-                className="px-4 py-2 rounded-lg text-sm bg-gradient-to-r from-blue-600 to-purple-600 text-white hover:from-blue-700 hover:to-purple-700 transition-all shadow-sm hover:shadow-md"
-              >
-                免费注册
-              </button>
-            </div>
-          )}
-        </div>
-      </header>
-      
-      {/* 主内容区 */}
-      <main className="flex-grow container mx-auto px-4 pt-24 pb-12">
-        {/* 英雄区域 */}
-        <div className="text-center py-12">
-          <h2 className="text-4xl md:text-5xl font-bold text-gray-900 mb-6">
-            安全、高效的
-            <span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-600 to-purple-600"> 文件存储平台</span>
-          </h2>
-          <p className="text-lg text-gray-600 mb-8 max-w-2xl mx-auto">
-            基于 MinIO 构建的企业级云存储解决方案,支持大文件分片上传、断点续传、
-            文件预览与分享,为您的数据提供银行级安全保障。
-          </p>
-          
-          <div className="flex flex-col sm:flex-row gap-4 justify-center">
-            <a
-              href={user ? '/admin/files' : '/register'}
-              className="px-8 py-3 rounded-lg bg-gradient-to-r from-blue-600 to-purple-600 text-white font-medium hover:from-blue-700 hover:to-purple-700 transition-all shadow-lg hover:shadow-xl"
-            >
-              {user ? '进入文件管理' : '立即开始使用'}
-            </a>
-            <a
-              href="/admin"
-              className="px-8 py-3 rounded-lg border border-gray-300 text-gray-700 font-medium hover:bg-gray-50 transition-colors"
-            >
-              管理后台
-            </a>
-          </div>
-        </div>
-
-        {/* 功能特性 */}
-        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mt-16">
-          <div className="bg-white rounded-xl p-6 shadow-sm border border-gray-100 hover:shadow-md transition-shadow">
-            <div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-4">
-              <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
-                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
-              </svg>
-            </div>
-            <h3 className="font-semibold text-lg mb-2">大文件上传</h3>
-            <p className="text-gray-600 text-sm">支持分片上传,单个文件最大可达 5TB,断点续传不中断</p>
-          </div>
-          
-          <div className="bg-white rounded-xl p-6 shadow-sm border border-gray-100 hover:shadow-md transition-shadow">
-            <div className="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center mb-4">
-              <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
-                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
-              </svg>
-            </div>
-            <h3 className="font-semibold text-lg mb-2">文件管理</h3>
-            <p className="text-gray-600 text-sm">完整的文件生命周期管理,支持文件预览、下载、分享和版本控制</p>
-          </div>
-          
-          <div className="bg-white rounded-xl p-6 shadow-sm border border-gray-100 hover:shadow-md transition-shadow">
-            <div className="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center mb-4">
-              <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-purple-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
-                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
-              </svg>
-            </div>
-            <h3 className="font-semibold text-lg mb-2">安全保障</h3>
-            <p className="text-gray-600 text-sm">端到端加密存储,多重备份机制,确保数据安全可靠</p>
-          </div>
-          
-          <div className="bg-white rounded-xl p-6 shadow-sm border border-gray-100 hover:shadow-md transition-shadow">
-            <div className="w-12 h-12 bg-orange-100 rounded-lg flex items-center justify-center mb-4">
-              <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-orange-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
-                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
-              </svg>
-            </div>
-            <h3 className="font-semibold text-lg mb-2">多端同步</h3>
-            <p className="text-gray-600 text-sm">支持Web、移动端等多平台访问,随时随地管理您的文件</p>
-          </div>
-        </div>
-
-        {/* 技术架构 */}
-        <div className="mt-16 bg-white rounded-xl p-8 shadow-sm border border-gray-100">
-          <h3 className="text-2xl font-bold text-center mb-8">技术架构</h3>
-          <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
-            <div className="text-center">
-              <div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
-                <span className="text-2xl font-bold text-blue-600">R</span>
-              </div>
-              <h4 className="font-semibold mb-2">React 18</h4>
-              <p className="text-sm text-gray-600">现代化的前端框架,提供流畅的用户体验</p>
-            </div>
-            <div className="text-center">
-              <div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
-                <span className="text-2xl font-bold text-green-600">M</span>
-              </div>
-              <h4 className="font-semibold mb-2">MinIO</h4>
-              <p className="text-sm text-gray-600">高性能对象存储,支持S3协议的标准云存储</p>
-            </div>
-            <div className="text-center">
-              <div className="w-16 h-16 bg-purple-100 rounded-full flex items-center justify-center mx-auto mb-4">
-                <span className="text-2xl font-bold text-purple-600">T</span>
-              </div>
-              <h4 className="font-semibold mb-2">TypeScript</h4>
-              <p className="text-sm text-gray-600">类型安全的开发体验,提升代码质量和可维护性</p>
-            </div>
-          </div>
-        </div>
-
-        {/* 文件管理功能预览 */}
-        <div className="mt-16">
-          <div className="text-center mb-8">
-            <h3 className="text-2xl font-bold mb-4">文件管理功能一览</h3>
-            <p className="text-gray-600">完整的文件生命周期管理解决方案</p>
-          </div>
-          
-          <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
-            <div className="bg-white rounded-xl p-6 shadow-sm border border-gray-100">
-              <h4 className="font-semibold text-lg mb-4 flex items-center">
-                <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
-                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
-                </svg>
-                核心功能
-              </h4>
-              <ul className="space-y-2 text-sm text-gray-600">
-                <li>• 支持多种文件格式上传(图片、文档、视频、音频等)</li>
-                <li>• 大文件分片上传,支持断点续传</li>
-                <li>• 文件预览功能(图片、PDF、文本等)</li>
-                <li>• 文件搜索和分类管理</li>
-                <li>• 文件分享和权限控制</li>
-              </ul>
-            </div>
-            
-            <div className="bg-white rounded-xl p-6 shadow-sm border border-gray-100">
-              <h4 className="font-semibold text-lg mb-4 flex items-center">
-                <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
-                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
-                </svg>
-                技术特性
-              </h4>
-              <ul className="space-y-2 text-sm text-gray-600">
-                <li>• RESTful API 设计,支持分页查询</li>
-                <li>• 文件元数据存储(类型、大小、描述等)</li>
-                <li>• 用户关联,支持个人文件管理</li>
-                <li>• 时间戳记录,完整的操作日志</li>
-                <li>• 响应式设计,适配多端访问</li>
-              </ul>
-            </div>
-          </div>
-        </div>
-      </main>
-      
-      {/* 页脚 */}
-      <footer className="bg-white border-t border-gray-100 py-8">
-        <div className="container mx-auto px-4">
-          <div className="text-center">
-            <p className="text-gray-600 mb-4">
-              基于 MinIO 的企业级云存储解决方案
-            </p>
-            <div className="flex justify-center space-x-6 text-sm">
-              <a href="/admin" className="text-blue-600 hover:text-blue-700 transition-colors">管理后台</a>
-              <a href="/ui" className="text-blue-600 hover:text-blue-700 transition-colors">API 文档</a>
-            </div>
-            <p className="text-gray-400 text-xs mt-4">
-              © {new Date().getFullYear()} 云存储平台. Built with React, TypeScript & MinIO
-            </p>
-          </div>
-        </div>
-      </footer>
-    </div>
-  );
-};
-
-export default HomePage;

+ 0 - 133
src/client/home/pages/LoginPage.tsx

@@ -1,133 +0,0 @@
-import React, { useState } from 'react';
-import { useForm } from 'react-hook-form';
-import { EyeIcon, EyeSlashIcon, UserIcon, LockClosedIcon } from '@heroicons/react/24/outline';
-import { useNavigate } from 'react-router-dom';
-import { useAuth } from '@/client/home/hooks/AuthProvider';
-
-const LoginPage: React.FC = () => {
-  const { register, handleSubmit, formState: { errors } } = useForm();
-  const [showPassword, setShowPassword] = useState(false);
-  const [loading, setLoading] = useState(false);
-  const { login } = useAuth();
-  const navigate = useNavigate();
-
-  const onSubmit = async (data: any) => {
-    try {
-      setLoading(true);
-      await login(data.username, data.password);
-      navigate('/');
-    } catch (error) {
-      console.error('Login error:', error);
-      alert((error as Error).message || '登录失败,请检查用户名和密码');
-    } finally {
-      setLoading(false);
-    }
-  };
-
-  return (
-    <div className="flex justify-center items-center min-h-screen bg-gray-100">
-      <div className="w-full max-w-md bg-white rounded-lg shadow-md overflow-hidden">
-        <div className="p-6 sm:p-8">
-          <div className="text-center mb-8">
-            <h2 className="text-2xl font-bold text-gray-900">网站登录</h2>
-            <p className="mt-2 text-sm text-gray-600">登录您的账号以继续</p>
-          </div>
-          
-          <form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
-            <div>
-              <label htmlFor="username" className="block text-sm font-medium text-gray-700 mb-1">
-                用户名
-              </label>
-              <div className="relative">
-                <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
-                  <UserIcon className="h-5 w-5 text-gray-400" />
-                </div>
-                <input
-                  id="username"
-                  type="text"
-                  className={`w-full pl-10 pr-3 py-2 border ${errors.username ? 'border-red-300' : 'border-gray-300'} rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
-                  placeholder="请输入用户名"
-                  {...register('username', { 
-                    required: '用户名不能为空',
-                    minLength: { value: 3, message: '用户名至少3个字符' }
-                  })}
-                />
-              </div>
-              {errors.username && (
-                <p className="mt-1 text-sm text-red-600">{errors.username.message?.toString()}</p>
-              )}
-            </div>
-            
-            <div>
-              <label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
-                密码
-              </label>
-              <div className="relative">
-                <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
-                  <LockClosedIcon className="h-5 w-5 text-gray-400" />
-                </div>
-                <input
-                  id="password"
-                  type={showPassword ? 'text' : 'password'}
-                  className={`w-full pl-10 pr-10 py-2 border ${errors.password ? 'border-red-300' : 'border-gray-300'} rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
-                  placeholder="请输入密码"
-                  {...register('password', { 
-                    required: '密码不能为空',
-                    minLength: { value: 6, message: '密码至少6个字符' }
-                  })}
-                />
-                <button 
-                  type="button"
-                  className="absolute inset-y-0 right-0 pr-3 flex items-center"
-                  onClick={() => setShowPassword(!showPassword)}
-                >
-                  {showPassword ? (
-                    <EyeSlashIcon className="h-5 w-5 text-gray-400" />
-                  ) : (
-                    <EyeIcon className="h-5 w-5 text-gray-400" />
-                  )}
-                </button>
-              </div>
-              {errors.password && (
-                <p className="mt-1 text-sm text-red-600">{errors.password.message?.toString()}</p>
-              )}
-            </div>
-            
-            <div>
-              <button
-                type="submit"
-                disabled={loading}
-                className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
-              >
-                {loading ? '登录中...' : '登录'}
-              </button>
-            </div>
-          </form>
-          
-          <div className="mt-6">
-            <div className="relative">
-              <div className="absolute inset-0 flex items-center">
-                <div className="w-full border-t border-gray-300"></div>
-              </div>
-              <div className="relative flex justify-center text-sm">
-                <span className="px-2 bg-white text-gray-500">还没有账号?</span>
-              </div>
-            </div>
-            
-            <div className="mt-4">
-              <button
-                type="button"
-                onClick={() => navigate('/register')}
-                className="w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
-              >
-                注册账号
-              </button>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-};
-
-export default LoginPage;

+ 0 - 153
src/client/home/pages/MemberPage.tsx

@@ -1,153 +0,0 @@
-import debug from 'debug';
-import React from 'react';
-import { UserIcon, PencilIcon } from '@heroicons/react/24/outline';
-import { useParams, useNavigate } from 'react-router-dom';
-import { useQuery } from '@tanstack/react-query';
-import type { InferResponseType } from 'hono/client';
-import { userClient } from '@/client/api';
-import { useAuth, User } from '@/client/home/hooks/AuthProvider';
-
-const MemberPage: React.FC = () => {
-  const navigate = useNavigate();
-  const { user, logout } = useAuth();
-
-  if (!user) {
-    return (
-      <div className="text-center py-12">
-        <h2 className="text-2xl font-bold text-gray-900 mb-4">用户不存在</h2>
-        <button
-          onClick={() => navigate('/')}
-          className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
-        >
-          返回首页
-        </button>
-      </div>
-    );
-  }
-
-  return (
-    <div className="min-h-screen bg-gray-50">
-      <div className="container mx-auto px-4 py-8 max-w-4xl">
-        {/* 用户资料卡片 */}
-        <div className="bg-white rounded-lg shadow-sm p-6 mb-8">
-          <div className="flex flex-col items-center">
-            <div className="w-24 h-24 rounded-full bg-gray-200 flex items-center justify-center mb-4">
-              {user.avatar ? (
-                <img src={user.avatar} alt={user.nickname || user.username} className="h-full w-full object-cover rounded-full" />
-              ) : (
-                <UserIcon className="h-12 w-12 text-gray-500" />
-              )}
-            </div>
-            
-            <h1 className="text-2xl font-bold text-gray-900 mb-1">{user.nickname || user.username}</h1>
-            
-            <div className="flex space-x-8 my-4">
-              <div className="text-center">
-                <p className="text-2xl font-semibold text-gray-900">0</p>
-                <p className="text-sm text-gray-500">内容</p>
-              </div>
-              <div className="text-center">
-                <p className="text-2xl font-semibold text-gray-900">0</p>
-                <p className="text-sm text-gray-500">关注</p>
-              </div>
-              <div className="text-center">
-                <p className="text-2xl font-semibold text-gray-900">0</p>
-                <p className="text-sm text-gray-500">粉丝</p>
-              </div>
-            </div>
-            
-            <div className="flex">
-              <button
-                onClick={() => navigate('/profile/edit')}
-                className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 flex items-center"
-              >
-                <PencilIcon className="w-4 h-4 mr-2" />
-                编辑资料
-              </button>
-              
-              <button
-                onClick={async () => {
-                  await logout();
-                  navigate('/');
-                }}
-                className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 ml-4"
-              >
-                退出登录
-              </button>
-
-            </div>
-            
-            {(user as any).bio && (
-              <p className="mt-4 text-center text-gray-600 max-w-lg">
-                {(user as any).bio}
-              </p>
-            )}
-            
-            <div className="flex items-center mt-4 space-x-4">
-              {(user as any).location && (
-                <div className="flex items-center text-gray-600">
-                  <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
-                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
-                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
-                  </svg>
-                  <span className="text-sm">{(user as any).location}</span>
-                </div>
-              )}
-              {(user as any).website && (
-                <a
-                  href={(user as any).website}
-                  target="_blank"
-                  rel="noopener noreferrer"
-                  className="flex items-center text-blue-600 hover:text-blue-800"
-                >
-                  <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
-                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6v6m0 0v6m0-6h-6" />
-                  </svg>
-                  <span className="text-sm truncate max-w-[150px]">{(user as any).website}</span>
-                </a>
-              )}
-            </div>
-          </div>
-        </div>
-        
-        {/* 用户内容区域 */}
-        <div className="bg-white rounded-lg shadow-sm p-6">
-          <h2 className="text-xl font-semibold mb-6">个人资料</h2>
-          
-          <div className="space-y-4">
-            <div className="border-b border-gray-100 pb-4">
-              <h3 className="text-sm font-medium text-gray-500 mb-1">用户名</h3>
-              <p className="text-gray-900">{user.username}</p>
-            </div>
-            
-            <div className="border-b border-gray-100 pb-4">
-              <h3 className="text-sm font-medium text-gray-500 mb-1">电子邮箱</h3>
-              <p className="text-gray-900">{user.email || '未设置'}</p>
-            </div>
-            
-            <div className="border-b border-gray-100 pb-4">
-              <h3 className="text-sm font-medium text-gray-500 mb-1">注册时间</h3>
-              <p className="text-gray-900">{user.createdAt ? new Date(user.createdAt).toLocaleDateString() : '未知'}</p>
-            </div>
-            
-            <div className="border-b border-gray-100 pb-4">
-              <h3 className="text-sm font-medium text-gray-500 mb-1">最后登录</h3>
-              <p className="text-gray-900">{user.updatedAt ? new Date(user.updatedAt).toLocaleString() : '从未登录'}</p>
-            </div>
-          </div>
-          
-          <div className="mt-8">
-            <button
-              onClick={() => navigate('/profile/security')}
-              className="w-full py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
-            >
-              安全设置
-            </button>
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-};
-
-export default MemberPage;

+ 0 - 190
src/client/home/pages/RegisterPage.tsx

@@ -1,190 +0,0 @@
-import React, { useState } from 'react';
-import { useForm } from 'react-hook-form';
-import { EyeIcon, EyeSlashIcon, UserIcon, LockClosedIcon } from '@heroicons/react/24/outline';
-import { useNavigate } from 'react-router-dom';
-import { useAuth } from '@/client/home/hooks/AuthProvider';
-import { authClient } from '@/client/api';
-
-const RegisterPage: React.FC = () => {
-  const { register, handleSubmit, watch, formState: { errors } } = useForm();
-  const [showPassword, setShowPassword] = useState(false);
-  const [showConfirmPassword, setShowConfirmPassword] = useState(false);
-  const [loading, setLoading] = useState(false);
-  const { login } = useAuth();
-  const navigate = useNavigate();
-  const password = watch('password', '');
-
-  const onSubmit = async (data: any) => {
-    try {
-      setLoading(true);
-      
-      // 调用注册API
-      const response = await authClient.register.$post({
-        json: {
-          username: data.username,
-          password: data.password,
-        }
-      });
-      
-      if (response.status !== 201) {
-        const result = await response.json();
-        throw new Error(result.message || '注册失败');
-      }
-      
-      // 注册成功后自动登录
-      await login(data.username, data.password);
-      
-      // 跳转到首页
-      navigate('/');
-    } catch (error) {
-      console.error('Registration error:', error);
-      alert((error as Error).message || '注册失败,请稍后重试');
-    } finally {
-      setLoading(false);
-    }
-  };
-
-  return (
-    <div className="flex justify-center items-center min-h-screen bg-gray-100">
-      <div className="w-full max-w-md bg-white rounded-lg shadow-md overflow-hidden">
-        <div className="p-6 sm:p-8">
-          <div className="text-center mb-8">
-            <h2 className="text-2xl font-bold text-gray-900">账号注册</h2>
-            <p className="mt-2 text-sm text-gray-600">创建新账号以开始使用</p>
-          </div>
-          
-          <form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
-            <div>
-              <label htmlFor="username" className="block text-sm font-medium text-gray-700 mb-1">
-                用户名
-              </label>
-              <div className="relative">
-                <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
-                  <UserIcon className="h-5 w-5 text-gray-400" />
-                </div>
-                <input
-                  id="username"
-                  type="text"
-                  className={`w-full pl-10 pr-3 py-2 border ${errors.username ? 'border-red-300' : 'border-gray-300'} rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
-                  placeholder="请输入用户名"
-                  {...register('username', { 
-                    required: '用户名不能为空',
-                    minLength: { value: 3, message: '用户名至少3个字符' },
-                    maxLength: { value: 20, message: '用户名不能超过20个字符' }
-                  })}
-                />
-              </div>
-              {errors.username && (
-                <p className="mt-1 text-sm text-red-600">{errors.username.message?.toString()}</p>
-              )}
-            </div>
-            
-            <div>
-              <label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
-                密码
-              </label>
-              <div className="relative">
-                <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
-                  <LockClosedIcon className="h-5 w-5 text-gray-400" />
-                </div>
-                <input
-                  id="password"
-                  type={showPassword ? 'text' : 'password'}
-                  className={`w-full pl-10 pr-10 py-2 border ${errors.password ? 'border-red-300' : 'border-gray-300'} rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
-                  placeholder="请输入密码"
-                  {...register('password', { 
-                    required: '密码不能为空',
-                    minLength: { value: 6, message: '密码至少6个字符' },
-                    maxLength: { value: 30, message: '密码不能超过30个字符' }
-                  })}
-                />
-                <button 
-                  type="button"
-                  className="absolute inset-y-0 right-0 pr-3 flex items-center"
-                  onClick={() => setShowPassword(!showPassword)}
-                >
-                  {showPassword ? (
-                    <EyeSlashIcon className="h-5 w-5 text-gray-400" />
-                  ) : (
-                    <EyeIcon className="h-5 w-5 text-gray-400" />
-                  )}
-                </button>
-              </div>
-              {errors.password && (
-                <p className="mt-1 text-sm text-red-600">{errors.password.message?.toString()}</p>
-              )}
-            </div>
-            
-            <div>
-              <label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
-                确认密码
-              </label>
-              <div className="relative">
-                <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
-                  <LockClosedIcon className="h-5 w-5 text-gray-400" />
-                </div>
-                <input
-                  id="confirmPassword"
-                  type={showConfirmPassword ? 'text' : 'password'}
-                  className={`w-full pl-10 pr-10 py-2 border ${errors.confirmPassword ? 'border-red-300' : 'border-gray-300'} rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
-                  placeholder="请再次输入密码"
-                  {...register('confirmPassword', { 
-                    required: '请确认密码',
-                    validate: value => value === password || '两次密码输入不一致'
-                  })}
-                />
-                <button 
-                  type="button"
-                  className="absolute inset-y-0 right-0 pr-3 flex items-center"
-                  onClick={() => setShowConfirmPassword(!showConfirmPassword)}
-                >
-                  {showConfirmPassword ? (
-                    <EyeSlashIcon className="h-5 w-5 text-gray-400" />
-                  ) : (
-                    <EyeIcon className="h-5 w-5 text-gray-400" />
-                  )}
-                </button>
-              </div>
-              {errors.confirmPassword && (
-                <p className="mt-1 text-sm text-red-600">{errors.confirmPassword.message?.toString()}</p>
-              )}
-            </div>
-            
-            <div>
-              <button
-                type="submit"
-                disabled={loading}
-                className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
-              >
-                {loading ? '注册中...' : '注册'}
-              </button>
-            </div>
-          </form>
-          
-          <div className="mt-6">
-            <div className="relative">
-              <div className="absolute inset-0 flex items-center">
-                <div className="w-full border-t border-gray-300"></div>
-              </div>
-              <div className="relative flex justify-center text-sm">
-                <span className="px-2 bg-white text-gray-500">已有账号?</span>
-              </div>
-            </div>
-            
-            <div className="mt-4">
-              <button
-                type="button"
-                onClick={() => navigate('/login')}
-                className="w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
-              >
-                返回登录
-              </button>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-};
-
-export default RegisterPage;

+ 0 - 49
src/client/home/routes.tsx

@@ -1,49 +0,0 @@
-import React from 'react';
-import { createBrowserRouter, Navigate } from 'react-router';
-import { ProtectedRoute } from './components/ProtectedRoute';
-import { ErrorPage } from './components/ErrorPage';
-import { NotFoundPage } from './components/NotFoundPage';
-import HomePage from './pages/HomePage';
-import { MainLayout } from './layouts/MainLayout';
-import LoginPage from './pages/LoginPage';
-import RegisterPage from './pages/RegisterPage';
-import MemberPage from './pages/MemberPage';
-
-export const router = createBrowserRouter([
-  {
-    path: '/',
-    element: <HomePage />
-  },
-  {
-    path: '/login',
-    element: <LoginPage />
-  },
-  {
-    path: '/register',
-    element: <RegisterPage />
-  },
-  {
-    path: '/member',
-    element: (
-      <ProtectedRoute>
-        <MainLayout />
-      </ProtectedRoute>
-    ),
-    children: [
-      {
-        path: '',
-        element: <MemberPage />
-      },
-      {
-        path: '*',
-        element: <NotFoundPage />,
-        errorElement: <ErrorPage />
-      },
-    ],
-  },
-  {
-    path: '*',
-    element: <NotFoundPage />,
-    errorElement: <ErrorPage />
-  },
-]);