纯前端修改点赞问题。

Change-Id: I99522d6492beb310ec048ff1191349c4a4436e09
diff --git a/Merge/front/src/components/PostDetailJWLLL.jsx b/Merge/front/src/components/PostDetailJWLLL.jsx
index b0ca84f..47f905f 100644
--- a/Merge/front/src/components/PostDetailJWLLL.jsx
+++ b/Merge/front/src/components/PostDetailJWLLL.jsx
@@ -1,18 +1,10 @@
-import React, { useState, useEffect, useCallback, useMemo } from 'react'
+import React, { useState, useEffect, useCallback } from 'react'
 import { useParams, useNavigate } from 'react-router-dom'
-import {
-  ArrowLeft,
-  ThumbsUp,
-  MessageCircle,
-  Share2,
-  BookmarkPlus,
-  Heart,
-  Eye,
-} from 'lucide-react'
+import { ArrowLeft, ThumbsUp, MessageCircle, Share2, BookmarkPlus, Heart, Eye } from 'lucide-react'
 import { searchAPI } from '../api/search_jwlll'
 import { getUserInfo } from '../utils/auth'
 import FollowButton from './FollowButton'
-import postsAPI from '../api/posts_api'                // ⇦ 包含 hasLikedPost、likePost、unlikePost
+import postsAPI from '../api/posts_api'
 import MediaPreview from './MediaPreview'
 import '../style/PostDetail.css'
 import dayjs from 'dayjs'
@@ -20,34 +12,24 @@
 export default function PostDetail() {
   const { id } = useParams()
   const navigate = useNavigate()
-
-  // ──────────────── 状态 ────────────────
   const [post, setPost] = useState(null)
   const [loading, setLoading] = useState(true)
   const [error, setError] = useState(null)
-
   const [liked, setLiked] = useState(false)
   const [bookmarked, setBookmarked] = useState(false)
   const [likeCount, setLikeCount] = useState(0)
-
   const [comments, setComments] = useState([])
   const [newComment, setNewComment] = useState('')
   const [showComments, setShowComments] = useState(false)
-
   const [isFollowing, setIsFollowing] = useState(false)
   const [authorInfo, setAuthorInfo] = useState(null)
-
   const [previewImg, setPreviewImg] = useState(null)
-  const [commentUserMap, setCommentUserMap] = useState({})      // user_id → username
-  const [commentUserAvatarMap, setCommentUserAvatarMap] = useState({}) // user_id → avatar
-
-  // 当前登录用户 ID(memo 化,组件整个生命周期只算一次)
-  const currentUserId = useMemo(() => {
-    const ui = getUserInfo()
-    return ui?.id || 'null'          // 未登录就给个默认值 3
-  }, [])
-
-  // ──────────────── 拉取帖子详情 ────────────────
+  const [commentUserMap, setCommentUserMap] = useState({}) // user_id: username
+  const [commentUserAvatarMap, setCommentUserAvatarMap] = useState({}) // user_id: avatar  // 获取当前用户ID
+  const getCurrentUserId = () => {
+    const userInfo = getUserInfo()
+    return userInfo?.id || '3' // 如果未登录或无用户信息,使用默认值3
+  }
   const fetchPostDetail = useCallback(async () => {
     setLoading(true)
     setError(null)
@@ -55,151 +37,153 @@
       const data = await searchAPI.getPostDetail(id)
       setPost(data)
       setLikeCount(data.heat || 0)
-    } catch (err) {
-      console.error('获取帖子详情失败:', err)
+      
+      // 检查当前用户是否已点赞
+      const currentUserId = getCurrentUserId()
+      try {
+        const hasLiked = await searchAPI.hasLiked(id, currentUserId)
+        setLiked(hasLiked)
+      } catch (error) {
+        console.error('检查点赞状态失败:', error)
+        setLiked(false) // 如果检查失败,默认为未点赞
+      }
+    } catch (error) {
+      console.error('获取帖子详情失败:', error)
       setError('帖子不存在或已被删除')
     } finally {
       setLoading(false)
     }
   }, [id])
 
-  // ──────────────── 拉取评论 ────────────────
   const fetchComments = useCallback(async () => {
     try {
       const data = await searchAPI.getComments(id)
       setComments(data.comments || [])
-    } catch (err) {
-      console.error('获取评论失败:', err)
+    } catch (error) {
+      console.error('获取评论失败:', error)
     }
   }, [id])
 
