blob: 6cddcbd9254ed1a7152a4c574ed54f0fd8a85b37 [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;
TRM-coding29174c22025-06-18 23:56:51 +0800282 try {
283 if (profileUser.is_following) {
284 await unfollowUserApi(profileUser.id);
285 showSnackbar('已取消关注');
286 } else {
287 await followUserApi(profileUser.id);
288 showSnackbar('关注成功');
289 }
TRM-coding29174c22025-06-18 23:56:51 +0800290 // 更新用户信息
291 const updatedUser = await getUser(userId);
292 setProfileUser(updatedUser.data);
22301008e25b4b02025-06-20 22:15:31 +0800293 // 关注/取关后强制刷新关注和粉丝列表,保证页面和数据库同步
294 if (activeTab === 2) {
295 const followingRes = await getUserFollowing(userId);
296 setFollowing(followingRes.data);
297 }
298 if (activeTab === 3) {
299 const followersRes = await getUserFollowers(userId);
300 setFollowers(followersRes.data.data);
301 }
TRM-coding29174c22025-06-18 23:56:51 +0800302 } catch (error) {
303 console.error('关注操作失败:', error);
304 showSnackbar('操作失败,请重试', 'error');
305 }
306 };
307
308 const handleFollowUser = async (followeeId) => {
309 try {
310 await followUserApi(followeeId);
311 showSnackbar('关注成功');
312
313 // 更新粉丝列表状态
314 setFollowers(prev => prev.map(user =>
315 user.id === followeeId ? {...user, is_following: true} : user
316 ));
317
318 // 更新当前用户关注数
319 if (currentUser) {
320 setCurrentUser(prev => ({
321 ...prev,
322 following_count: prev.following_count + 1
323 }));
324 }
325
326 } catch (error) {
327 console.error('关注操作失败:', error);
328 showSnackbar('关注失败,请重试', 'error');
329 }
330 };
331
332 const handleUnfollow = async (followeeId, e) => {
333 e.stopPropagation(); // 阻止事件冒泡
334
335 try {
336 await unfollowUserApi(followeeId);
337 showSnackbar('已取消关注');
338
339 // 更新关注列表
340 setFollowing(prev => prev.filter(user => user.id !== followeeId));
341
342 // 更新当前用户关注数
343 if (currentUser) {
344 setCurrentUser(prev => ({
345 ...prev,
346 following_count: prev.following_count - 1
347 }));
348 }
349
350 // 更新目标用户粉丝数
351 setProfileUser(prev => ({
352 ...prev,
353 followers_count: prev.followers_count - 1
354 }));
355
356 } catch (error) {
357 console.error('取消关注失败:', error);
358 showSnackbar('操作失败,请重试', 'error');
359 }
360 };
361
362 const handleFormChange = (e) => {
363 const { name, value } = e.target;
364 setFormData({ ...formData, [name]: value });
365 };
366
367 const handleUpdateProfile = async () => {
368 if (!profileUser) return;
369
370 try {
371 setUpdating(true);
372 const data = {
373 avatar: formData.avatar,
374 bio: formData.bio,
375 gender: formData.gender,
376 birthday: formData.birthday,
377 location: formData.location
378 };
379
380 // 调用更新API
381 const updatedUser = await updateUserApi(profileUser.id, data);
382
383 // 更新本地状态
384 setProfileUser({ ...profileUser, ...updatedUser.data });
385 setFormData({ ...formData, ...data });
386
387
388 showSnackbar('个人资料更新成功');
389 setIsEditing(false);
390
391 } catch (error) {
392 console.error('更新个人资料失败:', error);
393 showSnackbar('更新失败,请重试', 'error');
394 } finally {
395 setUpdating(false);
396 }
397 };
398
399 const navigateToUserProfile = (userId) => {
400 navigate(`/user/${userId}`);
401 };
402
TRM-coding2a8fd602025-06-19 19:33:16 +0800403 const handleMenuOpen = (e) => setAnchorEl(e.currentTarget);
404 const handleMenuClose = () => setAnchorEl(null);
405 const handleLogout = () => {
406 handleMenuClose();
407 // 清理本地存储
408 localStorage.clear();
409 // 在此处添加退出登录逻辑,比如清理本地存储并跳转
410 navigate('/login');
411 };
412
TRM-coding29174c22025-06-18 23:56:51 +0800413 if (loading) {
414 return (
415 <Box sx={{
416 display: 'flex',
417 justifyContent: 'center',
418 alignItems: 'center',
419 height: '100vh'
420 }}>
421 <CircularProgress size={60} />
422 </Box>
423 );
424 }
425
426 if (!profileUser) {
427 return (
428 <Box sx={{
429 display: 'flex',
430 justifyContent: 'center',
431 alignItems: 'center',
432 height: '100vh',
433 flexDirection: 'column'
434 }}>
435 <Typography variant="h6" sx={{ mb: 2 }}>用户不存在</Typography>
436 <Button variant="outlined" onClick={() => window.location.reload()}>
437 重新加载
438 </Button>
439 </Box>
440 );
441 }
442
443 const isOwnProfile = currentUser && currentUser.id === parseInt(userId);
444
445 return (
446 <ThemeProvider theme={theme}>
447 <Box sx={{
448 bgcolor: 'background.default',
449 minHeight: '100vh',
450 pb: isMobile ? 8 : 4
451 }}>
452 {/* 顶部横幅 */}
453 <Box sx={{
454 height: isMobile ? 200 : 250,
455 background: 'linear-gradient(135deg, #ff9a9e 0%, #fad0c4 100%)',
456 position: 'relative',
457 borderBottomLeftRadius: 24,
458 borderBottomRightRadius: 24,
459 boxShadow: 1
460 }}>
461 <Fab
462 color="primary"
463 size="small"
464 sx={{
465 position: 'absolute',
466 bottom: -20,
467 right: 16
468 }}
469 >
470 <CameraAlt />
471 </Fab>
472 </Box>
473
474 <Container maxWidth="lg">
475 {/* 用户信息区域 */}
476 <Box sx={{ px: isMobile ? 3 : 0, mt: -8, position: 'relative' }}>
477 <Grid container spacing={3}>
478 <Grid item xs={12} sm="auto">
479 <Badge
480 overlap="circular"
481 anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
482 badgeContent={
483 <IconButton
484 size="small"
485 sx={{
486 bgcolor: 'grey.200',
487 '&:hover': { bgcolor: 'grey.300' }
488 }}
489 onClick={() => setIsEditing(true)}
490 disabled={!isOwnProfile}
491 >
492 <Edit fontSize="small" />
493 </IconButton>
494 }
495 >
496 <Avatar
497 sx={{
498 width: 120,
499 height: 120,
500 border: '4px solid white',
501 boxShadow: 3
502 }}
503 src={profileUser.avatar || 'https://www.8848seo.cn/zb_users/upload/2023/02/20230210092856_68763.jpeg'}
504 />
505 </Badge>
506 </Grid>
507
508 <Grid item xs={12} sm>
509 <Box sx={{ display: 'flex', justifyContent: 'space-between', flexDirection: isMobile ? 'column' : 'row' }}>
510 <Box>
511 <Typography variant="h5" fontWeight="bold">
512 {profileUser.username}
513 </Typography>
514 <Typography variant="subtitle1" sx={{ mt: 0.5, maxWidth: 600 }}>
515 {profileUser.bio || '这个人很懒,还没有写简介~'}
516 </Typography>
517 <Box sx={{ display: 'flex', mt: 1, gap: 1, flexWrap: 'wrap' }}>
518 <Chip
519 icon={<LocationOn fontSize="small" />}
520 label={formData.location}
521 size="small"
522 variant="outlined"
523 />
524 <Chip
525 icon={<Cake fontSize="small" />}
526 label={formData.birthday}
527 size="small"
528 variant="outlined"
529 />
530 <Chip
531 icon={<Female fontSize="small" />}
532 label={formData.gender}
533 size="small"
534 variant="outlined"
535 />
536 </Box>
537 </Box>
538
539 <Box sx={{ mt: isMobile ? 2 : 0, alignSelf: 'flex-start' }}>
540 {!isOwnProfile && currentUser && (
541 <>
542 <Button
543 variant={profileUser.is_following ? "outlined" : "contained"}
544 color="primary"
545 onClick={handleFollowToggle}
546 sx={{
547 borderRadius: 20,
548 px: 3,
549 fontWeight: 'bold'
550 }}
551 >
552 {profileUser.is_following ? '已关注' : '关注'}
553 </Button>
TRM-coding2a8fd602025-06-19 19:33:16 +0800554 <IconButton sx={{ ml: 1 }} onClick={handleMenuOpen}>
TRM-coding29174c22025-06-18 23:56:51 +0800555 <MoreVert />
556 </IconButton>
TRM-coding2a8fd602025-06-19 19:33:16 +0800557 <Menu
558 anchorEl={anchorEl}
559 open={menuOpen}
560 onClose={handleMenuClose}
561 anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
562 transformOrigin={{ vertical: 'top', horizontal: 'right' }}
563 >
564 <MenuItem onClick={handleLogout}>退出登录</MenuItem>
565 </Menu>
TRM-coding29174c22025-06-18 23:56:51 +0800566 </>
567 )}
568 </Box>
569 </Box>
570
571 <Grid container spacing={2} sx={{ mt: 2 }}>
572 <Grid item>
573 <Box textAlign="center">
574 <Typography variant="h6">{posts.length}</Typography>
575 <Typography variant="body2" color="textSecondary">笔记</Typography>
576 </Box>
577 </Grid>
578 <Grid item>
579 <Box textAlign="center">
580 <Typography variant="h6">{profileUser.followers_count || 0}</Typography>
581 <Typography variant="body2" color="textSecondary">粉丝</Typography>
582 </Box>
583 </Grid>
584 <Grid item>
585 <Box textAlign="center">
586 <Typography variant="h6">{profileUser.following_count || 0}</Typography>
587 <Typography variant="body2" color="textSecondary">关注</Typography>
588 </Box>
589 </Grid>
590 <Grid item>
591 <Box textAlign="center">
592 {/* 使用真实数据:获赞与收藏总数 */}
593 <Typography variant="h6">
594 {(interactions.likes_count + interactions.favorites_count).toLocaleString()}
595 </Typography>
596 <Typography variant="body2" color="textSecondary">获赞与收藏</Typography>
597 </Box>
598 </Grid>
599 </Grid>
600 </Grid>
601 </Grid>
602 </Box>
603
604 {/* 标签栏 */}
605 <Box sx={{ mt: 4 }}>
606 <Tabs
607 value={activeTab}
608 onChange={handleTabChange}
609 variant={isMobile ? "fullWidth" : "standard"}
610 indicatorColor="primary"
611 textColor="primary"
612 sx={{
613 borderBottom: 1,
614 borderColor: 'divider'
615 }}
616 >
617 <Tab icon={isMobile ? <Collections /> : null} label="笔记" />
618 <Tab icon={isMobile ? <Bookmark /> : null} label="收藏" />
619 <Tab icon={isMobile ? <Group /> : null} label="关注" />
620 <Tab icon={isMobile ? <People /> : null} label="粉丝" />
621 </Tabs>
622 </Box>
623
624 {/* 内容区域 */}
625 <Box sx={{ mt: 3 }}>
626 {activeTab === 0 && (
627 <Grid container spacing={3}>
628 {tabLoading ? (
629 <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
630 <CircularProgress />
631 </Grid>
632 ) : posts.length > 0 ? (
633 posts.map((post, index) => (
634 <Grid item xs={12} sm={6} lg={3} key={post.id}>
635 <Card elevation={0} sx={{
636 bgcolor: 'white',
637 borderRadius: 3,
638 height: '100%',
639 display: 'flex',
640 flexDirection: 'column'
641 }}>
642 <CardMedia
643 component="img"
644 height="180"
645 image={`https://source.unsplash.com/random/400x300?${index + 1}`}
646 alt={post.title}
647 />
648 <CardContent sx={{ flexGrow: 1 }}>
649 <Typography gutterBottom variant="h6" component="div">
650 {post.title}
651 </Typography>
652 <Typography variant="body2" color="text.secondary">
653 {post.content.substring(0, 60)}...
654 </Typography>
655 </CardContent>
656 <CardActions sx={{ justifyContent: 'space-between', px: 2, pb: 2 }}>
657 <Box>
658 <IconButton aria-label="add to favorites">
659 <Favorite />
660 <Typography variant="body2" sx={{ ml: 1 }}>
661 {post.heat || Math.floor(Math.random() * 1000) + 1000}
662 </Typography>
663 </IconButton>
664 <IconButton aria-label="share">
665 <Share />
666 </IconButton>
667 </Box>
668 <Chip
669 label={post.type === 'image' ? '图文' : post.type === 'video' ? '视频' : '文档'}
670 size="small"
671 color="primary"
672 variant="outlined"
673 />
674 </CardActions>
675 </Card>
676 </Grid>
677 ))
678 ) : (
679 <Grid item xs={12}>
680 <Box sx={{
681 display: 'flex',
682 flexDirection: 'column',
683 alignItems: 'center',
684 py: 8,
685 textAlign: 'center'
686 }}>
687 <Collections sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
688 <Typography variant="h6" sx={{ mb: 1 }}>
689 还没有发布笔记
690 </Typography>
691 <Typography variant="body1" color="textSecondary" sx={{ mb: 3 }}>
692 {isOwnProfile ? '分享你的生活点滴吧~' : '该用户还没有发布任何笔记'}
693 </Typography>
694 {isOwnProfile && (
695 <Button variant="contained" color="primary">
696 发布第一篇笔记
697 </Button>
698 )}
699 </Box>
700 </Grid>
701 )}
702
703 {posts.length > 0 && (
704 <Grid item xs={12}>
705 <Box sx={{ display: 'flex', justifyContent: 'center', mt: 3 }}>
706 <Button
707 variant="outlined"
708 sx={{
709 borderRadius: 20,
710 px: 4,
711 display: 'flex',
712 alignItems: 'center'
713 }}
714 >
715 <ChevronLeft sx={{ mr: 1 }} />
716 上一页
717 <ChevronRight sx={{ ml: 2 }} />
718 </Button>
719 </Box>
720 </Grid>
721 )}
722 </Grid>
723 )}
724
725 {activeTab === 1 && (
726 <Grid container spacing={3}>
727 {tabLoading ? (
728 <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
729 <CircularProgress />
730 </Grid>
731 ) : favorites.length > 0 ? (
732 favorites.map((favorite) => (
733 <Grid item xs={12} sm={6} md={4} lg={3} key={favorite.id}>
734 <Card elevation={0} sx={{
735 bgcolor: 'white',
736 borderRadius: 3,
737 transition: 'transform 0.3s, box-shadow 0.3s',
738 '&:hover': {
739 transform: 'translateY(-5px)',
740 boxShadow: 3
741 }
742 }}>
743 <Box sx={{
744 height: 160,
745 position: 'relative',
746 borderTopLeftRadius: 16,
747 borderTopRightRadius: 16,
748 overflow: 'hidden'
749 }}>
750 <CardMedia
751 component="img"
752 height="160"
753 image={`https://source.unsplash.com/random/400x300?${favorite.id}`}
754 alt={favorite.title}
755 />
756 <Box sx={{
757 position: 'absolute',
758 top: 8,
759 right: 8,
760 bgcolor: 'rgba(0,0,0,0.6)',
761 color: 'white',
762 px: 1,
763 py: 0.5,
764 borderRadius: 4,
765 fontSize: 12
766 }}>
767 {favorite.type === 'image' ? '图文' : favorite.type === 'video' ? '视频' : '文档'}
768 </Box>
769 </Box>
770 <CardContent>
771 <Typography gutterBottom variant="subtitle1" fontWeight="medium">
772 {favorite.title}
773 </Typography>
774 <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
775 <Box sx={{ display: 'flex', alignItems: 'center' }}>
776 <Favorite fontSize="small" color="error" />
777 <Typography variant="body2" sx={{ ml: 0.5 }}>
778 {favorite.heat || Math.floor(Math.random() * 1000) + 1000}
779 </Typography>
780 </Box>
781 <Box sx={{ display: 'flex', alignItems: 'center' }}>
782 <Bookmark fontSize="small" color="primary" />
783 <Typography variant="body2" sx={{ ml: 0.5 }}>
784 {Math.floor(Math.random() * 500) + 100}
785 </Typography>
786 </Box>
787 </Box>
788 </CardContent>
789 </Card>
790 </Grid>
791 ))
792 ) : (
793 <Grid item xs={12}>
794 <Box sx={{
795 display: 'flex',
796 flexDirection: 'column',
797 alignItems: 'center',
798 py: 8,
799 textAlign: 'center'
800 }}>
801 <Bookmark sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
802 <Typography variant="h6" sx={{ mb: 1 }}>
803 {isOwnProfile ? '你还没有收藏内容' : '该用户没有收藏内容'}
804 </Typography>
805 <Typography variant="body1" color="textSecondary">
806 {isOwnProfile ? '看到喜欢的笔记可以收藏起来哦~' : ''}
807 </Typography>
808 </Box>
809 </Grid>
810 )}
811 </Grid>
812 )}
813
814 {activeTab === 2 && (
815 <Grid container spacing={3}>
816 {tabLoading ? (
817 <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
818 <CircularProgress />
819 </Grid>
820 ) : following.length > 0 ? (
821 following.map((follow) => (
822 <Grid item xs={12} sm={6} md={4} key={follow.id}>
823 <Paper
824 elevation={0}
825 sx={{
826 bgcolor: 'white',
827 borderRadius: 3,
828 p: 2,
829 display: 'flex',
830 alignItems: 'center',
831 cursor: 'pointer',
832 '&:hover': {
833 boxShadow: 1
834 }
835 }}
836 onClick={() => navigateToUserProfile(follow.id)}
837 >
838 <Avatar
839 src={follow.avatar || 'https://randomuser.me/api/portraits/men/22.jpg'}
840 sx={{ width: 60, height: 60 }}
841 />
842 <Box sx={{ ml: 2, flexGrow: 1 }}>
843 <Typography fontWeight="medium">{follow.username}</Typography>
844 <Typography variant="body2" color="textSecondary">
845 {follow.followers_count || Math.floor(Math.random() * 100) + 10} 粉丝
846 </Typography>
847 </Box>
848 {isOwnProfile && (
849 <Button
850 variant="outlined"
851 size="small"
852 sx={{ borderRadius: 20 }}
853 onClick={(e) => handleUnfollow(follow.id, e)}
854 >
855 已关注
856 </Button>
857 )}
858 </Paper>
859 </Grid>
860 ))
861 ) : (
862 <Grid item xs={12}>
863 <Box sx={{
864 display: 'flex',
865 flexDirection: 'column',
866 alignItems: 'center',
867 py: 8,
868 textAlign: 'center'
869 }}>
870 <Group sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
871 <Typography variant="h6" sx={{ mb: 1 }}>
872 {isOwnProfile ? '你还没有关注任何人' : '该用户还没有关注任何人'}
873 </Typography>
874 <Typography variant="body1" color="textSecondary">
875 {isOwnProfile ? '发现有趣的人并关注他们吧~' : ''}
876 </Typography>
877 </Box>
878 </Grid>
879 )}
880 </Grid>
881 )}
882 {activeTab === 3 && (
883 <Grid container spacing={3}>
884 {tabLoading ? (
885 <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
886 <CircularProgress />
887 </Grid>
888 ) : followers.length > 0 ? (
889 followers.map((follower) => (
890 <Grid item xs={12} sm={6} md={4} key={follower.id}>
891 <Paper
892 elevation={0}
893 sx={{
894 bgcolor: 'white',
895 borderRadius: 3,
896 p: 2,
897 display: 'flex',
898 alignItems: 'center',
899 cursor: 'pointer',
900 '&:hover': {
901 boxShadow: 1
902 }
903 }}
904 onClick={() => navigateToUserProfile(follower.id)}
905 >
906 <Avatar
907 src={follower.avatar || 'https://randomuser.me/api/portraits/men/22.jpg'}
908 sx={{ width: 60, height: 60 }}
909 />
910 <Box sx={{ ml: 2, flexGrow: 1 }}>
911 <Typography fontWeight="medium">{follower.username}</Typography>
912 <Typography variant="body2" color="textSecondary">
913 {follower.bio || '暂无简介'}
914 </Typography>
915 </Box>
916 {currentUser && currentUser.id !== follower.id && (
917 <Button
918 variant={follower.is_following ? "outlined" : "contained"}
919 color="primary"
920 size="small"
921 sx={{ borderRadius: 20 }}
922 onClick={(e) => {
923 e.stopPropagation();
924 if (follower.is_following) {
925 handleUnfollow(follower.id, e);
926 } else {
927 handleFollowUser(follower.id);
928 }
929 }}
930 >
931 {follower.is_following ? '已关注' : '关注'}
932 </Button>
933 )}
934 </Paper>
935 </Grid>
936 ))
937 ) : (
938 <Grid item xs={12}>
939 <Box sx={{
940 display: 'flex',
941 flexDirection: 'column',
942 alignItems: 'center',
943 py: 8,
944 textAlign: 'center'
945 }}>
946 <People sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
947 <Typography variant="h6" sx={{ mb: 1 }}>
948 {isOwnProfile ? '你还没有粉丝' : '该用户还没有粉丝'}
949 </Typography>
950 <Typography variant="body1" color="textSecondary">
951 {isOwnProfile ? '分享更多内容来吸引粉丝吧~' : ''}
952 </Typography>
953 </Box>
954 </Grid>
955 )}
956 </Grid>
957 )}
958 </Box>
959 </Container>
960
961 {/* 底部导航栏 - 仅移动端显示 */}
962 {isMobile && (
963 <Box sx={{
964 position: 'fixed',
965 bottom: 0,
966 left: 0,
967 right: 0,
968 bgcolor: 'white',
969 boxShadow: 3,
970 py: 1,
971 display: 'flex',
972 justifyContent: 'space-around'
973 }}>
974 <IconButton color="primary">
975 <Search fontSize="large" />
976 </IconButton>
977 <IconButton>
978 <Collections fontSize="large" />
979 </IconButton>
980 <Fab color="primary" size="medium" sx={{ mt: -2 }}>
981 <Add />
982 </Fab>
983 <IconButton>
984 <Notifications fontSize="large" />
985 </IconButton>
986 <IconButton>
987 <Person fontSize="large" />
988 </IconButton>
989 </Box>
990 )}
991
992 {/* 编辑资料模态框 */}
993 {isEditing && (
994 <Box sx={{
995 position: 'fixed',
996 top: 0,
997 left: 0,
998 right: 0,
999 bottom: 0,
1000 bgcolor: 'rgba(0,0,0,0.5)',
1001 zIndex: 1300,
1002 display: 'flex',
1003 alignItems: 'center',
1004 justifyContent: 'center',
1005 px: 2
1006 }}>
1007 <Paper sx={{
1008 width: '100%',
1009 maxWidth: 600,
1010 borderRadius: 4,
1011 overflow: 'hidden'
1012 }}>
1013 <Box sx={{
1014 bgcolor: 'primary.main',
1015 color: 'white',
1016 p: 2,
1017 display: 'flex',
1018 justifyContent: 'space-between',
1019 alignItems: 'center'
1020 }}>
1021 <Typography variant="h6">编辑资料</Typography>
1022 <IconButton color="inherit" onClick={() => setIsEditing(false)}>
1023 <Close />
1024 </IconButton>
1025 </Box>
1026
1027 <Box sx={{ p: 3 }}>
1028 <Box sx={{ display: 'flex', justifyContent: 'center', mb: 3 }}>
1029 <Badge
1030 overlap="circular"
1031 anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
1032 badgeContent={
1033 <IconButton
1034 size="small"
1035 sx={{
1036 bgcolor: 'grey.200',
1037 '&:hover': { bgcolor: 'grey.300' }
1038 }}
1039 >
1040 <CameraAlt fontSize="small" />
1041 </IconButton>
1042 }
1043 >
1044 <Avatar
1045 sx={{ width: 100, height: 100 }}
1046 src={formData.avatar || 'https://www.8848seo.cn/zb_users/upload/2023/02/20230210092856_68763.jpeg'}
1047 />
1048 </Badge>
1049 </Box>
1050
1051 <TextField
1052 fullWidth
1053 label="用户名"
1054 value={profileUser.username}
1055 margin="normal"
1056 disabled
1057 />
1058
1059 <TextField
1060 fullWidth
1061 name="avatar"
1062 label="头像URL"
1063 value={formData.avatar}
1064 onChange={handleFormChange}
1065 margin="normal"
1066 />
1067
1068 <TextField
1069 fullWidth
1070 name="bio"
1071 label="个人简介"
1072 value={formData.bio}
1073 onChange={handleFormChange}
1074 margin="normal"
1075 multiline
1076 rows={3}
1077 />
1078
1079 <Grid container spacing={2} sx={{ mt: 1 }}>
1080 <Grid item xs={6}>
1081 <TextField
1082 select
1083 fullWidth
1084 name="gender"
1085 label="性别"
1086 value={formData.gender}
1087 onChange={handleFormChange}
1088 margin="normal"
1089 >
1090 <MenuItem value="female">
1091 <Box sx={{ display: 'flex', alignItems: 'center' }}>
1092 <Female sx={{ mr: 1 }} />
1093 </Box>
1094 </MenuItem>
1095 <MenuItem value="male">
1096 <Box sx={{ display: 'flex', alignItems: 'center' }}>
1097 <Male sx={{ mr: 1 }} />
1098 </Box>
1099 </MenuItem>
1100 <MenuItem value="other">
1101 <Box sx={{ display: 'flex', alignItems: 'center' }}>
1102 <Public sx={{ mr: 1 }} /> 其他
1103 </Box>
1104 </MenuItem>
1105 </TextField>
1106 </Grid>
1107 <Grid item xs={6}>
1108 <TextField
1109 fullWidth
1110 name="birthday"
1111 label="生日"
1112 type="date"
1113 value={formData.birthday}
1114 onChange={handleFormChange}
1115 margin="normal"
1116 InputLabelProps={{ shrink: true }}
1117 />
1118 </Grid>
1119 </Grid>
1120
1121 <TextField
1122 fullWidth
1123 name="location"
1124 label="地区"
1125 value={formData.location}
1126 onChange={handleFormChange}
1127 margin="normal"
1128 InputProps={{
1129 startAdornment: (
1130 <InputAdornment position="start">
1131 <LocationOn />
1132 </InputAdornment>
1133 ),
1134 }}
1135 />
1136
1137 <Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 3 }}>
1138 <Button
1139 variant="outlined"
1140 sx={{ width: '48%' }}
1141 onClick={() => setIsEditing(false)}
1142 disabled={updating}
1143 >
1144 取消
1145 </Button>
1146 <Button
1147 variant="contained"
1148 color="primary"
1149 sx={{ width: '48%' }}
1150 onClick={handleUpdateProfile}
1151 disabled={updating}
1152 >
1153 {updating ? <CircularProgress size={24} /> : '保存'}
1154 </Button>
1155 </Box>
1156 </Box>
1157 </Paper>
1158 </Box>
1159 )}
1160
1161 {/* 提示信息 */}
1162 <Snackbar
1163 open={snackbar.open}
1164 autoHideDuration={3000}
1165 onClose={() => setSnackbar({ ...snackbar, open: false })}
1166 anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
1167 >
1168 <Alert
1169 severity={snackbar.severity}
1170 sx={{ width: '100%' }}
1171 onClose={() => setSnackbar({ ...snackbar, open: false })}
1172 >
1173 {snackbar.message}
1174 </Alert>
1175 </Snackbar>
1176 </Box>
1177 </ThemeProvider>
1178 );
1179};
1180
1181export default UserProfile;