稳定运行版本v1
Change-Id: Id2cb7e1c6d3fbe156911e42fa342cab74f817193
diff --git a/Merge/back_wzy/utils/__pycache__/Fpost.cpython-310.pyc b/Merge/back_wzy/utils/__pycache__/Fpost.cpython-310.pyc
index dde50ba..c4b0fad 100644
--- a/Merge/back_wzy/utils/__pycache__/Fpost.cpython-310.pyc
+++ b/Merge/back_wzy/utils/__pycache__/Fpost.cpython-310.pyc
Binary files differ
diff --git a/Merge/front/src/api/posts_trm.js b/Merge/front/src/api/posts_trm.js
index 43d7105..d6303a7 100644
--- a/Merge/front/src/api/posts_trm.js
+++ b/Merge/front/src/api/posts_trm.js
@@ -105,7 +105,16 @@
body: JSON.stringify({ userid })
})
if (!res.ok) throw new Error(`fetchUserList: ${res.status}`)
- return res.json()
+
+ const json = await res.json()
+ console.log('fetchUserList response:', json)
+
+ // handle unauthorized
+ if (json.status === 'error' && json.message === 'Unauthorized') {
+ throw new Error('Unauthorized')
+ }
+
+ return json
}
export async function giveAdmin(targetId) {
diff --git a/Merge/front/src/components/HomeFeed.jsx b/Merge/front/src/components/HomeFeed.jsx
index 2e42621..82e027a 100644
--- a/Merge/front/src/components/HomeFeed.jsx
+++ b/Merge/front/src/components/HomeFeed.jsx
@@ -259,8 +259,8 @@
return {
id: d.id,
title: d.title,
- authorId: d.user_id,
- avatar: `https://i.pravatar.cc/40?img=${d.user_id}`,
+ author: `作者 ${d.user_id}`,
+ // avatar: `https://i.pravatar.cc/40?img=${d.user_id}`,
img: d.media_urls?.[0] || '', // 用第一张媒体作为封面
likes: d.heat
}
diff --git a/Merge/front/src/components/PerformanceLogs.js b/Merge/front/src/components/PerformanceLogs.js
index 00f7e7d..f87ef7a 100644
--- a/Merge/front/src/components/PerformanceLogs.js
+++ b/Merge/front/src/components/PerformanceLogs.js
@@ -8,11 +8,29 @@
function PerformanceLogs({ userId }) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
+ const [unauthorized, setUnauthorized] = useState(false);
useEffect(() => {
setLoading(true);
fetchSysCost(userId)
- .then(list => {
+ .then(result => {
+ // 检查是否是权限错误
+ if (result && result.status === 'error' && result.message === 'Unauthorized') {
+ setUnauthorized(true);
+ setData([]);
+ return;
+ }
+
+ // 确保数据是数组格式
+ let list = [];
+ if (Array.isArray(result)) {
+ list = result;
+ } else if (Array.isArray(result.data)) {
+ list = result.data;
+ } else if (Array.isArray(result.syscost)) {
+ list = result.syscost;
+ }
+
const msList = list.map(item => ({
...item,
elapsed_time: item.elapsed_time * 1000,
@@ -23,8 +41,15 @@
}));
console.log('Converted data:', msList[0]); // debug first item
setData(msList);
+ setUnauthorized(false);
})
- .catch(err => console.error('fetchSysCost error:', err))
+ .catch(err => {
+ console.error('fetchSysCost error:', err);
+ if (err.message === 'Unauthorized') {
+ setUnauthorized(true);
+ setData([]);
+ }
+ })
.finally(() => setLoading(false));
}, [userId]);
@@ -58,6 +83,35 @@
);
}
+ if (unauthorized) {
+ return (
+ <section className="dashboard-performance">
+ <div style={{
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ height: '400px',
+ flexDirection: 'column',
+ textAlign: 'center'
+ }}>
+ <div style={{
+ fontSize: '18px',
+ color: '#ff4d4f',
+ marginBottom: '10px'
+ }}>
+ 权限不足,无法访问性能监控数据
+ </div>
+ <div style={{
+ fontSize: '14px',
+ color: '#666'
+ }}>
+ 请联系管理员获取相应权限
+ </div>
+ </div>
+ </section>
+ );
+ }
+
return (
<section className="dashboard-performance">
{/* 响应时间图表 */}
diff --git a/Merge/front/src/components/UserManagement.js b/Merge/front/src/components/UserManagement.js
index bed6b8f..8eb48e3 100644
--- a/Merge/front/src/components/UserManagement.js
+++ b/Merge/front/src/components/UserManagement.js
@@ -19,16 +19,41 @@
const { userId: superAdminId } = useParams()
const [users, setUsers] = useState([])
+ const [unauthorized, setUnauthorized] = useState(false)
useEffect(() => {
async function load() {
try {
// 调用接口获取用户列表
const data = await fetchUserList(superAdminId)
- setUsers(data)
+
+ // 检查是否是权限错误
+ if (data && data.status === 'error' && data.message === 'Unauthorized') {
+ setUnauthorized(true)
+ setUsers([])
+ return
+ }
+
+ // 确保数据是数组格式
+ let userList = []
+ if (Array.isArray(data)) {
+ userList = data
+ } else if (Array.isArray(data.data)) {
+ userList = data.data
+ } else if (Array.isArray(data.users)) {
+ userList = data.users
+ }
+
+ setUsers(userList)
+ setUnauthorized(false)
} catch (e) {
console.error(e)
- message.error('获取用户列表失败:' + e.message)
+ if (e.message === 'Unauthorized') {
+ setUnauthorized(true)
+ setUsers([])
+ } else {
+ message.error('获取用户列表失败:' + e.message)
+ }
}
}
@@ -41,13 +66,22 @@
// 处理角色变更
const handleRoleChange = async (userId, newRole) => {
try {
+ let result
if (newRole === '用户') {
- await giveUser(superAdminId, userId)
+ result = await giveUser(userId)
} else if (newRole === '管理员') {
- await giveAdmin(superAdminId, userId)
+ result = await giveAdmin(userId)
} else if (newRole === '超级管理员') {
- await giveSuperAdmin(superAdminId, userId)
+ result = await giveSuperAdmin(userId)
}
+
+ // 检查返回结果是否有权限错误
+ if (result && result.status === 'error' && result.message === 'Unauthorized') {
+ setUnauthorized(true)
+ message.error('权限不足,无法执行此操作')
+ return
+ }
+
// 本地更新状态
setUsers(us =>
us.map(u => (u.id === userId ? { ...u, role: newRole } : u))
@@ -55,7 +89,12 @@
message.success('修改成功')
} catch (e) {
console.error(e)
- message.error('修改失败:' + e.message)
+ if (e.message === 'Unauthorized') {
+ setUnauthorized(true)
+ message.error('权限不足,无法执行此操作')
+ } else {
+ message.error('修改失败:' + e.message)
+ }
}
}
@@ -91,12 +130,23 @@
return (
<div className="admin-container">
- <Table
- dataSource={users}
- columns={columns}
- rowKey="id"
- pagination={false}
- />
+ {unauthorized ? (
+ <div style={{
+ textAlign: 'center',
+ padding: '50px',
+ fontSize: '18px',
+ color: '#ff4d4f'
+ }}>
+ 权限不足,无法访问用户管理功能
+ </div>
+ ) : (
+ <Table
+ dataSource={users}
+ columns={columns}
+ rowKey="id"
+ pagination={false}
+ />
+ )}
</div>
)
}
diff --git a/Merge/front/src/components/UserProfile.jsx b/Merge/front/src/components/UserProfile.jsx
index 6cddcbd..c957473 100644
--- a/Merge/front/src/components/UserProfile.jsx
+++ b/Merge/front/src/components/UserProfile.jsx
@@ -30,14 +30,15 @@
CircularProgress,
Snackbar,
Alert,
- Menu
+ Menu,
+ Pagination,
+ Stack
} from '@mui/material';
import { useParams } from 'react-router-dom';
import {
CameraAlt,
Edit,
Favorite,
- Bookmark,
Share,
MoreVert,
LocationOn,
@@ -50,11 +51,9 @@
Notifications,
Person,
Collections,
- Group,
ChevronLeft,
ChevronRight,
- Close,
- People
+ Close
} from '@mui/icons-material';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import { Link, useNavigate } from 'react-router-dom';
@@ -64,14 +63,12 @@
getCurrentUser,
getUser,
updateUser as updateUserApi,
- getFavorites,
followUser as followUserApi,
unfollowUser as unfollowUserApi,
getUserPosts,
- getUserFollowing,
- getUserInteractions,
- getUserFollowers
+ getUserInteractions
} from '../api/api_ljc';
+import { fetchPost } from '../api/posts_wzy';
// 创建小红书主题
const theme = createTheme({
@@ -122,21 +119,24 @@
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState(0);
const [isEditing, setIsEditing] = useState(false);
- const [followers, setFollowers] = useState([]);
const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: 'success' });
const [anchorEl, setAnchorEl] = useState(null);
// 用户数据状态
const [currentUser, setCurrentUser] = useState(null);
const [profileUser, setProfileUser] = useState(null);
- const [favorites, setFavorites] = useState([]);
- const [following, setFollowing] = useState([]);
const [posts, setPosts] = useState([]);
+ const [allPosts, setAllPosts] = useState([]); // 存储所有帖子数据
const [interactions, setInteractions] = useState({
likes_count: 0,
favorites_count: 0
});
+ // 分页状态
+ const [currentPage, setCurrentPage] = useState(1);
+ const [postsPerPage] = useState(8); // 每页显示8个帖子
+ const [totalPages, setTotalPages] = useState(0);
+
// 加载状态
const [loading, setLoading] = useState(true);
const [updating, setUpdating] = useState(false);
@@ -158,6 +158,30 @@
setSnackbar({ open: true, message, severity });
};
+ // 分页处理函数
+ const updatePaginatedPosts = (page, allPostsData) => {
+ const startIndex = (page - 1) * postsPerPage;
+ const endIndex = startIndex + postsPerPage;
+ const paginatedPosts = allPostsData.slice(startIndex, endIndex);
+ setPosts(paginatedPosts);
+ setTotalPages(Math.ceil(allPostsData.length / postsPerPage));
+ };
+
+ const handlePageChange = (newPage) => {
+ if (newPage >= 1 && newPage <= totalPages) {
+ setCurrentPage(newPage);
+ updatePaginatedPosts(newPage, allPosts);
+ }
+ };
+
+ const handlePrevPage = () => {
+ handlePageChange(currentPage - 1);
+ };
+
+ const handleNextPage = () => {
+ handlePageChange(currentPage + 1);
+ };
+
// 加载用户数据
useEffect(() => {
const fetchInteractions = async () => {
@@ -181,9 +205,9 @@
showSnackbar('关注成功');
// 更新粉丝列表状态(将刚关注的用户标记为已关注)
- setFollowers(prev => prev.map(user =>
- user.id === followeeId ? { ...user, is_following: true } : user
- ));
+ // setFollowers(prev => prev.map(user =>
+ // user.id === followeeId ? { ...user, is_following: true } : user
+ // ));
// 更新当前用户关注数
if (currentUser) {
@@ -220,7 +244,31 @@
// 获取用户帖子
const postsRes = await getUserPosts(userId);
- setPosts(postsRes.data);
+
+ // 为了拿到 media_urls,这里需要拉取每个帖子的详情
+ const postsWithDetails = await Promise.all(
+ postsRes.data.map(async post => {
+ try {
+ const detail = await fetchPost(post.id);
+ return {
+ ...post,
+ media_urls: detail.media_urls || [],
+ user_id: detail.user_id
+ };
+ } catch (error) {
+ console.error(`获取帖子 ${post.id} 详情失败:`, error);
+ return {
+ ...post,
+ media_urls: [],
+ user_id: null
+ };
+ }
+ })
+ );
+
+ setAllPosts(postsWithDetails); // 存储所有帖子
+ updatePaginatedPosts(1, postsWithDetails); // 初始化第一页数据
+ setCurrentPage(1); // 重置到第一页
// 获取用户互动数据(获赞和收藏数量)
const interactionsRes = await getUserInteractions(userId);
@@ -239,41 +287,11 @@
// 根据标签页加载数据
useEffect(() => {
- const fetchTabData = async () => {
- if (!profileUser) return;
-
- try {
- setTabLoading(true);
-
- if (activeTab === 1) {
- // 加载收藏数据
- const favoritesRes = await getFavorites(userId);
- setFavorites(favoritesRes.data);
- } else if (activeTab === 2) {
- // 加载关注列表
- const followingRes = await getUserFollowing(userId);
- setFollowing(followingRes.data);
- console.log(followingRes.data)
- } else if (activeTab === 3) {
- // 加载粉丝列表
- const followersRes = await getUserFollowers(userId);
- //
- setFollowers(followersRes.data.data);
- console.log(followersRes.data.data)
- }
-
- } catch (error) {
- console.error('加载数据失败:', error);
- showSnackbar('加载数据失败,请重试', 'error');
- } finally {
- setTabLoading(false);
- }
- };
-
- fetchTabData();
+ // 由于只有笔记标签页,不需要根据activeTab加载不同数据
}, [activeTab, userId, profileUser]);
const handleTabChange = (event, newValue) => {
+ // 由于只有一个标签页,不需要切换逻辑
setActiveTab(newValue);
};
@@ -290,15 +308,6 @@
// 更新用户信息
const updatedUser = await getUser(userId);
setProfileUser(updatedUser.data);
- // 关注/取关后强制刷新关注和粉丝列表,保证页面和数据库同步
- if (activeTab === 2) {
- const followingRes = await getUserFollowing(userId);
- setFollowing(followingRes.data);
- }
- if (activeTab === 3) {
- const followersRes = await getUserFollowers(userId);
- setFollowers(followersRes.data.data);
- }
} catch (error) {
console.error('关注操作失败:', error);
showSnackbar('操作失败,请重试', 'error');
@@ -310,11 +319,6 @@
await followUserApi(followeeId);
showSnackbar('关注成功');
- // 更新粉丝列表状态
- setFollowers(prev => prev.map(user =>
- user.id === followeeId ? {...user, is_following: true} : user
- ));
-
// 更新当前用户关注数
if (currentUser) {
setCurrentUser(prev => ({
@@ -336,9 +340,6 @@
await unfollowUserApi(followeeId);
showSnackbar('已取消关注');
- // 更新关注列表
- setFollowing(prev => prev.filter(user => user.id !== followeeId));
-
// 更新当前用户关注数
if (currentUser) {
setCurrentUser(prev => ({
@@ -571,7 +572,7 @@
<Grid container spacing={2} sx={{ mt: 2 }}>
<Grid item>
<Box textAlign="center">
- <Typography variant="h6">{posts.length}</Typography>
+ <Typography variant="h6">{allPosts.length}</Typography>
<Typography variant="body2" color="textSecondary">笔记</Typography>
</Box>
</Grid>
@@ -615,346 +616,117 @@
}}
>
<Tab icon={isMobile ? <Collections /> : null} label="笔记" />
- <Tab icon={isMobile ? <Bookmark /> : null} label="收藏" />
- <Tab icon={isMobile ? <Group /> : null} label="关注" />
- <Tab icon={isMobile ? <People /> : null} label="粉丝" />
</Tabs>
</Box>
{/* 内容区域 */}
<Box sx={{ mt: 3 }}>
- {activeTab === 0 && (
- <Grid container spacing={3}>
- {tabLoading ? (
- <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
- <CircularProgress />
- </Grid>
- ) : posts.length > 0 ? (
- posts.map((post, index) => (
- <Grid item xs={12} sm={6} lg={3} key={post.id}>
- <Card elevation={0} sx={{
- bgcolor: 'white',
- borderRadius: 3,
- height: '100%',
- display: 'flex',
- flexDirection: 'column'
- }}>
+ {/* 只保留笔记标签页 */}
+ <Grid container spacing={3}>
+ {tabLoading ? (
+ <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
+ <CircularProgress />
+ </Grid>
+ ) : allPosts.length === 0 ? (
+ <Grid item xs={12}>
+ <Box sx={{
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ py: 8,
+ textAlign: 'center'
+ }}>
+ <Collections sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
+ <Typography variant="h6" sx={{ mb: 1 }}>
+ 还没有发布笔记
+ </Typography>
+ <Typography variant="body1" color="textSecondary" sx={{ mb: 3 }}>
+ {isOwnProfile ? '分享你的生活点滴吧~' : '该用户还没有发布任何笔记'}
+ </Typography>
+ {isOwnProfile && (
+ <Button variant="contained" color="primary">
+ 发布第一篇笔记
+ </Button>
+ )}
+ </Box>
+ </Grid>
+ ) : (
+ // 显示当前页的帖子
+ posts.map((post, index) => (
+ <Grid item xs={12} sm={6} lg={3} key={post.id}>
+ <Card elevation={0} sx={{
+ bgcolor: 'white',
+ borderRadius: 3,
+ height: '100%',
+ display: 'flex',
+ flexDirection: 'column'
+ }}>
+ {/* 只有当帖子有 media_urls 时才显示图片 */}
+ {post.media_urls && post.media_urls.length > 0 && (
<CardMedia
component="img"
height="180"
- image={`https://source.unsplash.com/random/400x300?${index + 1}`}
+ image={post.media_urls[0]}
alt={post.title}
/>
- <CardContent sx={{ flexGrow: 1 }}>
- <Typography gutterBottom variant="h6" component="div">
- {post.title}
- </Typography>
- <Typography variant="body2" color="text.secondary">
- {post.content.substring(0, 60)}...
- </Typography>
- </CardContent>
- <CardActions sx={{ justifyContent: 'space-between', px: 2, pb: 2 }}>
- <Box>
- <IconButton aria-label="add to favorites">
- <Favorite />
- <Typography variant="body2" sx={{ ml: 1 }}>
- {post.heat || Math.floor(Math.random() * 1000) + 1000}
- </Typography>
- </IconButton>
- <IconButton aria-label="share">
- <Share />
- </IconButton>
- </Box>
- <Chip
- label={post.type === 'image' ? '图文' : post.type === 'video' ? '视频' : '文档'}
- size="small"
- color="primary"
- variant="outlined"
- />
- </CardActions>
- </Card>
- </Grid>
- ))
- ) : (
- <Grid item xs={12}>
- <Box sx={{
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- py: 8,
- textAlign: 'center'
- }}>
- <Collections sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
- <Typography variant="h6" sx={{ mb: 1 }}>
- 还没有发布笔记
- </Typography>
- <Typography variant="body1" color="textSecondary" sx={{ mb: 3 }}>
- {isOwnProfile ? '分享你的生活点滴吧~' : '该用户还没有发布任何笔记'}
- </Typography>
- {isOwnProfile && (
- <Button variant="contained" color="primary">
- 发布第一篇笔记
- </Button>
)}
- </Box>
+ <CardContent sx={{ flexGrow: 1 }}>
+ <Typography gutterBottom variant="h6" component="div">
+ {post.title}
+ </Typography>
+ <Typography variant="body2" color="text.secondary">
+ {post.content ? post.content.substring(0, 60) + '...' : '暂无内容'}
+ </Typography>
+ </CardContent>
+ <CardActions sx={{ justifyContent: 'space-between', px: 2, pb: 2 }}>
+ <Box>
+ <IconButton aria-label="add to favorites">
+ <Favorite />
+ <Typography variant="body2" sx={{ ml: 1 }}>
+ {post.heat || Math.floor(Math.random() * 1000) + 1000}
+ </Typography>
+ </IconButton>
+ <IconButton aria-label="share">
+ <Share />
+ </IconButton>
+ </Box>
+ <Chip
+ label={post.type === 'image' ? '图文' : post.type === 'video' ? '视频' : '文档'}
+ size="small"
+ color="primary"
+ variant="outlined"
+ />
+ </CardActions>
+ </Card>
</Grid>
- )}
-
- {posts.length > 0 && (
- <Grid item xs={12}>
- <Box sx={{ display: 'flex', justifyContent: 'center', mt: 3 }}>
- <Button
- variant="outlined"
- sx={{
- borderRadius: 20,
- px: 4,
- display: 'flex',
- alignItems: 'center'
- }}
- >
- <ChevronLeft sx={{ mr: 1 }} />
- 上一页
- <ChevronRight sx={{ ml: 2 }} />
- </Button>
- </Box>
- </Grid>
- )}
- </Grid>
- )}
-
- {activeTab === 1 && (
- <Grid container spacing={3}>
- {tabLoading ? (
- <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
- <CircularProgress />
- </Grid>
- ) : favorites.length > 0 ? (
- favorites.map((favorite) => (
- <Grid item xs={12} sm={6} md={4} lg={3} key={favorite.id}>
- <Card elevation={0} sx={{
- bgcolor: 'white',
- borderRadius: 3,
- transition: 'transform 0.3s, box-shadow 0.3s',
- '&:hover': {
- transform: 'translateY(-5px)',
- boxShadow: 3
+ ))
+ )}
+
+ {/* 分页组件 */}
+ {allPosts.length > postsPerPage && (
+ <Grid item xs={12}>
+ <Stack spacing={2} alignItems="center" sx={{ mt: 4 }}>
+ <Typography variant="body2" color="textSecondary">
+ 共 {allPosts.length} 篇笔记,第 {currentPage} 页,共 {totalPages} 页
+ </Typography>
+ <Pagination
+ count={totalPages}
+ page={currentPage}
+ onChange={(event, page) => handlePageChange(page)}
+ color="primary"
+ size={isMobile ? "small" : "medium"}
+ showFirstButton
+ showLastButton
+ sx={{
+ '& .MuiPaginationItem-root': {
+ borderRadius: 2,
}
- }}>
- <Box sx={{
- height: 160,
- position: 'relative',
- borderTopLeftRadius: 16,
- borderTopRightRadius: 16,
- overflow: 'hidden'
- }}>
- <CardMedia
- component="img"
- height="160"
- image={`https://source.unsplash.com/random/400x300?${favorite.id}`}
- alt={favorite.title}
- />
- <Box sx={{
- position: 'absolute',
- top: 8,
- right: 8,
- bgcolor: 'rgba(0,0,0,0.6)',
- color: 'white',
- px: 1,
- py: 0.5,
- borderRadius: 4,
- fontSize: 12
- }}>
- {favorite.type === 'image' ? '图文' : favorite.type === 'video' ? '视频' : '文档'}
- </Box>
- </Box>
- <CardContent>
- <Typography gutterBottom variant="subtitle1" fontWeight="medium">
- {favorite.title}
- </Typography>
- <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
- <Box sx={{ display: 'flex', alignItems: 'center' }}>
- <Favorite fontSize="small" color="error" />
- <Typography variant="body2" sx={{ ml: 0.5 }}>
- {favorite.heat || Math.floor(Math.random() * 1000) + 1000}
- </Typography>
- </Box>
- <Box sx={{ display: 'flex', alignItems: 'center' }}>
- <Bookmark fontSize="small" color="primary" />
- <Typography variant="body2" sx={{ ml: 0.5 }}>
- {Math.floor(Math.random() * 500) + 100}
- </Typography>
- </Box>
- </Box>
- </CardContent>
- </Card>
- </Grid>
- ))
- ) : (
- <Grid item xs={12}>
- <Box sx={{
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- py: 8,
- textAlign: 'center'
- }}>
- <Bookmark sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
- <Typography variant="h6" sx={{ mb: 1 }}>
- {isOwnProfile ? '你还没有收藏内容' : '该用户没有收藏内容'}
- </Typography>
- <Typography variant="body1" color="textSecondary">
- {isOwnProfile ? '看到喜欢的笔记可以收藏起来哦~' : ''}
- </Typography>
- </Box>
- </Grid>
- )}
- </Grid>
- )}
-
- {activeTab === 2 && (
- <Grid container spacing={3}>
- {tabLoading ? (
- <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
- <CircularProgress />
- </Grid>
- ) : following.length > 0 ? (
- following.map((follow) => (
- <Grid item xs={12} sm={6} md={4} key={follow.id}>
- <Paper
- elevation={0}
- sx={{
- bgcolor: 'white',
- borderRadius: 3,
- p: 2,
- display: 'flex',
- alignItems: 'center',
- cursor: 'pointer',
- '&:hover': {
- boxShadow: 1
- }
- }}
- onClick={() => navigateToUserProfile(follow.id)}
- >
- <Avatar
- src={follow.avatar || 'https://randomuser.me/api/portraits/men/22.jpg'}
- sx={{ width: 60, height: 60 }}
- />
- <Box sx={{ ml: 2, flexGrow: 1 }}>
- <Typography fontWeight="medium">{follow.username}</Typography>
- <Typography variant="body2" color="textSecondary">
- {follow.followers_count || Math.floor(Math.random() * 100) + 10} 粉丝
- </Typography>
- </Box>
- {isOwnProfile && (
- <Button
- variant="outlined"
- size="small"
- sx={{ borderRadius: 20 }}
- onClick={(e) => handleUnfollow(follow.id, e)}
- >
- 已关注
- </Button>
- )}
- </Paper>
- </Grid>
- ))
- ) : (
- <Grid item xs={12}>
- <Box sx={{
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- py: 8,
- textAlign: 'center'
- }}>
- <Group sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
- <Typography variant="h6" sx={{ mb: 1 }}>
- {isOwnProfile ? '你还没有关注任何人' : '该用户还没有关注任何人'}
- </Typography>
- <Typography variant="body1" color="textSecondary">
- {isOwnProfile ? '发现有趣的人并关注他们吧~' : ''}
- </Typography>
- </Box>
- </Grid>
- )}
- </Grid>
- )}
- {activeTab === 3 && (
- <Grid container spacing={3}>
- {tabLoading ? (
- <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
- <CircularProgress />
- </Grid>
- ) : followers.length > 0 ? (
- followers.map((follower) => (
- <Grid item xs={12} sm={6} md={4} key={follower.id}>
- <Paper
- elevation={0}
- sx={{
- bgcolor: 'white',
- borderRadius: 3,
- p: 2,
- display: 'flex',
- alignItems: 'center',
- cursor: 'pointer',
- '&:hover': {
- boxShadow: 1
- }
- }}
- onClick={() => navigateToUserProfile(follower.id)}
- >
- <Avatar
- src={follower.avatar || 'https://randomuser.me/api/portraits/men/22.jpg'}
- sx={{ width: 60, height: 60 }}
- />
- <Box sx={{ ml: 2, flexGrow: 1 }}>
- <Typography fontWeight="medium">{follower.username}</Typography>
- <Typography variant="body2" color="textSecondary">
- {follower.bio || '暂无简介'}
- </Typography>
- </Box>
- {currentUser && currentUser.id !== follower.id && (
- <Button
- variant={follower.is_following ? "outlined" : "contained"}
- color="primary"
- size="small"
- sx={{ borderRadius: 20 }}
- onClick={(e) => {
- e.stopPropagation();
- if (follower.is_following) {
- handleUnfollow(follower.id, e);
- } else {
- handleFollowUser(follower.id);
- }
- }}
- >
- {follower.is_following ? '已关注' : '关注'}
- </Button>
- )}
- </Paper>
- </Grid>
- ))
- ) : (
- <Grid item xs={12}>
- <Box sx={{
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- py: 8,
- textAlign: 'center'
- }}>
- <People sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
- <Typography variant="h6" sx={{ mb: 1 }}>
- {isOwnProfile ? '你还没有粉丝' : '该用户还没有粉丝'}
- </Typography>
- <Typography variant="body1" color="textSecondary">
- {isOwnProfile ? '分享更多内容来吸引粉丝吧~' : ''}
- </Typography>
- </Box>
- </Grid>
- )}
- </Grid>
- )}
+ }}
+ />
+ </Stack>
+ </Grid>
+ )}
+ </Grid>
</Box>
</Container>