blob: 802c4b6932a3de682abe35edb2a034a2209ad4e0 [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,
trm9984ee52025-06-20 15:16:56 +000056 Close
TRM-coding29174c22025-06-18 23:56:51 +080057} from '@mui/icons-material';
58import { createTheme, ThemeProvider } from '@mui/material/styles';
59import { Link, useNavigate } from 'react-router-dom';
60
61// 导入API服务
62import {
63 getCurrentUser,
64 getUser,
65 updateUser as updateUserApi,
TRM-coding29174c22025-06-18 23:56:51 +080066 followUser as followUserApi,
67 unfollowUser as unfollowUserApi,
68 getUserPosts,
trm9984ee52025-06-20 15:16:56 +000069 getUserInteractions
TRM-coding29174c22025-06-18 23:56:51 +080070} from '../api/api_ljc';
trm9984ee52025-06-20 15:16:56 +000071import { fetchPost } from '../api/posts_wzy';
TRM-coding29174c22025-06-18 23:56:51 +080072
73// 创建小红书主题
74const theme = createTheme({
75 palette: {
76 primary: {
77 main: '#ff4081',
78 },
79 secondary: {
80 main: '#f50057',
81 },
82 background: {
83 default: '#f5f5f5',
84 },
85 },
86 typography: {
87 fontFamily: '"PingFang SC", "Helvetica Neue", Arial, sans-serif',
88 h5: {
89 fontWeight: 600,
90 },
91 subtitle1: {
92 color: 'rgba(0, 0, 0, 0.6)',
93 },
94 },
95 components: {
96 MuiButton: {
97 styleOverrides: {
98 root: {
99 borderRadius: 20,
100 textTransform: 'none',
101 fontWeight: 500,
102 },
103 },
104 },
105 MuiCard: {
106 styleOverrides: {
107 root: {
108 borderRadius: 16,
109 },
110 },
111 },
112 },
113});
114
115const UserProfile = () => {
116 const { userId } = useParams();
117 const theme = useTheme();
118 const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
119 const navigate = useNavigate();
120 const [activeTab, setActiveTab] = useState(0);
121 const [isEditing, setIsEditing] = useState(false);
TRM-coding29174c22025-06-18 23:56:51 +0800122 const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: 'success' });
TRM-coding2a8fd602025-06-19 19:33:16 +0800123 const [anchorEl, setAnchorEl] = useState(null);
TRM-coding29174c22025-06-18 23:56:51 +0800124
125 // 用户数据状态
126 const [currentUser, setCurrentUser] = useState(null);
127 const [profileUser, setProfileUser] = useState(null);
TRM-coding29174c22025-06-18 23:56:51 +0800128 const [posts, setPosts] = useState([]);
trm9984ee52025-06-20 15:16:56 +0000129 const [allPosts, setAllPosts] = useState([]); // 存储所有帖子数据
TRM-coding29174c22025-06-18 23:56:51 +0800130 const [interactions, setInteractions] = useState({
131 likes_count: 0,
132 favorites_count: 0
133 });
134
trm9984ee52025-06-20 15:16:56 +0000135 // 分页状态
136 const [currentPage, setCurrentPage] = useState(1);
137 const [postsPerPage] = useState(8); // 每页显示8个帖子
138 const [totalPages, setTotalPages] = useState(0);
139
TRM-coding29174c22025-06-18 23:56:51 +0800140 // 加载状态
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
trm9984ee52025-06-20 15:16:56 +0000161 // 分页处理函数
162 const updatePaginatedPosts = (page, allPostsData) => {
163 const startIndex = (page - 1) * postsPerPage;
164 const endIndex = startIndex + postsPerPage;
165 const paginatedPosts = allPostsData.slice(startIndex, endIndex);
166 setPosts(paginatedPosts);
167 setTotalPages(Math.ceil(allPostsData.length / postsPerPage));
168 };
169
170 const handlePageChange = (newPage) => {
171 if (newPage >= 1 && newPage <= totalPages) {
172 setCurrentPage(newPage);
173 updatePaginatedPosts(newPage, allPosts);
174 }
175 };
176
177 const handlePrevPage = () => {
178 handlePageChange(currentPage - 1);
179 };
180
181 const handleNextPage = () => {
182 handlePageChange(currentPage + 1);
183 };
184
TRM-coding29174c22025-06-18 23:56:51 +0800185 // 加载用户数据
186 useEffect(() => {
187 const fetchInteractions = async () => {
188 try {
189 const response = await getUserInteractions(userId);
190 if (response.data.success) {
191 setInteractions(response.data.data);
192 } else {
193 console.error(response.data.error);
194 }
195 } catch (error) {
196 console.error('获取互动数据失败:', error);
197 }
198 };
199
200 fetchInteractions();
201
202 const handleFollowUser = async (followeeId) => {
203 try {
204 await followUserApi(followeeId);
205 showSnackbar('关注成功');
206
207 // 更新粉丝列表状态(将刚关注的用户标记为已关注)
trm9984ee52025-06-20 15:16:56 +0000208 // setFollowers(prev => prev.map(user =>
209 // user.id === followeeId ? { ...user, is_following: true } : user
210 // ));
TRM-coding29174c22025-06-18 23:56:51 +0800211
212 // 更新当前用户关注数
213 if (currentUser) {
214 setCurrentUser(prev => ({
215 ...prev,
216 following_count: prev.following_count + 1
217 }));
218 }
219
220 } catch (error) {
221 console.error('关注操作失败:', error);
222 showSnackbar('关注失败,请重试', 'error');
223 }
224 };
225 const fetchData = async () => {
226 try {
227 setLoading(true);
228
229 // 获取当前登录用户
trma6b60ef2025-06-21 01:47:09 +0000230 const currentUserRes = await getUser(userId);
TRM-coding29174c22025-06-18 23:56:51 +0800231 setCurrentUser(currentUserRes.data);
232
233 // 获取目标用户信息
234 console.log('userId', userId)
235 const profileUserRes = await getUser(userId);
236 setProfileUser(profileUserRes.data);
237 setFormData({
238 avatar: profileUserRes.data.avatar || '',
239 bio: profileUserRes.data.bio || '',
240 gender: profileUserRes.data.gender || '',
241 birthday: profileUserRes.data.birthday || '',
242 location: profileUserRes.data.location || ''
243 });
244
245 // 获取用户帖子
246 const postsRes = await getUserPosts(userId);
trm9984ee52025-06-20 15:16:56 +0000247
248 // 为了拿到 media_urls,这里需要拉取每个帖子的详情
249 const postsWithDetails = await Promise.all(
250 postsRes.data.map(async post => {
251 try {
252 const detail = await fetchPost(post.id);
253 return {
254 ...post,
255 media_urls: detail.media_urls || [],
256 user_id: detail.user_id
257 };
258 } catch (error) {
259 console.error(`获取帖子 ${post.id} 详情失败:`, error);
260 return {
261 ...post,
262 media_urls: [],
263 user_id: null
264 };
265 }
266 })
267 );
268
269 setAllPosts(postsWithDetails); // 存储所有帖子
270 updatePaginatedPosts(1, postsWithDetails); // 初始化第一页数据
271 setCurrentPage(1); // 重置到第一页
TRM-coding29174c22025-06-18 23:56:51 +0800272
273 // 获取用户互动数据(获赞和收藏数量)
274 const interactionsRes = await getUserInteractions(userId);
275 setInteractions(interactionsRes.data);
276
277 } catch (error) {
278 console.error('获取用户数据失败:', error);
279 showSnackbar('获取用户数据失败,请重试', 'error');
280 } finally {
281 setLoading(false);
282 }
283 };
284
285 fetchData();
286 }, [userId]);
287
288 // 根据标签页加载数据
289 useEffect(() => {
trm9984ee52025-06-20 15:16:56 +0000290 // 由于只有笔记标签页,不需要根据activeTab加载不同数据
TRM-coding29174c22025-06-18 23:56:51 +0800291 }, [activeTab, userId, profileUser]);
292
293 const handleTabChange = (event, newValue) => {
trm9984ee52025-06-20 15:16:56 +0000294 // 由于只有一个标签页,不需要切换逻辑
TRM-coding29174c22025-06-18 23:56:51 +0800295 setActiveTab(newValue);
296 };
297
298 const handleFollowToggle = async () => {
299 if (!currentUser || !profileUser) return;
TRM-coding29174c22025-06-18 23:56:51 +0800300 try {
301 if (profileUser.is_following) {
302 await unfollowUserApi(profileUser.id);
303 showSnackbar('已取消关注');
304 } else {
305 await followUserApi(profileUser.id);
306 showSnackbar('关注成功');
307 }
TRM-coding29174c22025-06-18 23:56:51 +0800308 // 更新用户信息
309 const updatedUser = await getUser(userId);
310 setProfileUser(updatedUser.data);
TRM-coding29174c22025-06-18 23:56:51 +0800311 } catch (error) {
312 console.error('关注操作失败:', error);
313 showSnackbar('操作失败,请重试', 'error');
314 }
315 };
316
317 const handleFollowUser = async (followeeId) => {
318 try {
319 await followUserApi(followeeId);
320 showSnackbar('关注成功');
321
TRM-coding29174c22025-06-18 23:56:51 +0800322 // 更新当前用户关注数
323 if (currentUser) {
324 setCurrentUser(prev => ({
325 ...prev,
326 following_count: prev.following_count + 1
327 }));
328 }
329
330 } catch (error) {
331 console.error('关注操作失败:', error);
332 showSnackbar('关注失败,请重试', 'error');
333 }
334 };
335
336 const handleUnfollow = async (followeeId, e) => {
337 e.stopPropagation(); // 阻止事件冒泡
338
339 try {
340 await unfollowUserApi(followeeId);
341 showSnackbar('已取消关注');
342
TRM-coding29174c22025-06-18 23:56:51 +0800343 // 更新当前用户关注数
344 if (currentUser) {
345 setCurrentUser(prev => ({
346 ...prev,
347 following_count: prev.following_count - 1
348 }));
349 }
350
351 // 更新目标用户粉丝数
352 setProfileUser(prev => ({
353 ...prev,
354 followers_count: prev.followers_count - 1
355 }));
356
357 } catch (error) {
358 console.error('取消关注失败:', error);
359 showSnackbar('操作失败,请重试', 'error');
360 }
361 };
362
363 const handleFormChange = (e) => {
364 const { name, value } = e.target;
365 setFormData({ ...formData, [name]: value });
366 };
367
368 const handleUpdateProfile = async () => {
369 if (!profileUser) return;
370
371 try {
372 setUpdating(true);
373 const data = {
374 avatar: formData.avatar,
375 bio: formData.bio,
376 gender: formData.gender,
377 birthday: formData.birthday,
378 location: formData.location
379 };
380
381 // 调用更新API
382 const updatedUser = await updateUserApi(profileUser.id, data);
383
384 // 更新本地状态
385 setProfileUser({ ...profileUser, ...updatedUser.data });
386 setFormData({ ...formData, ...data });
387
388
389 showSnackbar('个人资料更新成功');
390 setIsEditing(false);
391
392 } catch (error) {
393 console.error('更新个人资料失败:', error);
394 showSnackbar('更新失败,请重试', 'error');
395 } finally {
396 setUpdating(false);
397 }
398 };
399
400 const navigateToUserProfile = (userId) => {
401 navigate(`/user/${userId}`);
402 };
403
TRM-coding2a8fd602025-06-19 19:33:16 +0800404 const handleMenuOpen = (e) => setAnchorEl(e.currentTarget);
405 const handleMenuClose = () => setAnchorEl(null);
406 const handleLogout = () => {
407 handleMenuClose();
408 // 清理本地存储
409 localStorage.clear();
410 // 在此处添加退出登录逻辑,比如清理本地存储并跳转
411 navigate('/login');
412 };
413
TRM-coding29174c22025-06-18 23:56:51 +0800414 if (loading) {
415 return (
416 <Box sx={{
417 display: 'flex',
418 justifyContent: 'center',
419 alignItems: 'center',
420 height: '100vh'
421 }}>
422 <CircularProgress size={60} />
423 </Box>
424 );
425 }
426
427 if (!profileUser) {
428 return (
429 <Box sx={{
430 display: 'flex',
431 justifyContent: 'center',
432 alignItems: 'center',
433 height: '100vh',
434 flexDirection: 'column'
435 }}>
436 <Typography variant="h6" sx={{ mb: 2 }}>用户不存在</Typography>
437 <Button variant="outlined" onClick={() => window.location.reload()}>
438 重新加载
439 </Button>
440 </Box>
441 );
442 }
443
trma6b60ef2025-06-21 01:47:09 +0000444 console.log(currentUser.id)
TRM-coding29174c22025-06-18 23:56:51 +0800445 const isOwnProfile = currentUser && currentUser.id === parseInt(userId);
446
447 return (
448 <ThemeProvider theme={theme}>
449 <Box sx={{
450 bgcolor: 'background.default',
451 minHeight: '100vh',
452 pb: isMobile ? 8 : 4
453 }}>
454 {/* 顶部横幅 */}
455 <Box sx={{
456 height: isMobile ? 200 : 250,
457 background: 'linear-gradient(135deg, #ff9a9e 0%, #fad0c4 100%)',
458 position: 'relative',
459 borderBottomLeftRadius: 24,
460 borderBottomRightRadius: 24,
461 boxShadow: 1
462 }}>
463 <Fab
464 color="primary"
465 size="small"
466 sx={{
467 position: 'absolute',
468 bottom: -20,
469 right: 16
470 }}
471 >
472 <CameraAlt />
473 </Fab>
474 </Box>
475
476 <Container maxWidth="lg">
477 {/* 用户信息区域 */}
478 <Box sx={{ px: isMobile ? 3 : 0, mt: -8, position: 'relative' }}>
479 <Grid container spacing={3}>
480 <Grid item xs={12} sm="auto">
481 <Badge
482 overlap="circular"
483 anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
484 badgeContent={
485 <IconButton
486 size="small"
487 sx={{
488 bgcolor: 'grey.200',
489 '&:hover': { bgcolor: 'grey.300' }
490 }}
491 onClick={() => setIsEditing(true)}
492 disabled={!isOwnProfile}
493 >
494 <Edit fontSize="small" />
495 </IconButton>
496 }
497 >
498 <Avatar
499 sx={{
500 width: 120,
501 height: 120,
502 border: '4px solid white',
503 boxShadow: 3
504 }}
505 src={profileUser.avatar || 'https://www.8848seo.cn/zb_users/upload/2023/02/20230210092856_68763.jpeg'}
506 />
507 </Badge>
508 </Grid>
509
510 <Grid item xs={12} sm>
511 <Box sx={{ display: 'flex', justifyContent: 'space-between', flexDirection: isMobile ? 'column' : 'row' }}>
512 <Box>
513 <Typography variant="h5" fontWeight="bold">
514 {profileUser.username}
515 </Typography>
516 <Typography variant="subtitle1" sx={{ mt: 0.5, maxWidth: 600 }}>
517 {profileUser.bio || '这个人很懒,还没有写简介~'}
518 </Typography>
519 <Box sx={{ display: 'flex', mt: 1, gap: 1, flexWrap: 'wrap' }}>
520 <Chip
521 icon={<LocationOn fontSize="small" />}
522 label={formData.location}
523 size="small"
524 variant="outlined"
525 />
526 <Chip
527 icon={<Cake fontSize="small" />}
528 label={formData.birthday}
529 size="small"
530 variant="outlined"
531 />
532 <Chip
533 icon={<Female fontSize="small" />}
534 label={formData.gender}
535 size="small"
536 variant="outlined"
537 />
538 </Box>
539 </Box>
540
541 <Box sx={{ mt: isMobile ? 2 : 0, alignSelf: 'flex-start' }}>
542 {!isOwnProfile && currentUser && (
543 <>
544 <Button
545 variant={profileUser.is_following ? "outlined" : "contained"}
546 color="primary"
547 onClick={handleFollowToggle}
548 sx={{
549 borderRadius: 20,
550 px: 3,
551 fontWeight: 'bold'
552 }}
553 >
554 {profileUser.is_following ? '已关注' : '关注'}
555 </Button>
TRM-coding2a8fd602025-06-19 19:33:16 +0800556 <IconButton sx={{ ml: 1 }} onClick={handleMenuOpen}>
TRM-coding29174c22025-06-18 23:56:51 +0800557 <MoreVert />
558 </IconButton>
TRM-coding2a8fd602025-06-19 19:33:16 +0800559 <Menu
560 anchorEl={anchorEl}
561 open={menuOpen}
562 onClose={handleMenuClose}
563 anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
564 transformOrigin={{ vertical: 'top', horizontal: 'right' }}
565 >
566 <MenuItem onClick={handleLogout}>退出登录</MenuItem>
567 </Menu>
TRM-coding29174c22025-06-18 23:56:51 +0800568 </>
569 )}
570 </Box>
571 </Box>
572
573 <Grid container spacing={2} sx={{ mt: 2 }}>
574 <Grid item>
575 <Box textAlign="center">
trm9984ee52025-06-20 15:16:56 +0000576 <Typography variant="h6">{allPosts.length}</Typography>
TRM-coding29174c22025-06-18 23:56:51 +0800577 <Typography variant="body2" color="textSecondary">笔记</Typography>
578 </Box>
579 </Grid>
580 <Grid item>
581 <Box textAlign="center">
582 <Typography variant="h6">{profileUser.followers_count || 0}</Typography>
583 <Typography variant="body2" color="textSecondary">粉丝</Typography>
584 </Box>
585 </Grid>
586 <Grid item>
587 <Box textAlign="center">
588 <Typography variant="h6">{profileUser.following_count || 0}</Typography>
589 <Typography variant="body2" color="textSecondary">关注</Typography>
590 </Box>
591 </Grid>
592 <Grid item>
593 <Box textAlign="center">
594 {/* 使用真实数据:获赞与收藏总数 */}
595 <Typography variant="h6">
596 {(interactions.likes_count + interactions.favorites_count).toLocaleString()}
597 </Typography>
598 <Typography variant="body2" color="textSecondary">获赞与收藏</Typography>
599 </Box>
600 </Grid>
601 </Grid>
602 </Grid>
603 </Grid>
604 </Box>
605
606 {/* 标签栏 */}
607 <Box sx={{ mt: 4 }}>
608 <Tabs
609 value={activeTab}
610 onChange={handleTabChange}
611 variant={isMobile ? "fullWidth" : "standard"}
612 indicatorColor="primary"
613 textColor="primary"
614 sx={{
615 borderBottom: 1,
616 borderColor: 'divider'
617 }}
618 >
619 <Tab icon={isMobile ? <Collections /> : null} label="笔记" />
TRM-coding29174c22025-06-18 23:56:51 +0800620 </Tabs>
621 </Box>
622
623 {/* 内容区域 */}
624 <Box sx={{ mt: 3 }}>
trm9984ee52025-06-20 15:16:56 +0000625 {/* 只保留笔记标签页 */}
626 <Grid container spacing={3}>
627 {tabLoading ? (
628 <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
629 <CircularProgress />
630 </Grid>
631 ) : allPosts.length === 0 ? (
632 <Grid item xs={12}>
633 <Box sx={{
634 display: 'flex',
635 flexDirection: 'column',
636 alignItems: 'center',
637 py: 8,
638 textAlign: 'center'
639 }}>
640 <Collections sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
641 <Typography variant="h6" sx={{ mb: 1 }}>
642 还没有发布笔记
643 </Typography>
644 <Typography variant="body1" color="textSecondary" sx={{ mb: 3 }}>
645 {isOwnProfile ? '分享你的生活点滴吧~' : '该用户还没有发布任何笔记'}
646 </Typography>
647 {isOwnProfile && (
648 <Button variant="contained" color="primary">
649 发布第一篇笔记
650 </Button>
651 )}
652 </Box>
653 </Grid>
654 ) : (
655 // 显示当前页的帖子
656 posts.map((post, index) => (
657 <Grid item xs={12} sm={6} lg={3} key={post.id}>
658 <Card elevation={0} sx={{
659 bgcolor: 'white',
660 borderRadius: 3,
661 height: '100%',
662 display: 'flex',
663 flexDirection: 'column'
664 }}>
665 {/* 只有当帖子有 media_urls 时才显示图片 */}
666 {post.media_urls && post.media_urls.length > 0 && (
TRM-coding29174c22025-06-18 23:56:51 +0800667 <CardMedia
668 component="img"
669 height="180"
trm9984ee52025-06-20 15:16:56 +0000670 image={post.media_urls[0]}
TRM-coding29174c22025-06-18 23:56:51 +0800671 alt={post.title}
672 />
TRM-coding29174c22025-06-18 23:56:51 +0800673 )}
trm9984ee52025-06-20 15:16:56 +0000674 <CardContent sx={{ flexGrow: 1 }}>
675 <Typography gutterBottom variant="h6" component="div">
676 {post.title}
677 </Typography>
678 <Typography variant="body2" color="text.secondary">
679 {post.content ? post.content.substring(0, 60) + '...' : '暂无内容'}
680 </Typography>
681 </CardContent>
682 <CardActions sx={{ justifyContent: 'space-between', px: 2, pb: 2 }}>
683 <Box>
684 <IconButton aria-label="add to favorites">
685 <Favorite />
686 <Typography variant="body2" sx={{ ml: 1 }}>
687 {post.heat || Math.floor(Math.random() * 1000) + 1000}
688 </Typography>
689 </IconButton>
690 <IconButton aria-label="share">
691 <Share />
692 </IconButton>
693 </Box>
694 <Chip
695 label={post.type === 'image' ? '图文' : post.type === 'video' ? '视频' : '文档'}
696 size="small"
697 color="primary"
698 variant="outlined"
699 />
700 </CardActions>
701 </Card>
TRM-coding29174c22025-06-18 23:56:51 +0800702 </Grid>
trm9984ee52025-06-20 15:16:56 +0000703 ))
704 )}
705
706 {/* 分页组件 */}
707 {allPosts.length > postsPerPage && (
708 <Grid item xs={12}>
709 <Stack spacing={2} alignItems="center" sx={{ mt: 4 }}>
710 <Typography variant="body2" color="textSecondary">
711 {allPosts.length} 篇笔记,第 {currentPage} 页,共 {totalPages}
712 </Typography>
713 <Pagination
714 count={totalPages}
715 page={currentPage}
716 onChange={(event, page) => handlePageChange(page)}
717 color="primary"
718 size={isMobile ? "small" : "medium"}
719 showFirstButton
720 showLastButton
721 sx={{
722 '& .MuiPaginationItem-root': {
723 borderRadius: 2,
TRM-coding29174c22025-06-18 23:56:51 +0800724 }
trm9984ee52025-06-20 15:16:56 +0000725 }}
726 />
727 </Stack>
728 </Grid>
729 )}
730 </Grid>
TRM-coding29174c22025-06-18 23:56:51 +0800731 </Box>
732 </Container>
733
734 {/* 底部导航栏 - 仅移动端显示 */}
735 {isMobile && (
736 <Box sx={{
737 position: 'fixed',
738 bottom: 0,
739 left: 0,
740 right: 0,
741 bgcolor: 'white',
742 boxShadow: 3,
743 py: 1,
744 display: 'flex',
745 justifyContent: 'space-around'
746 }}>
747 <IconButton color="primary">
748 <Search fontSize="large" />
749 </IconButton>
750 <IconButton>
751 <Collections fontSize="large" />
752 </IconButton>
753 <Fab color="primary" size="medium" sx={{ mt: -2 }}>
754 <Add />
755 </Fab>
756 <IconButton>
757 <Notifications fontSize="large" />
758 </IconButton>
759 <IconButton>
760 <Person fontSize="large" />
761 </IconButton>
762 </Box>
763 )}
764
765 {/* 编辑资料模态框 */}
766 {isEditing && (
767 <Box sx={{
768 position: 'fixed',
769 top: 0,
770 left: 0,
771 right: 0,
772 bottom: 0,
773 bgcolor: 'rgba(0,0,0,0.5)',
774 zIndex: 1300,
775 display: 'flex',
776 alignItems: 'center',
777 justifyContent: 'center',
778 px: 2
779 }}>
780 <Paper sx={{
781 width: '100%',
782 maxWidth: 600,
783 borderRadius: 4,
784 overflow: 'hidden'
785 }}>
786 <Box sx={{
787 bgcolor: 'primary.main',
788 color: 'white',
789 p: 2,
790 display: 'flex',
791 justifyContent: 'space-between',
792 alignItems: 'center'
793 }}>
794 <Typography variant="h6">编辑资料</Typography>
795 <IconButton color="inherit" onClick={() => setIsEditing(false)}>
796 <Close />
797 </IconButton>
798 </Box>
799
800 <Box sx={{ p: 3 }}>
801 <Box sx={{ display: 'flex', justifyContent: 'center', mb: 3 }}>
802 <Badge
803 overlap="circular"
804 anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
805 badgeContent={
806 <IconButton
807 size="small"
808 sx={{
809 bgcolor: 'grey.200',
810 '&:hover': { bgcolor: 'grey.300' }
811 }}
812 >
813 <CameraAlt fontSize="small" />
814 </IconButton>
815 }
816 >
817 <Avatar
818 sx={{ width: 100, height: 100 }}
819 src={formData.avatar || 'https://www.8848seo.cn/zb_users/upload/2023/02/20230210092856_68763.jpeg'}
820 />
821 </Badge>
822 </Box>
823
824 <TextField
825 fullWidth
826 label="用户名"
827 value={profileUser.username}
828 margin="normal"
829 disabled
830 />
831
832 <TextField
833 fullWidth
834 name="avatar"
835 label="头像URL"
836 value={formData.avatar}
837 onChange={handleFormChange}
838 margin="normal"
839 />
840
841 <TextField
842 fullWidth
843 name="bio"
844 label="个人简介"
845 value={formData.bio}
846 onChange={handleFormChange}
847 margin="normal"
848 multiline
849 rows={3}
850 />
851
852 <Grid container spacing={2} sx={{ mt: 1 }}>
853 <Grid item xs={6}>
854 <TextField
855 select
856 fullWidth
857 name="gender"
858 label="性别"
859 value={formData.gender}
860 onChange={handleFormChange}
861 margin="normal"
862 >
863 <MenuItem value="female">
864 <Box sx={{ display: 'flex', alignItems: 'center' }}>
865 <Female sx={{ mr: 1 }} />
866 </Box>
867 </MenuItem>
868 <MenuItem value="male">
869 <Box sx={{ display: 'flex', alignItems: 'center' }}>
870 <Male sx={{ mr: 1 }} />
871 </Box>
872 </MenuItem>
873 <MenuItem value="other">
874 <Box sx={{ display: 'flex', alignItems: 'center' }}>
875 <Public sx={{ mr: 1 }} /> 其他
876 </Box>
877 </MenuItem>
878 </TextField>
879 </Grid>
880 <Grid item xs={6}>
881 <TextField
882 fullWidth
883 name="birthday"
884 label="生日"
885 type="date"
886 value={formData.birthday}
887 onChange={handleFormChange}
888 margin="normal"
889 InputLabelProps={{ shrink: true }}
890 />
891 </Grid>
892 </Grid>
893
894 <TextField
895 fullWidth
896 name="location"
897 label="地区"
898 value={formData.location}
899 onChange={handleFormChange}
900 margin="normal"
901 InputProps={{
902 startAdornment: (
903 <InputAdornment position="start">
904 <LocationOn />
905 </InputAdornment>
906 ),
907 }}
908 />
909
910 <Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 3 }}>
911 <Button
912 variant="outlined"
913 sx={{ width: '48%' }}
914 onClick={() => setIsEditing(false)}
915 disabled={updating}
916 >
917 取消
918 </Button>
919 <Button
920 variant="contained"
921 color="primary"
922 sx={{ width: '48%' }}
923 onClick={handleUpdateProfile}
924 disabled={updating}
925 >
926 {updating ? <CircularProgress size={24} /> : '保存'}
927 </Button>
928 </Box>
929 </Box>
930 </Paper>
931 </Box>
932 )}
933
934 {/* 提示信息 */}
935 <Snackbar
936 open={snackbar.open}
937 autoHideDuration={3000}
938 onClose={() => setSnackbar({ ...snackbar, open: false })}
939 anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
940 >
941 <Alert
942 severity={snackbar.severity}
943 sx={{ width: '100%' }}
944 onClose={() => setSnackbar({ ...snackbar, open: false })}
945 >
946 {snackbar.message}
947 </Alert>
948 </Snackbar>
949 </Box>
950 </ThemeProvider>
951 );
952};
953
954export default UserProfile;