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