فهرست منبع

✨ feat(home): 支持未登录用户访问首页

- 重构客户端路由逻辑,默认路由指向mobile/index
- 移除首页和搜索接口的authMiddleware认证中间件
- 修改useHomeData和useSearch hooks的enabled条件,允许未登录访问
- 为未登录用户设置默认空用户统计数据
- 优化userId获取逻辑,未登录时设为undefined

♻️ refactor(client): 简化路由判断逻辑

- 精简客户端路由条件判断代码
- 移除默认首页重定向逻辑
yourname 8 ماه پیش
والد
کامیت
336f151575

+ 0 - 124
src/client/admin-shadcn/components/DataTablePagination.tsx

@@ -1,124 +0,0 @@
-import React from 'react';
-import {
-  Pagination,
-  PaginationContent,
-  PaginationEllipsis,
-  PaginationItem,
-  PaginationLink,
-  PaginationNext,
-  PaginationPrevious,
-} from '@/client/components/ui/pagination';
-
-interface DataTablePaginationProps {
-  currentPage: number;
-  totalCount: number;
-  pageSize: number;
-  onPageChange: (page: number, pageSize: number) => void;
-}
-
-export const DataTablePagination: React.FC<DataTablePaginationProps> = ({
-  currentPage,
-  totalCount,
-  pageSize,
-  onPageChange,
-}) => {
-  const totalPages = Math.ceil(totalCount / pageSize);
-
-  const getPageNumbers = () => {
-    const pages = [];
-    
-    if (totalPages <= 7) {
-      // 如果总页数小于等于7,显示所有页码
-      for (let i = 1; i <= totalPages; i++) {
-        pages.push(i);
-      }
-    } else {
-      // 显示当前页附近的页码
-      const startPage = Math.max(1, currentPage - 2);
-      const endPage = Math.min(totalPages, currentPage + 2);
-      
-      // 始终显示第一页
-      pages.push(1);
-      
-      // 添加省略号和中间页码
-      if (startPage > 2) {
-        pages.push('...');
-      }
-      
-      for (let i = Math.max(2, startPage); i <= Math.min(totalPages - 1, endPage); i++) {
-        pages.push(i);
-      }
-      
-      if (endPage < totalPages - 1) {
-        pages.push('...');
-      }
-      
-      // 始终显示最后一页
-      pages.push(totalPages);
-    }
-    
-    return pages;
-  };
-
-  const pageNumbers = getPageNumbers();
-
-  return (
-    <div className="flex justify-between items-center mt-4">
-      <Pagination>
-        <PaginationContent>
-          <PaginationItem>
-            <PaginationPrevious
-              href="#"
-              onClick={(e) => {
-                e.preventDefault();
-                if (currentPage > 1) {
-                  onPageChange(currentPage - 1, pageSize);
-                }
-              }}
-              aria-disabled={currentPage <= 1}
-              className={currentPage <= 1 ? "pointer-events-none opacity-50" : ""}
-            />
-          </PaginationItem>
-          
-          {pageNumbers.map((page, index) => {
-            if (page === '...') {
-              return (
-                <PaginationItem key={`ellipsis-${index}`}>
-                  <PaginationEllipsis />
-                </PaginationItem>
-              );
-            }
-            return (
-              <PaginationItem key={page}>
-                <PaginationLink
-                  href="#"
-                  isActive={page === currentPage}
-                  onClick={(e) => {
-                    e.preventDefault();
-                    onPageChange(page as number, pageSize);
-                  }}
-                >
-                  {page}
-                </PaginationLink>
-              </PaginationItem>
-            );
-          })}
-          
-          <PaginationItem>
-            <PaginationNext
-              href="#"
-              onClick={(e) => {
-                e.preventDefault();
-                if (currentPage < totalPages) {
-                  onPageChange(currentPage + 1, pageSize);
-                }
-              }}
-              aria-disabled={currentPage >= totalPages}
-              className={currentPage >= totalPages ? "pointer-events-none opacity-50" : ""}
-            />
-          </PaginationItem>
-        </PaginationContent>
-      </Pagination>
-    </div>
-  );
-};

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

@@ -1,43 +0,0 @@
-import React from 'react';
-import { useRouteError, useNavigate } from 'react-router';
-import { Alert, AlertDescription, AlertTitle } from '@/client/components/ui/alert';
-import { Button } from '@/client/components/ui/button';
-import { AlertCircle } from 'lucide-react';
-
-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 variant="destructive" className="mb-4">
-          <AlertCircle className="h-4 w-4" />
-          <AlertTitle>{error?.message || '未知错误'}</AlertTitle>
-          <AlertDescription>
-            {error?.stack ? (
-              <pre className="text-xs overflow-auto p-2 bg-muted rounded mt-2">
-                {error.stack}
-              </pre>
-            ) : null}
-          </AlertDescription>
-        </Alert>
-        <div className="flex gap-4">
-          <Button 
-            onClick={() => navigate(0)}
-          >
-            重新加载
-          </Button>
-          <Button 
-            variant="outline"
-            onClick={() => navigate('/admin')}
-          >
-            返回首页
-          </Button>
-        </div>
-      </div>
-    </div>
-  );
-};

+ 0 - 25
src/client/admin-shadcn/components/NotFoundPage.tsx

@@ -1,25 +0,0 @@
-import React from 'react';
-import { useNavigate } from 'react-router';
-import { Button } from '@/client/components/ui/button';
-
-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-muted-foreground">
-          您访问的页面不存在或已被移除
-        </p>
-        <div className="flex gap-4">
-          <Button 
-            onClick={() => navigate('/admin')}
-          >
-            返回首页
-          </Button>
-        </div>
-      </div>
-    </div>
-  );
-};

+ 0 - 38
src/client/admin-shadcn/components/ProtectedRoute.tsx

@@ -1,38 +0,0 @@
-import React, { useEffect } from 'react';
-import { useNavigate } from 'react-router';
-import { useAuth } from '../hooks/AuthProvider';
-import { Skeleton } from '@/client/components/ui/skeleton';
-
-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="space-y-2">
-          <Skeleton className="h-12 w-12 rounded-full" />
-          <div className="space-y-2">
-            <Skeleton className="h-4 w-[250px]" />
-            <Skeleton className="h-4 w-[200px]" />
-          </div>
-        </div>
-      </div>
-    );
-  }
-  
-  // 如果未认证且不再加载中,不显示任何内容(等待重定向)
-  if (!isAuthenticated) {
-    return null;
-  }
-  
-  return children;
-};

+ 0 - 140
src/client/admin-shadcn/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 - 53
src/client/admin-shadcn/index.tsx

@@ -1,53 +0,0 @@
-import { createRoot } from 'react-dom/client'
-import { RouterProvider } from 'react-router';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { Toaster } from '@/client/components/ui/sonner';
-import dayjs from 'dayjs';
-import weekday from 'dayjs/plugin/weekday';
-import localeData from 'dayjs/plugin/localeData';
-import 'dayjs/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({
-  defaultOptions: {
-    queries: {
-      retry: 1,
-      refetchOnWindowFocus: false,
-    },
-  },
-});
-
-// 应用入口组件
-const App = () => {
-  return (
-    <QueryClientProvider client={queryClient}>
-      <AuthProvider>
-        <RouterProvider router={router} />
-        <Toaster 
-          position="top-right"
-          expand={false}
-          richColors
-          closeButton
-        />
-      </AuthProvider>
-    </QueryClientProvider>
-  )
-};
-
-const rootElement = document.getElementById('root')
-if (rootElement) {
-  const root = createRoot(rootElement)
-  root.render(
-    <App />
-  )
-}

+ 0 - 256
src/client/admin-shadcn/layouts/MainLayout.tsx

