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