实现了个人主页和主页的连接,以及个人主页界面的初步实现,为后续作品、通知显示预留出位置

Change-Id: Iad88dde17107c9d229de1d88a0ea696962560e67
diff --git a/src/AppLayout.tsx b/src/AppLayout.tsx
index ba15c74..93cef48 100644
--- a/src/AppLayout.tsx
+++ b/src/AppLayout.tsx
@@ -73,9 +73,9 @@
     const dropdownMenuItems = [
         {
             key: 'profile',
-            label: '个人中心',
+            label: '个人主页',
             icon: <UserOutlined />,
-            onClick: () => navigate('/profile'),
+            onClick: () => navigate('/user'),
         },
         {
             key: 'settings',
diff --git a/src/feature/user/UserHome.tsx b/src/feature/user/UserHome.tsx
new file mode 100644
index 0000000..5f925dc
--- /dev/null
+++ b/src/feature/user/UserHome.tsx
@@ -0,0 +1,370 @@
+import { Avatar, Button, Card, Col, Row, Space, Typography, Divider, Badge, Empty, Spin } from 'antd';
+import { UserOutlined, MailOutlined, PlusOutlined, BellOutlined, EyeOutlined, CalendarOutlined } from '@ant-design/icons';
+import axios from 'axios';
+import { useEffect, useState } from 'react';
+import { useAppSelector, useAppDispatch } from '../../store/hooks';
+import { getUserInfo } from './userSlice';
+
+const { Title, Text, Paragraph } = Typography;
+
+// 定义 WorkResponse 接口,与后端 WorkResponse 类对应
+interface WorkResponse {
+  id: number;
+  title: string;
+  author: string;
+  views: number;
+  categoryId: number;
+  description: string;
+  createTime: string;
+}
+
+// 模拟通知数据
+const mockNotifications = [
+  {
+    id: 1,
+    title: '系统通知',
+    content: '您的作品《创意设计》获得了新的点赞!',
+    time: '2小时前',
+    type: 'like',
+    unread: true
+  },
+  {
+    id: 2,
+    title: '评论通知',
+    content: '用户"设计师小王"评论了您的作品',
+    time: '1天前',
+    type: 'comment',
+    unread: true
+  },
+  {
+    id: 3,
+    title: '系统消息',
+    content: '平台将于本周末进行系统维护',
+    time: '3天前',
+    type: 'system',
+    unread: false
+  }
+];
+
+function UserHome() {
+  const userState = useAppSelector(state => state.user);
+  const dispatch = useAppDispatch();
+  const [userWorks, setUserWorks] = useState<WorkResponse[]>([]);
+  const [loading, setLoading] = useState(false);
+  const [pageLoading, setPageLoading] = useState(true);
+
+  // 检查token并获取用户信息
+  useEffect(() => {
+    const initializeUser = async () => {
+      const token = localStorage.getItem('token');
+      if (!token) {
+        // 如果没有token,重定向到登录页
+        window.location.href = '/login';
+        return;
+      }
+
+      // 如果用户信息为空或状态为idle,重新获取
+      if (!userState.username || userState.status === 'idle') {
+        try {
+          await dispatch(getUserInfo()).unwrap();
+        } catch (error) {
+          console.error('获取用户信息失败:', error);
+          // 如果获取用户信息失败,可能token过期,重定向到登录页
+          localStorage.removeItem('token');
+          window.location.href = '/login';
+          return;
+        }
+      }
+      setPageLoading(false);
+    };
+
+    initializeUser();
+  }, [dispatch, userState.username, userState.status]);
+
+  // 获取用户作品
+  useEffect(() => {
+    const fetchUserWorks = async () => {
+      if (!userState.username) return;
+
+      try {
+        setLoading(true);
+        const token = localStorage.getItem('token');
+        if (token) {
+          const response = await axios.get('/api/works/works/byAuthor', {
+            headers: {
+              token: token
+            },
+            params: {
+              author: userState.username
+            }
+          });
+          if (response.data.code === 200) {
+            setUserWorks(response.data.data || []);
+          } else {
+            console.error('获取作品失败:', response.data.message);
+            setUserWorks([]);
+          }
+        }
+      } catch (error) {
+        console.error('获取用户作品信息失败:', error);
+        setUserWorks([]);
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    // 只有当用户信息加载完成且用户名存在时才获取作品
+    if (userState.username && userState.status === 'succeeded') {
+      fetchUserWorks();
+    }
+  }, [userState.username, userState.status]);
+
+  // 格式化时间显示
+  const formatDate = (dateString: string) => {
+    return new Date(dateString).toLocaleDateString('zh-CN');
+  };
+
+  // 获取分类名称
+  const getCategoryName = (categoryId: number) => {
+    const categories: { [key: number]: string } = {
+      1: '文学创作',
+      2: '视觉设计',
+      3: '音乐创作',
+      4: '影视制作',
+      5: '其他'
+    };
+    return categories[categoryId] || '未分类';
+  };
+
+  // 如果页面正在初始化,显示加载状态
+  if (pageLoading || userState.status === 'loading') {
+    return (
+      <div style={{ 
+        display: 'flex', 
+        justifyContent: 'center', 
+        alignItems: 'center', 
+        minHeight: '400px' 
+      }}>
+        <Spin size="large" tip="加载用户信息中..." />
+      </div>
+    );
+  }
+
+  // 如果获取用户信息失败
+  if (userState.status === 'failed' || !userState.username) {
+    return (
+      <div style={{ 
+        display: 'flex', 
+        justifyContent: 'center', 
+        alignItems: 'center', 
+        minHeight: '400px',
+        flexDirection: 'column'
+      }}>
+        <Empty 
+          description="无法获取用户信息"
+          style={{ marginBottom: 16 }}
+        />
+        <Button 
+          type="primary" 
+          onClick={() => dispatch(getUserInfo())}
+        >
+          重新加载
+        </Button>
+      </div>
+    );
+  }
+
+  return (
+    <div className="user-home-container" style={{ padding: '0 24px' }}>
+      {/* 用户信息横栏 */}
+      <Card 
+        style={{ 
+          marginBottom: 24, 
+          background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
+          border: 'none'
+        }}
+      >
+        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
+          <Space size="large" align="center">
+            <Avatar 
+              size={80} 
+              icon={<UserOutlined />} 
+              style={{ backgroundColor: '#fff', color: '#667eea' }}
+            />
+            <div>
+              <Title level={2} style={{ color: 'white', margin: 0 }}>
+                {userState.username}
+              </Title>
+              <Text style={{ color: 'rgba(255,255,255,0.8)', fontSize: '16px' }}>
+                <MailOutlined /> {userState.email || '暂无邮箱'}
+              </Text>
+              <div style={{ marginTop: 8 }}>
+                <Text style={{ color: 'rgba(255,255,255,0.6)' }}>
+                  用户ID: {userState.userid}
+                </Text>
+              </div>
+            </div>
+          </Space>
+          <div style={{ textAlign: 'center', color: 'white' }}>
+            <div style={{ fontSize: '24px', fontWeight: 'bold' }}>{userWorks.length}</div>
+            <div style={{ opacity: 0.8 }}>发布作品</div>
+          </div>
+        </div>
+      </Card>
+
+      {/* 两栏布局 */}
+      <Row gutter={24}>
+        {/* 左侧:作品展示栏 */}
+        <Col span={16}>
+          <Card 
+            title={
+              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
+                <span>我的作品</span>
+                <Button 
+                  type="primary" 
+                  icon={<PlusOutlined />}
+                  onClick={() => {
+                    // 这里后续添加跳转到发布作品页面的逻辑
+                    console.log('跳转到发布作品页面');
+                  }}
+                >
+                  发布作品
+                </Button>
+              </div>
+            }
+            style={{ minHeight: '600px' }}
+          >
+            {loading ? (
+              <div style={{ textAlign: 'center', padding: '50px 0' }}>
+                <Spin size="large" tip="加载作品中..." />
+              </div>
+            ) : userWorks.length === 0 ? (
+              <Empty 
+                description="暂无作品,快去发布第一个作品吧!"
+                style={{ padding: '50px 0' }}
+              />
+            ) : (
+              <Row gutter={[16, 16]}>
+                {userWorks.map((work) => (
+                  <Col span={12} key={work.id}>
+                    <Card 
+                      hoverable
+                      style={{ height: '100%' }}
+                      actions={[
+                        <div key="views" style={{ color: '#666' }}>
+                          <EyeOutlined /> {work.views}
+                        </div>,
+                        <div key="category" style={{ color: '#666' }}>
+                          {getCategoryName(work.categoryId)}
+                        </div>
+                      ]}
+                    >
+                      <Card.Meta
+                        title={
+                          <div style={{ 
+                            overflow: 'hidden', 
+                            textOverflow: 'ellipsis', 
+                            whiteSpace: 'nowrap' 
+                          }}>
+                            {work.title}
+                          </div>
+                        }
+                        description={
+                          <div>
+                            <Paragraph 
+                              ellipsis={{ rows: 2 }} 
+                              style={{ marginBottom: 8, minHeight: '40px' }}
+                            >
+                              {work.description || '暂无描述'}
+                            </Paragraph>
+                            <div style={{ 
+                              display: 'flex', 
+                              justifyContent: 'space-between', 
+                              fontSize: '12px', 
+                              color: '#999' 
+                            }}>
+                              <span>
+                                <CalendarOutlined /> {formatDate(work.createTime)}
+                              </span>
+                              <span>ID: {work.id}</span>
+                            </div>
+                          </div>
+                        }
+                      />
+                    </Card>
+                  </Col>
+                ))}
+              </Row>
+            )}
+          </Card>
+        </Col>
+
+        {/* 右侧:通知栏 */}
+        <Col span={8}>
+          <Card 
+            title={
+              <div style={{ display: 'flex', alignItems: 'center' }}>
+                <BellOutlined style={{ marginRight: 8 }} />
+                通知中心
+                <Badge count={2} style={{ marginLeft: 8 }} />
+              </div>
+            }
+            style={{ minHeight: '600px' }}
+          >
+            <div>
+              {mockNotifications.map((notification, index) => (
+                <div key={notification.id}>
+                  <div 
+                    style={{ 
+                      padding: '12px',
+                      backgroundColor: notification.unread ? '#f6ffed' : 'transparent',
+                      borderRadius: '4px',
+                    }}
+                  >
+                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
+                      <div style={{ flex: 1 }}>
+                        <div style={{ 
+                          fontWeight: notification.unread ? 'bold' : 'normal',
+                          marginBottom: '4px',
+                          display: 'flex',
+                          alignItems: 'center'
+                        }}>
+                          {notification.title}
+                          {notification.unread && (
+                            <Badge 
+                              color="red" 
+                              style={{ marginLeft: '8px' }}
+                            />
+                          )}
+                        </div>
+                        <div style={{ 
+                          color: '#666', 
+                          fontSize: '14px', 
+                          lineHeight: '1.4',
+                          marginBottom: '8px'
+                        }}>
+                          {notification.content}
+                        </div>
+                        <div style={{ color: '#999', fontSize: '12px' }}>
+                          {notification.time}
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                  {index < mockNotifications.length - 1 && <Divider style={{ margin: '0' }} />}
+                </div>
+              ))}
+              
+              {/* 查看更多按钮 */}
+              <div style={{ textAlign: 'center', marginTop: '16px' }}>
+                <Button type="link">查看全部通知</Button>
+              </div>
+            </div>
+          </Card>
+        </Col>
+      </Row>
+    </div>
+  );
+}
+
+export default UserHome;
\ No newline at end of file
diff --git a/src/feature/user/UserLayout.tsx b/src/feature/user/UserLayout.tsx
new file mode 100644
index 0000000..cb88dde
--- /dev/null
+++ b/src/feature/user/UserLayout.tsx
@@ -0,0 +1,18 @@
+import { Layout } from 'antd';
+import { Outlet } from 'react-router';
+
+const { Content } = Layout;
+
+function UserLayout() {
+  return (
+        
+        <Content style={{ margin: '24px 16px 0' }}>
+          <div style={{ padding: 24, minHeight: 360 }}>
+            <Outlet /> {/* 这里会渲染子路由对应的组件 */}
+          </div>
+        </Content>
+      
+  );
+}
+
+export default UserLayout;
\ No newline at end of file
diff --git a/src/routes/routes.ts b/src/routes/routes.ts
index 401b6bb..d380bde 100644
--- a/src/routes/routes.ts
+++ b/src/routes/routes.ts
@@ -11,6 +11,8 @@
 import Work from "../feature/work/Work";
 import CreateWork from "../feature/work/CreateWork";
 
+import UserHome from "../feature/user/UserHome";
+
 // 创建受保护的组件
 const ProtectedHome = withProtect(Home);
 const ProtectedWork = withProtect(Work);
@@ -36,6 +38,10 @@
         {path: 'categories/other', Component: OtherCategory,},
         {path: '/works/:id', Component: WorkPage, },
       {
+        path: "user",
+        Component: UserHome,
+      },
+      {
         Component: AuthLayout,
         children: [
           { path: "/login", Component: Login },