blob: 0b17b1f21ec0151e7c905e0d1fe462162eeef5b0 [file] [log] [blame]
import React, { useState, useEffect } from 'react'
import { ThumbsUp } from 'lucide-react'
import '../style/HomeFeed.css'
const categories = [
'推荐','穿搭','美食','彩妆','影视',
'职场','情感','家居','游戏','旅行','健身'
]
const recommendModes = [
{ label: '标签推荐', value: 'tag' },
{ label: '协同过滤推荐', value: 'cf' }
]
const DEFAULT_USER_ID = '3' // 确保数据库有此用户
const DEFAULT_TAGS = ['美食','影视','穿搭'] // 可根据实际数据库调整
export default function HomeFeed() {
const [activeCat, setActiveCat] = useState('推荐')
const [items, setItems] = useState([])
const [search, setSearch] = useState('')
const [loading, setLoading] = useState(false)
const [recMode, setRecMode] = useState('tag')
const [userTags, setUserTags] = useState([])
const [recCFNum, setRecCFNum] = useState(20)
// 首次进入首页自动推荐内容
useEffect(() => {
if (activeCat === '推荐') {
fetchUserTagsAndRecommend()
} else {
fetchContent()
}
// eslint-disable-next-line
}, [])
// 切换推荐模式或分类时刷新内容
useEffect(() => {
if (activeCat === '推荐') {
fetchUserTagsAndRecommend()
} else {
fetchContent()
}
// eslint-disable-next-line
}, [activeCat, recMode])
// 获取用户兴趣标签后再推荐
const fetchUserTagsAndRecommend = async () => {
setLoading(true)
let tags = []
try {
const res = await fetch(`/user_tags?user_id=${DEFAULT_USER_ID}`)
const data = await res.json()
tags = Array.isArray(data.tags) && data.tags.length > 0 ? data.tags : DEFAULT_TAGS
setUserTags(tags)
} catch {
tags = DEFAULT_TAGS
setUserTags(tags)
}
if (recMode === 'tag') {
await fetchTagRecommend(tags)
} else {
await fetchCFRecommend()
}
setLoading(false)
}
const fetchContent = async (keyword = '') => {
setLoading(true)
try {
const res = await fetch('/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ keyword: keyword || activeCat, category: activeCat === '推荐' ? undefined : activeCat })
})
const data = await res.json()
setItems(data.results || [])
} catch (e) {
setItems([])
}
setLoading(false)
}
// 标签推荐
const fetchTagRecommend = async (tags) => {
setLoading(true)
try {
const res = await fetch('/recommend_tags', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user_id: DEFAULT_USER_ID, tags })
})
const data = await res.json()
setItems(data.recommendations || [])
} catch (e) {
setItems([])
}
setLoading(false)
}
// 协同过滤推荐
const fetchCFRecommend = async (topN = recCFNum) => {
setLoading(true)
try {
const res = await fetch('/user_based_recommend', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user_id: DEFAULT_USER_ID, top_n: topN })
})
const data = await res.json()
setItems(data.recommendations || [])
} catch (e) {
setItems([])
}
setLoading(false)
}
const handleSearch = e => {
e.preventDefault()
fetchContent(search)
}
return (
<div className="home-feed">
{/* 推荐模式切换,仅在推荐页显示 */}
{activeCat === '推荐' && (
<div style={{marginBottom:12, display:'flex', alignItems:'center', gap:16}}>
<span style={{marginRight:8}}>推荐模式:</span>
<div style={{display:'flex', gap:8}}>
{recommendModes.map(m => (
<button
key={m.value}
className={recMode===m.value? 'rec-btn styled active':'rec-btn styled'}
onClick={() => setRecMode(m.value)}
type="button"
style={{
borderRadius: 20,
padding: '4px 18px',
border: recMode===m.value ? '2px solid #e84c4a' : '1px solid #ccc',
background: recMode===m.value ? '#fff0f0' : '#fff',
color: recMode===m.value ? '#e84c4a' : '#333',
fontWeight: recMode===m.value ? 600 : 400,
cursor: 'pointer',
transition: 'all 0.2s',
outline: 'none',
}}
>{m.label}</button>
))}
</div>
{/* 协同过滤推荐数量选择 */}
{recMode === 'cf' && (
<div style={{display:'flex',alignItems:'center',gap:4}}>
<span>推荐数量:</span>
<select value={recCFNum} onChange={e => { setRecCFNum(Number(e.target.value)); fetchCFRecommend(Number(e.target.value)) }} style={{padding:'2px 8px',borderRadius:6,border:'1px solid #ccc'}}>
{[10, 20, 30, 50].map(n => <option key={n} value={n}>{n}</option>)}
</select>
</div>
)}
</div>
)}
{/* 搜索栏 */}
<form className="feed-search" onSubmit={handleSearch} style={{marginBottom:16, display:'flex', gap:8, alignItems:'center'}}>
<input
type="text"
className="search-input"
placeholder="搜索内容/标题/标签"
value={search}
onChange={e => setSearch(e.target.value)}
/>
<button type="submit" className="search-btn">搜索</button>
</form>
{/* 顶部分类 */}
<nav className="feed-tabs">
{categories.map(cat => (
<button
key={cat}
className={cat === activeCat ? 'tab active' : 'tab'}
onClick={() => { setActiveCat(cat); setSearch('') }}
>
{cat}
</button>
))}
</nav>
{/* 瀑布流卡片区 */}
<div className="feed-grid">
{loading ? <div style={{padding:32}}>加载中...</div> :
items.length === 0 ? <div style={{padding:32, color:'#aaa'}}>暂无推荐内容</div> :
items.map(item => (
<div key={item.id} className="feed-card">
{/* 封面图 */}
{/* <img className="card-img" src={item.img} alt={item.title} /> */}
{/* 标题 */}
<h3 className="card-title">{item.title}</h3>
{/* 内容摘要 */}
<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={item.author} /> */}
<span className="username">{item.author || '佚名'}</span>
</div>
<div className="card-likes">
<ThumbsUp size={16} />
<span className="likes-count">{item.heat || 0}</span>
</div>
</div>
</div>
))}
</div>
</div>
)
}