Explorar el Código

✨ feat(login): 优化登录页面功能与样式

- 移除未使用的Link和authClient导入
- 修复表单提交时的变量名错误,将values改为data
- 移除message组件依赖,使用原生alert替代
- 更新页面标题和描述文本,使其更通用
- 优化输入框聚焦样式,添加focus:border-transparent
- 将密码显示切换的div改为button元素,符合语义化要求
- 为注册按钮添加type="button"属性,防止表单提交

✨ feat(member): 重构用户资料页面为成员页面

- 重命名UserProfilePage为MemberPage,更新导出名称
- 移除antd组件依赖,使用原生HTML和Tailwind CSS重构页面
- 优化用户头像显示逻辑,使用条件渲染
- 重构用户统计数据展示,简化为内容、关注和粉丝三个统计项
- 添加用户资料详情区域,展示用户名、邮箱、注册时间和最后登录信息
- 添加安全设置按钮,链接到安全设置页面
- 移除未使用的followerCount和followingCount状态
- 优化错误日志输出,使用console.error替代rpcLogger
- 简化加载状态,使用CSS动画替代Spin组件
- 更新页面布局结构,使用Tailwind CSS实现响应式设计
yourname hace 4 meses
padre
commit
b790f30a04
Se han modificado 2 ficheros con 124 adiciones y 171 borrados
  1. 17 19
      src/client/home/pages/LoginPage.tsx
  2. 107 152
      src/client/home/pages/MemberPage.tsx

+ 17 - 19
src/client/home/pages/LoginPage.tsx