@@ -1,256 +0,0 @@
-import { useState, useEffect, useMemo } from 'react';
-import {
-  Outlet,
-  useLocation,
-} from 'react-router';
-import {
-  Bell,
-  Menu,
-  User,
-  ChevronDown
-} from 'lucide-react';
-import { useAuth } from '../hooks/AuthProvider';
-import { useMenu, type MenuItem } from '../menu';
-import { getGlobalConfig } from '@/client/utils/utils';
-import { Button } from '@/client/components/ui/button';
-import { Input } from '@/client/components/ui/input';
-import { Avatar, AvatarFallback, AvatarImage } from '@/client/components/ui/avatar';
-import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/client/components/ui/dropdown-menu';
-import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/client/components/ui/sheet';
-import { ScrollArea } from '@/client/components/ui/scroll-area';
-import { cn } from '@/client/lib/utils';
-import { Badge } from '@/client/components/ui/badge';
-/**
- * 主布局组件
- * 包含侧边栏、顶部导航和内容区域
- */
-export const MainLayout = () => {
-  const { user } = useAuth();
-  const [showBackTop, setShowBackTop] = useState(false);
-  const location = useLocation();
-  const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
-  
-  // 使用菜单hook
-  const {
-    menuItems,
-    userMenuItems,
-    collapsed,
-    setCollapsed,
-    handleMenuClick
-  } = useMenu();
-  
-  // 获取当前选中的菜单项
-  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';
-  
-
-  // 侧边栏内容
-  const SidebarContent = () => (
-    <div className="flex h-full flex-col">
-      <div className="p-4 border-b">
-        <h2 className="text-lg font-semibold truncate">
-          {collapsed ? '应用' : appName}
-        </h2>
-        {!collapsed && (
-          <div className="mt-4">
-            <Input
-              placeholder="搜索菜单..."
-              className="h-8"
-            />
-          </div>
-        )}
-      </div>
-      
-      <ScrollArea className="flex-1">
-        <nav className="p-2">
-          {menuItems.map((item) => (
-            <div key={item.key}>
-              <Button
-                variant={selectedKey === item.key ? "default" : "ghost"}
-                className={cn(
-                  "w-full justify-start mb-1",
-                  selectedKey === item.key && "bg-primary text-primary-foreground"
-                )}
-                onClick={() => {
-                  handleMenuClick(item);
-                  setIsMobileMenuOpen(false);
-                }}
-              >
-                {item.icon}
-                {!collapsed && <span className="ml-2">{item.label}</span>}
-              </Button>
-              
-              {item.children && !collapsed && (
-                <div className="ml-4">
-                  {item.children.map((child) => (
-                    <Button
-                      key={child.key}
-                      variant={selectedKey === child.key ? "default" : "ghost"}
-                      className={cn(
-                        "w-full justify-start mb-1 text-sm",
-                        selectedKey === child.key && "bg-primary text-primary-foreground"
-                      )}
-                      onClick={() => {
-                        handleMenuClick(child);
-                        setIsMobileMenuOpen(false);
-                      }}
-                    >
-                      {child.icon && <span className="ml-2">{child.icon}</span>}
-                      <span className={child.icon ? "ml-2" : "ml-6"}>{child.label}</span>
-                    </Button>
-                  ))}
-                </div>
-              )}
-            </div>
-          ))}
-        </nav>
-      </ScrollArea>
-    </div>
-  );
-
-  return (
-    <div className="flex h-screen bg-background">
-      {/* Desktop Sidebar */}
-      <aside className={cn(
-        "hidden md:block border-r bg-background transition-all duration-200",
-        collapsed ? "w-16" : "w-64"
-      )}>
-        <SidebarContent />
-      </aside>
-
-      {/* Mobile Sidebar */}
-      <Sheet open={isMobileMenuOpen} onOpenChange={setIsMobileMenuOpen}>
-        <SheetContent side="left" className="w-64 p-0">
-          <SheetHeader className="p-4">
-            <SheetTitle>{appName}</SheetTitle>
-          </SheetHeader>
-          <SidebarContent />
-        </SheetContent>
-      </Sheet>
-
-      <div className="flex-1 flex flex-col overflow-hidden">
-        {/* Header */}
-        <header className="flex h-16 items-center justify-between border-b bg-background px-4">
-          <div className="flex items-center gap-2">
-            <Button
-              variant="ghost"
-              size="icon"
-              className="md:hidden"
-              onClick={() => setIsMobileMenuOpen(true)}
-            >
-              <Menu className="h-4 w-4" />
-            </Button>
-            <Button
-              variant="ghost"
-              size="icon"
-              className="hidden md:block"
-              onClick={() => setCollapsed(!collapsed)}
-            >
-              <Menu className="h-4 w-4" />
-            </Button>
-          </div>
-
-          <div className="flex items-center gap-4">
-            <Button variant="ghost" size="icon" className="relative">
-              <Bell className="h-4 w-4" />
-              <Badge className="absolute -top-1 -right-1 h-5 w-5 flex items-center justify-center text-xs">
-                5
-              </Badge>
-            </Button>
-
-            <DropdownMenu>
-              <DropdownMenuTrigger asChild>
-                <Button variant="ghost" className="relative h-8 w-8 rounded-full">
-                  <Avatar className="h-8 w-8">
-                    <AvatarImage
-                      src={user?.avatar || 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?q=80&w=40&auto=format&fit=crop'}
-                      alt={user?.username || 'User'}
-                    />
-                    <AvatarFallback>
-                      <User className="h-4 w-4" />
-                    </AvatarFallback>
-                  </Avatar>
-                </Button>
-              </DropdownMenuTrigger>
-              <DropdownMenuContent className="w-56" align="end" forceMount>
-                <DropdownMenuLabel className="font-normal">
-                  <div className="flex flex-col space-y-1">
-                    <p className="text-sm font-medium leading-none">
-                      {user?.nickname || user?.username}
-                    </p>
-                    <p className="text-xs leading-none text-muted-foreground">
-                      {user?.email}
-                    </p>
-                  </div>
-                </DropdownMenuLabel>
-                <DropdownMenuSeparator />
-                {userMenuItems.map((item) => (
-                  item.type === 'separator' ? (
-                    <DropdownMenuSeparator key={item.key} />
-                  ) : (
-                    <DropdownMenuItem key={item.key} onClick={item.onClick}>
-                      {item.icon && item.icon}
-                      <span>{item.label}</span>
-                    </DropdownMenuItem>
-                  )
-                ))}
-              </DropdownMenuContent>
-            </DropdownMenu>
-          </div>
-        </header>
-
-        {/* Main Content */}
-        <main className="flex-1 overflow-auto p-4">
-          <div className="max-w-7xl mx-auto">
-            <Outlet />
-          </div>
-          
-          {/* Back to top button */}
-          {showBackTop && (
-            <Button
-              size="icon"
-              className="fixed bottom-4 right-4 rounded-full shadow-lg"
-              onClick={scrollToTop}
-            >
-              <ChevronDown className="h-4 w-4 rotate-180" />
-            </Button>
-          )}
-        </main>
-      </div>
-    </div>
-  );
-};

+ 0 - 148
src/client/admin-shadcn/menu.tsx

@@ -1,148 +0,0 @@
-import React from 'react';
-import { useNavigate } from 'react-router';
-import { useAuth } from './hooks/AuthProvider';
-import {
-  Users,
-  Settings,
-  User,
-  LogOut,
-  BarChart3,
-  LayoutDashboard
-} from 'lucide-react';
-
-export interface MenuItem {
-  key: string;
-  label: string;
-  icon?: React.ReactNode;
-  children?: MenuItem[];
-  path?: string;
-  permission?: string;
-  onClick?: () => void;
-}
-
-/**
- * 菜单搜索 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 menuItems: MenuItem[] = [
-    {
-      key: 'dashboard',
-      label: '控制台',
-      icon: <LayoutDashboard className="h-4 w-4" />,
-      path: '/admin/dashboard'
-    },
-    {
-      key: 'users',
-      label: '用户管理',
-      icon: <Users className="h-4 w-4" />,
-      path: '/admin/users',
-      permission: 'user:manage'
-    },
-    {
-      key: 'analytics',
-      label: '数据分析',
-      icon: <BarChart3 className="h-4 w-4" />,
-      path: '/admin/analytics',
-      permission: 'analytics:view'
-    },
-    {
-      key: 'settings',
-      label: '系统设置',
-      icon: <Settings className="h-4 w-4" />,
-      path: '/admin/settings',
-      permission: 'settings:manage'
-    },
-  ];
-
-  // 用户菜单项
-  const userMenuItems = [
-    {
-      key: 'profile',
-      label: '个人资料',
-      icon: <User className="mr-2 h-4 w-4" />,
-      onClick: () => navigate('/admin/profile')
-    },
-    {
-      key: 'settings',
-      label: '账户设置',
-      icon: <Settings className="mr-2 h-4 w-4" />,
-      onClick: () => navigate('/admin/account-settings')
-    },
-    {
-      type: 'separator',
-      key: 'divider',
-    },
-    {
-      key: 'logout',
-      label: '退出登录',
-      icon: <LogOut className="mr-2 h-4 w-4" />,
-      onClick: () => handleLogout()
-    }
-  ];
-
-  // 处理菜单点击
-  const handleMenuClick = (item: MenuItem) => {
-    if (item.path) {
-      navigate(item.path);
-    }
-    if (item.onClick) {
-      item.onClick();
-    }
-  };
-
-  return {
-    menuItems,
-    userMenuItems,
-    collapsed,
-    setCollapsed,
-    handleMenuClick,
-  };
-};

+ 0 - 237
src/client/admin-shadcn/pages/Dashboard.tsx

@@ -1,237 +0,0 @@
-import React from 'react';
-import { Users, Bell, Eye, TrendingUp, TrendingDown, Activity } from 'lucide-react';
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
-import { Badge } from '@/client/components/ui/badge';
-import { Progress } from '@/client/components/ui/progress';
-
-// 仪表盘页面
-export const DashboardPage = () => {
-  const stats = [
-    {
-      title: '活跃用户',
-      value: '112,893',
-      icon: Users,
-      color: 'text-blue-500',
-      bgColor: 'bg-blue-50',
-      trend: 12.5,
-      trendDirection: 'up',
-      description: '较昨日增长 12.5%',
-    },
-    {
-      title: '系统消息',
-      value: '93',
-      icon: Bell,
-      color: 'text-yellow-500',
-      bgColor: 'bg-yellow-50',
-      trend: 0,
-      trendDirection: 'neutral',
-      description: '其中 5 条未读',
-    },
-    {
-      title: '在线用户',
-      value: '1,128',
-      icon: Eye,
-      color: 'text-purple-500',
-      bgColor: 'bg-purple-50',
-      trend: 32.1,
-      trendDirection: 'up',
-      description: '当前在线率 32.1%',
-    },
-  ];
-
-  const recentActivities = [
-    {
-      id: 1,
-      user: '张三',
-      action: '登录系统',
-      time: '2分钟前',
-      status: 'success',
-    },
-    {
-      id: 2,
-      user: '李四',
-      action: '创建了新用户',
-      time: '5分钟前',
-      status: 'info',
-    },
-    {
-      id: 3,
-      user: '王五',
-      action: '删除了用户',
-      time: '10分钟前',
-      status: 'warning',
-    },
-    {
-      id: 4,
-      user: '赵六',
-      action: '修改了配置',
-      time: '15分钟前',
-      status: 'info',
-    },
-  ];
-
-  const systemMetrics = [
-    {
-      name: 'CPU使用率',
-      value: 65,
-      color: 'bg-green-500',
-    },
-    {
-      name: '内存使用率',
-      value: 78,
-      color: 'bg-blue-500',
-    },
-    {
-      name: '磁盘使用率',
-      value: 45,
-      color: 'bg-purple-500',
-    },
-    {
-      name: '网络使用率',
-      value: 32,
-      color: 'bg-orange-500',
-    },
-  ];
-
-  return (
-    <div className="space-y-6">
-      <div>
-        <h1 className="text-3xl font-bold tracking-tight">仪表盘</h1>
-        <p className="text-muted-foreground">
-          欢迎回来!这里是系统概览和关键指标。
-        </p>
-      </div>
-
-      {/* 统计卡片 */}
-      <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
-        {stats.map((stat, index) => {
-          const Icon = stat.icon;
-          return (
-            <Card key={index} className="transition-all duration-300 hover:shadow-lg">
-              <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
-                <CardTitle className="text-sm font-medium">
-                  {stat.title}
-                </CardTitle>
-                <div className={`p-2 rounded-full ${stat.bgColor}`}>
-                  <Icon className={`h-4 w-4 ${stat.color}`} />
-                </div>
-              </CardHeader>
-              <CardContent>
-                <div className="text-2xl font-bold">{stat.value}</div>
-                <div className="flex items-center space-x-2">
-                  {stat.trendDirection === 'up' && (
-                    <TrendingUp className="h-4 w-4 text-green-500" />
-                  )}
-                  {stat.trendDirection === 'down' && (
-                    <TrendingDown className="h-4 w-4 text-red-500" />
-                  )}
-                  <p className="text-xs text-muted-foreground">{stat.description}</p>
-                </div>
-              </CardContent>
-            </Card>
-          );
-        })}
-      </div>
-
-      <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
-        {/* 系统性能 */}
-        <Card className="lg:col-span-4">
-          <CardHeader>
-            <CardTitle>系统性能</CardTitle>
-            <CardDescription>
-              当前系统各项资源的使用情况
-            </CardDescription>
-          </CardHeader>
-          <CardContent>
-            <div className="space-y-4">
-              {systemMetrics.map((metric, index) => (
-                <div key={index} className="space-y-2">
-                  <div className="flex justify-between text-sm">
-                    <span className="font-medium">{metric.name}</span>
-                    <span className="text-muted-foreground">{metric.value}%</span>
-                  </div>
-                  <Progress value={metric.value} className="h-2" />
-                </div>
-              ))}
-            </div>
-          </CardContent>
-        </Card>
-
-        {/* 最近活动 */}
-        <Card className="lg:col-span-3">
-          <CardHeader>
-            <CardTitle>最近活动</CardTitle>
-            <CardDescription>
-              系统最新操作记录
-            </CardDescription>
-          </CardHeader>
-          <CardContent>
-            <div className="space-y-4">
-              {recentActivities.map((activity) => (
-                <div key={activity.id} className="flex items-center space-x-4">
-                  <div className="flex-shrink-0">
-                    <div className={`p-2 rounded-full ${
-                      activity.status === 'success' ? 'bg-green-100' :
-                      activity.status === 'warning' ? 'bg-yellow-100' : 'bg-blue-100'
-                    }`}>
-                      <Activity className={`h-4 w-4 ${
-                        activity.status === 'success' ? 'text-green-600' :
-                        activity.status === 'warning' ? 'text-yellow-600' : 'text-blue-600'
-                      }`} />
-                    </div>
-                  </div>
-                  <div className="flex-1 space-y-1">
-                    <p className="text-sm font-medium">
-                      {activity.user} {activity.action}
-                    </p>
-                    <p className="text-sm text-muted-foreground">
-                      {activity.time}
-                    </p>
-                  </div>
-                </div>
-              ))}
-            </div>
-          </CardContent>
-        </Card>
-      </div>
-
-      {/* 快捷操作 */}
-      <Card>
-        <CardHeader>
-          <CardTitle>快捷操作</CardTitle>
-          <CardDescription>
-            常用的管理功能
-          </CardDescription>
-        </CardHeader>
-        <CardContent>
-          <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
-            <Card className="hover:shadow-md transition-all cursor-pointer">
-              <CardHeader className="pb-3">
-                <CardTitle className="text-base">用户管理</CardTitle>
-                <CardDescription>查看和管理所有用户</CardDescription>
-              </CardHeader>
-            </Card>
-            <Card className="hover:shadow-md transition-all cursor-pointer">
-              <CardHeader className="pb-3">
-                <CardTitle className="text-base">系统设置</CardTitle>
-                <CardDescription>配置系统参数</CardDescription>
-              </CardHeader>
-            </Card>
-            <Card className="hover:shadow-md transition-all cursor-pointer">
-              <CardHeader className="pb-3">
-                <CardTitle className="text-base">数据备份</CardTitle>
-                <CardDescription>执行数据备份操作</CardDescription>
-              </CardHeader>
-            </Card>
-            <Card className="hover:shadow-md transition-all cursor-pointer">
-              <CardHeader className="pb-3">
-                <CardTitle className="text-base">日志查看</CardTitle>
-                <CardDescription>查看系统日志</CardDescription>
-              </CardHeader>
-            </Card>
-          </div>
-        </CardContent>
-      </Card>
-    </div>
-  );
-};

