增加首页异步获取功能

Change-Id: I22f030ace40fcd26d9e57c122f6055da26b176ad
diff --git a/Merge/back_wzy/routes/__pycache__/posts.cpython-310.pyc b/Merge/back_wzy/routes/__pycache__/posts.cpython-310.pyc
index a7623b2..19bfef0 100644
--- a/Merge/back_wzy/routes/__pycache__/posts.cpython-310.pyc
+++ b/Merge/back_wzy/routes/__pycache__/posts.cpython-310.pyc
Binary files differ
diff --git a/Merge/front/src/components/HomeFeed.jsx b/Merge/front/src/components/HomeFeed.jsx
index e30eb73..b959fd9 100644
--- a/Merge/front/src/components/HomeFeed.jsx
+++ b/Merge/front/src/components/HomeFeed.jsx
@@ -26,60 +26,77 @@
   // 获取当前用户ID,如果未登录则使用默认值
   const getCurrentUserId = () => {
     const userInfo = getUserInfo()
-    return userInfo?.id ? String(userInfo.id) : DEFAULT_USER_ID  }
+    return userInfo?.id ? String(userInfo.id) : DEFAULT_USER_ID
+  }
   
-  const [items, setItems]         = useState([])
-  const [loading, setLoading]     = useState(true)
-  const [error, setError]         = useState(null)
-    // JWLLL 搜索推荐相关状态
+  const [items, setItems] = useState([])
+  const [loading, setLoading] = useState(true)
+  const [error, setError] = useState(null)
+  // JWLLL 搜索推荐相关状态
   const [search, setSearch] = useState('')
   const [recMode, setRecMode] = useState('tag')
   const [recCFNum, setRecCFNum] = useState(20)
-  const [useSearchRecommend, setUseSearchRecommend] = useState(false) // 是否使用搜索推荐模式  // JWLLL 搜索推荐功能函数
-  const [userMap, setUserMap] = useState({}) // user_id: {username, nickname}
-    // JWLLL搜索推荐内容
+  const [useSearchRecommend, setUseSearchRecommend] = useState(false)
+  const [userMap, setUserMap] = useState({})
+  
+  // 异步加载单个帖子的详细信息
+  const loadPostDetails = async (postId, index) => {
+    try {
+      const d = await fetchPost(postId)
+      setItems(prevItems => {
+        const newItems = [...prevItems]
+        if (newItems[index]) {
+          newItems[index] = {
+            ...newItems[index],
+            media: d.media_urls?.[0] || '',
+            content: d.content || '',
+            mediaUrls: d.media_urls || [],
+            author: `作者 ${d.user_id}`,
+            authorId: d.user_id,
+            avatar: `http://192.168.5.200:8080/static/profile.webp`,
+            detailsLoaded: true
+          }
+        }
+        return newItems
+      })
+    } catch (error) {
+      console.error(`加载帖子 ${postId} 详情失败:`, error)
+    }
+  }
+
+  // JWLLL搜索推荐内容
   const fetchSearchContent = useCallback(async (keyword = '') => {
     setLoading(true)
     setError(null)
     try {
       const data = await searchAPI.search(keyword, undefined)
-      // 新增:拉取详情,保证和推荐一致
-      const detailed = await Promise.all(
-        (data.results || []).map(async item => {
-          try {
-            const d = await fetchPost(item.id)
-            return {
-              id:     d.id,
-              title:  d.title,
-              author: `作者 ${d.user_id}`,
-              avatar: `https://i.pravatar.cc/40?img=${d.user_id}`,
-              media:  d.media_urls?.[0] || '', // 改为 media,支持图片和视频
-              likes:  d.heat,
-              content: d.content || '',
-              mediaUrls: d.media_urls || [] // 保存所有媒体URL
-            }
-          } catch {
-            return {
-              id: item.id,
-              title: item.title,
-              author: item.author || '佚名',
-              avatar: `https://i.pravatar.cc/40?img=${item.id}`,
-              media: item.img || '',
-              likes: item.heat || 0,
-              content: item.content || '',
-              mediaUrls: []
-            }
-          }
-        })
-      )
-      setItems(detailed)
+      // 先设置基础数据,快速显示
+      const basicItems = (data.results || []).map(item => ({
+        id: item.id,
+        title: item.title,
+        author: item.author || '佚名',
+        avatar: `https://i.pravatar.cc/40?img=${item.id}`,
+        media: item.img || '',
+        likes: item.heat || 0,
+        content: item.content || '',
+        mediaUrls: [],
+        detailsLoaded: false
+      }))
+      setItems(basicItems)
+      setLoading(false)
+      
+      // 异步加载详细信息
+      basicItems.forEach((item, index) => {
+        loadPostDetails(item.id, index)
+      })
     } catch (e) {
       console.error('搜索失败:', e)
       setError('搜索失败')
       setItems([])
+      setLoading(false)
     }
-    setLoading(false)
   }, [])