@@ -1,28 +1,24 @@
-import React from 'react';
+import React, { useState } 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 { useNavigate } from 'react-router-dom';
 import { useAuth } from '@/client/home/hooks/AuthProvider';
 
 const LoginPage: React.FC = () => {
-  const { register, handleSubmit, formState: { errors }, watch } = useForm();
-  const [showPassword, setShowPassword] = React.useState(false);
+  const { register, handleSubmit, formState: { errors } } = useForm();
+  const [showPassword, setShowPassword] = useState(false);
   const [loading, setLoading] = useState(false);
   const { login } = useAuth();
   const navigate = useNavigate();
-  const password = watch('password', '');
 
   const onSubmit = async (data: any) => {
     try {
       setLoading(true);
-      await login(values.username, values.password);
-      
-      message.success('登录成功');
+      await login(data.username, data.password);
       navigate('/');
     } catch (error) {
       console.error('Login error:', error);
-      message.error((error as Error).message || '登录失败,请检查用户名和密码');
+      alert((error as Error).message || '登录失败,请检查用户名和密码');
     } finally {
       setLoading(false);
     }
@@ -33,8 +29,8 @@ const LoginPage: React.FC = () => {
       <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>
+            <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">
@@ -49,9 +45,9 @@ const LoginPage: React.FC = () => {
                 <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`}
+                  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', {
+                  {...register('username', { 
                     required: '用户名不能为空',
                     minLength: { value: 3, message: '用户名至少3个字符' }
                   })}
@@ -73,15 +69,16 @@ const LoginPage: React.FC = () => {
                 <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`}
+                  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', {
+                  {...register('password', { 
                     required: '密码不能为空',
                     minLength: { value: 6, message: '密码至少6个字符' }
                   })}
                 />
-                <div
-                  className="absolute inset-y-0 right-0 pr-3 flex items-center cursor-pointer"
+                <button 
+                  type="button"
+                  className="absolute inset-y-0 right-0 pr-3 flex items-center"
                   onClick={() => setShowPassword(!showPassword)}
                 >
                   {showPassword ? (
@@ -89,7 +86,7 @@ const LoginPage: React.FC = () => {
                   ) : (
                     <EyeIcon className="h-5 w-5 text-gray-400" />
                   )}
-                </div>
+                </button>
               </div>
               {errors.password && (
                 <p className="mt-1 text-sm text-red-600">{errors.password.message?.toString()}</p>
@@ -119,6 +116,7 @@ const LoginPage: React.FC = () => {
             
             <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"
               >

+ 107 - 152
src/client/home/pages/MemberPage.tsx

@@ -1,13 +1,13 @@
 import debug from 'debug';
 const rpcLogger = debug('frontend:api:rpc');
 import React, { useEffect, useState } from 'react';
-import { UserIcon, EditIcon, ExternalLinkIcon, Loader2Icon } from '@heroicons/react/24/outline';
+import { UserIcon, PencilIcon } 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 UserProfilePage: React.FC = () => {
+const MemberPage: React.FC = () => {
   const [user, setUser] = useState<UserEntity | null>(null);
   const [loading, setLoading] = useState(true);
   const { id: userId } = useParams<{ id: string }>();
@@ -31,7 +31,7 @@ const UserProfilePage: React.FC = () => {
       const userData = await response.json();
       setUser(userData);
     } catch (error) {
-      rpcLogger.error('Error fetching user profile:', error);
+      console.error('Error fetching user profile:', error);
     } finally {
       setLoading(false);
     }
@@ -43,183 +43,138 @@ const UserProfilePage: React.FC = () => {
 
   if (loading) {
     return (
-      <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '50vh' }}>
-        <Spin size="large" />
+      <div className="flex justify-center items-center min-h-[50vh]">
+        <div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
       </div>
     );
   }
 
   if (!user) {
     return (
-      <div style={{ textAlign: 'center', padding: '50px' }}>
-        <Title level={3}>用户不存在</Title>
-        <Button onClick={() => navigate('/')}>返回首页</Button>
+      <div className="text-center py-12">
+        <h2 className="text-2xl font-bold text-gray-900 mb-4">用户不存在</h2>
+        <button 
+          onClick={() => navigate('/')}
+          className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
+        >
+          返回首页
+        </button>
       </div>
     );
   }
 
   return (
-    <Layout>
-      <Content style={{ padding: '24px', maxWidth: 1200, margin: '0 auto', width: '100%' }}>
-        <Card style={{ marginBottom: 24 }}>
-          <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', padding: '24px 0' }}>
-            <Avatar 
-              src={user.avatar || <UserOutlined style={{ fontSize: 64 }} />} 
-              size={128} 
-              style={{ marginBottom: 16 }}
-            />
-            <Title level={2} style={{ margin: 0 }}>{user.nickname || user.username}</Title>
+    <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 style={{ display: 'flex', margin: '16px 0' }}>
-              <div style={{ textAlign: 'center', margin: '0 24px' }}>
-                <Text strong style={{ fontSize: 24 }}>0</Text>
-                <br />
-                <Text>帖子</Text>
+            <div className="flex space-x-8 my-4">
+              <div className="text-center">
+                <p className="text-2xl font-semibold text-gray-900">0</p>
+                <p className="text-sm text-gray-500">内容</p>
               </div>
-              <div style={{ textAlign: 'center', margin: '0 24px' }}>
-                <Text strong style={{ fontSize: 24 }}>{followerCount}</Text>
-                <br />
-                <Text>粉丝</Text>
+              <div className="text-center">
+                <p className="text-2xl font-semibold text-gray-900">0</p>
+                <p className="text-sm text-gray-500">关注</p>
               </div>
-              <div style={{ textAlign: 'center', margin: '0 24px' }}>
-                <Text strong style={{ fontSize: 24 }}>{followingCount}</Text>
-                <br />
-                <Text>关注</Text>
+              <div className="text-center">
+                <p className="text-2xl font-semibold text-gray-900">0</p>
+                <p className="text-sm text-gray-500">粉丝</p>
               </div>
             </div>
             
-            {currentUser && currentUser.id !== user.id ? (
-              <Button 
-                type={isFollowing ? "default" : "primary"} 
-                icon={isFollowing ? <UserDeleteOutlined /> : <UserAddOutlined />}
-                onClick={handleFollowToggle}
+            {currentUser && currentUser.id === user.id && (
+              <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"
               >
-                {isFollowing ? '取消关注' : '关注'}
-              </Button>
-            ) : currentUser && currentUser.id === user.id ? (
-              <Button icon={<EditOutlined />} onClick={() => navigate('/profile/edit')}>
+                <PencilIcon className="w-4 h-4 mr-2" />
                 编辑资料
-              </Button>
-            ) : null}
+              </button>
+            )}
             
-            {user.bio && (
-              <Paragraph style={{ marginTop: 16, maxWidth: 600, textAlign: 'center' }}>
-                {user.bio}
-              </Paragraph>
+            {(user as any).bio && (
+              <p className="mt-4 text-center text-gray-600 max-w-lg">
+                {(user as any).bio}
+              </p>
             )}
             
-            <div style={{ display: 'flex', alignItems: 'center', marginTop: 8 }}>
-              {user.location && (
-                <Text style={{ marginRight: 16 }}>{user.location}</Text>
+            <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.website && (
-                <Text 
-                  style={{ color: '#1890ff', cursor: 'pointer' }}
-                  onClick={() => window.open(user.website, '_blank')}
+              {(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"
                 >
-                  {user.website}
-                </Text>
+                  <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>
-        </Card>
+        </div>
         
-        <Tabs defaultActiveKey="posts" style={{ marginBottom: 24 }}>
-          <TabPane tab="帖子" key="posts">
-            <List
-              dataSource={[]} // 这里应该是用户的帖子数据
-              renderItem={item => (
-                <List.Item
-                  actions={[
-                    <Button icon={<HeartOutlined />}>
-                      {item.likesCount > 0 && item.likesCount}
-                    </Button>
-                  ]}
-                >
-                  <List.Item.Meta
-                    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>
-              )}
-              locale={{ emptyText: '该用户暂无帖子' }}
-            />
-          </TabPane>
-          <TabPane tab="关注" key="following">
-            <List
-              dataSource={[]} // 这里应该是用户关注的人数据
-              renderItem={item => (
-                <List.Item
-                  actions={[
-                    <Button 
-                      size="small" 
-                      type={item.isFollowing ? "default" : "primary"}
-                      onClick={() => {/* 关注/取消关注逻辑 */}}
-                    >
-                      {item.isFollowing ? '已关注' : '关注'}
-                    </Button>
-                  ]}
-                >
-                  <List.Item.Meta
-                    avatar={<Avatar src={item.avatar || <UserOutlined />} />}
-                    title={
-                      <Text 
-                        style={{ cursor: 'pointer' }}
-                        onClick={() => navigate(`/users/${item.id}`)}
-                      >
-                        {item.nickname || item.username}
-                      </Text>
-                    }
-                    description={item.bio || '暂无简介'}
-                  />
-                </List.Item>
-              )}
-              locale={{ emptyText: '该用户暂无关注' }}
-            />
-          </TabPane>
-          <TabPane tab="粉丝" key="followers">
-            <List
-              dataSource={[]} // 这里应该是用户的粉丝数据
-              renderItem={item => (
-                <List.Item
-                  actions={[
-                    <Button 
-                      size="small" 
-                      type={item.isFollowing ? "default" : "primary"}
-                      onClick={() => {/* 关注/取消关注逻辑 */}}
-                    >
-                      {item.isFollowing ? '已关注' : '关注'}
-                    </Button>
-                  ]}
-                >
-                  <List.Item.Meta
-                    avatar={<Avatar src={item.avatar || <UserOutlined />} />}
-                    title={
-                      <Text 
-                        style={{ cursor: 'pointer' }}
-                        onClick={() => navigate(`/users/${item.id}`)}
-                      >
-                        {item.nickname || item.username}
-                      </Text>
-                    }
-                    description={item.bio || '暂无简介'}
-                  />
-                </List.Item>
-              )}
-              locale={{ emptyText: '该用户暂无粉丝' }}
-            />
-          </TabPane>
-        </Tabs>
-      </Content>
-    </Layout>
+        {/* 用户内容区域 */}
+        <div className="bg-white rounded-lg shadow-sm p-6">
+          <h2 className="text-xl font-semibold mb-6">个人资料</h2>
+          
+          <div className="space-y-4">
+            <div className="border-b border-gray-100 pb-4">
+              <h3 className="text-sm font-medium text-gray-500 mb-1">用户名</h3>
+              <p className="text-gray-900">{user.username}</p>
+            </div>
+            
+            <div className="border-b border-gray-100 pb-4">
+              <h3 className="text-sm font-medium text-gray-500 mb-1">电子邮箱</h3>
+              <p className="text-gray-900">{user.email || '未设置'}</p>
+            </div>
+            
+            <div className="border-b border-gray-100 pb-4">
+              <h3 className="text-sm font-medium text-gray-500 mb-1">注册时间</h3>
+              <p className="text-gray-900">{user.createdAt ? new Date(user.createdAt).toLocaleDateString() : '未知'}</p>
+            </div>
+            
+            <div className="border-b border-gray-100 pb-4">
+              <h3 className="text-sm font-medium text-gray-500 mb-1">最后登录</h3>
+              <p className="text-gray-900">{user.updatedAt ? new Date(user.updatedAt).toLocaleString() : '从未登录'}</p>
+            </div>
+          </div>
+          
+          <div className="mt-8">
+            <button
+              onClick={() => navigate('/profile/security')}
+              className="w-full py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
+            >
+              安全设置
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
   );
 };
 
-export default UserProfilePage;
+export default MemberPage;