blob: 604d83c225a6b8bee5107e9fad74c3751bc3bd86 [file] [log] [blame]
TRM-coding29174c22025-06-18 23:56:51 +08001import React, { useState, useEffect, Profiler } from 'react';
2import {
3 Box,
4 Grid,
5 Typography,
6 Avatar,
7 Button,
8 Tabs,
9 Tab,
10 Card,
11 CardMedia,
12 CardContent,
13 CardActions,
14 IconButton,
15 Divider,
16 List,
17 ListItem,
18 ListItemAvatar,
19 ListItemText,
20 TextField,
21 InputAdornment,
22 Chip,
23 Badge,
24 Fab,
25 Paper,
26 MenuItem,
27 Container,
28 useMediaQuery,
29 useTheme,
30 CircularProgress,
31 Snackbar,
TRM-coding2a8fd602025-06-19 19:33:16 +080032 Alert,
trm9984ee52025-06-20 15:16:56 +000033 Menu,
34 Pagination,
35 Stack
TRM-coding29174c22025-06-18 23:56:51 +080036} from '@mui/material';
37import { useParams } from 'react-router-dom';
38import {
39 CameraAlt,
40 Edit,
41 Favorite,
TRM-coding29174c22025-06-18 23:56:51 +080042 Share,
43 MoreVert,
44 LocationOn,
45 Cake,
46 Female,
47 Male,
48 Public,
49 Add,
50 Search,
51 Notifications,
52 Person,
53 Collections,
TRM-coding29174c22025-06-18 23:56:51 +080054 ChevronLeft,
55 ChevronRight,
95630366911172812025-06-21 16:41:18 +080056 Close,
57 Bookmark,
58 Group,
59 People
TRM-coding29174c22025-06-18 23:56:51 +080060} from '@mui/icons-material';
61import { createTheme, ThemeProvider } from '@mui/material/styles';
62import { Link, useNavigate } from 'react-router-dom';
63
64// 导入API服务
65import {
66 getCurrentUser,
67 getUser,
68 updateUser as updateUserApi,
TRM-coding29174c22025-06-18 23:56:51 +080069 followUser as followUserApi,
70 unfollowUser as unfollowUserApi,
71 getUserPosts,
95630366911172812025-06-21 16:41:18 +080072 getUserInteractions,
73 getUserFollowers,
74 getFavorites,
75 getUserFollowing
TRM-coding29174c22025-06-18 23:56:51 +080076} from '../api/api_ljc';
trm9984ee52025-06-20 15:16:56 +000077import { fetchPost } from '../api/posts_wzy';
TRM-coding29174c22025-06-18 23:56:51 +080078
79// 创建小红书主题
80const theme = createTheme({
81 palette: {
82 primary: {
83 main: '#ff4081',
84 },
85 secondary: {
86 main: '#f50057',
87 },
88 background: {
89 default: '#f5f5f5',
90 },
91 },
92 typography: {
93 fontFamily: '"PingFang SC", "Helvetica Neue", Arial, sans-serif',
94 h5: {
95 fontWeight: 600,
96 },
97 subtitle1: {
98 color: 'rgba(0, 0, 0, 0.6)',
99 },
100 },
101 components: {
102 MuiButton: {
103 styleOverrides: {
104 root: {
105 borderRadius: 20,
106 textTransform: 'none',
107 fontWeight: 500,
108 },
109 },
110 },
111 MuiCard: {
112 styleOverrides: {
113 root: {
114 borderRadius: 16,
115 },
116 },
117 },
118 },
119});
120
121const UserProfile = () => {
122 const { userId } = useParams();
123 const theme = useTheme();
124 const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
125 const navigate = useNavigate();
126 const [activeTab, setActiveTab] = useState(0);
127 const [isEditing, setIsEditing] = useState(false);
95630366911172812025-06-21 16:41:18 +0800128 const [favorites, setFavorites] = useState([]);
129 const [following, setFollowing] = useState([]);
130 const [followers, setFollowers] = useState([]);
131 const [follower, setFollower] = useState([]);
TRM-coding29174c22025-06-18 23:56:51 +0800132 const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: 'success' });
TRM-coding2a8fd602025-06-19 19:33:16 +0800133 const [anchorEl, setAnchorEl] = useState(null);
TRM-coding29174c22025-06-18 23:56:51 +0800134
135 // 用户数据状态
136 const [currentUser, setCurrentUser] = useState(null);
137 const [profileUser, setProfileUser] = useState(null);
TRM-coding29174c22025-06-18 23:56:51 +0800138 const [posts, setPosts] = useState([]);
trm9984ee52025-06-20 15:16:56 +0000139 const [allPosts, setAllPosts] = useState([]); // 存储所有帖子数据
TRM-coding29174c22025-06-18 23:56:51 +0800140 const [interactions, setInteractions] = useState({
141 likes_count: 0,
142 favorites_count: 0
143 });
144
trm9984ee52025-06-20 15:16:56 +0000145 // 分页状态
146 const [currentPage, setCurrentPage] = useState(1);
147 const [postsPerPage] = useState(8); // 每页显示8个帖子
148 const [totalPages, setTotalPages] = useState(0);
149
TRM-coding29174c22025-06-18 23:56:51 +0800150 // 加载状态
151 const [loading, setLoading] = useState(true);
152 const [updating, setUpdating] = useState(false);
153 const [tabLoading, setTabLoading] = useState(false);
154
155 // 表单状态
156 const [formData, setFormData] = useState({
157 avatar: '',
158 bio: '',
159 gender: '',
160 birthday: '',
161 location: ''
162 });
163
TRM-coding2a8fd602025-06-19 19:33:16 +0800164 const menuOpen = Boolean(anchorEl);
165
TRM-coding29174c22025-06-18 23:56:51 +0800166 // 显示提示信息
167 const showSnackbar = (message, severity = 'success') => {
168 setSnackbar({ open: true, message, severity });
169 };
170
trm9984ee52025-06-20 15:16:56 +0000171 // 分页处理函数
172 const updatePaginatedPosts = (page, allPostsData) => {
173 const startIndex = (page - 1) * postsPerPage;
174 const endIndex = startIndex + postsPerPage;
175 const paginatedPosts = allPostsData.slice(startIndex, endIndex);
176 setPosts(paginatedPosts);
177 setTotalPages(Math.ceil(allPostsData.length / postsPerPage));
178 };
179
180 const handlePageChange = (newPage) => {
181 if (newPage >= 1 && newPage <= totalPages) {
182 setCurrentPage(newPage);
183 updatePaginatedPosts(newPage, allPosts);
184 }
185 };
186
187 const handlePrevPage = () => {
188 handlePageChange(currentPage - 1);
189 };
190
191 const handleNextPage = () => {
192 handlePageChange(currentPage + 1);
193 };
194
TRM-coding29174c22025-06-18 23:56:51 +0800195 // 加载用户数据
196 useEffect(() => {
197 const fetchInteractions = async () => {
198 try {
199 const response = await getUserInteractions(userId);
200 if (response.data.success) {
201 setInteractions(response.data.data);
202 } else {
203 console.error(response.data.error);
204 }
205 } catch (error) {
206 console.error('获取互动数据失败:', error);
207 }
208 };
209
210 fetchInteractions();
211
95630366911172812025-06-21 16:41:18 +0800212
TRM-coding29174c22025-06-18 23:56:51 +0800213 const fetchData = async () => {
214 try {
215 setLoading(true);
216
217 // 获取当前登录用户
9563036699de8c092025-06-21 16:41:18 +0800218 const currentUserRes = await getUser(userId);
TRM-coding29174c22025-06-18 23:56:51 +0800219 setCurrentUser(currentUserRes.data);
220
221 // 获取目标用户信息
222 console.log('userId', userId)
223 const profileUserRes = await getUser(userId);
224 setProfileUser(profileUserRes.data);
225 setFormData({
226 avatar: profileUserRes.data.avatar || '',
227 bio: profileUserRes.data.bio || '',
228 gender: profileUserRes.data.gender || '',
229 birthday: profileUserRes.data.birthday || '',
230 location: profileUserRes.data.location || ''
231 });
95630366911172812025-06-21 16:41:18 +0800232
233 if (activeTab === 1) {
234 // 加载收藏数据
235 const favoritesRes = await getFavorites(userId);
236 setFavorites(favoritesRes.data);
237 } else if (activeTab === 2) {
238 // 加载关注列表
239 const followingRes = await getUserFollowing(userId);
240 setFollowing(followingRes.data);
241 console.log("following",followingRes.data)
242 } else if (activeTab === 3) {
243 // 加载粉丝列表
244 const followersRes = await getUserFollowers(userId);
245 //
246 setFollowers(followersRes.data.data);
247 }
TRM-coding29174c22025-06-18 23:56:51 +0800248
249 // 获取用户帖子
250 const postsRes = await getUserPosts(userId);
trm9984ee52025-06-20 15:16:56 +0000251
252 // 为了拿到 media_urls,这里需要拉取每个帖子的详情
253 const postsWithDetails = await Promise.all(
254 postsRes.data.map(async post => {
255 try {
256 const detail = await fetchPost(post.id);
257 return {
258 ...post,
259 media_urls: detail.media_urls || [],
260 user_id: detail.user_id
261 };
262 } catch (error) {
263 console.error(`获取帖子 ${post.id} 详情失败:`, error);
264 return {
265 ...post,
266 media_urls: [],
267 user_id: null
268 };
269 }
270 })
271 );
272
273 setAllPosts(postsWithDetails); // 存储所有帖子
274 updatePaginatedPosts(1, postsWithDetails); // 初始化第一页数据
275 setCurrentPage(1); // 重置到第一页
TRM-coding29174c22025-06-18 23:56:51 +0800276
277 // 获取用户互动数据(获赞和收藏数量)
278 const interactionsRes = await getUserInteractions(userId);
279 setInteractions(interactionsRes.data);
280
281 } catch (error) {
282 console.error('获取用户数据失败:', error);
283 showSnackbar('获取用户数据失败,请重试', 'error');
284 } finally {
285 setLoading(false);
286 }
287 };
288
289 fetchData();
95630366911172812025-06-21 16:41:18 +0800290 }, [activeTab,userId]);
TRM-coding29174c22025-06-18 23:56:51 +0800291
292 // 根据标签页加载数据
293 useEffect(() => {
trm9984ee52025-06-20 15:16:56 +0000294 // 由于只有笔记标签页,不需要根据activeTab加载不同数据
TRM-coding29174c22025-06-18 23:56:51 +0800295 }, [activeTab, userId, profileUser]);
296
297 const handleTabChange = (event, newValue) => {
trm9984ee52025-06-20 15:16:56 +0000298 // 由于只有一个标签页,不需要切换逻辑
TRM-coding29174c22025-06-18 23:56:51 +0800299 setActiveTab(newValue);
300 };
301
302 const handleFollowToggle = async () => {
303 if (!currentUser || !profileUser) return;
TRM-coding29174c22025-06-18 23:56:51 +0800304 try {
305 if (profileUser.is_following) {
306 await unfollowUserApi(profileUser.id);
307 showSnackbar('已取消关注');
308 } else {
309 await followUserApi(profileUser.id);
310 showSnackbar('关注成功');
311 }
TRM-coding29174c22025-06-18 23:56:51 +0800312 // 更新用户信息
313 const updatedUser = await getUser(userId);
314 setProfileUser(updatedUser.data);
TRM-coding29174c22025-06-18 23:56:51 +0800315 } catch (error) {
316 console.error('关注操作失败:', error);
317 showSnackbar('操作失败,请重试', 'error');
318 }
319 };
320
95630366911172812025-06-21 16:41:18 +0800321 const handleFollowUser = async (userId,followeeId) => {
TRM-coding29174c22025-06-18 23:56:51 +0800322 try {
95630366911172812025-06-21 16:41:18 +0800323 await followUserApi(userId,followeeId);
TRM-coding29174c22025-06-18 23:56:51 +0800324 showSnackbar('关注成功');
325
95630366911172812025-06-21 16:41:18 +0800326 // 更新粉丝列表中的关注状态
327 setFollowers(prevFollowers =>
328 prevFollowers.map(follower =>
329 follower.id === followeeId
330 ? { ...follower, is_following: true }
331 : follower
332 )
333 );
334
335 // 更新当前用户的关注数
336 if (currentUser) {
337 setCurrentUser(prev => ({
338 ...prev,
339 following_count: prev.following_count + 1
340 }));
341 }
342
343 // 如果被关注的用户是当前用户的粉丝,更新粉丝数
344 setFollowers(prevFollowers =>
345 prevFollowers.map(follower =>
346 follower.id === followeeId
347 ? {
348 ...follower,
349 followers_count: follower.followers_count + 1
350 }
351 : follower
352 )
353 );
354
TRM-coding29174c22025-06-18 23:56:51 +0800355
356 } catch (error) {
357 console.error('关注操作失败:', error);
358 showSnackbar('关注失败,请重试', 'error');
359 }
360 };
361
95630366911172812025-06-21 16:41:18 +0800362 const handleUnfollow = async (userId,followeeId, e) => {
363 // e.stopPropagation(); // 阻止事件冒泡
TRM-coding29174c22025-06-18 23:56:51 +0800364
365 try {
95630366911172812025-06-21 16:41:18 +0800366 await unfollowUserApi(userId,followeeId);
TRM-coding29174c22025-06-18 23:56:51 +0800367 showSnackbar('已取消关注');
368
95630366911172812025-06-21 16:41:18 +0800369 // 更新关注列表 - 移除取消关注的用户
370 setFollowing(prevFollowing =>
371 prevFollowing.filter(user => user.id !== followeeId)
372 );
373
374 // 更新粉丝列表 - 更新关注状态
375 setFollowers(prevFollowers =>
376 prevFollowers.map(follower =>
377 follower.id === followeeId
378 ? { ...follower, is_following: false }
379 : follower
380 )
381 );
382
TRM-coding29174c22025-06-18 23:56:51 +0800383 // 更新当前用户关注数
384 if (currentUser) {
385 setCurrentUser(prev => ({
386 ...prev,
387 following_count: prev.following_count - 1
388 }));
389 }
390
391 // 更新目标用户粉丝数
392 setProfileUser(prev => ({
393 ...prev,
394 followers_count: prev.followers_count - 1
395 }));
396
397 } catch (error) {
398 console.error('取消关注失败:', error);
399 showSnackbar('操作失败,请重试', 'error');
400 }
401 };
402
403 const handleFormChange = (e) => {
404 const { name, value } = e.target;
405 setFormData({ ...formData, [name]: value });
406 };
407
408 const handleUpdateProfile = async () => {
409 if (!profileUser) return;
410
411 try {
412 setUpdating(true);
413 const data = {
414 avatar: formData.avatar,
415 bio: formData.bio,
416 gender: formData.gender,
417 birthday: formData.birthday,
418 location: formData.location
419 };
420
421 // 调用更新API
422 const updatedUser = await updateUserApi(profileUser.id, data);
423
424 // 更新本地状态
425 setProfileUser({ ...profileUser, ...updatedUser.data });
426 setFormData({ ...formData, ...data });
427
428
429 showSnackbar('个人资料更新成功');
430 setIsEditing(false);
431
432 } catch (error) {
433 console.error('更新个人资料失败:', error);
434 showSnackbar('更新失败,请重试', 'error');
435 } finally {
436 setUpdating(false);
437 }
438 };
439
440 const navigateToUserProfile = (userId) => {
441 navigate(`/user/${userId}`);
442 };
443
TRM-coding2a8fd602025-06-19 19:33:16 +0800444 const handleMenuOpen = (e) => setAnchorEl(e.currentTarget);
445 const handleMenuClose = () => setAnchorEl(null);
446 const handleLogout = () => {
447 handleMenuClose();
trm6589bba2025-06-27 16:10:04 +0000448 // 清理本地存储中的token和用户信息
449 localStorage.removeItem('token');
450 localStorage.removeItem('user');
451 localStorage.removeItem('userId');
TRM-coding2a8fd602025-06-19 19:33:16 +0800452 localStorage.clear();
trm6589bba2025-06-27 16:10:04 +0000453
454 showSnackbar('已成功注销', 'success');
455
456 // 跳转到首页
TRM-coding2a8fd602025-06-19 19:33:16 +0800457 navigate('/login');
458 };
459
TRM-coding29174c22025-06-18 23:56:51 +0800460 if (loading) {
461 return (
462 <Box sx={{
463 display: 'flex',
464 justifyContent: 'center',
465 alignItems: 'center',
466 height: '100vh'
467 }}>
468 <CircularProgress size={60} />
469 </Box>
470 );
471 }
472
473 if (!profileUser) {
474 return (
475 <Box sx={{
476 display: 'flex',
477 justifyContent: 'center',
478 alignItems: 'center',
479 height: '100vh',
480 flexDirection: 'column'
481 }}>
482 <Typography variant="h6" sx={{ mb: 2 }}>用户不存在</Typography>
483 <Button variant="outlined" onClick={() => window.location.reload()}>
484 重新加载
485 </Button>
486 </Box>
487 );
488 }
489
9563036699de8c092025-06-21 16:41:18 +0800490 console.log(currentUser.id)
TRM-coding29174c22025-06-18 23:56:51 +0800491 const isOwnProfile = currentUser && currentUser.id === parseInt(userId);
492
493 return (
494 <ThemeProvider theme={theme}>
495 <Box sx={{
496 bgcolor: 'background.default',
497 minHeight: '100vh',
498 pb: isMobile ? 8 : 4
499 }}>
500 {/* 顶部横幅 */}
501 <Box sx={{
502 height: isMobile ? 200 : 250,
503 background: 'linear-gradient(135deg, #ff9a9e 0%, #fad0c4 100%)',
504 position: 'relative',
505 borderBottomLeftRadius: 24,
506 borderBottomRightRadius: 24,
507 boxShadow: 1
508 }}>
509 <Fab
510 color="primary"
511 size="small"
512 sx={{
513 position: 'absolute',
514 bottom: -20,
515 right: 16
516 }}
517 >
518 <CameraAlt />
519 </Fab>
520 </Box>
521
522 <Container maxWidth="lg">
523 {/* 用户信息区域 */}
524 <Box sx={{ px: isMobile ? 3 : 0, mt: -8, position: 'relative' }}>
525 <Grid container spacing={3}>
526 <Grid item xs={12} sm="auto">
527 <Badge
528 overlap="circular"
529 anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
530 badgeContent={
531 <IconButton
532 size="small"
533 sx={{
534 bgcolor: 'grey.200',
535 '&:hover': { bgcolor: 'grey.300' }
536 }}
537 onClick={() => setIsEditing(true)}
538 disabled={!isOwnProfile}
539 >
540 <Edit fontSize="small" />
541 </IconButton>
542 }
543 >
544 <Avatar
545 sx={{
546 width: 120,
547 height: 120,
548 border: '4px solid white',
549 boxShadow: 3
550 }}
551 src={profileUser.avatar || 'https://www.8848seo.cn/zb_users/upload/2023/02/20230210092856_68763.jpeg'}
552 />
553 </Badge>
554 </Grid>
555
556 <Grid item xs={12} sm>
557 <Box sx={{ display: 'flex', justifyContent: 'space-between', flexDirection: isMobile ? 'column' : 'row' }}>
558 <Box>
559 <Typography variant="h5" fontWeight="bold">
560 {profileUser.username}
561 </Typography>
562 <Typography variant="subtitle1" sx={{ mt: 0.5, maxWidth: 600 }}>
563 {profileUser.bio || '这个人很懒,还没有写简介~'}
564 </Typography>
565 <Box sx={{ display: 'flex', mt: 1, gap: 1, flexWrap: 'wrap' }}>
566 <Chip
567 icon={<LocationOn fontSize="small" />}
568 label={formData.location}
569 size="small"
570 variant="outlined"
571 />
572 <Chip
573 icon={<Cake fontSize="small" />}
574 label={formData.birthday}
575 size="small"
576 variant="outlined"
577 />
578 <Chip
579 icon={<Female fontSize="small" />}
580 label={formData.gender}
581 size="small"
582 variant="outlined"
583 />
584 </Box>
585 </Box>
586
587 <Box sx={{ mt: isMobile ? 2 : 0, alignSelf: 'flex-start' }}>
trm6589bba2025-06-27 16:10:04 +0000588 {isOwnProfile ? (
589 // 如果是自己的个人资料页面,显示注销按钮
590 <Button
591 variant="outlined"
592 color="error"
593 onClick={handleLogout}
594 sx={{
595 borderRadius: 20,
596 px: 3,
597 fontWeight: 'bold'
598 }}
599 >
600 注销
601 </Button>
602 ) : currentUser && (
TRM-coding29174c22025-06-18 23:56:51 +0800603 <>
604 <Button
605 variant={profileUser.is_following ? "outlined" : "contained"}
606 color="primary"
607 onClick={handleFollowToggle}
608 sx={{
609 borderRadius: 20,
610 px: 3,
611 fontWeight: 'bold'
612 }}
613 >
614 {profileUser.is_following ? '已关注' : '关注'}
615 </Button>
TRM-coding2a8fd602025-06-19 19:33:16 +0800616 <IconButton sx={{ ml: 1 }} onClick={handleMenuOpen}>
TRM-coding29174c22025-06-18 23:56:51 +0800617 <MoreVert />
618 </IconButton>
TRM-coding2a8fd602025-06-19 19:33:16 +0800619 <Menu
620 anchorEl={anchorEl}
621 open={menuOpen}
622 onClose={handleMenuClose}
623 anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
624 transformOrigin={{ vertical: 'top', horizontal: 'right' }}
625 >
626 <MenuItem onClick={handleLogout}>退出登录</MenuItem>
627 </Menu>
TRM-coding29174c22025-06-18 23:56:51 +0800628 </>
629 )}
630 </Box>
631 </Box>
632
633 <Grid container spacing={2} sx={{ mt: 2 }}>
634 <Grid item>
635 <Box textAlign="center">
trm9984ee52025-06-20 15:16:56 +0000636 <Typography variant="h6">{allPosts.length}</Typography>
TRM-coding29174c22025-06-18 23:56:51 +0800637 <Typography variant="body2" color="textSecondary">笔记</Typography>
638 </Box>
639 </Grid>
640 <Grid item>
641 <Box textAlign="center">
642 <Typography variant="h6">{profileUser.followers_count || 0}</Typography>
643 <Typography variant="body2" color="textSecondary">粉丝</Typography>
644 </Box>
645 </Grid>
646 <Grid item>
647 <Box textAlign="center">
648 <Typography variant="h6">{profileUser.following_count || 0}</Typography>
649 <Typography variant="body2" color="textSecondary">关注</Typography>
650 </Box>
651 </Grid>
652 <Grid item>
653 <Box textAlign="center">
654 {/* 使用真实数据:获赞与收藏总数 */}
655 <Typography variant="h6">
656 {(interactions.likes_count + interactions.favorites_count).toLocaleString()}
657 </Typography>
658 <Typography variant="body2" color="textSecondary">获赞与收藏</Typography>
659 </Box>
660 </Grid>
661 </Grid>
662 </Grid>
663 </Grid>
664 </Box>
665
666 {/* 标签栏 */}
667 <Box sx={{ mt: 4 }}>
668 <Tabs
669 value={activeTab}
670 onChange={handleTabChange}
671 variant={isMobile ? "fullWidth" : "standard"}
672 indicatorColor="primary"
673 textColor="primary"
674 sx={{
675 borderBottom: 1,
676 borderColor: 'divider'
677 }}
678 >
679 <Tab icon={isMobile ? <Collections /> : null} label="笔记" />
95630366911172812025-06-21 16:41:18 +0800680 <Tab icon={isMobile ? <Bookmark /> : null} label="收藏" />
681 <Tab icon={isMobile ? <Group /> : null} label="关注" />
682 <Tab icon={isMobile ? <People /> : null} label="粉丝" />
TRM-coding29174c22025-06-18 23:56:51 +0800683 </Tabs>
684 </Box>
685
686 {/* 内容区域 */}
687 <Box sx={{ mt: 3 }}>
95630366911172812025-06-21 16:41:18 +0800688
689 {activeTab === 0 && (
690 <Grid container spacing={3}>
691 {tabLoading ? (
692 <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
693 <CircularProgress />
694 </Grid>
695 ) : posts.length > 0 ? (
696 posts.map((post, index) => (
697 <Grid item xs={12} sm={6} lg={3} key={post.id}>
698 <Card elevation={0} sx={{
699 bgcolor: 'white',
700 borderRadius: 3,
701 height: '100%',
702 display: 'flex',
703 flexDirection: 'column'
704 }}>
705 {/* 只有当帖子有 media_urls 时才显示图片 */}
trm9984ee52025-06-20 15:16:56 +0000706 {post.media_urls && post.media_urls.length > 0 && (
TRM-coding29174c22025-06-18 23:56:51 +0800707 <CardMedia
708 component="img"
709 height="180"
trm9984ee52025-06-20 15:16:56 +0000710 image={post.media_urls[0]}
TRM-coding29174c22025-06-18 23:56:51 +0800711 alt={post.title}
712 />
TRM-coding29174c22025-06-18 23:56:51 +0800713 )}
95630366911172812025-06-21 16:41:18 +0800714 <CardContent sx={{ flexGrow: 1 }}>
715 <Typography gutterBottom variant="h6" component="div">
716 {post.title}
717 </Typography>
718 <Typography variant="body2" color="text.secondary">
719 {post.content.substring(0, 60)}...
720 </Typography>
721 </CardContent>
722 <CardActions sx={{ justifyContent: 'space-between', px: 2, pb: 2 }}>
723 <Box>
724 <IconButton aria-label="add to favorites">
725 <Favorite />
726 <Typography variant="body2" sx={{ ml: 1 }}>
727 {post.heat || Math.floor(Math.random() * 1000) + 1000}
728 </Typography>
729 </IconButton>
730 <IconButton aria-label="share">
731 <Share />
732 </IconButton>
733 </Box>
734 <Chip
735 label={post.type === 'image' ? '图文' : post.type === 'video' ? '视频' : '文档'}
736 size="small"
737 color="primary"
738 variant="outlined"
739 />
740 </CardActions>
741 </Card>
742 </Grid>
743 ))
744 ) : (
745 <Grid item xs={12}>
746 <Box sx={{
747 display: 'flex',
748 flexDirection: 'column',
749 alignItems: 'center',
750 py: 8,
751 textAlign: 'center'
752 }}>
753 <Collections sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
754 <Typography variant="h6" sx={{ mb: 1 }}>
755 还没有发布笔记
756 </Typography>
757 <Typography variant="body1" color="textSecondary" sx={{ mb: 3 }}>
758 {isOwnProfile ? '分享你的生活点滴吧~' : '该用户还没有发布任何笔记'}
759 </Typography>
760 {isOwnProfile && (
761 <Button variant="contained" color="primary">
762 发布第一篇笔记
763 </Button>
764 )}
765 </Box>
22301069d1c06e62025-06-21 12:21:48 +0800766 </Grid>
95630366911172812025-06-21 16:41:18 +0800767 )}
768
769 {posts.length > 0 && (
770 <Grid item xs={12}>
771 <Box sx={{ display: 'flex', justifyContent: 'center', mt: 3 }}>
772 <Button
773 variant="outlined"
774 sx={{
775 borderRadius: 20,
776 px: 4,
777 display: 'flex',
778 alignItems: 'center'
779 }}
780 >
781 <ChevronLeft sx={{ mr: 1 }} />
782 上一页
783 <ChevronRight sx={{ ml: 2 }} />
784 </Button>
785 </Box>
786 </Grid>
787 )}
788 </Grid>
789 )}
790
791 {activeTab === 1 && (
792 <Grid container spacing={3}>
793 {tabLoading ? (
794 <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
795 <CircularProgress />
796 </Grid>
797 ) : favorites.length > 0 ? (
798 favorites.map((favorite) => (
799 <Grid item xs={12} sm={6} md={4} lg={3} key={favorite.id}>
800 <Card elevation={0} sx={{
801 bgcolor: 'white',
802 borderRadius: 3,
803 transition: 'transform 0.3s, box-shadow 0.3s',
804 '&:hover': {
805 transform: 'translateY(-5px)',
806 boxShadow: 3
956303669ad732502025-06-21 16:40:05 +0800807 }
95630366911172812025-06-21 16:41:18 +0800808 }}>
809 <Box sx={{
810 height: 160,
811 position: 'relative',
812 borderTopLeftRadius: 16,
813 borderTopRightRadius: 16,
814 overflow: 'hidden'
815 }}>
816 <CardMedia
817 component="img"
818 height="160"
819 image={`https://source.unsplash.com/random/400x300?${favorite.id}`}
820 alt={favorite.title}
821 />
822 <Box sx={{
823 position: 'absolute',
824 top: 8,
825 right: 8,
826 bgcolor: 'rgba(0,0,0,0.6)',
827 color: 'white',
828 px: 1,
829 py: 0.5,
830 borderRadius: 4,
831 fontSize: 12
832 }}>
833 {favorite.type === 'image' ? '图文' : favorite.type === 'video' ? '视频' : '文档'}
834 </Box>
835 </Box>
836 <CardContent>
837 <Typography gutterBottom variant="subtitle1" fontWeight="medium">
838 {favorite.title}
839 </Typography>
840 <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
841 <Box sx={{ display: 'flex', alignItems: 'center' }}>
842 <Favorite fontSize="small" color="error" />
843 <Typography variant="body2" sx={{ ml: 0.5 }}>
844 {favorite.heat || Math.floor(Math.random() * 1000) + 1000}
845 </Typography>
846 </Box>
847 <Box sx={{ display: 'flex', alignItems: 'center' }}>
848 <Bookmark fontSize="small" color="primary" />
849 <Typography variant="body2" sx={{ ml: 0.5 }}>
850 {Math.floor(Math.random() * 500) + 100}
851 </Typography>
852 </Box>
853 </Box>
854 </CardContent>
855 </Card>
856 </Grid>
857 ))
858 ) : (
859 <Grid item xs={12}>
860 <Box sx={{
861 display: 'flex',
862 flexDirection: 'column',
863 alignItems: 'center',
864 py: 8,
865 textAlign: 'center'
866 }}>
867 <Bookmark sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
868 <Typography variant="h6" sx={{ mb: 1 }}>
869 {isOwnProfile ? '你还没有收藏内容' : '该用户没有收藏内容'}
870 </Typography>
871 <Typography variant="body1" color="textSecondary">
872 {isOwnProfile ? '看到喜欢的笔记可以收藏起来哦~' : ''}
873 </Typography>
874 </Box>
875 </Grid>
876 )}
877 </Grid>
878 )}
879
880 {activeTab === 2 && (
881 <Grid container spacing={3}>
882 {tabLoading ? (
883 <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
884 <CircularProgress />
885 </Grid>
886 ) : following.length > 0 ? (
887 following.map((follow) => (
888 <Grid item xs={12} sm={6} md={4} key={follow.id}>
889 <Paper
890 elevation={0}
891 sx={{
892 bgcolor: 'white',
893 borderRadius: 3,
894 p: 2,
895 display: 'flex',
896 alignItems: 'center',
897 cursor: 'pointer',
898 '&:hover': {
899 boxShadow: 1
900 }
901 }}
902 onClick={() => navigateToUserProfile(follow.id)}
903 >
904 <Avatar
905 src={follow.avatar || 'https://randomuser.me/api/portraits/men/22.jpg'}
906 sx={{ width: 60, height: 60 }}
907 />
908 <Box sx={{ ml: 2, flexGrow: 1 }}>
909 <Typography fontWeight="medium">{follow.username}</Typography>
910 <Typography variant="body2" color="textSecondary">
911 {follow.followers_count || Math.floor(Math.random() * 100) + 10} 粉丝
912 </Typography>
913 </Box>
914 {isOwnProfile && (
915 <Button
916 variant="outlined"
917 size="small"
918 sx={{ borderRadius: 20 }}
919 onClick={(e) => {
920 e.stopPropagation();
921 handleUnfollow(userId,follow.id);
922 }}
923 >
924 已关注
925 </Button>
926 )}
927 </Paper>
928 </Grid>
929 ))
930 ) : (
931 <Grid item xs={12}>
932 <Box sx={{
933 display: 'flex',
934 flexDirection: 'column',
935 alignItems: 'center',
936 py: 8,
937 textAlign: 'center'
938 }}>
939 <Group sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
940 <Typography variant="h6" sx={{ mb: 1 }}>
941 {isOwnProfile ? '你还没有关注任何人' : '该用户还没有关注任何人'}
942 </Typography>
943 <Typography variant="body1" color="textSecondary">
944 {isOwnProfile ? '发现有趣的人并关注他们吧~' : ''}
945 </Typography>
946 </Box>
947 </Grid>
948 )}
949 </Grid>
950 )}
951 {activeTab === 3 && (
952 <Grid container spacing={3}>
953 {tabLoading ? (
954 <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
955 <CircularProgress />
956 </Grid>
957 ) : followers.length > 0 ? (
958 followers.map((follower) => (
959 <Grid item xs={12} sm={6} md={4} key={follower.id}>
960 <Paper
961 elevation={0}
962 sx={{
963 bgcolor: 'white',
964 borderRadius: 3,
965 p: 2,
966 display: 'flex',
967 alignItems: 'center',
968 cursor: 'pointer',
969 '&:hover': {
970 boxShadow: 1
971 }
972 }}
973 onClick={() => navigateToUserProfile(follower.id)}
974 >
975 <Avatar
976 src={follower.avatar || 'https://randomuser.me/api/portraits/men/22.jpg'}
977 sx={{ width: 60, height: 60 }}
978 />
979 <Box sx={{ ml: 2, flexGrow: 1 }}>
980 <Typography fontWeight="medium">{follower.username}</Typography>
981 <Typography variant="body2" color="textSecondary">
982 {follower.bio || '暂无简介'}
983 </Typography>
984 <Typography variant="body2" color="textSecondary">
985 {follower.followers_count} 粉丝
986 </Typography>
987 </Box>
988 {currentUser && currentUser.id !== follower.id && (
989 <Button
990 variant={follower.is_following ? "outlined" : "contained"}
991 color="primary"
992 size="small"
993 sx={{ borderRadius: 20 }}
994 onClick={(e) => {
995 e.stopPropagation();
996 if (follower.is_following) {
997 handleUnfollow(userId,follower.id);
998 } else {
999 handleFollowUser(userId,follower.id);
1000 }
1001 }}
1002 >
1003 {follower.is_following ? '已关注' : '关注'}
1004 </Button>
1005 )}
1006 </Paper>
1007 </Grid>
1008 ))
1009 ) : (
1010 <Grid item xs={12}>
1011 <Box sx={{
1012 display: 'flex',
1013 flexDirection: 'column',
1014 alignItems: 'center',
1015 py: 8,
1016 textAlign: 'center'
1017 }}>
1018 <People sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
1019 <Typography variant="h6" sx={{ mb: 1 }}>
1020 {isOwnProfile ? '你还没有粉丝' : '该用户还没有粉丝'}
1021 </Typography>
1022 <Typography variant="body1" color="textSecondary">
1023 {isOwnProfile ? '分享更多内容来吸引粉丝吧~' : ''}
1024 </Typography>
1025 </Box>
1026 </Grid>
1027 )}
1028 </Grid>
1029 )}
TRM-coding29174c22025-06-18 23:56:51 +08001030 </Box>
1031 </Container>
1032
1033 {/* 底部导航栏 - 仅移动端显示 */}
1034 {isMobile && (
1035 <Box sx={{
1036 position: 'fixed',
1037 bottom: 0,
1038 left: 0,
1039 right: 0,
1040 bgcolor: 'white',
1041 boxShadow: 3,
1042 py: 1,
1043 display: 'flex',
1044 justifyContent: 'space-around'
1045 }}>
1046 <IconButton color="primary">
1047 <Search fontSize="large" />
1048 </IconButton>
1049 <IconButton>
1050 <Collections fontSize="large" />
1051 </IconButton>
1052 <Fab color="primary" size="medium" sx={{ mt: -2 }}>
1053 <Add />
1054 </Fab>
1055 <IconButton>
1056 <Notifications fontSize="large" />
1057 </IconButton>
1058 <IconButton>
1059 <Person fontSize="large" />
1060 </IconButton>
1061 </Box>
1062 )}
1063
1064 {/* 编辑资料模态框 */}
1065 {isEditing && (
1066 <Box sx={{
1067 position: 'fixed',
1068 top: 0,
1069 left: 0,
1070 right: 0,
1071 bottom: 0,
1072 bgcolor: 'rgba(0,0,0,0.5)',
1073 zIndex: 1300,
1074 display: 'flex',
1075 alignItems: 'center',
1076 justifyContent: 'center',
1077 px: 2
1078 }}>
1079 <Paper sx={{
1080 width: '100%',
1081 maxWidth: 600,
1082 borderRadius: 4,
1083 overflow: 'hidden'
1084 }}>
1085 <Box sx={{
1086 bgcolor: 'primary.main',
1087 color: 'white',
1088 p: 2,
1089 display: 'flex',
1090 justifyContent: 'space-between',
1091 alignItems: 'center'
1092 }}>
1093 <Typography variant="h6">编辑资料</Typography>
1094 <IconButton color="inherit" onClick={() => setIsEditing(false)}>
1095 <Close />
1096 </IconButton>
1097 </Box>
1098
1099 <Box sx={{ p: 3 }}>
1100 <Box sx={{ display: 'flex', justifyContent: 'center', mb: 3 }}>
1101 <Badge
1102 overlap="circular"
1103 anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
1104 badgeContent={
1105 <IconButton
1106 size="small"
1107 sx={{
1108 bgcolor: 'grey.200',
1109 '&:hover': { bgcolor: 'grey.300' }
1110 }}
1111 >
1112 <CameraAlt fontSize="small" />
1113 </IconButton>
1114 }
1115 >
1116 <Avatar
1117 sx={{ width: 100, height: 100 }}
1118 src={formData.avatar || 'https://www.8848seo.cn/zb_users/upload/2023/02/20230210092856_68763.jpeg'}
1119 />
1120 </Badge>
1121 </Box>
1122
1123 <TextField
1124 fullWidth
1125 label="用户名"
1126 value={profileUser.username}
1127 margin="normal"
1128 disabled
1129 />
1130
1131 <TextField
1132 fullWidth
1133 name="avatar"
1134 label="头像URL"
1135 value={formData.avatar}
1136 onChange={handleFormChange}
1137 margin="normal"
1138 />
1139
1140 <TextField
1141 fullWidth
1142 name="bio"
1143 label="个人简介"
1144 value={formData.bio}
1145 onChange={handleFormChange}
1146 margin="normal"
1147 multiline
1148 rows={3}
1149 />
1150
1151 <Grid container spacing={2} sx={{ mt: 1 }}>
1152 <Grid item xs={6}>
1153 <TextField
1154 select
1155 fullWidth
1156 name="gender"
1157 label="性别"
1158 value={formData.gender}
1159 onChange={handleFormChange}
1160 margin="normal"
1161 >
1162 <MenuItem value="female">
1163 <Box sx={{ display: 'flex', alignItems: 'center' }}>
1164 <Female sx={{ mr: 1 }} />
1165 </Box>
1166 </MenuItem>
1167 <MenuItem value="male">
1168 <Box sx={{ display: 'flex', alignItems: 'center' }}>
1169 <Male sx={{ mr: 1 }} />
1170 </Box>
1171 </MenuItem>
1172 <MenuItem value="other">
1173 <Box sx={{ display: 'flex', alignItems: 'center' }}>
1174 <Public sx={{ mr: 1 }} /> 其他
1175 </Box>
1176 </MenuItem>
1177 </TextField>
1178 </Grid>
1179 <Grid item xs={6}>
1180 <TextField
1181 fullWidth
1182 name="birthday"
1183 label="生日"
1184 type="date"
1185 value={formData.birthday}
1186 onChange={handleFormChange}
1187 margin="normal"
1188 InputLabelProps={{ shrink: true }}
1189 />
1190 </Grid>
1191 </Grid>
1192
1193 <TextField
1194 fullWidth
1195 name="location"
1196 label="地区"
1197 value={formData.location}
1198 onChange={handleFormChange}
1199 margin="normal"
1200 InputProps={{
1201 startAdornment: (
1202 <InputAdornment position="start">
1203 <LocationOn />
1204 </InputAdornment>
1205 ),
1206 }}
1207 />
1208
1209 <Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 3 }}>
1210 <Button
1211 variant="outlined"
1212 sx={{ width: '48%' }}
1213 onClick={() => setIsEditing(false)}
1214 disabled={updating}
1215 >
1216 取消
1217 </Button>
1218 <Button
1219 variant="contained"
1220 color="primary"
1221 sx={{ width: '48%' }}
1222 onClick={handleUpdateProfile}
1223 disabled={updating}
1224 >
1225 {updating ? <CircularProgress size={24} /> : '保存'}
1226 </Button>
1227 </Box>
1228 </Box>
1229 </Paper>
1230 </Box>
1231 )}
1232
1233 {/* 提示信息 */}
1234 <Snackbar
1235 open={snackbar.open}
1236 autoHideDuration={3000}
1237 onClose={() => setSnackbar({ ...snackbar, open: false })}
1238 anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
1239 >
1240 <Alert
1241 severity={snackbar.severity}
1242 sx={{ width: '100%' }}
1243 onClose={() => setSnackbar({ ...snackbar, open: false })}
1244 >
1245 {snackbar.message}
1246 </Alert>
1247 </Snackbar>
1248 </Box>
1249 </ThemeProvider>
1250 );
1251};
1252
1253export default UserProfile;