修改前端推荐搜素样式,链接推荐搜索前后端

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