+
   // 标签推荐
   const fetchTagRecommend = useCallback(async (tags) => {
     setLoading(true)
@@ -87,44 +104,33 @@
     try {
       const currentUserId = getCurrentUserId()
       const data = await searchAPI.recommendByTags(currentUserId, tags)
-      // 新增:拉取详情,保证和原始数据一致
-      const detailed = await Promise.all(
-        (data.recommendations || []).map(async item => {
-          try {
-            const d = await fetchPost(item.id)
-            return {
-              id:     d.id,
-              title:  d.title,
-              author: `作者 ${d.user_id}`,
-              avatar: `https://i.pravatar.cc/40?img=${d.user_id}`,
-              media:  d.media_urls?.[0] || '', // 改为 media,支持图片和视频
-              likes:  d.heat,
-              content: d.content || '',
-              mediaUrls: d.media_urls || [] // 保存所有媒体URL
-            }
-          } catch {
-            // 拉详情失败时兜底
-            return {
-              id: item.id,
-              title: item.title,
-              author: item.author || '佚名',
-              avatar: `https://i.pravatar.cc/40?img=${item.id}`,
-              media: item.img || '',
-              likes: item.heat || 0,
-              content: item.content || '',
-              mediaUrls: []
-            }
-          }
-        })
-      )
-      setItems(detailed)
+      // 先设置基础数据,快速显示
+      const basicItems = (data.recommendations || []).map(item => ({
+        id: item.id,
+        title: item.title,
+        author: item.author || '佚名',
+        avatar: `https://i.pravatar.cc/40?img=${item.id}`,
+        media: item.img || '',
+        likes: item.heat || 0,
+        content: item.content || '',
+        mediaUrls: [],
+        detailsLoaded: false
+      }))
+      setItems(basicItems)
+      setLoading(false)
+      
+      // 异步加载详细信息
+      basicItems.forEach((item, index) => {
+        loadPostDetails(item.id, index)
+      })
     } catch (e) {
       console.error('标签推荐失败:', e)
       setError('标签推荐失败')
       setItems([])
+      setLoading(false)
     }
-    setLoading(false)
   }, [])
+
   // 协同过滤推荐
   const fetchCFRecommend = useCallback(async (topN = recCFNum) => {
     setLoading(true)
@@ -132,44 +138,33 @@
     try {
       const currentUserId = getCurrentUserId()
       const data = await searchAPI.userBasedRecommend(currentUserId, topN)
-      // 新增:拉取详情,保证和原始数据一致
-      const detailed = await Promise.all(
-        (data.recommendations || []).map(async item => {
-          try {
-            const d = await fetchPost(item.id)
-            return {
-              id:     d.id,
-              title:  d.title,
-              author: `作者 ${d.user_id}`,
-              avatar: `https://i.pravatar.cc/40?img=${d.user_id}`,
-              media:  d.media_urls?.[0] || '', // 改为 media,支持图片和视频
-              likes:  d.heat,
-              content: d.content || '',
-              mediaUrls: d.media_urls || [] // 保存所有媒体URL
-            }
-          } catch {
-            // 拉详情失败时兜底
-            return {
-              id: item.id,
-              title: item.title,
-              author: item.author || '佚名',
-              avatar: `https://i.pravatar.cc/40?img=${item.id}`,
-              media: item.img || '',
-              likes: item.heat || 0,
-              content: item.content || '',
-              mediaUrls: []
-            }
-          }
-        })
-      )
-      setItems(detailed)
+      // 先设置基础数据,快速显示
+      const basicItems = (data.recommendations || []).map(item => ({
+        id: item.id,
+        title: item.title,
+        author: item.author || '佚名',
+        avatar: `https://i.pravatar.cc/40?img=${item.id}`,
+        media: item.img || '',
+        likes: item.heat || 0,
+        content: item.content || '',
+        mediaUrls: [],
+        detailsLoaded: false
+      }))
+      setItems(basicItems)
+      setLoading(false)
+      
+      // 异步加载详细信息
+      basicItems.forEach((item, index) => {
+        loadPostDetails(item.id, index)
+      })
     } catch (e) {
       console.error('协同过滤推荐失败:', e)
       setError('协同过滤推荐失败')
       setItems([])
+      setLoading(false)
     }
-    setLoading(false)
   }, [recCFNum])
