Forráskód Böngészése

✨ feat(ui): 重构前端页面UI设计

- 移除Ant Design组件库,采用Tailwind CSS重构所有页面样式
- 优化首页布局,采用卡片式设计展示功能特点
- 改进登录页面UI,添加表单验证和密码显示/隐藏功能
- 统一页面风格,实现响应式设计适配各种设备屏幕
- 优化用户资料页面,简化界面元素和交互逻辑

♻️ refactor(client): 简化前端代码结构

- 移除首页内容流、帖子发布和社交功能相关代码
- 删除用户关注、推荐用户等社交功能实现
- 简化用户资料页面,移除关注状态和数量统计功能
- 清理未使用的API调用和状态管理逻辑
- 更新图标库,使用Heroicons替代Ant Design图标
yourname 4 hónapja
szülő
commit
2146348b50

+ 81 - 186
src/client/home/pages/HomePage.tsx

@@ -1,202 +1,97 @@
-import React, { useEffect, useState } from 'react';
-import { Layout, List, Card, Avatar, Button, Input, Space, Typography, Spin, Empty, Divider } from 'antd';
-import { UserOutlined, MessageOutlined, HeartOutlined, SendOutlined, UserAddOutlined } from '@ant-design/icons';
+import React from 'react';
 import { useAuth } from '@/client/home/hooks/AuthProvider';
 import { useNavigate } from 'react-router-dom';
