修改前端推荐搜素样式,链接推荐搜索前后端
Change-Id: I76d3be2a2cb6f553fc6c4512e11b6de3341ad0c4
diff --git a/JWLLL/API_front/src/components/HomeFeed.jsx b/JWLLL/API_front/src/components/HomeFeed.jsx
new file mode 100644
index 0000000..0b17b1f
--- /dev/null
+++ b/JWLLL/API_front/src/components/HomeFeed.jsx
@@ -0,0 +1,212 @@
+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>
+ )
+}
\ No newline at end of file