blob: 5f925dcb16938459cef045d97a33a6dc922607a4 [file] [log] [blame]
22301021313d1b22025-06-09 01:13:46 +08001import { Avatar, Button, Card, Col, Row, Space, Typography, Divider, Badge, Empty, Spin } from 'antd';
2import { UserOutlined, MailOutlined, PlusOutlined, BellOutlined, EyeOutlined, CalendarOutlined } from '@ant-design/icons';
3import axios from 'axios';
4import { useEffect, useState } from 'react';
5import { useAppSelector, useAppDispatch } from '../../store/hooks';
6import { getUserInfo } from './userSlice';
7
8const { Title, Text, Paragraph } = Typography;
9
10// 定义 WorkResponse 接口,与后端 WorkResponse 类对应
11interface WorkResponse {
12 id: number;
13 title: string;
14 author: string;
15 views: number;
16 categoryId: number;
17 description: string;
18 createTime: string;
19}
20
21// 模拟通知数据
22const mockNotifications = [
23 {
24 id: 1,
25 title: '系统通知',
26 content: '您的作品《创意设计》获得了新的点赞!',
27 time: '2小时前',
28 type: 'like',
29 unread: true
30 },
31 {
32 id: 2,
33 title: '评论通知',
34 content: '用户"设计师小王"评论了您的作品',
35 time: '1天前',
36 type: 'comment',
37 unread: true
38 },
39 {
40 id: 3,
41 title: '系统消息',
42 content: '平台将于本周末进行系统维护',
43 time: '3天前',
44 type: 'system',
45 unread: false
46 }
47];
48
49function UserHome() {
50 const userState = useAppSelector(state => state.user);
51 const dispatch = useAppDispatch();
52 const [userWorks, setUserWorks] = useState<WorkResponse[]>([]);
53 const [loading, setLoading] = useState(false);
54 const [pageLoading, setPageLoading] = useState(true);
55
56 // 检查token并获取用户信息
57 useEffect(() => {
58 const initializeUser = async () => {
59 const token = localStorage.getItem('token');
60 if (!token) {
61 // 如果没有token,重定向到登录页
62 window.location.href = '/login';
63 return;
64 }
65
66 // 如果用户信息为空或状态为idle,重新获取
67 if (!userState.username || userState.status === 'idle') {
68 try {
69 await dispatch(getUserInfo()).unwrap();
70 } catch (error) {
71 console.error('获取用户信息失败:', error);
72 // 如果获取用户信息失败,可能token过期,重定向到登录页
73 localStorage.removeItem('token');
74 window.location.href = '/login';
75 return;
76 }
77 }
78 setPageLoading(false);
79 };
80
81 initializeUser();
82 }, [dispatch, userState.username, userState.status]);
83
84 // 获取用户作品
85 useEffect(() => {
86 const fetchUserWorks = async () => {
87 if (!userState.username) return;
88
89 try {
90 setLoading(true);
91 const token = localStorage.getItem('token');
92 if (token) {
93 const response = await axios.get('/api/works/works/byAuthor', {
94 headers: {
95 token: token
96 },
97 params: {
98 author: userState.username
99 }
100 });
101 if (response.data.code === 200) {
102 setUserWorks(response.data.data || []);
103 } else {
104 console.error('获取作品失败:', response.data.message);
105 setUserWorks([]);
106 }
107 }
108 } catch (error) {
109 console.error('获取用户作品信息失败:', error);
110 setUserWorks([]);
111 } finally {
112 setLoading(false);
113 }
114 };
115
116 // 只有当用户信息加载完成且用户名存在时才获取作品
117 if (userState.username && userState.status === 'succeeded') {
118 fetchUserWorks();
119 }
120 }, [userState.username, userState.status]);
121
122 // 格式化时间显示
123 const formatDate = (dateString: string) => {
124 return new Date(dateString).toLocaleDateString('zh-CN');
125 };
126
127 // 获取分类名称
128 const getCategoryName = (categoryId: number) => {
129 const categories: { [key: number]: string } = {
130 1: '文学创作',
131 2: '视觉设计',
132 3: '音乐创作',
133 4: '影视制作',
134 5: '其他'
135 };
136 return categories[categoryId] || '未分类';
137 };
138
139 // 如果页面正在初始化,显示加载状态
140 if (pageLoading || userState.status === 'loading') {
141 return (
142 <div style={{
143 display: 'flex',
144 justifyContent: 'center',
145 alignItems: 'center',
146 minHeight: '400px'
147 }}>
148 <Spin size="large" tip="加载用户信息中..." />
149 </div>
150 );
151 }
152
153 // 如果获取用户信息失败
154 if (userState.status === 'failed' || !userState.username) {
155 return (
156 <div style={{
157 display: 'flex',
158 justifyContent: 'center',
159 alignItems: 'center',
160 minHeight: '400px',
161 flexDirection: 'column'
162 }}>
163 <Empty
164 description="无法获取用户信息"
165 style={{ marginBottom: 16 }}
166 />
167 <Button
168 type="primary"
169 onClick={() => dispatch(getUserInfo())}
170 >
171 重新加载
172 </Button>
173 </div>
174 );
175 }
176
177 return (
178 <div className="user-home-container" style={{ padding: '0 24px' }}>
179 {/* 用户信息横栏 */}
180 <Card
181 style={{
182 marginBottom: 24,
183 background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
184 border: 'none'
185 }}
186 >
187 <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
188 <Space size="large" align="center">
189 <Avatar
190 size={80}
191 icon={<UserOutlined />}
192 style={{ backgroundColor: '#fff', color: '#667eea' }}
193 />
194 <div>
195 <Title level={2} style={{ color: 'white', margin: 0 }}>
196 {userState.username}
197 </Title>
198 <Text style={{ color: 'rgba(255,255,255,0.8)', fontSize: '16px' }}>
199 <MailOutlined /> {userState.email || '暂无邮箱'}
200 </Text>
201 <div style={{ marginTop: 8 }}>
202 <Text style={{ color: 'rgba(255,255,255,0.6)' }}>
203 用户ID: {userState.userid}
204 </Text>
205 </div>
206 </div>
207 </Space>
208 <div style={{ textAlign: 'center', color: 'white' }}>
209 <div style={{ fontSize: '24px', fontWeight: 'bold' }}>{userWorks.length}</div>
210 <div style={{ opacity: 0.8 }}>发布作品</div>
211 </div>
212 </div>
213 </Card>
214
215 {/* 两栏布局 */}
216 <Row gutter={24}>
217 {/* 左侧:作品展示栏 */}
218 <Col span={16}>
219 <Card
220 title={
221 <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
222 <span>我的作品</span>
223 <Button
224 type="primary"
225 icon={<PlusOutlined />}
226 onClick={() => {
227 // 这里后续添加跳转到发布作品页面的逻辑
228 console.log('跳转到发布作品页面');
229 }}
230 >
231 发布作品
232 </Button>
233 </div>
234 }
235 style={{ minHeight: '600px' }}
236 >
237 {loading ? (
238 <div style={{ textAlign: 'center', padding: '50px 0' }}>
239 <Spin size="large" tip="加载作品中..." />
240 </div>
241 ) : userWorks.length === 0 ? (
242 <Empty
243 description="暂无作品,快去发布第一个作品吧!"
244 style={{ padding: '50px 0' }}
245 />
246 ) : (
247 <Row gutter={[16, 16]}>
248 {userWorks.map((work) => (
249 <Col span={12} key={work.id}>
250 <Card
251 hoverable
252 style={{ height: '100%' }}
253 actions={[
254 <div key="views" style={{ color: '#666' }}>
255 <EyeOutlined /> {work.views}
256 </div>,
257 <div key="category" style={{ color: '#666' }}>
258 {getCategoryName(work.categoryId)}
259 </div>
260 ]}
261 >
262 <Card.Meta
263 title={
264 <div style={{
265 overflow: 'hidden',
266 textOverflow: 'ellipsis',
267 whiteSpace: 'nowrap'
268 }}>
269 {work.title}
270 </div>
271 }
272 description={
273 <div>
274 <Paragraph
275 ellipsis={{ rows: 2 }}
276 style={{ marginBottom: 8, minHeight: '40px' }}
277 >
278 {work.description || '暂无描述'}
279 </Paragraph>
280 <div style={{
281 display: 'flex',
282 justifyContent: 'space-between',
283 fontSize: '12px',
284 color: '#999'
285 }}>
286 <span>
287 <CalendarOutlined /> {formatDate(work.createTime)}
288 </span>
289 <span>ID: {work.id}</span>
290 </div>
291 </div>
292 }
293 />
294 </Card>
295 </Col>
296 ))}
297 </Row>
298 )}
299 </Card>
300 </Col>
301
302 {/* 右侧:通知栏 */}
303 <Col span={8}>
304 <Card
305 title={
306 <div style={{ display: 'flex', alignItems: 'center' }}>
307 <BellOutlined style={{ marginRight: 8 }} />
308 通知中心
309 <Badge count={2} style={{ marginLeft: 8 }} />
310 </div>
311 }
312 style={{ minHeight: '600px' }}
313 >
314 <div>
315 {mockNotifications.map((notification, index) => (
316 <div key={notification.id}>
317 <div
318 style={{
319 padding: '12px',
320 backgroundColor: notification.unread ? '#f6ffed' : 'transparent',
321 borderRadius: '4px',
322 }}
323 >
324 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
325 <div style={{ flex: 1 }}>
326 <div style={{
327 fontWeight: notification.unread ? 'bold' : 'normal',
328 marginBottom: '4px',
329 display: 'flex',
330 alignItems: 'center'
331 }}>
332 {notification.title}
333 {notification.unread && (
334 <Badge
335 color="red"
336 style={{ marginLeft: '8px' }}
337 />
338 )}
339 </div>
340 <div style={{
341 color: '#666',
342 fontSize: '14px',
343 lineHeight: '1.4',
344 marginBottom: '8px'
345 }}>
346 {notification.content}
347 </div>
348 <div style={{ color: '#999', fontSize: '12px' }}>
349 {notification.time}
350 </div>
351 </div>
352 </div>
353 </div>
354 {index < mockNotifications.length - 1 && <Divider style={{ margin: '0' }} />}
355 </div>
356 ))}
357
358 {/* 查看更多按钮 */}
359 <div style={{ textAlign: 'center', marginTop: '16px' }}>
360 <Button type="link">查看全部通知</Button>
361 </div>
362 </div>
363 </Card>
364 </Col>
365 </Row>
366 </div>
367 );
368}
369
370export default UserHome;