blob: c957473ec6270f4d8d87746b742d7bcd8b864a72 [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 // 获取当前登录用户
230 const currentUserRes = await getCurrentUser();
231 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
444 const isOwnProfile = currentUser && currentUser.id === parseInt(userId);
445
446 return (
447 <ThemeProvider theme={theme}>
448 <Box sx={{
449 bgcolor: 'background.default',
450 minHeight: '100vh',
451 pb: isMobile ? 8 : 4
452 }}>
453 {/* 顶部横幅 */}
454 <Box sx={{
455 height: isMobile ? 200 : 250,
456 background: 'linear-gradient(135deg, #ff9a9e 0%, #fad0c4 100%)',
457 position: 'relative',
458 borderBottomLeftRadius: 24,
459 borderBottomRightRadius: 24,
460 boxShadow: 1
461 }}>
462 <Fab
463 color="primary"
464 size="small"
465 sx={{
466 position: 'absolute',
467 bottom: -20,
468 right: 16
469 }}
470 >
471 <CameraAlt />
472 </Fab>
473 </Box>
474
475 <Container maxWidth="lg">
476 {/* 用户信息区域 */}
477 <Box sx={{ px: isMobile ? 3 : 0, mt: -8, position: 'relative' }}>
478 <Grid container spacing={3}>
479 <Grid item xs={12} sm="auto">
480 <Badge
481 overlap="circular"
482 anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
483 badgeContent={
484 <IconButton
485 size="small"
486 sx={{
487 bgcolor: 'grey.200',
488 '&:hover': { bgcolor: 'grey.300' }
489 }}
490 onClick={() => setIsEditing(true)}
491 disabled={!isOwnProfile}
492 >
493 <Edit fontSize="small" />
494 </IconButton>
495 }
496 >
497 <Avatar
498 sx={{
499 width: 120,
500 height: 120,
501 border: '4px solid white',
502 boxShadow: 3
503 }}
504 src={profileUser.avatar || 'https://www.8848seo.cn/zb_users/upload/2023/02/20230210092856_68763.jpeg'}
505 />
506 </Badge>
507 </Grid>
508
509 <Grid item xs={12} sm>
510 <Box sx={{ display: 'flex', justifyContent: 'space-between', flexDirection: isMobile ? 'column' : 'row' }}>
511 <Box>
512 <Typography variant="h5" fontWeight="bold">
513 {profileUser.username}
514 </Typography>
515 <Typography variant="subtitle1" sx={{ mt: 0.5, maxWidth: 600 }}>
516 {profileUser.bio || '这个人很懒,还没有写简介~'}
517 </Typography>
518 <Box sx={{ display: 'flex', mt: 1, gap: 1, flexWrap: 'wrap' }}>
519 <Chip
520 icon={<LocationOn fontSize="small" />}
521 label={formData.location}
522 size="small"
523 variant="outlined"
524 />
525 <Chip
526 icon={<Cake fontSize="small" />}
527 label={formData.birthday}
528 size="small"
529 variant="outlined"
530 />
531 <Chip
532 icon={<Female fontSize="small" />}
533 label={formData.gender}
534 size="small"
535 variant="outlined"
536 />
537 </Box>
538 </Box>
539
540 <Box sx={{ mt: isMobile ? 2 : 0, alignSelf: 'flex-start' }}>
541 {!isOwnProfile && currentUser && (
542 <>
543 <Button
544 variant={profileUser.is_following ? "outlined" : "contained"}
545 color="primary"
546 onClick={handleFollowToggle}
547 sx={{
548 borderRadius: 20,
549 px: 3,
550 fontWeight: 'bold'
551 }}
552 >
553 {profileUser.is_following ? '已关注' : '关注'}
554 </Button>
TRM-coding2a8fd602025-06-19 19:33:16 +0800555 <IconButton sx={{ ml: 1 }} onClick={handleMenuOpen}>
TRM-coding29174c22025-06-18 23:56:51 +0800556 <MoreVert />
557 </IconButton>
TRM-coding2a8fd602025-06-19 19:33:16 +0800558 <Menu
559 anchorEl={anchorEl}
560 open={menuOpen}
561 onClose={handleMenuClose}
562 anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
563 transformOrigin={{ vertical: 'top', horizontal: 'right' }}
564 >
565 <MenuItem onClick={handleLogout}>退出登录</MenuItem>
566 </Menu>
TRM-coding29174c22025-06-18 23:56:51 +0800567 </>
568 )}
569 </Box>
570 </Box>
571
572 <Grid container spacing={2} sx={{ mt: 2 }}>
573 <Grid item>
574 <Box textAlign="center">
trm9984ee52025-06-20 15:16:56 +0000575 <Typography variant="h6">{allPosts.length}</Typography>
TRM-coding29174c22025-06-18 23:56:51 +0800576 <Typography variant="body2" color="textSecondary">笔记</Typography>
577 </Box>
578 </Grid>
579 <Grid item>
580 <Box textAlign="center">
581 <Typography variant="h6">{profileUser.followers_count || 0}</Typography>
582 <Typography variant="body2" color="textSecondary">粉丝</Typography>
583 </Box>
584 </Grid>
585 <Grid item>
586 <Box textAlign="center">
587 <Typography variant="h6">{profileUser.following_count || 0}</Typography>
588 <Typography variant="body2" color="textSecondary">关注</Typography>
589 </Box>
590 </Grid>
591 <Grid item>
592 <Box textAlign="center">
593 {/* 使用真实数据:获赞与收藏总数 */}
594 <Typography variant="h6">
595 {(interactions.likes_count + interactions.favorites_count).toLocaleString()}
596 </Typography>
597 <Typography variant="body2" color="textSecondary">获赞与收藏</Typography>
598 </Box>
599 </Grid>
600 </Grid>
601 </Grid>
602 </Grid>
603 </Box>
604
605 {/* 标签栏 */}
606 <Box sx={{ mt: 4 }}>
607 <Tabs
608 value={activeTab}
609 onChange={handleTabChange}
610 variant={isMobile ? "fullWidth" : "standard"}
611 indicatorColor="primary"
612 textColor="primary"
613 sx={{
614 borderBottom: 1,
615 borderColor: 'divider'
616 }}
617 >
618 <Tab icon={isMobile ? <Collections /> : null} label="笔记" />
TRM-coding29174c22025-06-18 23:56:51 +0800619 </Tabs>
620 </Box>
621
622 {/* 内容区域 */}
623 <Box sx={{ mt: 3 }}>
trm9984ee52025-06-20 15:16:56 +0000624 {/* 只保留笔记标签页 */}
625 <Grid container spacing={3}>
626 {tabLoading ? (
627 <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
628 <CircularProgress />
629 </Grid>
630 ) : allPosts.length === 0 ? (
631 <Grid item xs={12}>
632 <Box sx={{
633 display: 'flex',
634 flexDirection: 'column',
635 alignItems: 'center',
636 py: 8,
637 textAlign: 'center'
638 }}>
639 <Collections sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
640 <Typography variant="h6" sx={{ mb: 1 }}>
641 还没有发布笔记
642 </Typography>
643 <Typography variant="body1" color="textSecondary" sx={{ mb: 3 }}>
644 {isOwnProfile ? '分享你的生活点滴吧~' : '该用户还没有发布任何笔记'}
645 </Typography>
646 {isOwnProfile && (
647 <Button variant="contained" color="primary">
648 发布第一篇笔记
649 </Button>
650 )}
651 </Box>
652 </Grid>
653 ) : (
654 // 显示当前页的帖子
655 posts.map((post, index) => (
656 <Grid item xs={12} sm={6} lg={3} key={post.id}>
657 <Card elevation={0} sx={{
658 bgcolor: 'white',
659 borderRadius: 3,
660 height: '100%',
661 display: 'flex',
662 flexDirection: 'column'
663 }}>
664 {/* 只有当帖子有 media_urls 时才显示图片 */}
665 {post.media_urls && post.media_urls.length > 0 && (
TRM-coding29174c22025-06-18 23:56:51 +0800666 <CardMedia
667 component="img"
668 height="180"
trm9984ee52025-06-20 15:16:56 +0000669 image={post.media_urls[0]}
TRM-coding29174c22025-06-18 23:56:51 +0800670 alt={post.title}
671 />
TRM-coding29174c22025-06-18 23:56:51 +0800672 )}
trm9984ee52025-06-20 15:16:56 +0000673 <CardContent sx={{ flexGrow: 1 }}>
674 <Typography gutterBottom variant="h6" component="div">
675 {post.title}
676 </Typography>
677 <Typography variant="body2" color="text.secondary">
678 {post.content ? post.content.substring(0, 60) + '...' : '暂无内容'}
679 </Typography>
680 </CardContent>
681 <CardActions sx={{ justifyContent: 'space-between', px: 2, pb: 2 }}>
682 <Box>
683 <IconButton aria-label="add to favorites">
684 <Favorite />
685 <Typography variant="body2" sx={{ ml: 1 }}>
686 {post.heat || Math.floor(Math.random() * 1000) + 1000}
687 </Typography>
688 </IconButton>
689 <IconButton aria-label="share">
690 <Share />
691 </IconButton>
692 </Box>
693 <Chip
694 label={post.type === 'image' ? '图文' : post.type === 'video' ? '视频' : '文档'}
695 size="small"
696 color="primary"
697 variant="outlined"
698 />
699 </CardActions>
700 </Card>
TRM-coding29174c22025-06-18 23:56:51 +0800701 </Grid>
trm9984ee52025-06-20 15:16:56 +0000702 ))
703 )}
704
705 {/* 分页组件 */}
706 {allPosts.length > postsPerPage && (
707 <Grid item xs={12}>
708 <Stack spacing={2} alignItems="center" sx={{ mt: 4 }}>
709 <Typography variant="body2" color="textSecondary">
710 {allPosts.length} 篇笔记,第 {currentPage} 页,共 {totalPages}
711 </Typography>
712 <Pagination
713 count={totalPages}
714 page={currentPage}
715 onChange={(event, page) => handlePageChange(page)}
716 color="primary"
717 size={isMobile ? "small" : "medium"}
718 showFirstButton
719 showLastButton
720 sx={{
721 '& .MuiPaginationItem-root': {
722 borderRadius: 2,
TRM-coding29174c22025-06-18 23:56:51 +0800723 }
trm9984ee52025-06-20 15:16:56 +0000724 }}
725 />
726 </Stack>
727 </Grid>
728 )}
729 </Grid>
TRM-coding29174c22025-06-18 23:56:51 +0800730 </Box>
731 </Container>
732
733 {/* 底部导航栏 - 仅移动端显示 */}
734 {isMobile && (
735 <Box sx={{
736 position: 'fixed',
737 bottom: 0,
738 left: 0,
739 right: 0,
740 bgcolor: 'white',
741 boxShadow: 3,
742 py: 1,
743 display: 'flex',
744 justifyContent: 'space-around'
745 }}>
746 <IconButton color="primary">
747 <Search fontSize="large" />
748 </IconButton>
749 <IconButton>
750 <Collections fontSize="large" />
751 </IconButton>
752 <Fab color="primary" size="medium" sx={{ mt: -2 }}>
753 <Add />
754 </Fab>
755 <IconButton>
756 <Notifications fontSize="large" />
757 </IconButton>
758 <IconButton>
759 <Person fontSize="large" />
760 </IconButton>
761 </Box>
762 )}
763
764 {/* 编辑资料模态框 */}
765 {isEditing && (
766 <Box sx={{
767 position: 'fixed',
768 top: 0,
769 left: 0,
770 right: 0,
771 bottom: 0,
772 bgcolor: 'rgba(0,0,0,0.5)',
773 zIndex: 1300,
774 display: 'flex',
775 alignItems: 'center',
776 justifyContent: 'center',
777 px: 2
778 }}>
779 <Paper sx={{
780 width: '100%',
781 maxWidth: 600,
782 borderRadius: 4,
783 overflow: 'hidden'
784 }}>
785 <Box sx={{
786 bgcolor: 'primary.main',
787 color: 'white',
788 p: 2,
789 display: 'flex',
790 justifyContent: 'space-between',
791 alignItems: 'center'
792 }}>
793 <Typography variant="h6">编辑资料</Typography>
794 <IconButton color="inherit" onClick={() => setIsEditing(false)}>
795 <Close />
796 </IconButton>
797 </Box>
798
799 <Box sx={{ p: 3 }}>
800 <Box sx={{ display: 'flex', justifyContent: 'center', mb: 3 }}>
801 <Badge
802 overlap="circular"
803 anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
804 badgeContent={
805 <IconButton
806 size="small"
807 sx={{
808 bgcolor: 'grey.200',
809 '&:hover': { bgcolor: 'grey.300' }
810 }}
811 >
812 <CameraAlt fontSize="small" />
813 </IconButton>
814 }
815 >
816 <Avatar
817 sx={{ width: 100, height: 100 }}
818 src={formData.avatar || 'https://www.8848seo.cn/zb_users/upload/2023/02/20230210092856_68763.jpeg'}
819 />
820 </Badge>
821 </Box>
822
823 <TextField
824 fullWidth
825 label="用户名"
826 value={profileUser.username}
827 margin="normal"
828 disabled
829 />
830
831 <TextField
832 fullWidth
833 name="avatar"
834 label="头像URL"
835 value={formData.avatar}
836 onChange={handleFormChange}
837 margin="normal"
838 />
839
840 <TextField
841 fullWidth
842 name="bio"
843 label="个人简介"
844 value={formData.bio}
845 onChange={handleFormChange}
846 margin="normal"
847 multiline
848 rows={3}
849 />
850
851 <Grid container spacing={2} sx={{ mt: 1 }}>
852 <Grid item xs={6}>
853 <TextField
854 select
855 fullWidth
856 name="gender"
857 label="性别"
858 value={formData.gender}
859 onChange={handleFormChange}
860 margin="normal"
861 >
862 <MenuItem value="female">
863 <Box sx={{ display: 'flex', alignItems: 'center' }}>
864 <Female sx={{ mr: 1 }} />
865 </Box>
866 </MenuItem>
867 <MenuItem value="male">
868 <Box sx={{ display: 'flex', alignItems: 'center' }}>
869 <Male sx={{ mr: 1 }} />
870 </Box>
871 </MenuItem>
872 <MenuItem value="other">
873 <Box sx={{ display: 'flex', alignItems: 'center' }}>
874 <Public sx={{ mr: 1 }} /> 其他
875 </Box>
876 </MenuItem>
877 </TextField>
878 </Grid>
879 <Grid item xs={6}>
880 <TextField
881 fullWidth
882 name="birthday"
883 label="生日"
884 type="date"
885 value={formData.birthday}
886 onChange={handleFormChange}
887 margin="normal"
888 InputLabelProps={{ shrink: true }}
889 />
890 </Grid>
891 </Grid>
892
893 <TextField
894 fullWidth
895 name="location"
896 label="地区"
897 value={formData.location}
898 onChange={handleFormChange}
899 margin="normal"
900 InputProps={{
901 startAdornment: (
902 <InputAdornment position="start">
903 <LocationOn />
904 </InputAdornment>
905 ),
906 }}
907 />
908
909 <Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 3 }}>
910 <Button
911 variant="outlined"
912 sx={{ width: '48%' }}
913 onClick={() => setIsEditing(false)}
914 disabled={updating}
915 >
916 取消
917 </Button>
918 <Button
919 variant="contained"
920 color="primary"
921 sx={{ width: '48%' }}
922 onClick={handleUpdateProfile}
923 disabled={updating}
924 >
925 {updating ? <CircularProgress size={24} /> : '保存'}
926 </Button>
927 </Box>
928 </Box>
929 </Paper>
930 </Box>
931 )}
932
933 {/* 提示信息 */}
934 <Snackbar
935 open={snackbar.open}
936 autoHideDuration={3000}
937 onClose={() => setSnackbar({ ...snackbar, open: false })}
938 anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
939 >
940 <Alert
941 severity={snackbar.severity}
942 sx={{ width: '100%' }}
943 onClose={() => setSnackbar({ ...snackbar, open: false })}
944 >
945 {snackbar.message}
946 </Alert>
947 </Snackbar>
948 </Box>
949 </ThemeProvider>
950 );
951};
952
953export default UserProfile;