-import { postClient, userClient } from '@/client/api';
-import type { PostEntity } from '@/server/modules/posts/post.entity';
-import type { UserEntity } from '@/server/modules/users/user.entity';
-
-const { Header, Content, Footer } = Layout;
-const { Title, Text, Paragraph } = Typography;
 
 const HomePage: React.FC = () => {
-  const [posts, setPosts] = useState<PostEntity[]>([]);
-  const [loading, setLoading] = useState(true);
-  const [content, setContent] = useState('');
-  const [recommendedUsers, setRecommendedUsers] = useState<UserEntity[]>([]);
-  const [hasFollowing, setHasFollowing] = useState(true);
   const { user } = useAuth();
   const navigate = useNavigate();
 
-  // 获取首页内容流
-  // 获取推荐用户
-  const fetchRecommendedUsers = async () => {
-    try {
-      const response = await userClient.recommended.$get({
-        query: { limit: 5 }
-      });
-      
-      if (!response.ok) throw new Error('获取推荐用户失败');
-      
-      const data = await response.json();
-      setRecommendedUsers(data.data);
-    } catch (error) {
-      console.error('Error fetching recommended users:', error);
-    }
-  };
-
-  // 获取首页内容流
-  const fetchPosts = async () => {
-    try {
-      setLoading(true);
-      const response = await postClient.$get({
-        query: {
-          page: 1,
-          pageSize: 10
-        }
-      });
-      
-      if (!response.ok) throw new Error('获取内容失败');
-      
-      const data = await response.json();
-      setPosts(data.data);
-      setHasFollowing(data.meta?.hasFollowing ?? true);
-      
-      // 如果没有关注,则获取推荐用户
-      if (!data.meta?.hasFollowing) {
-        await fetchRecommendedUsers();
-      }
-    } catch (error) {
-      console.error('Error fetching posts:', error);
-    } finally {
-      setLoading(false);
-    }
-  };
-
-  // 创建新帖子
-  const handlePost = async () => {
-    if (!content.trim()) return;
-    
-    try {
-      const response = await postClient.$post({
-        json: {
-          content
-        }
-      });
-      
-      if (!response.ok) throw new Error('发布失败');
-      
-      const newPost = await response.json();
-      setPosts([newPost, ...posts]);
-      setContent('');
-    } catch (error) {
-      console.error('Error creating post:', error);
-    }
-  };
-
-  // 点赞帖子
-  const handleLike = async (postId: number) => {
-    try {
-      await postClient[':id'].like.$post({
-        param: { id: postId }
-      });
-      setPosts(posts.map(post =>
-        post.id === postId ? { ...post, likesCount: post.likesCount + 1 } : post
-      ));
-    } catch (error) {
-      console.error('Error liking post:', error);
-    }
-  };
-
-  // 关注用户
-  const handleFollow = async (userId: number) => {
-    try {
-      await userClient[':id'].follow.$post({
-        param: { id: userId }
-      });
-      
-      // 从推荐列表中移除已关注用户
-      setRecommendedUsers(recommendedUsers.filter(user => user.id !== userId));
-      
-      // 刷新帖子列表
-      fetchPosts();
-    } catch (error) {
-      console.error('Error following user:', error);
-    }
-  };
-
-  useEffect(() => {
-    fetchPosts();
-  }, []);
-
   return (
-    <Layout className="min-h-screen">
-      <Header style={{ position: 'fixed', zIndex: 1, width: '100%', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
-        <Title level={3} style={{ color: 'white', margin: 0 }}>社交媒体平台</Title>
-        <div style={{ display: 'flex', alignItems: 'center' }}>
-          <Button type="text" style={{ color: 'white', marginRight: 16 }} onClick={() => navigate('/follow?type=following')}>
-            关注
-          </Button>
-          <Button type="text" style={{ color: 'white', marginRight: 16 }} onClick={() => navigate('/follow?type=followers')}>
-            粉丝
-          </Button>
-          <Avatar icon={<UserOutlined />} style={{ marginRight: 16 }} onClick={() => navigate(`/users/${user?.id}`)} />
-          <Text style={{ color: 'white' }}>{user?.username}</Text>
+    <div className="min-h-screen bg-gray-50 flex flex-col">
+      {/* 顶部导航 */}
+      <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(`/users/${user?.id}`)}>
+                <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"
+              >
+                注册
+              </button>
+            </div>
+          )}
         </div>
-      </Header>
+      </header>
       
-      <Content style={{ padding: '0 50px', marginTop: 64 }}>
-        <div style={{ background: '#fff', padding: 24, marginTop: 20, borderRadius: 8, maxWidth: 800, margin: '0 auto' }}>
-          {/* 发布框 */}
-          <Card style={{ marginBottom: 24 }}>
-            <Space.Compact style={{ width: '100%' }}>
-              <Input.TextArea 
-                placeholder="分享你的想法..." 
-                rows={4}
-                value={content}
-                onChange={e => setContent(e.target.value)}
-              />
-              <Button type="primary" icon={<SendOutlined />} onClick={handlePost}>
-                发布
-              </Button>
-            </Space.Compact>
-          </Card>
+      {/* 主内容区 */}
+      <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>
           
-          {/* 内容流 */}
-          <Title level={4}>最新动态</Title>
-          {loading ? (
-            <Spin size="large" style={{ display: 'block', margin: '40px auto' }} />
-          ) : posts.length === 0 ? (
-            <Empty description="暂无内容" />
-          ) : (
-            <List
-              dataSource={posts}
-              renderItem={item => (
-                <List.Item
-                  key={item.id}
-                  actions={[
-                    <Button icon={<HeartOutlined />} onClick={() => handleLike(item.id)}>
-                      {item.likesCount > 0 && item.likesCount}
-                    </Button>,
-                    <Button icon={<MessageOutlined />}>{item.commentsCount > 0 && item.commentsCount}</Button>,
-                    <Button>分享</Button>
-                  ]}
-                >
-                  <List.Item.Meta
-                    avatar={<Avatar src={item.user?.avatar || <UserOutlined />} />}
-                    title={item.user?.username}
-                    description={
-                      <>
-                        <Paragraph>{item.content}</Paragraph>
-                        {item.images?.map((img, idx) => (
-                          <img key={idx} src={img} alt={`Post image ${idx}`} style={{ maxWidth: '100%', margin: '8px 0' }} />
-                        ))}
-                        <Text type="secondary">{new Date(item.createdAt).toLocaleString()}</Text>
-                      </>
-                    }
-                  />
-                </List.Item>
-              )}
-            />
-          )}
+          <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>
+          </div>
         </div>
-      </Content>
+      </main>
       
-      <Footer style={{ textAlign: 'center' }}>
-        社交媒体平台 ©{new Date().getFullYear()} Created with React & Ant Design
-      </Footer>
-    </Layout>
+      {/* 页脚 */}
+      <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>
+      </footer>
+    </div>
   );
 };
 