+ 0 - 178
src/client/admin-shadcn/pages/Login.tsx

@@ -1,178 +0,0 @@
-import { useState } from 'react';
-import { useNavigate } from 'react-router';
-import { useAuth } from '../hooks/AuthProvider';
-import { useForm } from 'react-hook-form';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { z } from 'zod';
-import { toast } from 'sonner';
-import { Eye, EyeOff, User, Lock } from 'lucide-react';
-import { Button } from '@/client/components/ui/button';
-import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/client/components/ui/card';
-import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
-import { Input } from '@/client/components/ui/input';
-
-// 表单验证Schema
-const loginSchema = z.object({
-  username: z.string().min(1, '请输入用户名'),
-  password: z.string().min(1, '请输入密码'),
-});
-
-type LoginFormData = z.infer<typeof loginSchema>;
-
-// 登录页面
-export const LoginPage = () => {
-  const { login } = useAuth();
-  const [isLoading, setIsLoading] = useState(false);
-  const [showPassword, setShowPassword] = useState(false);
-  const navigate = useNavigate();
-  
-  const form = useForm<LoginFormData>({
-    resolver: zodResolver(loginSchema),
-    defaultValues: {
-      username: '',
-      password: '',
-    },
-  });
-
-  const handleSubmit = async (data: LoginFormData) => {
-    try {
-      setIsLoading(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(data.username, data.password, latitude, longitude);
-      // 登录成功后跳转到管理后台首页
-      navigate('/admin/dashboard');
-      toast.success('登录成功!欢迎回来');
-    } catch (error: any) {
-      toast.error(error instanceof Error ? error.message : '登录失败');
-    } finally {
-      setIsLoading(false);
-    }
-  };
-  
-  return (
-    <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-50 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="mx-auto w-16 h-16 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-full flex items-center justify-center shadow-lg">
-            <User className="h-8 w-8 text-white" />
-          </div>
-          <h2 className="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">
-            管理后台登录
-          </h2>
-          <p className="mt-2 text-center text-sm text-gray-600">
-            请输入您的账号和密码继续操作
-          </p>
-        </div>
-        
-        <Card className="shadow-xl border-0">
-          <CardHeader className="text-center">
-            <CardTitle className="text-2xl">欢迎登录</CardTitle>
-            <CardDescription>
-              使用您的账户信息登录系统
-            </CardDescription>
-          </CardHeader>
-          <CardContent>
-            <Form {...form}>
-              <form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
-                <FormField
-                  control={form.control}
-                  name="username"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>用户名</FormLabel>
-                      <FormControl>
-                        <div className="relative">
-                          <User className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
-                          <Input
-                            placeholder="请输入用户名"
-                            className="pl-10"
-                            {...field}
-                          />
-                        </div>
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-                
-                <FormField
-                  control={form.control}
-                  name="password"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>密码</FormLabel>
-                      <FormControl>
-                        <div className="relative">
-                          <Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
-                          <Input
-                            type={showPassword ? 'text' : 'password'}
-                            placeholder="请输入密码"
-                            className="pl-10 pr-10"
-                            {...field}
-                          />
-                          <button
-                            type="button"
-                            className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
-                            onClick={() => setShowPassword(!showPassword)}
-                          >
-                            {showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
-                          </button>
-                        </div>
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-                
-                <Button
-                  type="submit"
-                  className="w-full"
-                  disabled={isLoading}
-                >
-                  {isLoading ? (
-                    <>
-                      <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
-                      登录中...
-                    </>
-                  ) : (
-                    '登录'
-                  )}
-                </Button>
-              </form>
-            </Form>
-          </CardContent>
-          <CardFooter className="flex flex-col items-center space-y-2">
-            <div className="text-sm text-gray-500">
-              测试账号: <span className="font-medium text-gray-700">admin</span> / <span className="font-medium text-gray-700">admin123</span>
-            </div>
-            <div className="text-xs text-gray-400">
-              © {new Date().getFullYear()} 管理系统. 保留所有权利.
-            </div>
-          </CardFooter>
-        </Card>
-        
-        <div className="text-center">
-          <p className="text-sm text-gray-500">
-            遇到问题?<a href="#" className="font-medium text-indigo-600 hover:text-indigo-500">联系管理员</a>
-          </p>
-        </div>
-      </div>
-    </div>
-  );
-};

+ 0 - 619
src/client/admin-shadcn/pages/Users.tsx

@@ -1,619 +0,0 @@
-import React, { useState } from 'react';
-import { useQuery } from '@tanstack/react-query';
-import { format } from 'date-fns';
-import { Plus, Search, Edit, Trash2 } from 'lucide-react';
-import { userClient } from '@/client/api';
-import type { InferRequestType, InferResponseType } from 'hono/client';
-import { Button } from '@/client/components/ui/button';
-import { Input } from '@/client/components/ui/input';
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
-import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/client/components/ui/table';
-import { Badge } from '@/client/components/ui/badge';
-import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/client/components/ui/dialog';
-import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
-import { DataTablePagination } from '@/client/admin-shadcn/components/DataTablePagination';
-import { useForm } from 'react-hook-form';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { toast } from 'sonner';
-import { Skeleton } from '@/client/components/ui/skeleton';
-import { Switch } from '@/client/components/ui/switch';
-import { DisabledStatus } from '@/share/types';
-import { CreateUserDto, UpdateUserDto } from '@/server/modules/users/user.schema';
-
-// 使用RPC方式提取类型
-type CreateUserRequest = InferRequestType<typeof userClient.$post>['json'];
-type UpdateUserRequest = InferRequestType<typeof userClient[':id']['$put']>['json'];
-type UserResponse = InferResponseType<typeof userClient.$get, 200>['data'][0];
-
-// 直接使用后端定义的 schema
-const createUserFormSchema = CreateUserDto;
-const updateUserFormSchema = UpdateUserDto;
-
-type CreateUserFormData = CreateUserRequest;
-type UpdateUserFormData = UpdateUserRequest;
-
-export const UsersPage = () => {
-  const [searchParams, setSearchParams] = useState({
-    page: 1,
-    limit: 10,
-    search: ''
-  });
-  const [isModalOpen, setIsModalOpen] = useState(false);
-  const [editingUser, setEditingUser] = useState<any>(null);
-  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
-  const [userToDelete, setUserToDelete] = useState<number | null>(null);
-
-  const [isCreateForm, setIsCreateForm] = useState(true);
-  
-  const createForm = useForm<CreateUserFormData>({
-    resolver: zodResolver(createUserFormSchema),
-    defaultValues: {
-      username: '',
-      nickname: undefined,
-      email: null,
-      phone: null,
-      name: null,
-      password: '',
-      isDisabled: DisabledStatus.ENABLED,
-    },
-  });
-
-  const updateForm = useForm<UpdateUserFormData>({
-    resolver: zodResolver(updateUserFormSchema),
-    defaultValues: {
-      username: undefined,
-      nickname: undefined,
-      email: null,
-      phone: null,
-      name: null,
-      password: undefined,
-      isDisabled: undefined,
-    },
-  });
-
-  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 totalCount = usersData?.pagination?.total || 0;
-
-  // 处理搜索
-  const handleSearch = (e: React.FormEvent) => {
-    e.preventDefault();
-    setSearchParams(prev => ({ ...prev, page: 1 }));
-  };
-
-  // 处理分页
-  const handlePageChange = (page: number, limit: number) => {
-    setSearchParams(prev => ({ ...prev, page, limit }));
-  };
-
-  // 打开创建用户对话框
-  const handleCreateUser = () => {
-    setEditingUser(null);
-    setIsCreateForm(true);
-    createForm.reset({
-      username: '',
-      nickname: null,
-      email: null,
-      phone: null,
-      name: null,
-      password: '',
-      isDisabled: DisabledStatus.ENABLED,
-    });
-    setIsModalOpen(true);
-  };
-
-  // 打开编辑用户对话框
-  const handleEditUser = (user: UserResponse) => {
-    setEditingUser(user);
-    setIsCreateForm(false);
-    updateForm.reset({
-      username: user.username,
-      nickname: user.nickname,
-      email: user.email,
-      phone: user.phone,
-      name: user.name,
-      isDisabled: user.isDisabled,
-    });
-    setIsModalOpen(true);
-  };
-
-  // 处理创建表单提交
-  const handleCreateSubmit = async (data: CreateUserFormData) => {
-    try {
-      const res = await userClient.$post({
-        json: data
-      });
-      if (res.status !== 201) {
-        throw new Error('创建用户失败');
-      }
-      toast.success('用户创建成功');
-      setIsModalOpen(false);
-      refetch();
-    } catch (error) {
-      console.error('创建用户失败:', error);
-      toast.error('创建失败,请重试');
-    }
-  };
-
-  // 处理更新表单提交
-  const handleUpdateSubmit = async (data: UpdateUserFormData) => {
-    if (!editingUser) return;
-    
-    try {
-      const res = await userClient[':id']['$put']({
-        param: { id: editingUser.id },
-        json: data
-      });
-      if (res.status !== 200) {
-        throw new Error('更新用户失败');
-      }
-      toast.success('用户更新成功');
-      setIsModalOpen(false);
-      refetch();
-    } catch (error) {
-      console.error('更新用户失败:', error);
-      toast.error('更新失败,请重试');
-    }
-  };
-
-  // 处理删除用户
-  const handleDeleteUser = (id: number) => {
-    setUserToDelete(id);
-    setDeleteDialogOpen(true);
-  };
-
-  const confirmDelete = async () => {
-    if (!userToDelete) return;
-    
-    try {
-      const res = await userClient[':id']['$delete']({
-        param: { id: userToDelete }
-      });
-      if (res.status !== 204) {
-        throw new Error('删除用户失败');
-      }
-      toast.success('用户删除成功');
-      refetch();
-    } catch (error) {
-      console.error('删除用户失败:', error);
-      toast.error('删除失败,请重试');
-    } finally {
-      setDeleteDialogOpen(false);
-      setUserToDelete(null);
-    }
-  };
-
-  // 渲染加载骨架
-  if (isLoading) {
-    return (
-      <div className="space-y-4">
-        <div className="flex justify-between items-center">
-          <h1 className="text-2xl font-bold">用户管理</h1>
-          <Button disabled>
-            <Plus className="mr-2 h-4 w-4" />
-            创建用户
-          </Button>
-        </div>
-        
-        <Card>
-          <CardHeader>
-            <Skeleton className="h-6 w-1/4" />
-          </CardHeader>
-          <CardContent>
-            <div className="space-y-2">
-              <Skeleton className="h-4 w-full" />
-              <Skeleton className="h-4 w-full" />
-              <Skeleton className="h-4 w-full" />
-            </div>
-          </CardContent>
-        </Card>
-      </div>
-    );
-  }
-
-  return (
-    <div className="space-y-4">
-      <div className="flex justify-between items-center">
-        <h1 className="text-2xl font-bold">用户管理</h1>
-        <Button onClick={handleCreateUser}>
-          <Plus className="mr-2 h-4 w-4" />
-          创建用户
-        </Button>
-      </div>
-
-      <Card>
-        <CardHeader>
-          <CardTitle>用户列表</CardTitle>
-          <CardDescription>
-            管理系统中的所有用户,共 {totalCount} 位用户
-          </CardDescription>
-        </CardHeader>
-        <CardContent>
-          <div className="mb-4">
-            <form onSubmit={handleSearch} className="flex gap-2">
-              <div className="relative flex-1 max-w-sm">
-                <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
-                <Input
-                  placeholder="搜索用户名、昵称或邮箱..."
-                  value={searchParams.search}
-                  onChange={(e) => setSearchParams(prev => ({ ...prev, search: e.target.value }))}
-                  className="pl-8"
-                />
-              </div>
-              <Button type="submit" variant="outline">
-                搜索
-              </Button>
-            </form>
-          </div>
-
-          <div className="rounded-md border">
-            <Table>
-              <TableHeader>
-                <TableRow>
-                  <TableHead>用户名</TableHead>
-                  <TableHead>昵称</TableHead>
-                  <TableHead>邮箱</TableHead>
-                  <TableHead>真实姓名</TableHead>
-                  <TableHead>角色</TableHead>
-                  <TableHead>状态</TableHead>
-                  <TableHead>创建时间</TableHead>
-                  <TableHead className="text-right">操作</TableHead>
-                </TableRow>
-              </TableHeader>
-              <TableBody>
-                {users.map((user) => (
-                  <TableRow key={user.id}>
-                    <TableCell className="font-medium">{user.username}</TableCell>
-                    <TableCell>{user.nickname || '-'}</TableCell>
-                    <TableCell>{user.email || '-'}</TableCell>
-                    <TableCell>{user.name || '-'}</TableCell>
-                    <TableCell>
-                      <Badge
-                        variant={user.roles?.some((role: any) => role.name === 'admin') ? 'destructive' : 'default'}
-                        className="capitalize"
-                      >
-                        {user.roles?.some((role: any) => role.name === 'admin') ? '管理员' : '普通用户'}
-                      </Badge>
-                    </TableCell>
-                    <TableCell>
-                      <Badge
-                        variant={user.isDisabled === 1 ? 'secondary' : 'default'}
-                      >
-                        {user.isDisabled === 1 ? '禁用' : '启用'}
-                      </Badge>
-                    </TableCell>
-                    <TableCell>
-                      {format(new Date(user.createdAt), 'yyyy-MM-dd HH:mm')}
-                    </TableCell>
-                    <TableCell className="text-right">
-                      <div className="flex justify-end gap-2">
-                        <Button
-                          variant="ghost"
-                          size="icon"
-                          onClick={() => handleEditUser(user)}
-                        >
-                          <Edit className="h-4 w-4" />
-                        </Button>
-                        <Button
-                          variant="ghost"
-                          size="icon"
-                          onClick={() => handleDeleteUser(user.id)}
-                        >
-                          <Trash2 className="h-4 w-4" />
-                        </Button>
-                      </div>
-                    </TableCell>
-                  </TableRow>
-                ))}
-              </TableBody>
-            </Table>
-          </div>
-
-          <DataTablePagination
-            currentPage={searchParams.page}
-            totalCount={totalCount}
-            pageSize={searchParams.limit}
-            onPageChange={handlePageChange}
-          />
-        </CardContent>
-      </Card>
-
-      {/* 创建/编辑用户对话框 */}
-      <Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
-        <DialogContent className="sm:max-w-[500px]">
-          <DialogHeader>
-            <DialogTitle>
-              {editingUser ? '编辑用户' : '创建用户'}
-            </DialogTitle>
-            <DialogDescription>
-              {editingUser ? '编辑现有用户信息' : '创建一个新的用户账户'}
-            </DialogDescription>
-          </DialogHeader>
-          
-          {isCreateForm ? (
-            <Form {...createForm}>
-              <form onSubmit={createForm.handleSubmit(handleCreateSubmit)} className="space-y-4">
-                <FormField
-                  control={createForm.control}
-                  name="username"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        用户名
-                        <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入用户名" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="nickname"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>昵称</FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入昵称" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="email"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>邮箱</FormLabel>
-                      <FormControl>
-                        <Input type="email" placeholder="请输入邮箱" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="phone"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>手机号</FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入手机号" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="name"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>真实姓名</FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入真实姓名" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="password"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        密码
-                        <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input type="password" placeholder="请输入密码" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="isDisabled"
-                  render={({ field }) => (
-                    <FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
-                      <div className="space-y-0.5">
-                        <FormLabel className="text-base">用户状态</FormLabel>
-                        <FormDescription>
-                          禁用后用户将无法登录系统
-                        </FormDescription>
-                      </div>
-                      <FormControl>
-                        <Switch
-                          checked={field.value === 1}
-                          onCheckedChange={(checked) => field.onChange(checked ? 1 : 0)}
-                        />
-                      </FormControl>
-                    </FormItem>
-                  )}
-                />
-
-                <DialogFooter>
-                  <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
-                    取消
-                  </Button>
-                  <Button type="submit">
-                    创建用户
-                  </Button>
-                </DialogFooter>
-              </form>
-            </Form>
-          ) : (
-            <Form {...updateForm}>
-              <form onSubmit={updateForm.handleSubmit(handleUpdateSubmit)} className="space-y-4">
-                <FormField
-                  control={updateForm.control}
-                  name="username"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        用户名
-                        <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入用户名" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="nickname"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>昵称</FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入昵称" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="email"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>邮箱</FormLabel>
-                      <FormControl>
-                        <Input type="email" placeholder="请输入邮箱" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="phone"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>手机号</FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入手机号" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="name"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>真实姓名</FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入真实姓名" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="password"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>新密码</FormLabel>
-                      <FormControl>
-                        <Input type="password" placeholder="留空则不修改密码" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="isDisabled"
-                  render={({ field }) => (
-                    <FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
-                      <div className="space-y-0.5">
-                        <FormLabel className="text-base">用户状态</FormLabel>
-                        <FormDescription>
-                          禁用后用户将无法登录系统
-                        </FormDescription>
-                      </div>
-                      <FormControl>
-                        <Switch
-                          checked={field.value === 1}
-                          onCheckedChange={(checked) => field.onChange(checked ? 1 : 0)}
-                        />
-                      </FormControl>
-                    </FormItem>
-                  )}
-                />
-
-                <DialogFooter>
-                  <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
-                    取消
-                  </Button>
-                  <Button type="submit">
-                    更新用户
-                  </Button>
-                </DialogFooter>
-              </form>
-            </Form>
-          )}
-        </DialogContent>
-      </Dialog>
-
-      {/* 删除确认对话框 */}
-      <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
-        <DialogContent>
-          <DialogHeader>
-            <DialogTitle>确认删除</DialogTitle>
-            <DialogDescription>
-              确定要删除这个用户吗?此操作无法撤销。
-            </DialogDescription>
-          </DialogHeader>
-          <DialogFooter>
-            <Button variant="outline" onClick={() => setDeleteDialogOpen(false)}>
-              取消
-            </Button>
-            <Button variant="destructive" onClick={confirmDelete}>
-              删除
-            </Button>
-          </DialogFooter>
-        </DialogContent>
-      </Dialog>
-    </div>
-  );
-};

+ 0 - 54
src/client/admin-shadcn/routes.tsx

@@ -1,54 +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';
-
-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: '*',
-        element: <NotFoundPage />,
-        errorElement: <ErrorPage />
-      },
-    ],
-  },
-  {
-    path: '*',
-    element: <NotFoundPage />,
-    errorElement: <ErrorPage />
-  },
-]);

