增加首页异步获取功能
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} />