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'; |
| 6 | import { getUserInfo } from './userSlice'; |
| 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 () => { |
| 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 | |
| 370 | export default UserHome; |