+ 0 - 65
src/client/home-shadcn/components/ErrorPage.tsx

@@ -1,65 +0,0 @@
-import React from 'react';
-import { useRouteError, useNavigate } from 'react-router';
-import { AlertCircle, RotateCcw, Home } from 'lucide-react';
-import { Button } from '@/client/components/ui/button';
-import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/client/components/ui/card';
-import { Alert, AlertDescription, AlertTitle } from '@/client/components/ui/alert';
-
-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-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 p-4">
-      <Card className="w-full max-w-md border-0 shadow-xl">
-        <CardHeader className="bg-destructive/10">
-          <CardTitle className="flex items-center space-x-2 text-destructive">
-            <AlertCircle className="h-6 w-6" />
-            <span>发生错误</span>
-          </CardTitle>
-          <CardDescription>
-            抱歉,页面加载时遇到了问题
-          </CardDescription>
-        </CardHeader>
-        
-        <CardContent className="space-y-4">
-          <Alert variant="destructive">
-            <AlertCircle className="h-4 w-4" />
-            <AlertTitle>错误详情</AlertTitle>
-            <AlertDescription>
-              {error?.message || '未知错误'}
-            </AlertDescription>
-          </Alert>
-          
-          {error?.stack && (
-            <div className="space-y-2">
-              <h4 className="text-sm font-medium text-muted-foreground">技术信息</h4>
-              <pre className="text-xs text-muted-foreground bg-muted/50 p-3 rounded-lg overflow-x-auto max-h-40">
-                {error.stack}
-              </pre>
-            </div>
-          )}
-        </CardContent>
-        
-        <CardFooter className="flex gap-2">
-          <Button
-            onClick={() => navigate(0)}
-            className="flex-1"
-            variant="outline"
-          >
-            <RotateCcw className="h-4 w-4 mr-2" />
-            重新加载
-          </Button>
-          <Button
-            onClick={() => navigate('/')}
-            className="flex-1"
-          >
-            <Home className="h-4 w-4 mr-2" />
-            返回首页
-          </Button>
-        </CardFooter>
-      </Card>
-    </div>
-  );
-};

