blob: 2e54fac87c7ae707a4616f0c02801e8c7d4c1743 [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 // 获取当前登录用户
trma6b60ef2025-06-21 01:47:09 +0000218 const currentUserRes = await getUser(userId);
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
trma6b60ef2025-06-21 01:47:09 +0000484 console.log(currentUser.id)
TRM-coding29174c22025-06-18 23:56:51 +0800485 const isOwnProfile = currentUser && currentUser.id === parseInt(userId);
486
487 return (
488 <ThemeProvider theme={theme}>
489 <Box sx={{
490 bgcolor: 'background.default',
491 minHeight: '100vh',
492 pb: isMobile ? 8 : 4
493 }}>
494 {/* 顶部横幅 */}
495 <Box sx={{
496 height: isMobile ? 200 : 250,
497 background: 'linear-gradient(135deg, #ff9a9e 0%, #fad0c4 100%)',
498 position: 'relative',
499 borderBottomLeftRadius: 24,
500 borderBottomRightRadius: 24,
501 boxShadow: 1
502 }}>
503 <Fab
504 color="primary"
505 size="small"
506 sx={{
507 position: 'absolute',
508 bottom: -20,
509 right: 16
510 }}
511 >
512 <CameraAlt />
513 </Fab>
514 </Box>
515
516 <Container maxWidth="lg">
517 {/* 用户信息区域 */}
518 <Box sx={{ px: isMobile ? 3 : 0, mt: -8, position: 'relative' }}>
519 <Grid container spacing={3}>
520 <Grid item xs={12} sm="auto">
521 <Badge
522 overlap="circular"
523 anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
524 badgeContent={
525 <IconButton
526 size="small"
527 sx={{
528 bgcolor: 'grey.200',
529 '&:hover': { bgcolor: 'grey.300' }
530 }}
531 onClick={() => setIsEditing(true)}
532 disabled={!isOwnProfile}
533 >
534 <Edit fontSize="small" />
535 </IconButton>
536 }
537 >
538 <Avatar
539 sx={{
540 width: 120,
541 height: 120,
542 border: '4px solid white',
543 boxShadow: 3
544 }}
545 src={profileUser.avatar || 'https://www.8848seo.cn/zb_users/upload/2023/02/20230210092856_68763.jpeg'}
546 />
547 </Badge>
548 </Grid>
549
550 <Grid item xs={12} sm>
551 <Box sx={{ display: 'flex', justifyContent: 'space-between', flexDirection: isMobile ? 'column' : 'row' }}>
552 <Box>
553 <Typography variant="h5" fontWeight="bold">
554 {profileUser.username}
555 </Typography>
556 <Typography variant="subtitle1" sx={{ mt: 0.5, maxWidth: 600 }}>
557 {profileUser.bio || '这个人很懒,还没有写简介~'}
558 </Typography>
559 <Box sx={{ display: 'flex', mt: 1, gap: 1, flexWrap: 'wrap' }}>
560 <Chip
561 icon={<LocationOn fontSize="small" />}
562 label={formData.location}
563 size="small"
564 variant="outlined"
565 />
566 <Chip
567 icon={<Cake fontSize="small" />}
568 label={formData.birthday}
569 size="small"
570 variant="outlined"
571 />
572 <Chip
573 icon={<Female fontSize="small" />}
574 label={formData.gender}
575 size="small"
576 variant="outlined"
577 />
578 </Box>
579 </Box>
580
581 <Box sx={{ mt: isMobile ? 2 : 0, alignSelf: 'flex-start' }}>
582 {!isOwnProfile && currentUser && (
583 <>
584 <Button
585 variant={profileUser.is_following ? "outlined" : "contained"}
586 color="primary"
587 onClick={handleFollowToggle}
588 sx={{
589 borderRadius: 20,
590 px: 3,
591 fontWeight: 'bold'
592 }}
593 >
594 {profileUser.is_following ? '已关注' : '关注'}
595 </Button>
TRM-coding2a8fd602025-06-19 19:33:16 +0800596 <IconButton sx={{ ml: 1 }} onClick={handleMenuOpen}>
TRM-coding29174c22025-06-18 23:56:51 +0800597 <MoreVert />
598 </IconButton>
TRM-coding2a8fd602025-06-19 19:33:16 +0800599 <Menu
600 anchorEl={anchorEl}
601 open={menuOpen}
602 onClose={handleMenuClose}
603 anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
604 transformOrigin={{ vertical: 'top', horizontal: 'right' }}
605 >
606 <MenuItem onClick={handleLogout}>退出登录</MenuItem>
607 </Menu>
TRM-coding29174c22025-06-18 23:56:51 +0800608 </>
609 )}
610 </Box>
611 </Box>
612
613 <Grid container spacing={2} sx={{ mt: 2 }}>
614 <Grid item>
615 <Box textAlign="center">
trm9984ee52025-06-20 15:16:56 +0000616 <Typography variant="h6">{allPosts.length}</Typography>
TRM-coding29174c22025-06-18 23:56:51 +0800617 <Typography variant="body2" color="textSecondary">笔记</Typography>
618 </Box>
619 </Grid>
620 <Grid item>
621 <Box textAlign="center">
622 <Typography variant="h6">{profileUser.followers_count || 0}</Typography>
623 <Typography variant="body2" color="textSecondary">粉丝</Typography>
624 </Box>
625 </Grid>
626 <Grid item>
627 <Box textAlign="center">
628 <Typography variant="h6">{profileUser.following_count || 0}</Typography>
629 <Typography variant="body2" color="textSecondary">关注</Typography>
630 </Box>
631 </Grid>
632 <Grid item>
633 <Box textAlign="center">
634 {/* 使用真实数据:获赞与收藏总数 */}
635 <Typography variant="h6">
636 {(interactions.likes_count + interactions.favorites_count).toLocaleString()}
637 </Typography>
638 <Typography variant="body2" color="textSecondary">获赞与收藏</Typography>
639 </Box>
640 </Grid>
641 </Grid>
642 </Grid>
643 </Grid>
644 </Box>
645
646 {/* 标签栏 */}
647 <Box sx={{ mt: 4 }}>
648 <Tabs
649 value={activeTab}
650 onChange={handleTabChange}
651 variant={isMobile ? "fullWidth" : "standard"}
652 indicatorColor="primary"
653 textColor="primary"
654 sx={{
655 borderBottom: 1,
656 borderColor: 'divider'
657 }}
658 >
659 <Tab icon={isMobile ? <Collections /> : null} label="笔记" />
223010694a960a52025-06-21 12:21:48 +0800660 <Tab icon={isMobile ? <Bookmark /> : null} label="收藏" />
661 <Tab icon={isMobile ? <Group /> : null} label="关注" />
662 <Tab icon={isMobile ? <People /> : null} label="粉丝" />
TRM-coding29174c22025-06-18 23:56:51 +0800663 </Tabs>
664 </Box>
665
666 {/* 内容区域 */}
667 <Box sx={{ mt: 3 }}>
223010694a960a52025-06-21 12:21:48 +0800668
669 {activeTab === 0 && (
670 <Grid container spacing={3}>
671 {tabLoading ? (
672 <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
673 <CircularProgress />
674 </Grid>
675 ) : posts.length > 0 ? (
676 posts.map((post, index) => (
677 <Grid item xs={12} sm={6} lg={3} key={post.id}>
678 <Card elevation={0} sx={{
679 bgcolor: 'white',
680 borderRadius: 3,
681 height: '100%',
682 display: 'flex',
683 flexDirection: 'column'
684 }}>
685 {/* 只有当帖子有 media_urls 时才显示图片 */}
trm9984ee52025-06-20 15:16:56 +0000686 {post.media_urls && post.media_urls.length > 0 && (
TRM-coding29174c22025-06-18 23:56:51 +0800687 <CardMedia
688 component="img"
689 height="180"
trm9984ee52025-06-20 15:16:56 +0000690 image={post.media_urls[0]}
TRM-coding29174c22025-06-18 23:56:51 +0800691 alt={post.title}
692 />
TRM-coding29174c22025-06-18 23:56:51 +0800693 )}
223010694a960a52025-06-21 12:21:48 +0800694 <CardContent sx={{ flexGrow: 1 }}>
695 <Typography gutterBottom variant="h6" component="div">
696 {post.title}
697 </Typography>
698 <Typography variant="body2" color="text.secondary">
699 {post.content.substring(0, 60)}...
700 </Typography>
701 </CardContent>
702 <CardActions sx={{ justifyContent: 'space-between', px: 2, pb: 2 }}>
703 <Box>
704 <IconButton aria-label="add to favorites">
705 <Favorite />
706 <Typography variant="body2" sx={{ ml: 1 }}>
707 {post.heat || Math.floor(Math.random() * 1000) + 1000}
708 </Typography>
709 </IconButton>
710 <IconButton aria-label="share">
711 <Share />
712 </IconButton>
713 </Box>
714 <Chip
715 label={post.type === 'image' ? '图文' : post.type === 'video' ? '视频' : '文档'}
716 size="small"
717 color="primary"
718 variant="outlined"
719 />
720 </CardActions>
721 </Card>
722 </Grid>
723 ))
724 ) : (
725 <Grid item xs={12}>
726 <Box sx={{
727 display: 'flex',
728 flexDirection: 'column',
729 alignItems: 'center',
730 py: 8,
731 textAlign: 'center'
732 }}>
733 <Collections sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
734 <Typography variant="h6" sx={{ mb: 1 }}>
735 还没有发布笔记
736 </Typography>
737 <Typography variant="body1" color="textSecondary" sx={{ mb: 3 }}>
738 {isOwnProfile ? '分享你的生活点滴吧~' : '该用户还没有发布任何笔记'}
739 </Typography>
740 {isOwnProfile && (
741 <Button variant="contained" color="primary">
742 发布第一篇笔记
743 </Button>
744 )}
745 </Box>
TRM-coding29174c22025-06-18 23:56:51 +0800746 </Grid>
223010694a960a52025-06-21 12:21:48 +0800747 )}
748
749 {posts.length > 0 && (
750 <Grid item xs={12}>
751 <Box sx={{ display: 'flex', justifyContent: 'center', mt: 3 }}>
752 <Button
753 variant="outlined"
754 sx={{
755 borderRadius: 20,
756 px: 4,
757 display: 'flex',
758 alignItems: 'center'
759 }}
760 >
761 <ChevronLeft sx={{ mr: 1 }} />
762 上一页
763 <ChevronRight sx={{ ml: 2 }} />
764 </Button>
765 </Box>
766 </Grid>
767 )}
768 </Grid>
769 )}
770
771 {activeTab === 1 && (
772 <Grid container spacing={3}>
773 {tabLoading ? (
774 <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
775 <CircularProgress />
776 </Grid>
777 ) : favorites.length > 0 ? (
778 favorites.map((favorite) => (
779 <Grid item xs={12} sm={6} md={4} lg={3} key={favorite.id}>
780 <Card elevation={0} sx={{
781 bgcolor: 'white',
782 borderRadius: 3,
783 transition: 'transform 0.3s, box-shadow 0.3s',
784 '&:hover': {
785 transform: 'translateY(-5px)',
786 boxShadow: 3
TRM-coding29174c22025-06-18 23:56:51 +0800787 }
223010694a960a52025-06-21 12:21:48 +0800788 }}>
789 <Box sx={{
790 height: 160,
791 position: 'relative',
792 borderTopLeftRadius: 16,
793 borderTopRightRadius: 16,
794 overflow: 'hidden'
795 }}>
796 <CardMedia
797 component="img"
798 height="160"
799 image={`https://source.unsplash.com/random/400x300?${favorite.id}`}
800 alt={favorite.title}
801 />
802 <Box sx={{
803 position: 'absolute',
804 top: 8,
805 right: 8,
806 bgcolor: 'rgba(0,0,0,0.6)',
807 color: 'white',
808 px: 1,
809 py: 0.5,
810 borderRadius: 4,
811 fontSize: 12
812 }}>
813 {favorite.type === 'image' ? '图文' : favorite.type === 'video' ? '视频' : '文档'}
814 </Box>
815 </Box>
816 <CardContent>
817 <Typography gutterBottom variant="subtitle1" fontWeight="medium">
818 {favorite.title}
819 </Typography>
820 <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
821 <Box sx={{ display: 'flex', alignItems: 'center' }}>
822 <Favorite fontSize="small" color="error" />
823 <Typography variant="body2" sx={{ ml: 0.5 }}>
824 {favorite.heat || Math.floor(Math.random() * 1000) + 1000}
825 </Typography>
826 </Box>
827 <Box sx={{ display: 'flex', alignItems: 'center' }}>
828 <Bookmark fontSize="small" color="primary" />
829 <Typography variant="body2" sx={{ ml: 0.5 }}>
830 {Math.floor(Math.random() * 500) + 100}
831 </Typography>
832 </Box>
833 </Box>
834 </CardContent>
835 </Card>
836 </Grid>
837 ))
838 ) : (
839 <Grid item xs={12}>
840 <Box sx={{
841 display: 'flex',
842 flexDirection: 'column',
843 alignItems: 'center',
844 py: 8,
845 textAlign: 'center'
846 }}>
847 <Bookmark sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
848 <Typography variant="h6" sx={{ mb: 1 }}>
849 {isOwnProfile ? '你还没有收藏内容' : '该用户没有收藏内容'}
850 </Typography>
851 <Typography variant="body1" color="textSecondary">
852 {isOwnProfile ? '看到喜欢的笔记可以收藏起来哦~' : ''}
853 </Typography>
854 </Box>
855 </Grid>
856 )}
857 </Grid>
858 )}
859
860 {activeTab === 2 && (
861 <Grid container spacing={3}>
862 {tabLoading ? (
863 <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
864 <CircularProgress />
865 </Grid>
866 ) : following.length > 0 ? (
867 following.map((follow) => (
868 <Grid item xs={12} sm={6} md={4} key={follow.id}>
869 <Paper
870 elevation={0}
871 sx={{
872 bgcolor: 'white',
873 borderRadius: 3,
874 p: 2,
875 display: 'flex',
876 alignItems: 'center',
877 cursor: 'pointer',
878 '&:hover': {
879 boxShadow: 1
880 }
881 }}
882 onClick={() => navigateToUserProfile(follow.id)}
883 >
884 <Avatar
885 src={follow.avatar || 'https://randomuser.me/api/portraits/men/22.jpg'}
886 sx={{ width: 60, height: 60 }}
887 />
888 <Box sx={{ ml: 2, flexGrow: 1 }}>
889 <Typography fontWeight="medium">{follow.username}</Typography>
890 <Typography variant="body2" color="textSecondary">
891 {follow.followers_count || Math.floor(Math.random() * 100) + 10} 粉丝
892 </Typography>
893 </Box>
894 {isOwnProfile && (
895 <Button
896 variant="outlined"
897 size="small"
898 sx={{ borderRadius: 20 }}
899 onClick={(e) => {
900 e.stopPropagation();
901 handleUnfollow(userId,follow.id);
902 }}
903 >
904 已关注
905 </Button>
906 )}
907 </Paper>
908 </Grid>
909 ))
910 ) : (
911 <Grid item xs={12}>
912 <Box sx={{
913 display: 'flex',
914 flexDirection: 'column',
915 alignItems: 'center',
916 py: 8,
917 textAlign: 'center'
918 }}>
919 <Group sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
920 <Typography variant="h6" sx={{ mb: 1 }}>
921 {isOwnProfile ? '你还没有关注任何人' : '该用户还没有关注任何人'}
922 </Typography>
923 <Typography variant="body1" color="textSecondary">
924 {isOwnProfile ? '发现有趣的人并关注他们吧~' : ''}
925 </Typography>
926 </Box>
927 </Grid>
928 )}
929 </Grid>
930 )}
931 {activeTab === 3 && (
932 <Grid container spacing={3}>
933 {tabLoading ? (
934 <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
935 <CircularProgress />
936 </Grid>
937 ) : followers.length > 0 ? (
938 followers.map((follower) => (
939 <Grid item xs={12} sm={6} md={4} key={follower.id}>
940 <Paper
941 elevation={0}
942 sx={{
943 bgcolor: 'white',
944 borderRadius: 3,
945 p: 2,
946 display: 'flex',
947 alignItems: 'center',
948 cursor: 'pointer',
949 '&:hover': {
950 boxShadow: 1
951 }
952 }}
953 onClick={() => navigateToUserProfile(follower.id)}
954 >
955 <Avatar
956 src={follower.avatar || 'https://randomuser.me/api/portraits/men/22.jpg'}
957 sx={{ width: 60, height: 60 }}
958 />
959 <Box sx={{ ml: 2, flexGrow: 1 }}>
960 <Typography fontWeight="medium">{follower.username}</Typography>
961 <Typography variant="body2" color="textSecondary">
962 {follower.bio || '暂无简介'}
963 </Typography>
964 <Typography variant="body2" color="textSecondary">
965 {follower.followers_count} 粉丝
966 </Typography>
967 </Box>
968 {currentUser && currentUser.id !== follower.id && (
969 <Button
970 variant={follower.is_following ? "outlined" : "contained"}
971 color="primary"
972 size="small"
973 sx={{ borderRadius: 20 }}
974 onClick={(e) => {
975 e.stopPropagation();
976 if (follower.is_following) {
977 handleUnfollow(userId,follower.id);
978 } else {
979 handleFollowUser(userId,follower.id);
980 }
981 }}
982 >
983 {follower.is_following ? '已关注' : '关注'}
984 </Button>
985 )}
986 </Paper>
987 </Grid>
988 ))
989 ) : (
990 <Grid item xs={12}>
991 <Box sx={{
992 display: 'flex',
993 flexDirection: 'column',
994 alignItems: 'center',
995 py: 8,
996 textAlign: 'center'
997 }}>
998 <People sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
999 <Typography variant="h6" sx={{ mb: 1 }}>
1000 {isOwnProfile ? '你还没有粉丝' : '该用户还没有粉丝'}
1001 </Typography>
1002 <Typography variant="body1" color="textSecondary">
1003 {isOwnProfile ? '分享更多内容来吸引粉丝吧~' : ''}
1004 </Typography>
1005 </Box>
1006 </Grid>
1007 )}
1008 </Grid>
1009 )}
TRM-coding29174c22025-06-18 23:56:51 +08001010 </Box>
1011 </Container>
1012
1013 {/* 底部导航栏 - 仅移动端显示 */}
1014 {isMobile && (
1015 <Box sx={{
1016 position: 'fixed',
1017 bottom: 0,
1018 left: 0,
1019 right: 0,
1020 bgcolor: 'white',
1021 boxShadow: 3,
1022 py: 1,
1023 display: 'flex',
1024 justifyContent: 'space-around'
1025 }}>
1026 <IconButton color="primary">
1027 <Search fontSize="large" />
1028 </IconButton>
1029 <IconButton>
1030 <Collections fontSize="large" />
1031 </IconButton>
1032 <Fab color="primary" size="medium" sx={{ mt: -2 }}>
1033 <Add />
1034 </Fab>
1035 <IconButton>
1036 <Notifications fontSize="large" />
1037 </IconButton>
1038 <IconButton>
1039 <Person fontSize="large" />
1040 </IconButton>
1041 </Box>
1042 )}
1043
1044 {/* 编辑资料模态框 */}
1045 {isEditing && (
1046 <Box sx={{
1047 position: 'fixed',
1048 top: 0,
1049 left: 0,
1050 right: 0,
1051 bottom: 0,
1052 bgcolor: 'rgba(0,0,0,0.5)',
1053 zIndex: 1300,
1054 display: 'flex',
1055 alignItems: 'center',
1056 justifyContent: 'center',
1057 px: 2
1058 }}>
1059 <Paper sx={{
1060 width: '100%',
1061 maxWidth: 600,
1062 borderRadius: 4,
1063 overflow: 'hidden'
1064 }}>
1065 <Box sx={{
1066 bgcolor: 'primary.main',
1067 color: 'white',
1068 p: 2,
1069 display: 'flex',
1070 justifyContent: 'space-between',
1071 alignItems: 'center'
1072 }}>
1073 <Typography variant="h6">编辑资料</Typography>
1074 <IconButton color="inherit" onClick={() => setIsEditing(false)}>
1075 <Close />
1076 </IconButton>
1077 </Box>
1078
1079 <Box sx={{ p: 3 }}>
1080 <Box sx={{ display: 'flex', justifyContent: 'center', mb: 3 }}>
1081 <Badge
1082 overlap="circular"
1083 anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
1084 badgeContent={
1085 <IconButton
1086 size="small"
1087 sx={{
1088 bgcolor: 'grey.200',
1089 '&:hover': { bgcolor: 'grey.300' }
1090 }}
1091 >
1092 <CameraAlt fontSize="small" />
1093 </IconButton>
1094 }
1095 >
1096 <Avatar
1097 sx={{ width: 100, height: 100 }}
1098 src={formData.avatar || 'https://www.8848seo.cn/zb_users/upload/2023/02/20230210092856_68763.jpeg'}
1099 />
1100 </Badge>
1101 </Box>
1102
1103 <TextField
1104 fullWidth
1105 label="用户名"
1106 value={profileUser.username}
1107 margin="normal"
1108 disabled
1109 />
1110
1111 <TextField
1112 fullWidth
1113 name="avatar"
1114 label="头像URL"
1115 value={formData.avatar}
1116 onChange={handleFormChange}
1117 margin="normal"
1118 />
1119
1120 <TextField
1121 fullWidth
1122 name="bio"
1123 label="个人简介"
1124 value={formData.bio}
1125 onChange={handleFormChange}
1126 margin="normal"
1127 multiline
1128 rows={3}
1129 />
1130
1131 <Grid container spacing={2} sx={{ mt: 1 }}>
1132 <Grid item xs={6}>
1133 <TextField
1134 select
1135 fullWidth
1136 name="gender"
1137 label="性别"
1138 value={formData.gender}
1139 onChange={handleFormChange}
1140 margin="normal"
1141 >
1142 <MenuItem value="female">
1143 <Box sx={{ display: 'flex', alignItems: 'center' }}>
1144 <Female sx={{ mr: 1 }} />
1145 </Box>
1146 </MenuItem>
1147 <MenuItem value="male">
1148 <Box sx={{ display: 'flex', alignItems: 'center' }}>
1149 <Male sx={{ mr: 1 }} />
1150 </Box>
1151 </MenuItem>
1152 <MenuItem value="other">
1153 <Box sx={{ display: 'flex', alignItems: 'center' }}>
1154 <Public sx={{ mr: 1 }} /> 其他
1155 </Box>
1156 </MenuItem>
1157 </TextField>
1158 </Grid>
1159 <Grid item xs={6}>
1160 <TextField
1161 fullWidth
1162 name="birthday"
1163 label="生日"
1164 type="date"
1165 value={formData.birthday}
1166 onChange={handleFormChange}
1167 margin="normal"
1168 InputLabelProps={{ shrink: true }}
1169 />
1170 </Grid>
1171 </Grid>
1172
1173 <TextField
1174 fullWidth
1175 name="location"
1176 label="地区"
1177 value={formData.location}
1178 onChange={handleFormChange}
1179 margin="normal"
1180 InputProps={{
1181 startAdornment: (
1182 <InputAdornment position="start">
1183 <LocationOn />
1184 </InputAdornment>
1185 ),
1186 }}
1187 />
1188
1189 <Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 3 }}>
1190 <Button
1191 variant="outlined"
1192 sx={{ width: '48%' }}
1193 onClick={() => setIsEditing(false)}
1194 disabled={updating}
1195 >
1196 取消
1197 </Button>
1198 <Button
1199 variant="contained"
1200 color="primary"
1201 sx={{ width: '48%' }}
1202 onClick={handleUpdateProfile}
1203 disabled={updating}
1204 >
1205 {updating ? <CircularProgress size={24} /> : '保存'}
1206 </Button>
1207 </Box>
1208 </Box>
1209 </Paper>
1210 </Box>
1211 )}
1212
1213 {/* 提示信息 */}
1214 <Snackbar
1215 open={snackbar.open}
1216 autoHideDuration={3000}
1217 onClose={() => setSnackbar({ ...snackbar, open: false })}
1218 anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
1219 >
1220 <Alert
1221 severity={snackbar.severity}
1222 sx={{ width: '100%' }}
1223 onClose={() => setSnackbar({ ...snackbar, open: false })}
1224 >
1225 {snackbar.message}
1226 </Alert>
1227 </Snackbar>
1228 </Box>
1229 </ThemeProvider>
1230 );
1231};
1232
1233export default UserProfile;