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