+ 0 - 49
src/client/home-shadcn/components/NotFoundPage.tsx

@@ -1,49 +0,0 @@
-import React from 'react';
-import { useNavigate } from 'react-router';
-import { ArrowLeft, Home } from 'lucide-react';
-import { Button } from '@/client/components/ui/button';
-import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/client/components/ui/card';
-
-export const NotFoundPage = () => {
-  const navigate = useNavigate();
-  
-  return (
-    <div className="flex flex-col items-center justify-center min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 p-4">
-      <Card className="w-full max-w-md border-0 shadow-xl">
-        <CardHeader className="text-center">
-          <div className="mx-auto flex h-20 w-20 items-center justify-center rounded-full bg-primary/10 mb-4">
-            <span className="text-4xl font-bold text-primary">404</span>
-          </div>
-          <CardTitle className="text-2xl">页面未找到</CardTitle>
-          <CardDescription>
-            抱歉,您访问的页面不存在或已被移除
-          </CardDescription>
-        </CardHeader>
-        
-        <CardContent className="text-center">
-          <p className="text-muted-foreground">
-            请检查URL是否正确,或尝试以下操作:
-          </p>
-        </CardContent>
-        
-        <CardFooter className="flex gap-2">
-          <Button
-            onClick={() => navigate(-1)}
-            variant="outline"
-            className="flex-1"
-          >
-            <ArrowLeft className="h-4 w-4 mr-2" />
-            返回上一页
-          </Button>
-          <Button
-            onClick={() => navigate('/')}
-            className="flex-1"
-          >
-            <Home className="h-4 w-4 mr-2" />
-            返回首页
-          </Button>
-        </CardFooter>
-      </Card>
-    </div>
-  );
-};

+ 0 - 44
src/client/home-shadcn/components/ProtectedRoute.tsx

