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