-  // ──────────────── 组件挂载:帖子详情 + 评论 + 点赞状态 ────────────────
-  useEffect(() => {
-    fetchPostDetail()
-    fetchComments()
-
-    // 检查我是否点过赞
-    if (currentUserId) {
-      ;(async () => {
-        try {
-          const res = await postsAPI.hasLikedPost(id, currentUserId)
-          setLiked(!!res.liked)
-        } catch (err) {
-          console.error('检查点赞状态失败:', err)
-        }
-      })()
-    }
-  }, [fetchPostDetail, fetchComments, id, currentUserId])
-
-  // ──────────────── 检查是否关注作者 ────────────────
+  // 检查当前用户是否已关注发帖人
   useEffect(() => {
     if (post && post.user_id) {
+      // 这里假设有API postsAPI.getUserFollowing
       const checkFollow = async () => {
         try {
-          if (!currentUserId) return
-          const res = await postsAPI.getUserFollowing(currentUserId)
+          const userInfo = getUserInfo()
+          if (!userInfo?.id) return
+          const res = await postsAPI.getUserFollowing(userInfo.id)
           if (Array.isArray(res)) {
-            setIsFollowing(res.some((u) => u.id === post.user_id))
+            setIsFollowing(res.some(u => u.id === post.user_id))
           } else if (Array.isArray(res.following)) {
-            setIsFollowing(res.following.some((u) => u.id === post.user_id))
+            setIsFollowing(res.following.some(u => u.id === post.user_id))
           }
         } catch {}
       }
       checkFollow()
     }
-  }, [post, currentUserId])
+  }, [post])
 
-  // ──────────────── 作者信息 ────────────────
+  // 拉取发帖人信息
   useEffect(() => {
     if (post && post.user_id) {
-      postsAPI
-        .getUser(post.user_id)
-        .then((res) => setAuthorInfo(res || {}))
-        .catch(() => setAuthorInfo({}))
+      postsAPI.getUser(post.user_id).then(res => setAuthorInfo(res || {})).catch(() => setAuthorInfo({}))
     }
   }, [post])
 
-  // ──────────────── 拉取评论用户昵称 / 头像 ────────────────
+  // 拉取所有评论用户昵称
   const fetchCommentUserNames = async (userIds) => {
     const map = {}
-    await Promise.all(
-      userIds.map(async (uid) => {
-        try {
-          const user = await postsAPI.getUser(uid)
-          map[uid] = user.username || user.nickname || `用户${uid}`
-        } catch {
-          map[uid] = `用户${uid}`
-        }
-      })
-    )
+    await Promise.all(userIds.map(async uid => {
+      try {
+        const user = await postsAPI.getUser(uid)
+        map[uid] = user.username || user.nickname || `用户${uid}`
+      } catch {
+        map[uid] = `用户${uid}`
+      }
+    }))
     setCommentUserMap(map)
   }
 
+  // 拉取所有评论用户头像
   const fetchCommentUserAvatars = async (userIds) => {
     const map = {}
-    await Promise.all(
-      userIds.map(async (uid) => {
-        try {
-          const user = await postsAPI.getUser(uid)
-          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}`
-        } catch {
-          map[uid] = `https://i.pravatar.cc/40?img=${uid}`
-        }
-      })
-    )
+    await Promise.all(userIds.map(async uid => {
+      try {
+        const user = await postsAPI.getUser(uid)
+        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}`)
+      } catch {
+        map[uid] = `https://i.pravatar.cc/40?img=${uid}`
+      }
+    }))
     setCommentUserAvatarMap(map)
   }
 
   useEffect(() => {
+    fetchPostDetail()
+    fetchComments()
+  }, [fetchPostDetail, fetchComments])
+
+  // 评论区用户昵称和头像拉取
+  useEffect(() => {
     if (comments.length > 0) {
-      const uidSet = new Set(comments.map((c) => c.user_id).filter(Boolean))
-      const ids = [...uidSet]
-      fetchCommentUserNames(ids)
-      fetchCommentUserAvatars(ids)
+      const userIds = [...new Set(comments.map(c => c.user_id).filter(Boolean))]
+      fetchCommentUserNames(userIds)
+      fetchCommentUserAvatars(userIds)
     }
   }, [comments])