@@ -1,44 +0,0 @@
-import React, { useEffect } from 'react';
-import { useNavigate } from 'react-router';
-import { useAuth } from '../hooks/AuthProvider';
-import { Skeleton } from '@/client/components/ui/skeleton';
-import { Card, CardContent } from '@/client/components/ui/card';
-
-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 min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
-        <Card className="border-0 shadow-lg">
-          <CardContent className="flex flex-col items-center space-y-4 py-12 px-8">
-            <div className="relative">
-              <Skeleton className="h-12 w-12 rounded-full" />
-              <div className="absolute inset-0 rounded-full bg-primary/20 animate-pulse" />
-            </div>
-            <div className="space-y-2 text-center">
-              <Skeleton className="h-4 w-32" />
-              <Skeleton className="h-3 w-24" />
-            </div>
-          </CardContent>
-        </Card>
-      </div>
-    );
-  }
-  
-  // 如果未认证且不再加载中,不显示任何内容(等待重定向)
-  if (!isAuthenticated) {
-    return null;
-  }
-  
-  return children;
-};

+ 0 - 140
src/client/home-shadcn/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 - 36
src/client/home-shadcn/index.tsx

@@ -1,36 +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'
-import { Toaster } from '@/client/components/ui/sonner'
-
-// 创建QueryClient实例
-const queryClient = new QueryClient();
-
-// 应用入口组件
-const App = () => {
-  return (
-    <QueryClientProvider client={queryClient}>
-      <AuthProvider>
-        <RouterProvider router={router} />
-        <Toaster 
-          position="top-right"
-          expand={false}
-          richColors
-          closeButton
-          duration={3000}
-        />
-      </AuthProvider>
-    </QueryClientProvider>
-  )
-};
-
-const rootElement = document.getElementById('root')
-if (rootElement) {
-  const root = createRoot(rootElement)
-  root.render(
-    <App />
-  )
-}

+ 0 - 14
src/client/home-shadcn/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 - 210
src/client/home-shadcn/pages/HomePage.tsx

@@ -1,210 +0,0 @@
-import React from 'react';
-import { useAuth } from '@/client/home-shadcn/hooks/AuthProvider';
-import { useNavigate } from 'react-router-dom';
-import { Button } from '@/client/components/ui/button';
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
-import { Avatar, AvatarFallback, AvatarImage } from '@/client/components/ui/avatar';
-import { Badge } from '@/client/components/ui/badge';
-import { 
-  ShieldCheck, 
-  UserCircle, 
-  Smartphone, 
-  LogIn, 
-  UserPlus,
-  ExternalLink
-} from 'lucide-react';
-
-const HomePage: React.FC = () => {
-  const { user } = useAuth();
-  const navigate = useNavigate();
-
-  return (
-    <div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
-      {/* 顶部导航 */}
-      <header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
-        <div className="container mx-auto flex h-16 items-center justify-between">
-          <div className="flex items-center space-x-2">
-            <ShieldCheck className="h-6 w-6 text-primary" />
-            <h1 className="text-xl font-bold">网站首页</h1>
-          </div>
-          
-          <div className="flex items-center space-x-4">
-            {user ? (
-              <div 
-                className="flex items-center space-x-3 cursor-pointer hover:bg-muted/50 rounded-lg px-3 py-2 transition-colors"
-                onClick={() => navigate('/member')}
-              >
-                <Avatar className="h-8 w-8">
-                  <AvatarImage src={`https://avatar.vercel.sh/${user.username}`} />
-                  <AvatarFallback className="bg-primary text-primary-foreground">
-                    {user.username?.charAt(0).toUpperCase()}
-                  </AvatarFallback>
-                </Avatar>
-                <span className="hidden md:inline text-sm font-medium">{user.username}</span>
-              </div>
-            ) : (
-              <div className="flex items-center space-x-2">
-                <Button
-                  variant="ghost"
-                  size="sm"
-                  onClick={() => navigate('/login')}
-                  className="flex items-center space-x-1"
-                >
-                  <LogIn className="h-4 w-4" />
-                  <span>登录</span>
-                </Button>
-                <Button
-                  size="sm"
-                  onClick={() => navigate('/register')}
-                  className="flex items-center space-x-1"
-                >
-                  <UserPlus className="h-4 w-4" />
-                  <span>注册</span>
-                </Button>
-              </div>
-            )}
-          </div>
-        </div>
-      </header>
-
-      {/* 主内容区 */}
-      <main className="container mx-auto py-8">
-        <div className="mx-auto max-w-6xl space-y-8">
-          {/* 欢迎区域 */}
-          <Card className="border-0 shadow-lg">
-            <CardHeader>
-              <CardTitle className="text-3xl font-bold bg-gradient-to-r from-primary to-purple-600 bg-clip-text text-transparent">
-                欢迎使用网站模板
-              </CardTitle>
-              <CardDescription className="text-lg">
-                这是一个现代化的网站首页模板,采用 shadcn/ui 设计系统构建
-              </CardDescription>
-            </CardHeader>
-            <CardContent>
-              <p className="text-muted-foreground">
-                已集成完整的用户认证系统,包含登录、注册和用户中心功能。
-                使用 React 19 + TypeScript + shadcn/ui + Tailwind CSS 技术栈。
-              </p>
-            </CardContent>
-          </Card>
-
-          {/* 功能特性卡片 */}
-          <div className="grid gap-6 md:grid-cols-3">
-            <Card className="border-0 shadow-lg hover:shadow-xl transition-shadow">
-              <CardHeader>
-                <div className="flex items-center space-x-2">
-                  <div className="p-2 bg-blue-100 rounded-lg">
-                    <ShieldCheck className="h-6 w-6 text-blue-600" />
-                  </div>
-                  <CardTitle className="text-lg">用户认证</CardTitle>
-                </div>
-              </CardHeader>
-              <CardContent>
-                <CardDescription>
-                  完整的登录、注册功能,基于 JWT 的现代化认证系统
-                </CardDescription>
-                <Badge variant="secondary" className="mt-2">安全认证</Badge>
-              </CardContent>
-            </Card>
-
-            <Card className="border-0 shadow-lg hover:shadow-xl transition-shadow">
-              <CardHeader>
-                <div className="flex items-center space-x-2">
-                  <div className="p-2 bg-green-100 rounded-lg">
-                    <UserCircle className="h-6 w-6 text-green-600" />
-                  </div>
-                  <CardTitle className="text-lg">用户中心</CardTitle>
-                </div>
-              </CardHeader>
-              <CardContent>
-                <CardDescription>
-                  用户可以查看和管理个人信息,支持头像上传和个人资料编辑
-                </CardDescription>
-                <Badge variant="secondary" className="mt-2">个人管理</Badge>
-              </CardContent>
-            </Card>
-
-            <Card className="border-0 shadow-lg hover:shadow-xl transition-shadow">
-              <CardHeader>
-                <div className="flex items-center space-x-2">
-                  <div className="p-2 bg-purple-100 rounded-lg">
-                    <Smartphone className="h-6 w-6 text-purple-600" />
-                  </div>
-                  <CardTitle className="text-lg">响应式设计</CardTitle>
-                </div>
-              </CardHeader>
-              <CardContent>
-                <CardDescription>
-                  适配各种设备屏幕,采用移动端优先的设计理念
-                </CardDescription>
-                <Badge variant="secondary" className="mt-2">移动优先</Badge>
-              </CardContent>
-            </Card>
-          </div>
-
-          {/* 快速链接 */}
-          <Card className="border-0 shadow-lg">
-            <CardHeader>
-              <CardTitle className="flex items-center space-x-2">
-                <ExternalLink className="h-5 w-5" />
-                <span>快速访问</span>
-              </CardTitle>
-            </CardHeader>
-            <CardContent>
-              <div className="flex flex-wrap gap-4">
-                <Button
-                  variant="outline"
-                  onClick={() => window.open('/admin', '_blank')}
-                  className="flex items-center space-x-2"
-                >
-                  <span>管理后台</span>
-                  <ExternalLink className="h-4 w-4" />
-                </Button>
-                <Button
-                  variant="outline"
-                  onClick={() => window.open('/ui', '_blank')}
-                  className="flex items-center space-x-2"
-                >
-                  <span>API 文档</span>
-                  <ExternalLink className="h-4 w-4" />
-                </Button>
-              </div>
-            </CardContent>
-          </Card>
-        </div>
-      </main>
-
-      {/* 页脚 */}
-      <footer className="border-t bg-background">
-        <div className="container mx-auto py-6">
-          <div className="flex flex-col items-center justify-center space-y-2">
-            <p className="text-sm text-muted-foreground">
-              网站模板 ©{new Date().getFullYear()} Created with React & shadcn/ui
-            </p>
-            <div className="flex items-center space-x-4 text-sm">
-              <Button
-                variant="link"
-                size="sm"
-                onClick={() => window.open('/admin', '_blank')}
-                className="text-muted-foreground hover:text-primary"
-              >
-                管理后台
-              </Button>
-              <span className="text-muted-foreground">•</span>
-              <Button
-                variant="link"
-                size="sm"
-                onClick={() => window.open('/ui', '_blank')}
-                className="text-muted-foreground hover:text-primary"
-              >
-                API 文档
-              </Button>
-            </div>
-          </div>
-        </div>
-      </footer>
-    </div>
-  );
-};
-
-export default HomePage;

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

