|
|
@@ -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>
|
|
|
);
|
|
|
};
|
|
|
|