Переглянути джерело

✨ feat(home-shadcn): 使用 shadcn/ui 全面重构用户界面

- 将首页、登录、注册、会员中心页面从传统 Tailwind 样式迁移到 shadcn/ui 组件系统
- 引入现代化设计:渐变背景、阴影卡片、圆角按钮、图标装饰
- 新增功能特性展示区,包含安全认证、用户管理、响应式设计的卡片
- 登录/注册页面集成 zod 表单验证、sonner 提示、Lucide 图标库
- 会员中心页面新增个人资料详情展示,包含注册时间、最后登录时间
- 统一使用日期格式化库 date-fns 处理时间显示
- 优化导航体验:粘性头部、悬停效果、快速访问按钮
- 改进响应式布局:移动端优先的网格系统、自适应间距
yourname 4 місяців тому
батько
коміт
b2be94dc1e

+ 185 - 78
src/client/home-shadcn/pages/HomePage.tsx

@@ -1,98 +1,205 @@
 import React from 'react';
-import { useAuth } from '@/client/home/hooks/AuthProvider';
+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-gray-50 flex flex-col">
+    <div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
       {/* 顶部导航 */}
-      <header className="bg-blue-600 text-white shadow-md fixed w-full z-10">
-        <div className="container mx-auto px-4 py-3 flex justify-between items-center">
-          <h1 className="text-xl font-bold">网站首页</h1>
-          {user ? (
-            <div className="flex items-center space-x-4">
-              <div className="flex items-center cursor-pointer" onClick={() => navigate(`/member`)}>
-                <div className="w-8 h-8 rounded-full bg-white text-blue-600 flex items-center justify-center mr-2">
-                  <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
-                    <path fillRule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clipRule="evenodd" />
-                  </svg>
-                </div>
-                <span className="hidden md:inline">{user.username}</span>
-              </div>
-            </div>
-          ) : (
-            <div className="flex space-x-2">
-              <button 
-                onClick={() => navigate('/login')}
-                className="px-3 py-1 rounded text-sm bg-white text-blue-600 hover:bg-blue-50"
-              >
-                登录
-              </button>
-              <button 
-                onClick={() => navigate('/register')}
-                className="px-3 py-1 rounded text-sm bg-white text-blue-600 hover:bg-blue-50"
+      <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')}
               >
-                注册
-              </button>
-            </div>
-          )}
+                <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="flex-grow container mx-auto px-4 pt-24 pb-12">
-        <div className="bg-white rounded-lg shadow-sm p-6 md:p-8">
-          <h2 className="text-2xl font-bold text-gray-900 mb-4">欢迎使用网站模板</h2>
-          <p className="text-gray-600 mb-6">
-            这是一个通用的网站首页模板,您可以根据需要进行自定义。
-            已包含基础的登录、注册和用户中心功能。
-          </p>
-          
-          <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-8">
-            <div className="bg-blue-50 p-5 rounded-lg text-center">
-              <div className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
-                <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
-                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
-                </svg>
-              </div>
-              <h3 className="font-semibold text-lg mb-2">用户认证</h3>
-              <p className="text-gray-600 text-sm">完整的登录、注册功能,保护您的网站安全</p>
-            </div>
-            
-            <div className="bg-green-50 p-5 rounded-lg text-center">
-              <div className="w-12 h-12 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
-                <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
-                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
-                </svg>
-              </div>
-              <h3 className="font-semibold text-lg mb-2">用户中心</h3>
-              <p className="text-gray-600 text-sm">用户可以查看和管理个人信息</p>
-            </div>
-            
-            <div className="bg-purple-50 p-5 rounded-lg text-center">
-              <div className="w-12 h-12 bg-purple-100 rounded-full flex items-center justify-center mx-auto mb-4">
-                <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-purple-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
-                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4" />
-                </svg>
-              </div>
-              <h3 className="font-semibold text-lg mb-2">响应式设计</h3>
-              <p className="text-gray-600 text-sm">适配各种设备屏幕,提供良好的用户体验</p>
-            </div>
+      <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={() => navigate('/admin')}
+                  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="bg-white border-t border-gray-200 py-4">
-        <div className="container mx-auto px-4 text-center text-gray-500 text-sm">
-          网站模板 ©{new Date().getFullYear()} Created with React & Tailwind CSS
-          <div className="mt-2 space-x-4">
-            <a href="/admin" className="text-blue-600 hover:underline">管理后台</a>
-            <span className="text-gray-300">|</span>
-            <a href="/ui" className="text-blue-600 hover:underline">Api</a>
+      <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={() => navigate('/admin')}
+                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>

+ 111 - 111
src/client/home-shadcn/pages/LoginPage.tsx

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

+ 208 - 127
src/client/home-shadcn/pages/MemberPage.tsx

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

+ 142 - 154
src/client/home-shadcn/pages/RegisterPage.tsx

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

+ 1 - 1
src/client/index.tsx

@@ -2,5 +2,5 @@
 if (window.location.pathname.startsWith('/admin')) {
   import('./admin-shadcn/index')
 } else {
-  import('./home/index')
+  import('./home-shadcn/index')
 }