@@ -1,133 +0,0 @@
-import React from 'react';
-import { useForm } from 'react-hook-form';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { z } from 'zod';
-import { Link, useNavigate } from 'react-router-dom';
-import { useAuth } from '@/client/home-shadcn/hooks/AuthProvider';
-import { toast } from 'sonner';
-
-import { Button } from '@/client/components/ui/button';
-import { Input } from '@/client/components/ui/input';
-import { Label } from '@/client/components/ui/label';
-import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/client/components/ui/card';
-import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
-import { Eye, EyeOff, User, Lock } from 'lucide-react';
-
-const loginSchema = z.object({
-  username: z.string().min(3, '用户名至少3个字符'),
-  password: z.string().min(6, '密码至少6个字符'),
-});
-
-type LoginFormData = z.infer<typeof loginSchema>;
-
-const LoginPage: React.FC = () => {
-  const { login } = useAuth();
-  const navigate = useNavigate();
-  
-  const form = useForm<LoginFormData>({
-    resolver: zodResolver(loginSchema),
-    defaultValues: {
-      username: '',
-      password: '',
-    },
-  });
-
-  const onSubmit = async (data: LoginFormData) => {
-    try {
-      await login(data.username, data.password);
-      toast.success('登录成功!');
-      navigate('/');
-    } catch (error) {
-      toast.error(error instanceof Error ? error.message : '登录失败,请检查用户名和密码');
-    }
-  };
-
-  return (
-    <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100 px-4 py-12">
-      <Card className="w-full max-w-md border-0 shadow-xl">
-        <CardHeader className="space-y-1">
-          <CardTitle className="text-2xl font-bold text-center">欢迎回来</CardTitle>
-          <CardDescription className="text-center">
-            登录您的账号以继续
-          </CardDescription>
-        </CardHeader>
-        
-        <CardContent>
-          <Form {...form}>
-            <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
-              <FormField
-                control={form.control}
-                name="username"
-                render={({ field }) => (
-                  <FormItem>
-                    <FormLabel>用户名</FormLabel>
-                    <FormControl>
-                      <div className="relative">
-                        <User className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
-                        <Input
-                          placeholder="请输入用户名"
-                          className="pl-10"
-                          {...field}
-                        />
-                      </div>
-                    </FormControl>
-                    <FormMessage />
-                  </FormItem>
-                )}
-              />
-
-              <FormField
-                control={form.control}
-                name="password"
-                render={({ field }) => (
-                  <FormItem>
-                    <FormLabel>密码</FormLabel>
-                    <FormControl>
-                      <div className="relative">
-                        <Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
-                        <Input
-                          type="password"
-                          placeholder="请输入密码"
-                          className="pl-10"
-                          {...field}
-                        />
-                      </div>
-                    </FormControl>
-                    <FormMessage />
-                  </FormItem>
-                )}
-              />
-
-              <Button 
-                type="submit" 
-                className="w-full"
-                disabled={form.formState.isSubmitting}
-              >
-                {form.formState.isSubmitting ? '登录中...' : '登录'}
-              </Button>
-            </form>
-          </Form>
-        </CardContent>
-
-        <CardFooter className="flex flex-col space-y-4">
-          <div className="text-sm text-center">
-            <span className="text-muted-foreground">还没有账号?</span>
-            <Button
-              variant="link"
-              className="px-1"
-              asChild
-            >
-              <Link to="/register">立即注册</Link>
-            </Button>
-          </div>
-          
-          <div className="text-xs text-center text-muted-foreground">
-            <p>测试账号:admin / admin123</p>
-          </div>
-        </CardFooter>
-      </Card>
-    </div>
-  );
-};
-
-export default LoginPage;

+ 0 - 234
src/client/home-shadcn/pages/MemberPage.tsx

@@ -1,234 +0,0 @@
-import React from 'react';
-import { useNavigate } from 'react-router-dom';
-import { useAuth } from '@/client/home-shadcn/hooks/AuthProvider';
-import { Button } from '@/client/components/ui/button';
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
-import { Avatar, AvatarFallback, AvatarImage } from '@/client/components/ui/avatar';
-import { Badge } from '@/client/components/ui/badge';
-import { Separator } from '@/client/components/ui/separator';
-import { 
-  User, 
-  MapPin, 
-  Globe, 
-  Calendar, 
-  LogOut, 
-  Settings,
-  UserCog,
-  ShieldCheck,
-  Clock
-} from 'lucide-react';
-import { format } from 'date-fns';
-import { zhCN } from 'date-fns/locale';
-
-const MemberPage: React.FC = () => {
-  const navigate = useNavigate();
-  const { user, logout } = useAuth();
-
-  if (!user) {
-    return (
-      <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100 px-4">
-        <Card className="max-w-md">
-          <CardHeader>
-            <CardTitle>用户不存在</CardTitle>
-            <CardDescription>请先登录后再访问此页面</CardDescription>
-          </CardHeader>
-          <CardContent>
-            <Button onClick={() => navigate('/')} className="w-full">
-              返回首页
-            </Button>
-          </CardContent>
-        </Card>
-      </div>
-    );
-  }
-
-  return (
-    <div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
-      <div className="container mx-auto py-8">
-        <div className="mx-auto max-w-4xl space-y-8">
-          {/* 用户资料卡片 */}
-          <Card className="border-0 shadow-lg">
-            <CardContent className="pt-6">
-              <div className="flex flex-col items-center space-y-4">
-                <Avatar className="h-24 w-24">
-                  <AvatarImage 
-                    src={user.avatar || `https://avatar.vercel.sh/${user.username}`} 
-                    alt={user.nickname || user.username}
-                  />
-                  <AvatarFallback className="text-2xl bg-primary text-primary-foreground">
-                    {user.username?.charAt(0).toUpperCase()}
-                  </AvatarFallback>
-                </Avatar>
-                
-                <div className="text-center space-y-2">
-                  <h1 className="text-3xl font-bold">{user.nickname || user.username}</h1>
-                  <p className="text-muted-foreground">@{user.username}</p>
-                </div>
-
-                <div className="flex items-center space-x-4">
-                  <div className="text-center">
-                    <p className="text-2xl font-bold">0</p>
-                    <p className="text-sm text-muted-foreground">内容</p>
-                  </div>
-                  <Separator orientation="vertical" className="h-8" />
-                  <div className="text-center">
-                    <p className="text-2xl font-bold">0</p>
-                    <p className="text-sm text-muted-foreground">关注</p>
-                  </div>
-                  <Separator orientation="vertical" className="h-8" />
-                  <div className="text-center">
-                    <p className="text-2xl font-bold">0</p>
-                    <p className="text-sm text-muted-foreground">粉丝</p>
-                  </div>
-                </div>
-
-                <div className="flex items-center space-x-2">
-                  <Button 
-                    onClick={() => navigate('/profile/edit')}
-                    className="flex items-center space-x-2"
-                  >
-                    <UserCog className="h-4 w-4" />
-                    <span>编辑资料</span>
-                  </Button>
-                  
-                  <Button 
-                    variant="outline"
-                    onClick={async () => {
-                      await logout();
-                      navigate('/');
-                    }}
-                    className="flex items-center space-x-2"
-                  >
-                    <LogOut className="h-4 w-4" />
-                    <span>退出登录</span>
-                  </Button>
-                </div>
-              </div>
-            </CardContent>
-          </Card>
-
-          {/* 个人资料详情 */}
-          <Card className="border-0 shadow-lg">
-            <CardHeader>
-              <CardTitle className="flex items-center space-x-2">
-                <User className="h-5 w-5" />
-                <span>个人资料</span>
-              </CardTitle>
-            </CardHeader>
-            <CardContent className="space-y-6">
-              <div className="grid gap-4">
-                <div className="space-y-1">
-                  <div className="flex items-center space-x-2 text-sm text-muted-foreground">
-                    <User className="h-4 w-4" />
-                    <span>用户名</span>
-                  </div>
-                  <p className="font-medium">{user.username}</p>
-                </div>
-
-                <div className="space-y-1">
-                  <div className="flex items-center space-x-2 text-sm text-muted-foreground">
-                    <ShieldCheck className="h-4 w-4" />
-                    <span>邮箱</span>
-                  </div>
-                  <p className="font-medium">{user.email || '未设置'}</p>
-                </div>
-
-                {(user as any).location && (
-                  <div className="space-y-1">
-                    <div className="flex items-center space-x-2 text-sm text-muted-foreground">
-                      <MapPin className="h-4 w-4" />
-                      <span>位置</span>
-                    </div>
-                    <p className="font-medium">{(user as any).location}</p>
-                  </div>
-                )}
-
-                {(user as any).website && (
-                  <div className="space-y-1">
-                    <div className="flex items-center space-x-2 text-sm text-muted-foreground">
-                      <Globe className="h-4 w-4" />
-                      <span>网站</span>
-                    </div>
-                    <a 
-                      href={(user as any).website}
-                      target="_blank"
-                      rel="noopener noreferrer"
-                      className="font-medium text-primary hover:underline"
-                    >
-                      {(user as any).website}
-                    </a>
-                  </div>
-                )}
-
-                {(user as any).bio && (
-                  <div className="space-y-1">
-                    <div className="flex items-center space-x-2 text-sm text-muted-foreground">
-                      <User className="h-4 w-4" />
-                      <span>个人简介</span>
-                    </div>
-                    <p className="font-medium">{(user as any).bio}</p>
-                  </div>
-                )}
-              </div>
-
-              <Separator />
-
-              <div className="grid gap-4 md:grid-cols-2">
-                <div className="space-y-1">
-                  <div className="flex items-center space-x-2 text-sm text-muted-foreground">
-                    <Calendar className="h-4 w-4" />
-                    <span>注册时间</span>
-                  </div>
-                  <p className="font-medium">
-                    {user.createdAt ? format(new Date(user.createdAt), 'yyyy年MM月dd日', { locale: zhCN }) : '未知'}
-                  </p>
-                </div>
-
-                <div className="space-y-1">
-                  <div className="flex items-center space-x-2 text-sm text-muted-foreground">
-                    <Clock className="h-4 w-4" />
-                    <span>最后登录</span>
-                  </div>
-                  <p className="font-medium">
-                    {user.updatedAt ? format(new Date(user.updatedAt), 'yyyy年MM月dd日 HH:mm', { locale: zhCN }) : '从未登录'}
-                  </p>
-                </div>
-              </div>
-            </CardContent>
-          </Card>
-
-          {/* 设置区域 */}
-          <Card className="border-0 shadow-lg">
-            <CardHeader>
-              <CardTitle className="flex items-center space-x-2">
-                <Settings className="h-5 w-5" />
-                <span>账号设置</span>
-              </CardTitle>
-            </CardHeader>
-            <CardContent className="space-y-4">
-              <Button 
-                variant="outline" 
-                className="w-full justify-start"
-                onClick={() => navigate('/profile/security')}
-              >
-                <ShieldCheck className="h-4 w-4 mr-2" />
-                <span>安全设置</span>
-              </Button>
-              
-              <Button 
-                variant="outline" 
-                className="w-full justify-start"
-                onClick={() => navigate('/profile/preferences')}
-              >
-                <Settings className="h-4 w-4 mr-2" />
-                <span>偏好设置</span>
-              </Button>
-            </CardContent>
-          </Card>
-        </div>
-      </div>
-    </div>
-  );
-};
-
-export default MemberPage;

