fix-img
Change-Id: Ida77fc6aed06b28e41e2abcb6ae09d5f63d016f2
diff --git a/src/pages/Forum/posts-main/components/CreatePostButton.jsx b/src/pages/Forum/posts-main/components/CreatePostButton.jsx
index 0f23e82..e324056 100644
--- a/src/pages/Forum/posts-main/components/CreatePostButton.jsx
+++ b/src/pages/Forum/posts-main/components/CreatePostButton.jsx
@@ -2,20 +2,18 @@
import axios from 'axios';
import { Edit } from '@icon-park/react';
import './CreatePostButton.css';
-
-const user = JSON.parse(localStorage.getItem('user')); // user = { user_id: 123, ... }
-const userId = user?.user_id;
-
+import { useUser } from '../../../../context/UserContext';
const CreatePostButton = () => {
- const [showModal, setShowModal] = useState(false);
+ const { user } = useUser();
+ const userId = user?.userId; // 这里改为 userId,跟 UserContext 统一
+ const [showModal, setShowModal] = useState(false);
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const [previewUrls, setPreviewUrls] = useState([]);
const [files, setFiles] = useState([]);
- // 选择图片并预览
const handleImageChange = (e) => {
const selectedFiles = Array.from(e.target.files);
if (!selectedFiles.length) return;
@@ -24,6 +22,10 @@
};
const handleSubmit = async () => {
+ if (!userId) {
+ alert('用户未登录或用户ID未获取到,无法发帖');
+ return;
+ }
if (!title.trim() || !content.trim()) {
alert('标题和内容均为必填项');
return;
@@ -34,7 +36,7 @@
formData.append('postContent', content.trim());
files.forEach(file => {
- formData.append('imageUrl', file); // 多文件使用同一个字段名
+ formData.append('imageUrl', file);
});
try {
@@ -46,7 +48,6 @@
}
);
- // 清空表单
setTitle('');
setContent('');
setFiles([]);
@@ -119,4 +120,3 @@
};
export default CreatePostButton;
-
diff --git a/src/pages/Forum/posts-main/components/PostList.jsx b/src/pages/Forum/posts-main/components/PostList.jsx
index 9e088ea..c1d9b7b 100644
--- a/src/pages/Forum/posts-main/components/PostList.jsx
+++ b/src/pages/Forum/posts-main/components/PostList.jsx
@@ -3,8 +3,16 @@
import { Link } from 'wouter';
import { GoodTwo, Star, Delete } from '@icon-park/react';
import { likePost } from '../../posts-detail/api';
+import { formatAvatarUrl } from '../../../../components/utils/avatar';
import './PostList.css';
+// 修改后的封面图 URL 拼接函数
+const formatImageUrl = (url) => {
+ if (!url) return '';
+ const filename = url.split('/').pop(); // 提取文件名部分
+ return `http://localhost:8080/uploads/post/${filename}`;
+};
+
const PostList = ({ search }) => {
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
@@ -21,49 +29,26 @@
setErrorMsg('');
try {
const res = await axios.get(`/echo/forum/posts/getAllPosts`, {
- params: {
- page,
- pageSize: size,
- sortBy: 'createdAt',
- order: 'desc'
- }
+ params: { page, pageSize: size, sortBy: 'createdAt', order: 'desc' }
});
- // 检查响应结构是否符合预期
- if (!res.data || !Array.isArray(res.data.posts)) {
- throw new Error('API返回格式不正确');
- }
+ if (!res.data || !Array.isArray(res.data.posts)) throw new Error('API返回格式不正确');
- const postsData = res.data.posts || [];
+ const postsData = res.data.posts;
- const userIds = [...new Set(postsData.map(post => post.user_id))];
+ const filteredPosts = postsData.filter(post =>
+ post.title?.toLowerCase().includes(search.toLowerCase())
+ );
- const profiles = await Promise.all(userIds.map(async id => {
- try {
- const r = await axios.get(`/echo/user/profile`, {
- params: { user_id: id }
- });
- return { id, profile: r.data };
- } catch (e) {
- return { id, profile: { nickname: '未知用户', avatar_url: 'default-avatar.png' } };
- }
+ const postsWithProfiles = filteredPosts.map(post => ({
+ ...post,
+ username: post.username || '未知用户',
+ avatarUrl: formatAvatarUrl(post.avatarUrl || ''),
+ liked: false,
+ collected: false,
+ commentCount: 0
}));
- const userMap = {};
- profiles.forEach(({ id, profile }) => {
- userMap[id] = profile;
- });
-
- const postsWithProfiles = postsData
- .filter(post => post.title.toLowerCase().includes(search.toLowerCase()))
- .map(post => ({
- ...post,
- userProfile: userMap[post.user_id] || { nickname: '未知用户', avatar_url: 'default-avatar.png' },
- liked: false,
- collected: false,
- commentCount: 0
- }));
-
setPosts(postsWithProfiles);
setTotal(res.data.total || 0);
} catch (err) {
@@ -78,77 +63,65 @@
}, [page, search]);
const toggleLike = async (postNo, liked, userId) => {
- try {
- if (liked) {
- // 修改为 POST 请求,并带上 user_id 参数
- await axios.post(`/echo/forum/posts/${postNo}/unlike`, {
- user_id: userId
- });
- } else {
- await likePost(postNo, userId); // 你已有的点赞逻辑
+ try {
+ if (liked) {
+ await axios.post(`/echo/forum/posts/${postNo}/unlike`, { user_id: userId });
+ } else {
+ await likePost(postNo, userId);
+ }
+
+ setPosts(posts =>
+ posts.map(post =>
+ post.postNo === postNo
+ ? {
+ ...post,
+ liked: !liked,
+ likeCount: liked ? post.likeCount - 1 : post.likeCount + 1
+ }
+ : post
+ )
+ );
+ } catch (err) {
+ console.error('点赞失败:', err);
+ alert('点赞操作失败,请稍后重试');
}
+ };
- setPosts(posts =>
- posts.map(post =>
- post.postNo === postNo
- ? { ...post, liked: !liked, likeCount: liked ? post.likeCount - 1 : post.likeCount + 1 }
- : post
- )
- );
- } catch (err) {
- console.error('点赞失败:', err);
- alert('点赞操作失败,请稍后重试');
- }
-};
-
-
- // 收藏帖子
const toggleCollect = async (postNo, collected, userId) => {
- try {
- if (collected) {
- // 取消收藏:DELETE 请求 + JSON 请求体
- await axios.delete(`/echo/forum/posts/${postNo}/uncollect`, {
- data: { user_id: userId } // 注意:DELETE 请求的请求体需放在 data 字段中
- });
- } else {
- // 收藏:POST 请求 + JSON 请求体
- await axios.post(`/echo/forum/posts/${postNo}/collect`, {
- user_id: userId
- });
+ try {
+ if (collected) {
+ await axios.delete(`/echo/forum/posts/${postNo}/uncollect`, {
+ data: { user_id: userId }
+ });
+ } else {
+ await axios.post(`/echo/forum/posts/${postNo}/collect`, {
+ user_id: userId
+ });
+ }
+
+ setPosts(posts =>
+ posts.map(post =>
+ post.postNo === postNo
+ ? {
+ ...post,
+ collected: !collected,
+ collectCount: collected ? post.collectCount - 1 : post.collectCount + 1
+ }
+ : post
+ )
+ );
+ } catch (err) {
+ console.error('收藏操作失败:', err);
+ alert('收藏操作失败,请稍后重试');
}
+ };
- setPosts(posts =>
- posts.map(post =>
- post.postNo === postNo
- ? {
- ...post,
- collected: !collected,
- collectCount: collected ? post.collectCount - 1 : post.collectCount + 1
- }
- : post
- )
- );
- } catch (err) {
- console.error('收藏操作失败:', err.response?.data || err.message);
- alert('收藏操作失败,请稍后重试');
- }
-};
-
-
-
- // 删除帖子
const handleDeletePost = async (postNo) => {
if (window.confirm('确定要删除这篇帖子吗?')) {
try {
await axios.delete(`/echo/forum/posts/${postNo}/deletePost`);
-
- // 从列表中移除已删除的帖子
setPosts(posts => posts.filter(post => post.postNo !== postNo));
-
- // 如果删除后当前页没有帖子了,尝试加载上一页
- if (posts.length === 1 && page > 1) {
- setPage(page - 1);
- }
+ if (posts.length === 1 && page > 1) setPage(page - 1);
} catch (err) {
console.error('删除帖子失败:', err);
alert('删除帖子失败,请稍后再试');
@@ -161,43 +134,52 @@
{loading ? <p>加载中...</p> :
errorMsg ? <p className="error-text">{errorMsg}</p> :
posts.length === 0 ? <p>暂无帖子。</p> :
- posts.map(post => (
- <div
- key={post.postNo}
- className="post-card"
- style={{ backgroundColor: '#e9ded2' }}
- >
- <div className="user-info">
- <img className="avatar" src={post.userProfile.avatar_url} alt="头像" />
- <span className="nickname" style={{ color: '#755e50' }}>{post.userProfile.nickname}</span>
- </div>
- {post.imgUrl && (
- <img className="cover-image" src={post.imgUrl} alt="封面" />
- )}
- <h3 style={{ color: '#000000' }}>{post.title}</h3>
- <div className="post-meta">
- <span>发布时间:{new Date(post.createdAt).toLocaleString()}</span>
- <div className="post-actions">
- <button className="icon-btn" onClick={() => toggleLike(post.postNo, post.liked, post.user_id)}>
- <GoodTwo theme="outline" size="24" fill={post.liked ? '#f00' : '#fff'} />
- <span>{post.likeCount}</span>
- </button>
+ posts.map(post => {
+ const createdAtDate = new Date(post.createdAt);
+ const timeText = isNaN(createdAtDate.getTime()) ? '时间未设置' : createdAtDate.toLocaleString();
- <button className="icon-btn" onClick={() => toggleCollect(post.postNo, post.collected, post.user_id)}>
- <Star theme="outline" size="24" fill={post.collected ? '#ffd700' : '#fff'} />
- <span>{post.collectCount}</span>
- </button>
-
- <button className="icon-btn" onClick={() => handleDeletePost(post.postNo)}>
- <Delete theme="outline" size="24" fill="#333" />
- </button>
+ let coverImage = null;
+ if (post.imgUrl) {
+ const imgs = post.imgUrl.split(',').map(i => i.trim()).filter(Boolean);
+ coverImage = imgs.length > 0 ? formatImageUrl(imgs[0]) : null;
+ }
+
+ return (
+ <div key={post.postNo} className="post-card" style={{ backgroundColor: '#e9ded2' }}>
+ <div className="user-info">
+ <img
+ className="avatar"
+ src={post.avatarUrl}
+ alt="头像"
+ />
+ <span className="nickname" style={{ color: '#755e50' }}>{post.username}</span>
+ </div>
+
+ {coverImage && <img className="cover-image" src={coverImage} alt="封面" />}
+
+ <h3 style={{ color: '#000000' }}>{post.title || '无标题'}</h3>
+ <div className="post-meta">
+ <span>发布时间:{timeText}</span>
+ <div className="post-actions">
+ <button className="icon-btn" onClick={() => toggleLike(post.postNo, post.liked, post.user_id)}>
+ <GoodTwo theme="outline" size="24" fill={post.liked ? '#f00' : '#fff'} />
+ <span>{post.likeCount}</span>
+ </button>
+ <button className="icon-btn" onClick={() => toggleCollect(post.postNo, post.collected, post.user_id)}>
+ <Star theme="outline" size="24" fill={post.collected ? '#ffd700' : '#fff'} />
+ <span>{post.collectCount}</span>
+ </button>
+ <button className="icon-btn" onClick={() => handleDeletePost(post.postNo)}>
+ <Delete theme="outline" size="24" fill="#333" />
+ </button>
+ </div>
+ </div>
+ <div className="detail-button-wrapper">
+ <Link href={`/forum/post/${post.postNo}`} className="detail-button">查看详情</Link>
</div>
</div>
- <div className="detail-button-wrapper">
- <Link href={`/forum/post/${post.postNo}`} className="detail-button">查看详情</Link>
- </div>
- </div>
- ))
+ );
+ })
}
<div className="pagination">
@@ -209,4 +191,4 @@
);
};
-export default PostList;
\ No newline at end of file
+export default PostList;