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