+ 0 - 178
src/client/home-shadcn/pages/RegisterPage.tsx

@@ -1,178 +0,0 @@
-import React from 'react';
-import { useForm } from 'react-hook-form';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { z } from 'zod';
-import { Link, useNavigate } from 'react-router-dom';
-import { useAuth } from '@/client/home-shadcn/hooks/AuthProvider';
-import { toast } from 'sonner';
-import { authClient } from '@/client/api';
-
-import { Button } from '@/client/components/ui/button';
-import { Input } from '@/client/components/ui/input';
-import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/client/components/ui/card';
-import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
-import { Eye, EyeOff, User, Lock } from 'lucide-react';
-
-const registerSchema = z.object({
-  username: z.string()
-    .min(3, '用户名至少3个字符')
-    .max(20, '用户名不能超过20个字符')
-    .regex(/^[a-zA-Z0-9_-]+$/, '用户名只能包含字母、数字、下划线和连字符'),
-  password: z.string()
-    .min(6, '密码至少6个字符')
-    .max(30, '密码不能超过30个字符'),
-  confirmPassword: z.string(),
-}).refine((data) => data.password === data.confirmPassword, {
-  message: '两次密码输入不一致',
-  path: ['confirmPassword'],
-});
-
-type RegisterFormData = z.infer<typeof registerSchema>;
-
-const RegisterPage: React.FC = () => {
-  const { login } = useAuth();
-  const navigate = useNavigate();
-  
-  const form = useForm<RegisterFormData>({
-    resolver: zodResolver(registerSchema),
-    defaultValues: {
-      username: '',
-      password: '',
-      confirmPassword: '',
-    },
-  });
-
-  const onSubmit = async (data: RegisterFormData) => {
-    try {
-      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);
-      toast.success('注册成功!正在为您登录...');
-      navigate('/');
-    } catch (error) {
-      toast.error(error instanceof Error ? error.message : '注册失败,请稍后重试');
-    }
-  };
-
-  return (
-    <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100 px-4 py-12">
-      <Card className="w-full max-w-md border-0 shadow-xl">
-        <CardHeader className="space-y-1">
-          <CardTitle className="text-2xl font-bold text-center">创建账号</CardTitle>
-          <CardDescription className="text-center">
-            填写以下信息创建新账号
-          </CardDescription>
-        </CardHeader>
-        
-        <CardContent>
-          <Form {...form}>
-            <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
-              <FormField
-                control={form.control}
-                name="username"
-                render={({ field }) => (
-                  <FormItem>
-                    <FormLabel>用户名</FormLabel>
-                    <FormControl>
-                      <div className="relative">
-                        <User className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
-                        <Input
-                          placeholder="请输入用户名"
-                          className="pl-10"
-                          {...field}
-                        />
-                      </div>
-                    </FormControl>
-                    <FormMessage />
-                  </FormItem>
-                )}
-              />
-
-              <FormField
-                control={form.control}
-                name="password"
-                render={({ field }) => (
-                  <FormItem>
-                    <FormLabel>密码</FormLabel>
-                    <FormControl>
-                      <div className="relative">
-                        <Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
-                        <Input
-                          type="password"
-                          placeholder="请输入密码"
-                          className="pl-10"
-                          {...field}
-                        />
-                      </div>
-                    </FormControl>
-                    <FormMessage />
-                  </FormItem>
-                )}
-              />
-
-              <FormField
-                control={form.control}
-                name="confirmPassword"
-                render={({ field }) => (
-                  <FormItem>
-                    <FormLabel>确认密码</FormLabel>
-                    <FormControl>
-                      <div className="relative">
-                        <Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
-                        <Input
-                          type="password"
-                          placeholder="请再次输入密码"
-                          className="pl-10"
-                          {...field}
-                        />
-                      </div>
-                    </FormControl>
-                    <FormMessage />
-                  </FormItem>
-                )}
-              />
-
-              <Button 
-                type="submit" 
-                className="w-full"
-                disabled={form.formState.isSubmitting}
-              >
-                {form.formState.isSubmitting ? '注册中...' : '注册账号'}
-              </Button>
-            </form>
-          </Form>
-        </CardContent>
-
-        <CardFooter className="flex flex-col space-y-4">
-          <div className="text-sm text-center">
-            <span className="text-muted-foreground">已有账号?</span>
-            <Button
-              variant="link"
-              className="px-1"
-              asChild
-            >
-              <Link to="/login">立即登录</Link>
-            </Button>
-          </div>
-          
-          <div className="text-xs text-center text-muted-foreground">
-            <p>注册即表示您同意我们的服务条款</p>
-          </div>
-        </CardFooter>
-      </Card>
-    </div>
-  );
-};
-
-export default RegisterPage;

+ 0 - 49
src/client/home-shadcn/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 />
-  },
-]);

+ 7 - 22
src/client/index.tsx

@@ -1,23 +1,8 @@
-// 应用路由映射配置
-const pathname = window.location.pathname;
-
-// 管理后台
-if (pathname.startsWith('/admin')) {
-  import('./admin/index');
-} 
-// 大屏展示
-else if (pathname.startsWith('/big')) {
-  import('./big-shadcn/index');
-}
-// 前台首页
-else if (pathname.startsWith('/home')) {
-  import('./home-shadcn/index');
-}
-// 默认首页 - 直接显示前台首页
-else {
-  // 重定向到home-shadcn首页
-  if (pathname === '/') {
-    window.history.replaceState(null, '', '/home');
-  }
-  import('./home-shadcn/index');
+// 如果当前是在 /big 下
+if (window.location.pathname.startsWith('/admin')) {
+  import('./admin/index')
+} else if (window.location.pathname.startsWith('/big')) {
+  import('./big-shadcn/index')
+} else {
+  import('./mobile/index')
 }

+ 2 - 2
src/client/mobile/hooks/useHomeData.ts

@@ -29,7 +29,7 @@ export const useHomeData = () => {
     },
     staleTime: 5 * 60 * 1000, // 5分钟
     gcTime: 10 * 60 * 1000, // 10分钟 (gcTime替代cacheTime)
-    enabled: !!user, // 需要登录
+    enabled: true, // 支持未登录访问
   });
 };
 
@@ -54,7 +54,7 @@ export const useSearch = (keyword: string, type: 'jobs' | 'knowledge' | 'compani
       }
       return response.json();
     },
-    enabled: !!keyword && !!user,
+    enabled: !!keyword,
     staleTime: 3 * 60 * 1000,
   });
 };

+ 11 - 3
src/server/api/home/index.ts

@@ -45,7 +45,6 @@ const SearchResponse = z.object({
 const getHomeRoute = createRoute({
   method: 'get',
   path: '/',
-  middleware: [authMiddleware],
   request: {
     query: z.object({
       limit: z.coerce.number().int().positive().max(20).optional().openapi({
@@ -74,7 +73,6 @@ const getHomeRoute = createRoute({
 const searchRoute = createRoute({
   method: 'get',
   path: '/search',
-  middleware: [authMiddleware],
   request: {
     query: SearchQuery
   },
@@ -102,7 +100,7 @@ const app = new OpenAPIHono<AuthContext>()
   try {
     const { limit = 10 } = c.req.valid('query');
     const user = c.get('user');
-    const userId = user?.id;
+    const userId = user?.id || undefined;
 
     const homeService = new HomeService(AppDataSource);
     const homeData = await homeService.getHomeData(userId);
@@ -114,6 +112,16 @@ const app = new OpenAPIHono<AuthContext>()
     homeData.silverTalents = homeData.silverTalents.slice(0, Math.min(limit, 3));
     homeData.silverPositions = homeData.silverPositions.slice(0, Math.min(limit, 3));
 
+    // 为未登录用户设置空的用户统计
+    if (!userId) {
+      homeData.userStats = {
+        pointBalance: 0,
+        timeBankHours: 0,
+        publishedCount: 0,
+        favoriteCount: 0
+      };
+    }
+
     return c.json(homeData as any, 200);
   } catch (error) {
     const { message = '获取首页数据失败' } = error as Error;