+ 104 - 62
src/client/home/pages/LoginPage.tsx

@@ -1,19 +1,19 @@
-import React, { useState } from 'react';
-import { Form, Input, Button, Card, Typography, message, Divider } from 'antd';
-import { UserOutlined, LockOutlined } from '@ant-design/icons';
+import React from 'react';
+import { useForm } from 'react-hook-form';
+import { EyeIcon, EyeOffIcon, UserIcon, LockClosedIcon } from '@heroicons/react/24/outline';
 import { Link, useNavigate } from 'react-router-dom';
 import { authClient } from '@/client/api';
 import { useAuth } from '@/client/home/hooks/AuthProvider';
 
-const { Title } = Typography;
-
 const LoginPage: React.FC = () => {
+  const { register, handleSubmit, formState: { errors }, watch } = useForm();
+  const [showPassword, setShowPassword] = React.useState(false);
   const [loading, setLoading] = useState(false);
-  const [form] = Form.useForm();
   const { login } = useAuth();
   const navigate = useNavigate();
+  const password = watch('password', '');
 
-  const handleSubmit = async (values: any) => {
+  const onSubmit = async (data: any) => {
     try {
       setLoading(true);
       await login(values.username, values.password);
@@ -29,63 +29,105 @@ const LoginPage: React.FC = () => {
   };
 
   return (
-    <div className="flex justify-center items-center min-h-screen bg-gray-50">
-      <Card className="w-full max-w-md shadow-lg">
-        <div className="text-center mb-6">
-          <Title level={2}>社交媒体平台</Title>
-          <p className="text-gray-500">登录您的账号</p>
-        </div>
-        
-        <Form
-          form={form}
-          name="login_form"
-          layout="vertical"
-          onFinish={handleSubmit}
-        >
-          <Form.Item
-            name="username"
-            label="用户名"
-            rules={[{ required: true, message: '请输入用户名' }]}
-          >
-            <Input 
-              prefix={<UserOutlined className="text-primary" />} 
-              placeholder="请输入用户名" 
-            />
-          </Form.Item>
+    <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.Item
-            name="password"
-            label="密码"
-            rules={[{ required: true, message: '请输入密码' }]}
-          >
-            <Input.Password
-              prefix={<LockOutlined className="text-primary" />}
-              placeholder="请输入密码"
-            />
-          </Form.Item>
+          <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`}
+                  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`}
+                  placeholder="请输入密码"
+                  {...register('password', {
+                    required: '密码不能为空',
+                    minLength: { value: 6, message: '密码至少6个字符' }
+                  })}
+                />
+                <div
+                  className="absolute inset-y-0 right-0 pr-3 flex items-center cursor-pointer"
+                  onClick={() => setShowPassword(!showPassword)}
+                >
+                  {showPassword ? (
+                    <EyeOffIcon className="h-5 w-5 text-gray-400" />
+                  ) : (
+                    <EyeIcon className="h-5 w-5 text-gray-400" />
+                  )}
+                </div>
+              </div>
+              {errors.password && (
+                <p className="mt-1 text-sm text-red-600">{errors.password.message?.toString()}</p>
+              )}
+            </div>
+            
+            <div>
+              <button
+                type="submit"
+                disabled={loading}
+                className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
+              >
+                {loading ? '登录中...' : '登录'}
+              </button>
+            </div>
+          </form>
           
-          <Form.Item>
-            <Button 
-              type="primary" 
-              htmlType="submit" 
-              className="w-full h-10 text-base"
-              loading={loading}
-            >
-              登录
-            </Button>
-          </Form.Item>
-        </Form>
-        
-        <Divider>还没有账号?</Divider>
-        
-        <Button 
-          type="default" 
-          className="w-full"
-          onClick={() => navigate('/register')}
-        >
-          注册账号
-        </Button>
-      </Card>
+          <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
+                onClick={() => navigate('/register')}
+                className="w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
+              >
+                注册账号
+              </button>
+            </div>
+          </div>
+        </div>
+      </div>
     </div>
   );
 };

+ 1 - 56
src/client/home/pages/MemberPage.tsx

@@ -1,23 +1,15 @@
 import debug from 'debug';
 const rpcLogger = debug('frontend:api:rpc');
 import React, { useEffect, useState } from 'react';
-import { Layout, Card, Avatar, Button, Typography, List, Spin, Tabs, Divider, Badge } from 'antd';
-import { UserOutlined, EditOutlined, HeartOutlined, UserAddOutlined, UserDeleteOutlined } from '@ant-design/icons';
+import { UserIcon, EditIcon, ExternalLinkIcon, Loader2Icon } from '@heroicons/react/24/outline';
 import { useParams, useNavigate } from 'react-router-dom';
 import { userClient } from '@/client/api';
 import { useAuth } from '@/client/home/hooks/AuthProvider';
 import type { UserEntity } from '@/server/modules/users/user.entity';
 
-const { Content } = Layout;
-const { Title, Text, Paragraph } = Typography;
-const { TabPane } = Tabs;
-
 const UserProfilePage: React.FC = () => {
   const [user, setUser] = useState<UserEntity | null>(null);
   const [loading, setLoading] = useState(true);
-  const [isFollowing, setIsFollowing] = useState(false);
-  const [followerCount, setFollowerCount] = useState(0);
-  const [followingCount, setFollowingCount] = useState(0);
   const { id: userId } = useParams<{ id: string }>();
   const id = Number(userId);
   const navigate = useNavigate();
@@ -38,31 +30,6 @@ const UserProfilePage: React.FC = () => {
       
       const userData = await response.json();
       setUser(userData);
-      
-      // 获取关注状态
-      if (currentUser && currentUser.id !== Number(id)) {
-        rpcLogger('Checking follow status from user %s to user %s', currentUser.id, id);
-        const followStatus = await userClient[':id'].following['$get']({
-          param: { id },
-          query: { page: 1}
-        });
-        setIsFollowing(followStatus.ok);
-      }
-      
-      // 获取关注数量
-      rpcLogger('Fetching followers count for user: %s', id);
-      const followers = await userClient[':id'].followers['$get']({ 
-        param: { id },
-        query: { pageSize: 1 } 
-      });
-      rpcLogger('Fetching following count for user: %s', id);
-      const following = await userClient[':id'].following['$get']({ 
-        param: { id },
-        query: { pageSize: 1 } 
-      });
-      
-      setFollowerCount(await followers.json().then(data => data.pagination.total));
-      setFollowingCount(await following.json().then(data => data.pagination.total));
     } catch (error) {
       rpcLogger.error('Error fetching user profile:', error);
     } finally {
@@ -70,28 +37,6 @@ const UserProfilePage: React.FC = () => {
     }
   };
 
-  // 关注/取消关注用户
-  const handleFollowToggle = async () => {
-    if (!currentUser || !id) return;
-    
-    try {
-      if (isFollowing) {
-        rpcLogger('Unfollowing user: %s', id);
-        await userClient[':id'].follow['$delete']({ param: { id: Number(id) } });
-        setFollowerCount(prev => prev - 1);
-      } else {
-        rpcLogger('Following user: %s', id);
-        await userClient[':id'].follow['$post']({ 
-          param: { id: Number(id) },
-        });
-        setFollowerCount(prev => prev + 1);
-      }
-      setIsFollowing(!isFollowing);
-    } catch (error) {
-      console.error('Error toggling follow status:', error);
-    }
-  };
-
   useEffect(() => {
     fetchUserProfile();
   }, [id]);