blob: 0b17b1f21ec0151e7c905e0d1fe462162eeef5b0 [file] [log] [blame]
22301008af173152025-06-15 10:46:25 +08001import React, { useState, useEffect } from 'react'
2import { ThumbsUp } from 'lucide-react'
3import '../style/HomeFeed.css'
4
5const categories = [
6 '推荐','穿搭','美食','彩妆','影视',
7 '职场','情感','家居','游戏','旅行','健身'
8]
9
10const recommendModes = [
11 { label: '标签推荐', value: 'tag' },
12 { label: '协同过滤推荐', value: 'cf' }
13]
14
15const DEFAULT_USER_ID = '3' // 确保数据库有此用户
16const DEFAULT_TAGS = ['美食','影视','穿搭'] // 可根据实际数据库调整
17
18export default function HomeFeed() {
19 const [activeCat, setActiveCat] = useState('推荐')
20 const [items, setItems] = useState([])
21 const [search, setSearch] = useState('')
22 const [loading, setLoading] = useState(false)
23 const [recMode, setRecMode] = useState('tag')
24 const [userTags, setUserTags] = useState([])
25 const [recCFNum, setRecCFNum] = useState(20)
26
27 // 首次进入首页自动推荐内容
28 useEffect(() => {
29 if (activeCat === '推荐') {
30 fetchUserTagsAndRecommend()
31 } else {
32 fetchContent()
33 }
34 // eslint-disable-next-line
35 }, [])
36
37 // 切换推荐模式或分类时刷新内容
38 useEffect(() => {
39 if (activeCat === '推荐') {
40 fetchUserTagsAndRecommend()
41 } else {
42 fetchContent()
43 }
44 // eslint-disable-next-line
45 }, [activeCat, recMode])
46
47 // 获取用户兴趣标签后再推荐
48 const fetchUserTagsAndRecommend = async () => {
49 setLoading(true)
50 let tags = []
51 try {
52 const res = await fetch(`/user_tags?user_id=${DEFAULT_USER_ID}`)
53 const data = await res.json()
54 tags = Array.isArray(data.tags) && data.tags.length > 0 ? data.tags : DEFAULT_TAGS
55 setUserTags(tags)
56 } catch {
57 tags = DEFAULT_TAGS
58 setUserTags(tags)
59 }
60 if (recMode === 'tag') {
61 await fetchTagRecommend(tags)
62 } else {
63 await fetchCFRecommend()
64 }
65 setLoading(false)
66 }
67
68 const fetchContent = async (keyword = '') => {
69 setLoading(true)
70 try {
71 const res = await fetch('/search', {
72 method: 'POST',
73 headers: { 'Content-Type': 'application/json' },
74 body: JSON.stringify({ keyword: keyword || activeCat, category: activeCat === '推荐' ? undefined : activeCat })
75 })
76 const data = await res.json()
77 setItems(data.results || [])
78 } catch (e) {
79 setItems([])
80 }
81 setLoading(false)
82 }
83
84 // 标签推荐
85 const fetchTagRecommend = async (tags) => {
86 setLoading(true)
87 try {
88 const res = await fetch('/recommend_tags', {
89 method: 'POST',
90 headers: { 'Content-Type': 'application/json' },
91 body: JSON.stringify({ user_id: DEFAULT_USER_ID, tags })
92 })
93 const data = await res.json()
94 setItems(data.recommendations || [])
95 } catch (e) {
96 setItems([])
97 }
98 setLoading(false)
99 }
100
101 // 协同过滤推荐
102 const fetchCFRecommend = async (topN = recCFNum) => {
103 setLoading(true)
104 try {
105 const res = await fetch('/user_based_recommend', {
106 method: 'POST',
107 headers: { 'Content-Type': 'application/json' },
108 body: JSON.stringify({ user_id: DEFAULT_USER_ID, top_n: topN })
109 })
110 const data = await res.json()
111 setItems(data.recommendations || [])
112 } catch (e) {
113 setItems([])
114 }
115 setLoading(false)
116 }
117
118 const handleSearch = e => {
119 e.preventDefault()
120 fetchContent(search)
121 }
122
123 return (
124 <div className="home-feed">
125 {/* 推荐模式切换,仅在推荐页显示 */}
126 {activeCat === '推荐' && (
127 <div style={{marginBottom:12, display:'flex', alignItems:'center', gap:16}}>
128 <span style={{marginRight:8}}>推荐模式:</span>
129 <div style={{display:'flex', gap:8}}>
130 {recommendModes.map(m => (
131 <button
132 key={m.value}
133 className={recMode===m.value? 'rec-btn styled active':'rec-btn styled'}
134 onClick={() => setRecMode(m.value)}
135 type="button"
136 style={{
137 borderRadius: 20,
138 padding: '4px 18px',
139 border: recMode===m.value ? '2px solid #e84c4a' : '1px solid #ccc',
140 background: recMode===m.value ? '#fff0f0' : '#fff',
141 color: recMode===m.value ? '#e84c4a' : '#333',
142 fontWeight: recMode===m.value ? 600 : 400,
143 cursor: 'pointer',
144 transition: 'all 0.2s',
145 outline: 'none',
146 }}
147 >{m.label}</button>
148 ))}
149 </div>
150 {/* 协同过滤推荐数量选择 */}
151 {recMode === 'cf' && (
152 <div style={{display:'flex',alignItems:'center',gap:4}}>
153 <span>推荐数量:</span>
154 <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'}}>
155 {[10, 20, 30, 50].map(n => <option key={n} value={n}>{n}</option>)}
156 </select>
157 </div>
158 )}
159 </div>
160 )}
161 {/* 搜索栏 */}
162 <form className="feed-search" onSubmit={handleSearch} style={{marginBottom:16, display:'flex', gap:8, alignItems:'center'}}>
163 <input
164 type="text"
165 className="search-input"
166 placeholder="搜索内容/标题/标签"
167 value={search}
168 onChange={e => setSearch(e.target.value)}
169 />
170 <button type="submit" className="search-btn">搜索</button>
171 </form>
172 {/* 顶部分类 */}
173 <nav className="feed-tabs">
174 {categories.map(cat => (
175 <button
176 key={cat}
177 className={cat === activeCat ? 'tab active' : 'tab'}
178 onClick={() => { setActiveCat(cat); setSearch('') }}
179 >
180 {cat}
181 </button>
182 ))}
183 </nav>
184 {/* 瀑布流卡片区 */}
185 <div className="feed-grid">
186 {loading ? <div style={{padding:32}}>加载中...</div> :
187 items.length === 0 ? <div style={{padding:32, color:'#aaa'}}>暂无推荐内容</div> :
188 items.map(item => (
189 <div key={item.id} className="feed-card">
190 {/* 封面图 */}
191 {/* <img className="card-img" src={item.img} alt={item.title} /> */}
192 {/* 标题 */}
193 <h3 className="card-title">{item.title}</h3>
194 {/* 内容摘要 */}
195 <div className="card-content">{item.content?.slice(0, 60) || ''}</div>
196 {/* 底部作者 + 点赞 */}
197 <div className="card-footer">
198 <div className="card-author">
199 {/* <img className="avatar" src={item.avatar} alt={item.author} /> */}
200 <span className="username">{item.author || '佚名'}</span>
201 </div>
202 <div className="card-likes">
203 <ThumbsUp size={16} />
204 <span className="likes-count">{item.heat || 0}</span>
205 </div>
206 </div>
207 </div>
208 ))}
209 </div>
210 </div>
211 )
212}