blob: 9304b39040f24623aee99f30a9f55fb24a0d2772 [file] [log] [blame]
22301008e4acb922025-06-24 23:50:35 +08001import React, { useState, useEffect, useCallback } from 'react'
95630366980c1f272025-06-20 14:08:54 +08002import { useParams, useNavigate } from 'react-router-dom'
22301008e4acb922025-06-24 23:50:35 +08003import { ArrowLeft, ThumbsUp, MessageCircle, Share2, BookmarkPlus, Heart, Eye } from 'lucide-react'
95630366980c1f272025-06-20 14:08:54 +08004import { searchAPI } from '../api/search_jwlll'
22301008ba662fe2025-06-20 18:10:20 +08005import { getUserInfo } from '../utils/auth'
22301008e25b4b02025-06-20 22:15:31 +08006import FollowButton from './FollowButton'
22301008e4acb922025-06-24 23:50:35 +08007import postsAPI from '../api/posts_api'
9563036699de8c092025-06-21 16:41:18 +08008import MediaPreview from './MediaPreview'
95630366980c1f272025-06-20 14:08:54 +08009import '../style/PostDetail.css'
223010083c35e492025-06-20 22:24:22 +080010import dayjs from 'dayjs'
95630366986f92322025-06-26 18:29:49 +080011import { followUser, unfollowUser ,getUserFollowing} from '../api/api_ljc'
95630366980c1f272025-06-20 14:08:54 +080012
13export default function PostDetail() {
14 const { id } = useParams()
15 const navigate = useNavigate()
16 const [post, setPost] = useState(null)
17 const [loading, setLoading] = useState(true)
18 const [error, setError] = useState(null)
19 const [liked, setLiked] = useState(false)
20 const [bookmarked, setBookmarked] = useState(false)
21 const [likeCount, setLikeCount] = useState(0)
22 const [comments, setComments] = useState([])
23 const [newComment, setNewComment] = useState('')
24 const [showComments, setShowComments] = useState(false)
22301008e25b4b02025-06-20 22:15:31 +080025 const [isFollowing, setIsFollowing] = useState(false)
223010696c849b42025-06-26 18:27:52 +080026 const [followLoading, setFollowLoading] = useState(false)
22301008e25b4b02025-06-20 22:15:31 +080027 const [authorInfo, setAuthorInfo] = useState(null)
wu32b07822025-06-24 23:10:02 +080028 const [previewImg, setPreviewImg] = useState(null)
22301008e4acb922025-06-24 23:50:35 +080029 const [commentUserMap, setCommentUserMap] = useState({}) // user_id: username
30 const [commentUserAvatarMap, setCommentUserAvatarMap] = useState({}) // user_id: avatar // 获取当前用户ID
31 const getCurrentUserId = () => {
32 const userInfo = getUserInfo()
33 return userInfo?.id || '3' // 如果未登录或无用户信息,使用默认值3
34 }
22301008ba662fe2025-06-20 18:10:20 +080035 const fetchPostDetail = useCallback(async () => {
95630366980c1f272025-06-20 14:08:54 +080036 setLoading(true)
37 setError(null)
38 try {
39 const data = await searchAPI.getPostDetail(id)
40 setPost(data)
41 setLikeCount(data.heat || 0)
22301008e4acb922025-06-24 23:50:35 +080042
43 // 检查当前用户是否已点赞
44 const currentUserId = getCurrentUserId()
45 try {
46 const hasLiked = await searchAPI.hasLiked(id, currentUserId)
47 setLiked(hasLiked)
48 } catch (error) {
49 console.error('检查点赞状态失败:', error)
50 setLiked(false) // 如果检查失败,默认为未点赞
51 }
52 } catch (error) {
53 console.error('获取帖子详情失败:', error)
95630366980c1f272025-06-20 14:08:54 +080054 setError('帖子不存在或已被删除')
55 } finally {
56 setLoading(false)
57 }
22301008ba662fe2025-06-20 18:10:20 +080058 }, [id])
95630366980c1f272025-06-20 14:08:54 +080059
22301008ba662fe2025-06-20 18:10:20 +080060 const fetchComments = useCallback(async () => {
95630366980c1f272025-06-20 14:08:54 +080061 try {
62 const data = await searchAPI.getComments(id)
63 setComments(data.comments || [])
22301008e4acb922025-06-24 23:50:35 +080064 } catch (error) {
65 console.error('获取评论失败:', error)
95630366980c1f272025-06-20 14:08:54 +080066 }
22301008ba662fe2025-06-20 18:10:20 +080067 }, [id])
68
22301008e4acb922025-06-24 23:50:35 +080069 // 检查当前用户是否已关注发帖人
95630366986f92322025-06-26 18:29:49 +080070 const checkFollowStatus = useCallback(async (postUserId) => {
71 if (!postUserId) return;
72
73 const currentUserId = getCurrentUserId();
74 if (!currentUserId) return;
75
76 try {
77 const response = await getUserFollowing(currentUserId);
78 // 处理不同的响应结构
79 const followingList = Array.isArray(response) ? response : (response.data || []);
80
81 console.log(response)
82 // 检查目标用户是否在关注列表中
83 setIsFollowing(
84 followingList.some(user => user.id === postUserId)
85 );
86 } catch (error) {
87 console.error('获取关注状态失败:', error);
88 }
89}, []);
90
91// 当帖子数据加载后,检查关注状态
92useEffect(() => {
93 if (post && post.user_id) {
94 checkFollowStatus(post.user_id);
95 }
96}, [post, checkFollowStatus])
22301008e25b4b02025-06-20 22:15:31 +080097
22301008e4acb922025-06-24 23:50:35 +080098 // 拉取发帖人信息
22301008e25b4b02025-06-20 22:15:31 +080099 useEffect(() => {
100 if (post && post.user_id) {
22301008e4acb922025-06-24 23:50:35 +0800101 postsAPI.getUser(post.user_id).then(res => setAuthorInfo(res || {})).catch(() => setAuthorInfo({}))
22301008e25b4b02025-06-20 22:15:31 +0800102 }
103 }, [post])
104
22301008e4acb922025-06-24 23:50:35 +0800105 // 拉取所有评论用户昵称
22301008082d2ab2025-06-20 22:45:17 +0800106 const fetchCommentUserNames = async (userIds) => {
107 const map = {}
22301008e4acb922025-06-24 23:50:35 +0800108 await Promise.all(userIds.map(async uid => {
109 try {
110 const user = await postsAPI.getUser(uid)
111 map[uid] = user.username || user.nickname || `用户${uid}`
112 } catch {
113 map[uid] = `用户${uid}`
114 }
115 }))
22301008082d2ab2025-06-20 22:45:17 +0800116 setCommentUserMap(map)
117 }
118
22301008e4acb922025-06-24 23:50:35 +0800119 // 拉取所有评论用户头像
22301008082d2ab2025-06-20 22:45:17 +0800120 const fetchCommentUserAvatars = async (userIds) => {
121 const map = {}
22301008e4acb922025-06-24 23:50:35 +0800122 await Promise.all(userIds.map(async uid => {
123 try {
124 const user = await postsAPI.getUser(uid)
125 map[uid] = user.avatar && user.avatar.startsWith('http') ? user.avatar : (user.avatar ? `http://10.126.59.25:5715/static/${user.avatar}` : `https://i.pravatar.cc/40?img=${uid}`)
126 } catch {
127 map[uid] = `https://i.pravatar.cc/40?img=${uid}`
128 }
129 }))
22301008082d2ab2025-06-20 22:45:17 +0800130 setCommentUserAvatarMap(map)
131 }
132
22301008ba662fe2025-06-20 18:10:20 +0800133 useEffect(() => {
22301008e4acb922025-06-24 23:50:35 +0800134 fetchPostDetail()
135 fetchComments()
136 }, [fetchPostDetail, fetchComments])
137
138 // 评论区用户昵称和头像拉取
139 useEffect(() => {
22301008082d2ab2025-06-20 22:45:17 +0800140 if (comments.length > 0) {
22301008e4acb922025-06-24 23:50:35 +0800141 const userIds = [...new Set(comments.map(c => c.user_id).filter(Boolean))]
142 fetchCommentUserNames(userIds)
143 fetchCommentUserAvatars(userIds)
22301008082d2ab2025-06-20 22:45:17 +0800144 }
145 }, [comments])
22301008e4acb922025-06-24 23:50:35 +0800146 const handleBack = () => {
147 navigate(-1)
148 }
95630366980c1f272025-06-20 14:08:54 +0800149 const handleLike = async () => {
22301008e4acb922025-06-24 23:50:35 +0800150 const currentUserId = getCurrentUserId()
151 const originalLiked = liked
152 const originalLikeCount = likeCount
153
95630366980c1f272025-06-20 14:08:54 +0800154 try {
22301008e4acb922025-06-24 23:50:35 +0800155 // 先乐观更新UI,提供即时反馈
156 const newLiked = !liked
157 setLiked(newLiked)
158 setLikeCount(prev => newLiked ? prev + 1 : prev - 1)
159
160 // 调用后端API
161 if (newLiked) {
162 await searchAPI.likePost(id, currentUserId)
95630366980c1f272025-06-20 14:08:54 +0800163 } else {
22301008e4acb922025-06-24 23:50:35 +0800164 await searchAPI.unlikePost(id, currentUserId)
95630366980c1f272025-06-20 14:08:54 +0800165 }
22301008e4acb922025-06-24 23:50:35 +0800166
167 // API调用成功,状态已经更新,无需再次设置
168 } catch (error) {
169 console.error('点赞操作失败:', error)
170
171 // 检查是否是可以忽略的错误
172 const errorMessage = error.message || error.toString()
173 const isIgnorableError = errorMessage.includes('already liked') ||
174 errorMessage.includes('not liked yet') ||
175 errorMessage.includes('already favorited') ||
176 errorMessage.includes('not favorited yet')
177
178 if (isIgnorableError) {
179 // 这些错误可以忽略,因为最终状态是正确的
180 console.log('忽略重复操作错误:', errorMessage)
181 return
182 }
183
184 // 发生真正的错误时回滚到原始状态
185 setLiked(originalLiked)
186 setLikeCount(originalLikeCount)
187 alert('操作失败,请重试')
95630366980c1f272025-06-20 14:08:54 +0800188 }
189 }
190
191 const handleBookmark = () => {
192 setBookmarked(!bookmarked)
22301008e4acb922025-06-24 23:50:35 +0800193 // 实际项目中这里应该调用后端API保存收藏状态
95630366980c1f272025-06-20 14:08:54 +0800194 }
195
196 const handleShare = () => {
22301008e4acb922025-06-24 23:50:35 +0800197 // 分享功能
95630366980c1f272025-06-20 14:08:54 +0800198 if (navigator.share) {
199 navigator.share({
200 title: post?.title,
201 text: post?.content,
202 url: window.location.href,
203 })
204 } else {
22301008e4acb922025-06-24 23:50:35 +0800205 // 复制链接到剪贴板
95630366980c1f272025-06-20 14:08:54 +0800206 navigator.clipboard.writeText(window.location.href)
207 alert('链接已复制到剪贴板')
208 }
209 }
95630366980c1f272025-06-20 14:08:54 +0800210 const handleAddComment = async (e) => {
211 e.preventDefault()
212 if (!newComment.trim()) return
22301008e4acb922025-06-24 23:50:35 +0800213
95630366980c1f272025-06-20 14:08:54 +0800214 try {
22301008e4acb922025-06-24 23:50:35 +0800215 const currentUserId = getCurrentUserId()
22301008ba662fe2025-06-20 18:10:20 +0800216 await searchAPI.addComment(id, currentUserId, newComment)
95630366980c1f272025-06-20 14:08:54 +0800217 setNewComment('')
22301008e4acb922025-06-24 23:50:35 +0800218 fetchComments() // 刷新评论列表
219 } catch (error) {
220 console.error('添加评论失败:', error)
95630366980c1f272025-06-20 14:08:54 +0800221 alert('评论失败,请重试')
222 }
223 }
224
22301008e4acb922025-06-24 23:50:35 +0800225 // 关注后刷新关注状态
223010696c849b42025-06-26 18:27:52 +0800226 const handleFollowAction = async () => {
227 // 添加了加载状态和错误处理
228 setFollowLoading(true);
229 const currentUserId = getCurrentUserId()
230 try {
231 if (isFollowing) {
232 await unfollowUser(currentUserId, post.user_id);
233 } else {
234 await followUser(currentUserId, post.user_id);
22301008e4acb922025-06-24 23:50:35 +0800235 }
223010696c849b42025-06-26 18:27:52 +0800236 setIsFollowing(!isFollowing);
237 } catch (error) {
238 console.error(isFollowing ? '取消关注失败' : '关注失败', error);
239 alert(`操作失败: ${error.message || '请重试'}`);
240 } finally {
241 setFollowLoading(false);
22301008e4acb922025-06-24 23:50:35 +0800242 }
223010696c849b42025-06-26 18:27:52 +0800243};
22301008e25b4b02025-06-20 22:15:31 +0800244
95630366980c1f272025-06-20 14:08:54 +0800245 if (loading) {
246 return (
247 <div className="post-detail">
248 <div className="loading-container">
249 <div className="loading-spinner"></div>
250 <p>加载中...</p>
251 </div>
252 </div>
253 )
254 }
255
22301008e4acb922025-06-24 23:50:35 +0800256 // 优化错误和不存在的判断逻辑
95630366980c1f272025-06-20 14:08:54 +0800257 if (error) {
258 return (
259 <div className="post-detail">
260 <div className="error-container">
261 <h2>😔 出错了</h2>
262 <p>{error}</p>
263 <button onClick={handleBack} className="back-btn">
264 <ArrowLeft size={20} />
265 返回
266 </button>
267 </div>
268 </div>
269 )
270 }
271
22301008e4acb922025-06-24 23:50:35 +0800272 // 只有明确为 null 或 undefined 时才显示不存在
22301008e25b4b02025-06-20 22:15:31 +0800273 if (post === null || post === undefined) {
95630366980c1f272025-06-20 14:08:54 +0800274 return (
275 <div className="post-detail">
276 <div className="error-container">
22301008e25b4b02025-06-20 22:15:31 +0800277 <h2>😔 帖子不存在或已被删除</h2>
95630366980c1f272025-06-20 14:08:54 +0800278 <p>该帖子可能已被删除或不存在</p>
279 <button onClick={handleBack} className="back-btn">
280 <ArrowLeft size={20} />
281 返回
282 </button>
283 </div>
284 </div>
285 )
286 }
287
288 return (
289 <div className="post-detail">
290 {/* 顶部导航栏 */}
291 <header className="post-header">
292 <button onClick={handleBack} className="back-btn">
293 <ArrowLeft size={20} />
294 返回
295 </button>
296 <div className="header-actions">
297 <button onClick={handleShare} className="action-btn">
298 <Share2 size={20} />
299 </button>
22301008e4acb922025-06-24 23:50:35 +0800300 <button
301 onClick={handleBookmark}
95630366980c1f272025-06-20 14:08:54 +0800302 className={`action-btn ${bookmarked ? 'active' : ''}`}
303 >
304 <BookmarkPlus size={20} />
305 </button>
306 </div>
307 </header>
308
309 {/* 主要内容区 */}
310 <main className="post-content">
22301008e4acb922025-06-24 23:50:35 +0800311 {/* 帖子标题 */}
95630366980c1f272025-06-20 14:08:54 +0800312 <h1 className="post-title">{post.title}</h1>
313
22301008e4acb922025-06-24 23:50:35 +0800314 {/* 作者信息和元数据 */}
95630366980c1f272025-06-20 14:08:54 +0800315 <div className="post-meta">
316 <div className="author-info">
317 <div className="avatar">
22301008e4acb922025-06-24 23:50:35 +0800318 {authorInfo && authorInfo.avatar && authorInfo.avatar.startsWith('http') ? (
319 <img className="avatar" src={authorInfo.avatar} alt={authorInfo.username || authorInfo.nickname || post.author || '用户'} />
22301008e25b4b02025-06-20 22:15:31 +0800320 ) : (
22301008e4acb922025-06-24 23:50:35 +0800321 <img className="avatar" src={`https://i.pravatar.cc/40?img=${post.user_id}`} alt={authorInfo?.username || authorInfo?.nickname || post.author || '用户'} />
22301008e25b4b02025-06-20 22:15:31 +0800322 )}
95630366980c1f272025-06-20 14:08:54 +0800323 </div>
324 <div className="author-details">
22301008e4acb922025-06-24 23:50:35 +0800325 <span className="author-name">{authorInfo?.username || authorInfo?.nickname || post.author || '匿名用户'}</span>
wu32b07822025-06-24 23:10:02 +0800326 <span className="post-date">
22301008e4acb922025-06-24 23:50:35 +0800327 {post.created_at ? dayjs(post.created_at).format('YYYY-MM-DD HH:mm:ss') : '未知时间'}
wu32b07822025-06-24 23:10:02 +0800328 </span>
22301008e4acb922025-06-24 23:50:35 +0800329 {/* 关注按钮 */}
22301008e25b4b02025-06-20 22:15:31 +0800330 {post.user_id && (
95630366986f92322025-06-26 18:29:49 +0800331 <button
332 className={`follow-btn ${isFollowing ? 'following' : ''}`}
333 onClick={handleFollowAction}
334 disabled={followLoading}
335 style={{
336 marginLeft: '12px',
337 padding: '4px 12px',
338 borderRadius: '20px',
339 border: '1px solid #ccc',
340 background: isFollowing ? '#f0f0f0' : '#007bff',
341 color: isFollowing ? '#333' : 'white',
342 cursor: 'pointer',
343 fontSize: '14px'
344 }}
345 >
346 {followLoading ? '处理中...' : (isFollowing ? '已关注' : '关注')}
347 </button>
348 )}
95630366980c1f272025-06-20 14:08:54 +0800349 </div>
350 </div>
351 <div className="post-stats">
352 <span className="stat-item">
353 <Eye size={16} />
354 {post.views || 0}
355 </span>
356 <span className="stat-item">
357 <Heart size={16} />
358 {likeCount}
359 </span>
360 </div>
361 </div>
362
363 {/* 标签 */}
22301008e4acb922025-06-24 23:50:35 +0800364 {post.tags && post.tags.length > 0 && (
95630366980c1f272025-06-20 14:08:54 +0800365 <div className="post-tags">
22301008e4acb922025-06-24 23:50:35 +0800366 {post.tags.map((tag, index) => (
367 <span key={index} className="tag">{tag}</span>
95630366980c1f272025-06-20 14:08:54 +0800368 ))}
369 </div>
370 )}
371
22301008e4acb922025-06-24 23:50:35 +0800372 {/* 帖子媒体(支持多图/多视频) */}
22301008c9805072025-06-20 22:38:02 +0800373 {Array.isArray(post.media_urls) && post.media_urls.length > 0 && (
22301008e4acb922025-06-24 23:50:35 +0800374 <div className="post-media" style={{display:'flex',gap:8,marginBottom:16,flexWrap:'wrap'}}>
22301008c9805072025-06-20 22:38:02 +0800375 {post.media_urls.map((url, idx) => (
9563036699de8c092025-06-21 16:41:18 +0800376 <MediaPreview
377 key={idx}
378 url={url}
22301008e4acb922025-06-24 23:50:35 +0800379 alt={`媒体${idx+1}`}
9563036699de8c092025-06-21 16:41:18 +0800380 onClick={(mediaUrl) => {
22301008e4acb922025-06-24 23:50:35 +0800381 // 对于图片,显示预览
382 if (!mediaUrl.toLowerCase().includes('video') && !mediaUrl.includes('.mp4') && !mediaUrl.includes('.webm')) {
9563036699de8c092025-06-21 16:41:18 +0800383 setPreviewImg(mediaUrl)
384 }
385 }}
386 style={{ cursor: 'pointer' }}
387 maxWidth={320}
388 maxHeight={320}
389 />
22301008c9805072025-06-20 22:38:02 +0800390 ))}
391 </div>
392 )}
22301008e4acb922025-06-24 23:50:35 +0800393 {/* 大图预览弹窗 */}
22301008c9805072025-06-20 22:38:02 +0800394 {previewImg && (
22301008e4acb922025-06-24 23:50:35 +0800395 <div className="img-preview-mask" style={{position:'fixed',zIndex:9999,top:0,left:0,right:0,bottom:0,background:'rgba(0,0,0,0.7)',display:'flex',alignItems:'center',justifyContent:'center'}} onClick={()=>setPreviewImg(null)}>
396 <img src={previewImg} alt="大图预览" style={{maxWidth:'90vw',maxHeight:'90vh',borderRadius:12,boxShadow:'0 4px 24px #0008'}} />
22301008c9805072025-06-20 22:38:02 +0800397 </div>
398 )}
399
22301008e4acb922025-06-24 23:50:35 +0800400 {/* 帖子正文 */}
95630366980c1f272025-06-20 14:08:54 +0800401 <div className="post-body">
402 <p>{post.content}</p>
403 </div>
404
22301008e4acb922025-06-24 23:50:35 +0800405 {/* 类别信息 */}
95630366980c1f272025-06-20 14:08:54 +0800406 {(post.category || post.type) && (
407 <div className="post-category">
408 {post.category && (
409 <>
410 <span className="category-label">分类:</span>
411 <span className="category-name">{post.category}</span>
412 </>
413 )}
414 {post.type && (
415 <>
22301008e4acb922025-06-24 23:50:35 +0800416 <span className="category-label" style={{marginLeft: '1em'}}>类型:</span>
95630366980c1f272025-06-20 14:08:54 +0800417 <span className="category-name">{post.type}</span>
418 </>
419 )}
420 </div>
421 )}
422
423 {/* 评论区 */}
424 <div className="comments-section">
425 <div className="comments-header">
22301008e4acb922025-06-24 23:50:35 +0800426 <button
95630366980c1f272025-06-20 14:08:54 +0800427 onClick={() => setShowComments(!showComments)}
428 className="comments-toggle"
429 >
430 <MessageCircle size={20} />
431 评论 ({comments.length})
432 </button>
433 </div>
434
435 {showComments && (
436 <div className="comments-content">
22301008e4acb922025-06-24 23:50:35 +0800437 {/* 添加评论 */}
95630366980c1f272025-06-20 14:08:54 +0800438 <form onSubmit={handleAddComment} className="comment-form">
439 <textarea
440 value={newComment}
441 onChange={(e) => setNewComment(e.target.value)}
442 placeholder="写下你的评论..."
443 className="comment-input"
444 rows={3}
445 />
446 <button type="submit" className="comment-submit">
447 发布评论
448 </button>
449 </form>
450
451 {/* 评论列表 */}
452 <div className="comments-list">
453 {comments.length === 0 ? (
454 <p className="no-comments">暂无评论</p>
455 ) : (
22301008e4acb922025-06-24 23:50:35 +0800456 comments.map((comment, index) => (
457 <div key={index} className="comment-item">
95630366980c1f272025-06-20 14:08:54 +0800458 <div className="comment-author">
459 <div className="comment-avatar">
22301008e4acb922025-06-24 23:50:35 +0800460 <img className="avatar" src={commentUserAvatarMap[comment.user_id] || `https://i.pravatar.cc/40?img=${comment.user_id}`} alt={commentUserMap[comment.user_id] || comment.user_name || '用户'} />
95630366980c1f272025-06-20 14:08:54 +0800461 </div>
22301008e4acb922025-06-24 23:50:35 +0800462 <span className="comment-name">{commentUserMap[comment.user_id] || comment.user_name || '匿名用户'}</span>
95630366980c1f272025-06-20 14:08:54 +0800463 <span className="comment-time">
22301008e4acb922025-06-24 23:50:35 +0800464 {comment.create_time ? new Date(comment.create_time).toLocaleString('zh-CN') : ''}
95630366980c1f272025-06-20 14:08:54 +0800465 </span>
466 </div>
22301008e4acb922025-06-24 23:50:35 +0800467 <div className="comment-content">
468 {comment.content}
469 </div>
95630366980c1f272025-06-20 14:08:54 +0800470 </div>
471 ))
472 )}
473 </div>
474 </div>
475 )}
476 </div>
477 </main>
478
479 {/* 底部操作栏 */}
480 <footer className="post-footer">
481 <div className="action-bar">
22301008e4acb922025-06-24 23:50:35 +0800482 <button
483 onClick={handleLike}
95630366980c1f272025-06-20 14:08:54 +0800484 className={`action-button ${liked ? 'liked' : ''}`}
485 >
486 <ThumbsUp size={20} />
487 <span>{likeCount}</span>
488 </button>
22301008e4acb922025-06-24 23:50:35 +0800489
490 <button
95630366980c1f272025-06-20 14:08:54 +0800491 onClick={() => setShowComments(!showComments)}
492 className="action-button"
493 >
494 <MessageCircle size={20} />
495 <span>评论</span>
496 </button>
22301008e4acb922025-06-24 23:50:35 +0800497
95630366980c1f272025-06-20 14:08:54 +0800498 <button onClick={handleShare} className="action-button">
499 <Share2 size={20} />
500 <span>分享</span>
501 </button>
22301008e4acb922025-06-24 23:50:35 +0800502
503 <button
504 onClick={handleBookmark}
95630366980c1f272025-06-20 14:08:54 +0800505 className={`action-button ${bookmarked ? 'bookmarked' : ''}`}
506 >
507 <BookmarkPlus size={20} />
508 <span>收藏</span>
509 </button>
510 </div>
511 </footer>
512 </div>
513 )
514}