-
-  // ──────────────── 交互 handlers ────────────────
-  const handleBack = () => navigate(-1)
-
+  const handleBack = () => {
+    navigate(-1)
+  }
   const handleLike = async () => {
-    if (!currentUserId) {
-      alert('请先登录~')
-      return
-    }
-
+    const currentUserId = getCurrentUserId()
+    const originalLiked = liked
+    const originalLikeCount = likeCount
+    
     try {
-      if (liked) {
-        // 取消点赞
-        await postsAPI.unlikePost(id, currentUserId)
+      // 先乐观更新UI,提供即时反馈
+      const newLiked = !liked
+      setLiked(newLiked)
+      setLikeCount(prev => newLiked ? prev + 1 : prev - 1)
+      
+      // 调用后端API
+      if (newLiked) {
+        await searchAPI.likePost(id, currentUserId)
       } else {
-        // 点赞
-        await postsAPI.likePost(id, currentUserId)
+        await searchAPI.unlikePost(id, currentUserId)
       }
-
-      // —— 关键:操作成功后重新拉一次帖子详情(里面会 setLikeCount) —— 
-      await fetchPostDetail()
-
-      // —— 同步一下 liked 状态(可选,因为你直接用 fetchPostDetail 重置 likeCount,也可以重新查一下状态) —— 
-      const { liked: has } = await postsAPI.hasLikedPost(id, currentUserId)
-      setLiked(!!has)
-
-    } catch (err) {
-      console.error('点赞操作失败:', err)
+      
+      // API调用成功,状态已经更新,无需再次设置
+    } catch (error) {
+      console.error('点赞操作失败:', error)
+      
+      // 检查是否是可以忽略的错误
+      const errorMessage = error.message || error.toString()
+      const isIgnorableError = errorMessage.includes('already liked') || 
+                              errorMessage.includes('not liked yet') ||
+                              errorMessage.includes('already favorited') ||
+                              errorMessage.includes('not favorited yet')
+      
+      if (isIgnorableError) {
+        // 这些错误可以忽略,因为最终状态是正确的
+        console.log('忽略重复操作错误:', errorMessage)
+        return
+      }
+      
+      // 发生真正的错误时回滚到原始状态
+      setLiked(originalLiked)
+      setLikeCount(originalLikeCount)
+      alert('操作失败,请重试')
     }
   }
 
   const handleBookmark = () => {
     setBookmarked(!bookmarked)
-    // TODO: 调后端保存收藏状态
+    // 实际项目中这里应该调用后端API保存收藏状态
   }
 
   const handleShare = () => {
+    // 分享功能
     if (navigator.share) {
       navigator.share({
         title: post?.title,
@@ -207,27 +191,44 @@
         url: window.location.href,
       })
     } else {
+      // 复制链接到剪贴板
       navigator.clipboard.writeText(window.location.href)
       alert('链接已复制到剪贴板')
     }
   }
-
   const handleAddComment = async (e) => {
     e.preventDefault()
     if (!newComment.trim()) return
+
     try {
+      const currentUserId = getCurrentUserId()
       await searchAPI.addComment(id, currentUserId, newComment)
       setNewComment('')
-      fetchComments()
-    } catch (err) {
-      console.error('添加评论失败:', err)
+      fetchComments() // 刷新评论列表
+    } catch (error) {
+      console.error('添加评论失败:', error)
       alert('评论失败,请重试')
     }
   }
 
-  const handleFollowChange = (followed) => setIsFollowing(followed)
+  // 关注后刷新关注状态
+  const handleFollowChange = async (followed) => {
+    setIsFollowing(followed)
+    // 关注/取关后重新拉取一次关注状态,保证和数据库同步
+    if (post && post.user_id) {
+      try {
+        const userInfo = getUserInfo()
+        if (!userInfo?.id) return
+        const res = await postsAPI.getUserFollowing(userInfo.id)
+        if (Array.isArray(res)) {
+          setIsFollowing(res.some(u => u.id === post.user_id))
+        } else if (Array.isArray(res.following)) {
+          setIsFollowing(res.following.some(u => u.id === post.user_id))
+        }
+      } catch {}
+    }
+  }
 
-  // ──────────────── 渲染逻辑 ────────────────
   if (loading) {
     return (
       <div className="post-detail">
@@ -239,6 +240,7 @@
     )
   }
 
+  // 优化错误和不存在的判断逻辑
   if (error) {
     return (
       <div className="post-detail">
@@ -254,6 +256,7 @@
     )
   }
 
+  // 只有明确为 null 或 undefined 时才显示不存在
   if (post === null || post === undefined) {
     return (
       <div className="post-detail">
@@ -281,8 +284,8 @@
           <button onClick={handleShare} className="action-btn">
             <Share2 size={20} />
           </button>
-          <button
-            onClick={handleBookmark}
+          <button 
+            onClick={handleBookmark} 
             className={`action-btn ${bookmarked ? 'active' : ''}`}
           >
             <BookmarkPlus size={20} />
@@ -292,56 +295,31 @@
 
       {/* 主要内容区 */}
       <main className="post-content">
-        {/* 标题 */}
+        {/* 帖子标题 */}
         <h1 className="post-title">{post.title}</h1>
 
-        {/* 作者 & 元数据 */}
+        {/* 作者信息和元数据 */}
         <div className="post-meta">
           <div className="author-info">
             <div className="avatar">
-              {authorInfo?.avatar && authorInfo.avatar.startsWith('http') ? (
-                <img
-                  className="avatar"
-                  src={authorInfo.avatar}
-                  alt={
-                    authorInfo.username ||
-                    authorInfo.nickname ||
-                    post.author ||
-                    '用户'
-                  }
-                />
+              {authorInfo && authorInfo.avatar && authorInfo.avatar.startsWith('http') ? (
+                <img className="avatar" src={authorInfo.avatar} alt={authorInfo.username || authorInfo.nickname || post.author || '用户'} />
               ) : (
-                <img
-                  className="avatar"
-                  src={`https://i.pravatar.cc/40?img=${post.user_id}`}
-                  alt={
-                    authorInfo?.username ||
-                    authorInfo?.nickname ||
-                    post.author ||
-                    '用户'
-                  }
-                />
+                <img className="avatar" src={`https://i.pravatar.cc/40?img=${post.user_id}`} alt={authorInfo?.username || authorInfo?.nickname || post.author || '用户'} />
               )}
             </div>
             <div className="author-details">
-              <span className="author-name">
-                {authorInfo?.username ||
-                  authorInfo?.nickname ||
-                  post.author ||
-                  '匿名用户'}
-              </span>
+              <span className="author-name">{authorInfo?.username || authorInfo?.nickname || post.author || '匿名用户'}</span>
               <span className="post-date">
-                {post.created_at
-                  ? dayjs(post.created_at).format('YYYY-MM-DD HH:mm:ss')
-                  : '未知时间'}
+                {post.created_at ? dayjs(post.created_at).format('YYYY-MM-DD HH:mm:ss') : '未知时间'}
               </span>
-
+              {/* 关注按钮 */}
               {post.user_id && (
                 <FollowButton
                   userId={post.user_id}
                   isFollowing={isFollowing}
                   onFollowChange={handleFollowChange}
-                  style={{ marginLeft: 12 }}
+                  style={{marginLeft: 12}}
                 />
               )}
             </div>
@@ -359,40 +337,25 @@
         </div>
 
         {/* 标签 */}
-        {post.tags?.length > 0 && (
+        {post.tags && post.tags.length > 0 && (
           <div className="post-tags">
-            {post.tags.map((tag, idx) => (
-              <span key={idx} className="tag">
-                {tag}
-              </span>
+            {post.tags.map((tag, index) => (
+              <span key={index} className="tag">{tag}</span>
             ))}
           </div>
         )}
 
-        {/* 媒体 */}
+        {/* 帖子媒体(支持多图/多视频) */}
         {Array.isArray(post.media_urls) && post.media_urls.length > 0 && (
-          <div
-            className="post-media"
-            style={{
-              display: 'flex',
-              gap: 8,
-              marginBottom: 16,
-              flexWrap: 'wrap',
-            }}
-          >
+          <div className="post-media" style={{display:'flex',gap:8,marginBottom:16,flexWrap:'wrap'}}>
             {post.media_urls.map((url, idx) => (
               <MediaPreview
                 key={idx}
                 url={url}
-                alt={`媒体${idx + 1}`}
+                alt={`媒体${idx+1}`}
                 onClick={(mediaUrl) => {
-                  // 图片点击预览
-                  const lower = mediaUrl.toLowerCase()
-                  if (
-                    !lower.includes('video') &&
-                    !lower.endsWith('.mp4') &&
-                    !lower.endsWith('.webm')
-                  ) {
+                  // 对于图片,显示预览
+                  if (!mediaUrl.toLowerCase().includes('video') && !mediaUrl.includes('.mp4') && !mediaUrl.includes('.webm')) {
                     setPreviewImg(mediaUrl)
                   }
                 }}
@@ -403,42 +366,19 @@
             ))}
           </div>
         )}
+        {/* 大图预览弹窗 */}
         {previewImg && (
-          <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)}
-          >
-            <img
-              src={previewImg}
-              alt="大图预览"
-              style={{
-                maxWidth: '90vw',
-                maxHeight: '90vh',
-                borderRadius: 12,
-                boxShadow: '0 4px 24px #0008',
-              }}
-            />
+          <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)}>
+            <img src={previewImg} alt="大图预览" style={{maxWidth:'90vw',maxHeight:'90vh',borderRadius:12,boxShadow:'0 4px 24px #0008'}} />
           </div>
         )}
 
-        {/* 正文 */}
+        {/* 帖子正文 */}
         <div className="post-body">
           <p>{post.content}</p>
         </div>
 
-        {/* 分类 / 类型 */}
+        {/* 类别信息 */}
         {(post.category || post.type) && (
           <div className="post-category">
             {post.category && (
@@ -449,9 +389,7 @@
             )}
             {post.type && (
               <>
-                <span className="category-label" style={{ marginLeft: '1em' }}>
-                  类型:
-                </span>
+                <span className="category-label" style={{marginLeft: '1em'}}>类型:</span>
                 <span className="category-name">{post.type}</span>
               </>
             )}
@@ -461,7 +399,7 @@
         {/* 评论区 */}
         <div className="comments-section">
           <div className="comments-header">
-            <button
+            <button 
               onClick={() => setShowComments(!showComments)}
               className="comments-toggle"
             >
@@ -472,7 +410,7 @@
 
           {showComments && (
             <div className="comments-content">
-              {/* 新评论 */}
+              {/* 添加评论 */}
               <form onSubmit={handleAddComment} className="comment-form">
                 <textarea
                   value={newComment}
@@ -491,37 +429,20 @@
                 {comments.length === 0 ? (
                   <p className="no-comments">暂无评论</p>
                 ) : (
-                  comments.map((comment, idx) => (
-                    <div key={idx} className="comment-item">
+                  comments.map((comment, index) => (
+                    <div key={index} className="comment-item">
                       <div className="comment-author">
                         <div className="comment-avatar">
-                          <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 ||
-                              '用户'
-                            }
-                          />
+                          <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 || '用户'} />
                         </div>
-                        <span className="comment-name">
-                          {commentUserMap[comment.user_id] ||
-                            comment.user_name ||
-                            '匿名用户'}
-                        </span>
+                        <span className="comment-name">{commentUserMap[comment.user_id] || comment.user_name || '匿名用户'}</span>
                         <span className="comment-time">
-                          {comment.create_time
-                            ? new Date(comment.create_time).toLocaleString(
-                                'zh-CN'
-                              )
-                            : ''}
+                          {comment.create_time ? new Date(comment.create_time).toLocaleString('zh-CN') : ''}
                         </span>
                       </div>
-                      <div className="comment-content">{comment.content}</div>
+                      <div className="comment-content">
+                        {comment.content}
+                      </div>
                     </div>
                   ))
                 )}
@@ -534,29 +455,29 @@
       {/* 底部操作栏 */}
       <footer className="post-footer">
         <div className="action-bar">
-          <button
-            onClick={handleLike}
+          <button 
+            onClick={handleLike} 
             className={`action-button ${liked ? 'liked' : ''}`}
           >
             <ThumbsUp size={20} />
             <span>{likeCount}</span>
           </button>
-
-          <button
+          
+          <button 
             onClick={() => setShowComments(!showComments)}
             className="action-button"
           >
             <MessageCircle size={20} />
             <span>评论</span>
           </button>
-
+          
           <button onClick={handleShare} className="action-button">
             <Share2 size={20} />
             <span>分享</span>
           </button>
-
-          <button
-            onClick={handleBookmark}
+          
+          <button 
+            onClick={handleBookmark} 
             className={`action-button ${bookmarked ? 'bookmarked' : ''}`}
           >
             <BookmarkPlus size={20} />