+
   // 深度推荐
   const fetchDeepRecommend = useCallback(async (topN = 20) => {
     setLoading(true)
@@ -177,42 +172,32 @@
     try {
       const currentUserId = getCurrentUserId()
       const recs = await deepRecommend(currentUserId, topN)
-      // 拉取详情,保证和原始数据一致
-      const detailed = await Promise.all(
-        (recs || []).map(async item => {
-          try {
-            const d = await fetchPost(item.id)
-            return {
-              id:     d.id,
-              title:  d.title,
-              author: `作者 ${d.user_id}`,
-              avatar: `https://i.pravatar.cc/40?img=${d.user_id}`,
-              media:  d.media_urls?.[0] || '', // 改为 media,支持图片和视频
-              likes:  d.heat,
-              content: d.content || '',
-              mediaUrls: d.media_urls || [] // 保存所有媒体URL
-            }
-          } catch {
-            return {
-              id: item.id,
-              title: item.title,
-              author: item.author || '佚名',
-              avatar: `https://i.pravatar.cc/40?img=${item.id}`,
-              media: item.img || '',
-              likes: item.heat || 0,
-              content: item.content || '',
-              mediaUrls: []
-            }
-          }
-        })
-      )
-      setItems(detailed)
+      // 先设置基础数据,快速显示
+      const basicItems = (recs || []).map(item => ({
+        id: item.id,
+        title: item.title,
+        author: item.author || '佚名',
+        avatar: `https://i.pravatar.cc/40?img=${item.id}`,
+        media: item.img || '',
+        likes: item.heat || 0,
+        content: item.content || '',
+        mediaUrls: [],
+        detailsLoaded: false
+      }))
+      setItems(basicItems)
+      setLoading(false)
+      
+      // 异步加载详细信息
+      basicItems.forEach((item, index) => {
+        loadPostDetails(item.id, index)
+      })
     } catch (e) {
       setError('深度推荐失败')
       setItems([])
+      setLoading(false)
     }
-    setLoading(false)
   }, [])
+
   // 获取用户兴趣标签后再推荐
   const fetchUserTagsAndRecommend = useCallback(async () => {
     setLoading(true)
@@ -253,29 +238,43 @@
     const loadPosts = async () => {
       try {
         const list = await fetchPosts()  // [{id, title, heat, created_at}, …]
-        // 为了拿到 media_urls 和 user_id,这里再拉详情
-        const detailed = await Promise.all(
-          list.map(async p => {
-            const d = await fetchPost(p.id)
-            return {
-              id:     d.id,
-              title:  d.title,
-              author: `作者 ${d.user_id}`,
-              authorId: d.user_id,
-              avatar: `http://192.168.5.200:8080/static/profile.webp`,
-              media:  d.media_urls?.[0] || '', // 改为 media,支持图片和视频
-              likes:  d.heat,
-              mediaUrls: d.media_urls || [] // 保存所有媒体URL
-            }
-          })
-        )
-        setItems(detailed)
-        // 拉取所有涉及用户的昵称
-        const userIds = [...new Set(detailed.map(i => i.authorId))]
-        fetchUserNames(userIds)
+        // 先设置基础数据,快速显示
+        const basicItems = list.map(p => ({
+          id: p.id,
+          title: p.title,
+          author: '加载中...',
+          authorId: null,
+          avatar: `http://192.168.5.200:8080/static/profile.webp`,
+          media: '',
+          likes: p.heat,
+          mediaUrls: [],
+          detailsLoaded: false
+        }))
+        setItems(basicItems)
+        setLoading(false)
+        
+        // 异步加载详细信息
+        basicItems.forEach((item, index) => {
+          loadPostDetails(item.id, index)
+        })
+        
+        // 异步加载用户信息
+        setTimeout(async () => {
+          const allItems = await Promise.all(
+            list.map(async p => {
+              try {
+                const d = await fetchPost(p.id)
+                return { ...p, user_id: d.user_id }
+              } catch {
+                return p
+              }
+            })
+          )
+          const userIds = [...new Set(allItems.map(i => i.user_id).filter(Boolean))]
+          fetchUserNames(userIds)
+        }, 100)
       } catch (e) {
         setError(e.message)
-      } finally {
         setLoading(false)
       }
     }
@@ -286,7 +285,7 @@
     } else {
       loadPosts()
     }
-  }, [useSearchRecommend, fetchUserTagsAndRecommend])  // 切换推荐模式时的额外处理
+  }, [useSearchRecommend, fetchUserTagsAndRecommend])
   useEffect(() => {
     if (useSearchRecommend) {
       fetchUserTagsAndRecommend()
@@ -408,7 +407,7 @@
           ) : (
             items.map(item => (
               <div key={item.id} className="feed-card" onClick={() => handlePostClick(item.id)}>
-                {item.media && (
+                {item.media ? (
                   <MediaPreview
                     url={item.media}
                     alt={item.title}
@@ -421,13 +420,25 @@
                     }}
                     style={{ cursor: 'pointer' }}
                   />
-                )}
+                ) : !item.detailsLoaded ? (
+                  <div className="card-img-placeholder" style={{
+                    height: '200px',
+                    background: '#f5f5f5',
+                    display: 'flex',
+                    alignItems: 'center',
+                    justifyContent: 'center',
+                    color: '#999',
+                    fontSize: '14px'
+                  }}>
+                    加载中...
+                  </div>
+                ) : null}
                 <h3 className="card-title">{item.title}</h3>
                 {item.content && <div className="card-content">{item.content.slice(0, 60) || ''}</div>}
                 <div className="card-footer">
                   <div className="card-author">
-                    <img className="avatar" src={item.avatar} alt={userMap[item.authorId] || item.authorId} />
-                    <span className="username">{userMap[item.authorId] || item.authorId}</span>
+                    <img className="avatar" src={item.avatar} alt={userMap[item.authorId] || item.authorId || item.author} />
+                    <span className="username">{userMap[item.authorId] || item.author}</span>
                   </div>
                   <div className="card-likes">
                     <ThumbsUp size={16} />