优化种子评分、热门资源推荐

Change-Id: I9007f3d1d77df8a1d3eeb33c35897ea04c8e3c16
diff --git a/src/pages/InterestGroup/InterestGroup.jsx b/src/pages/InterestGroup/InterestGroup.jsx
index 0b5b002..b3ae332 100644
--- a/src/pages/InterestGroup/InterestGroup.jsx
+++ b/src/pages/InterestGroup/InterestGroup.jsx
@@ -1,137 +1,3 @@
-// // import React, { useEffect } from 'react';
-// // import Header from '../../components/Header';
-// // import { useGroupStore } from '../../context/useGroupStore';
-// // // import GroupFilters from './GroupFilters';
-// // import GroupList from './GroupList';
-// // import GroupPagination from './GroupPagination';
-// // import './InterestGroup.css';
-// // const InterestGroup = () => {
-// //   const { fetchGroupList, setPage, handleSearch } = useGroupStore();
-
-// //   // 初始化加载
-// //   useEffect(() => {
-// //     fetchGroupList();
-// //   }, [fetchGroupList]);
-
-// //   return (
-// //     <div className="interest-group-container">
-// //       <Header />
-// //       <div className="interest-group-card">
-// //         {/* <GroupFilters /> */}
-// //         <GroupList />
-// //         <GroupPagination />
-// //       </div>
-// //     </div>
-// //   );
-// // };
-
-// // export default InterestGroup;
-
-// import React, { useEffect, useState } from 'react';
-// import Header from '../../components/Header';
-// import { useGroupStore } from '../../context/useGroupStore';
-// import GroupList from './GroupList';
-// import GroupPagination from './GroupPagination';
-// import './InterestGroup.css';
-// import axios from 'axios';
-
-// const InterestGroup = () => {
-//   const { fetchGroupList } = useGroupStore();
-
-//   const [showModal, setShowModal] = useState(false);
-//   const [groupName, setGroupName] = useState('');
-//   const [groupDescription, setGroupDescription] = useState('');
-
-//   useEffect(() => {
-//     fetchGroupList();
-//   }, [fetchGroupList]);
-
-//   const handleCreateGroup = async () => {
-//   try {
-//     const res = await axios.post('http://localhost:3011/echo/groups/createGroup', {
-//       user_id: 1,
-//       group_name: groupName,               // ✅ 改为 snake_case
-//       description: groupDescription,
-//       time: new Date().toISOString(),
-//       category: '默认分类',
-//       cover_image: 'https://picsum.photos/300/200',
-//     });
-
-//     if (res.status === 200 && res.data.status === 'success') {
-//       alert('小组创建成功');
-//       setShowModal(false);
-//       setGroupName('');
-//       setGroupDescription('');
-//       fetchGroupList();
-//     } else {
-//       alert('创建失败: ' + res.data.message);
-//     }
-//   } catch (error) {
-//     alert('创建失败,请检查网络或输入');
-//     console.error(error);
-//   }
-// };
-
-
-//   // const handleCreateGroup = async () => {
-//   //   try {
-//   //     const res = await axios.post('/createGroup', {
-//   //       groupName,
-//   //       description: groupDescription,
-//   //     });
-//   //     if (res.status === 200) {
-//   //       alert('小组创建成功');
-//   //       setShowModal(false);
-//   //       setGroupName('');
-//   //       setGroupDescription('');
-//   //       fetchGroupList(); // 刷新列表
-//   //     }
-//   //   } catch (error) {
-//   //     alert('创建失败,请重试');
-//   //   }
-//   // };
-
-//   return (
-//     <div className="interest-group-container">
-//       <Header />
-//       <div className="interest-group-card">
-//         <button className="create-group-btn" onClick={() => setShowModal(true)}>
-//           创建小组
-//         </button>
-
-//         {showModal && (
-//           <div className="modal-overlay">
-//             <div className="modal-content">
-//               <h2>创建新小组</h2>
-//               <input
-//                 type="text"
-//                 placeholder="小组名称"
-//                 value={groupName}
-//                 onChange={(e) => setGroupName(e.target.value)}
-//               />
-//               <textarea
-//                 placeholder="小组简介"
-//                 value={groupDescription}
-//                 onChange={(e) => setGroupDescription(e.target.value)}
-//               />
-//               <div className="modal-buttons">
-//                 <button onClick={handleCreateGroup}>确定</button>
-//                 <button onClick={() => setShowModal(false)}>取消</button>
-//               </div>
-//             </div>
-//           </div>
-//         )}
-
-//         <GroupList />
-//         <GroupPagination />
-//       </div>
-//     </div>
-//   );
-// };
-
-// export default InterestGroup;
-
-
 import React, { useEffect, useState } from 'react';
 import Header from '../../components/Header';
 import { useGroupStore } from '../../context/useGroupStore';
diff --git a/src/pages/SeedList/Recommend/Recommend.css b/src/pages/SeedList/Recommend/Recommend.css
index 1eb3541..cf46b86 100644
--- a/src/pages/SeedList/Recommend/Recommend.css
+++ b/src/pages/SeedList/Recommend/Recommend.css
@@ -1,4 +1,4 @@
-.recommend-wrapper {
+/* .recommend-wrapper {
   padding: 20px;
   background-color: #fff;
 }
@@ -35,4 +35,56 @@
 .paid-title {
   font-size: 16px;
   padding: 10px;
+} */
+
+
+.recommendation-page {
+  padding: 20px;
+  background-color: #f8f9fa;
+}
+
+.recommendation-page h2 {
+  font-size: 1.5rem;
+  margin-bottom: 10px;
+  color: #333;
+}
+
+.seed-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16px;
+  margin-bottom: 32px;
+}
+
+.seed-card {
+  width: 160px;
+  background: white;
+  border-radius: 12px;
+  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
+  overflow: hidden;
+  transition: transform 0.2s ease;
+  cursor: pointer;
+}
+
+.seed-card:hover {
+  transform: translateY(-4px);
+}
+
+.seed-card img {
+  width: 100%;
+  height: 220px;
+  object-fit: cover;
+}
+
+.seed-card .title {
+  padding: 8px;
+  font-size: 0.95rem;
+  text-align: center;
+  color: #333;
+}
+
+.login-reminder {
+  font-size: 1rem;
+  color: #888;
+  padding: 10px;
 }
diff --git a/src/pages/SeedList/Recommend/Recommend.jsx b/src/pages/SeedList/Recommend/Recommend.jsx
index dd38184..8270145 100644
--- a/src/pages/SeedList/Recommend/Recommend.jsx
+++ b/src/pages/SeedList/Recommend/Recommend.jsx
@@ -1,50 +1,48 @@
-// export default Recommend;
 import React, { useEffect, useState } from 'react';
+import axios from 'axios';
 import './Recommend.css';
+import { useUser } from '../../../context/UserContext';
 
 const Recommend = () => {
-  const [paidLists, setPaidLists] = useState([]);
-  const [loading, setLoading] = useState(true);
+  const { user } = useUser();
+  const [popularSeeds, setPopularSeeds] = useState([]);
+  const [recommendedSeeds, setRecommendedSeeds] = useState([]);
 
   useEffect(() => {
-    const fetchPaidLists = async () => {
-      try {
-        
-        const response = await fetch(`/echo/recommendations/paid?user_id=1`);
-        console.log('请求地址:', `${process.env.REACT_APP_API_BASE}/echo/recommendations/paid?user_id=1`);
-
-
-        // const response = await fetch('/echo/recommendations/paid?user_id=1');
-        const data = await response.json();
-        if (data.status === 'success') {
-          setPaidLists(data.paid_recommendations);
-        } else {
-          console.warn('获取付费片单失败');
-        }
-      } catch (error) {
-        console.error('请求出错:', error);
-      } finally {
-        setLoading(false);
-      }
-    };
-
-    fetchPaidLists();
+    // 获取热门资源
+    axios
+      .get('/echo/recommendation/popular', { params: { limit: 16 } })
+      .then((res) => setPopularSeeds(res.data))
+      .catch((err) => console.error('获取热门资源失败', err));
   }, []);
 
+  useEffect(() => {
+    // 获取个性化推荐
+    if (user && user.userId) {
+      axios
+        .get(`/echo/recommendation/seeds/${user.userId}`)
+        .then((res) => setRecommendedSeeds(res.data))
+        .catch((err) => console.error('获取个性化推荐失败', err));
+    }
+  }, [user]);
+
+  const renderSeedCard = (seed) => (
+    <div className="seed-card" key={seed.id}>
+      <img src={seed.coverUrl || '/default-cover.jpg'} alt={seed.title} />
+      <div className="title">{seed.title}</div>
+    </div>
+  );
+
   return (
-    <div className="recommend-wrapper">
-      <h2 className="recommend-section-title">付费片单</h2>
-      {loading ? (
-        <p>加载中...</p>
+    <div className="recommendation-page">
+      <h2>🎬 正在热映</h2>
+      <div className="seed-list">{popularSeeds.map(renderSeedCard)}</div>
+
+      <h2>🎯 个性化推荐</h2>
+      {user ? (
+        <div className="seed-list">{recommendedSeeds.map(renderSeedCard)}</div>
       ) : (
-        <div className="recommend-paid-row">
-          {paidLists.map((item) => (
-            <div className="paid-card" key={item.group_id}>
-              <img src={item.image_url} alt={item.group_name} className="paid-cover" />
-              <div className="paid-title">{item.group_name}</div>
-            </div>
-          ))}
-        </div>
+        <div className="login-reminder">请登录以获取个性化推荐</div>
       )}
     </div>
   );
diff --git a/src/pages/SeedList/SeedDetail/SeedDetail.jsx b/src/pages/SeedList/SeedDetail/SeedDetail.jsx
index 0cb738c..4ab9274 100644
--- a/src/pages/SeedList/SeedDetail/SeedDetail.jsx
+++ b/src/pages/SeedList/SeedDetail/SeedDetail.jsx
@@ -4,6 +4,7 @@
 import Header from '../../../components/Header';
 import './SeedDetail.css';
 import { useUser } from '../../../context/UserContext';
+import SeedRating from './SeedRating';
 
 const SeedDetail = () => {
   const params = useParams();
@@ -214,6 +215,8 @@
         <div className="action-buttons">
           <button className="btn" onClick={() => handleDownload(seed.id)}>下载</button>
           <button className="btn-outline" onClick={handleCollect}>收藏</button>
+          {/* <button className="btn" onClick={handleCollect}>收藏</button> */}
+          <SeedRating seedId={seed.id} />
         </div>
 
         <hr className="divider" />
diff --git a/src/pages/SeedList/SeedDetail/SeedRating.css b/src/pages/SeedList/SeedDetail/SeedRating.css
new file mode 100644
index 0000000..e40c055
--- /dev/null
+++ b/src/pages/SeedList/SeedDetail/SeedRating.css
@@ -0,0 +1,23 @@
+.seed-rating {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 18px;
+}
+
+.star {
+  cursor: pointer;
+  color: #ccc;
+  font-size: 24px;
+  transition: color 0.3s;
+}
+
+.star.active {
+  color: rgba(240, 184, 62, 0.916);
+}
+
+.thank-you {
+  margin-left: 10px;
+  font-size: 14px;
+  color: rgba(222, 91, 111, 0.982);
+}
diff --git a/src/pages/SeedList/SeedDetail/SeedRating.jsx b/src/pages/SeedList/SeedDetail/SeedRating.jsx
new file mode 100644
index 0000000..1841443
--- /dev/null
+++ b/src/pages/SeedList/SeedDetail/SeedRating.jsx
@@ -0,0 +1,125 @@
+// import React, { useState, useEffect } from 'react';
+// import axios from 'axios';
+// import './SeedRating.css';
+// import { useUser } from '../../../context/UserContext';
+
+// const SeedRating = ({ seedId }) => {
+//   const { user } = useUser();
+//   const [score, setScore] = useState(0);
+//   const [submitted, setSubmitted] = useState(false);
+
+// const handleRating = async (newScore) => {
+//   if (!user || !user.userId) {
+//     alert('请先登录再评分');
+//     return;
+//   }
+
+//   try {
+//     const res = await axios.post('/echo/ratings', null, {
+//       params: {
+//         userId: user.userId,
+//         seedId,
+//         score: newScore,
+//       },
+//     });
+
+//     if (res.data && res.data.status === 'success') {
+//       setScore(newScore);
+//       setSubmitted(true);
+//       alert('评分成功');
+//     } else {
+//       alert('评分失败,请稍后再试');
+//     }
+//   } catch (error) {
+//     console.error('评分出错:', error);
+//     alert('评分请求失败');
+//   }
+// };
+
+
+
+//   return (
+//     <div className="seed-rating">
+//       <span>评分:</span>
+//       {[1, 2, 3, 4, 5].map((star) => (
+//         <span
+//           key={star}
+//           className={`star ${star <= score ? 'active' : ''}`}
+//         //   onClick={() => !submitted && handleRating(star)}
+//           onClick={() => handleRating(star)}
+
+//         >
+//           ★
+//         </span>
+//       ))}
+//       {submitted && <span className="thank-you">感谢您的评分!</span>}
+//     </div>
+//   );
+// };
+
+// export default SeedRating;
+
+import React, { useState, useEffect } from 'react';
+import axios from 'axios';
+import './SeedRating.css';
+import { useUser } from '../../../context/UserContext';
+
+const SeedRating = ({ seedId }) => {
+  const { user } = useUser();
+  const [score, setScore] = useState(0);           // 最终提交的分数
+  const [hoverScore, setHoverScore] = useState(0); // 鼠标悬停显示
+  const [submitted, setSubmitted] = useState(false);
+  const [loading, setLoading] = useState(false);   // 防止重复点击
+
+  const handleRating = async (newScore) => {
+    if (!user || !user.userId) {
+      alert('请先登录再评分');
+      return;
+    }
+    if (submitted || loading) return;
+
+    setLoading(true);
+    try {
+      const res = await axios.post('/echo/ratings', null, {
+        params: {
+          userId: user.userId,
+          seedId,
+          score: newScore,
+        },
+      });
+
+      if (res.data?.status === 'success') {
+        setScore(newScore);
+        setSubmitted(true);
+        alert('评分成功');
+      } else {
+        alert('评分失败,请稍后再试');
+      }
+    } catch (error) {
+      console.error('评分出错:', error);
+      alert('评分请求失败');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  return (
+    <div className="seed-rating">
+      <span>评分:</span>
+      {[1, 2, 3, 4, 5].map((star) => (
+        <span
+          key={star}
+          className={`star ${star <= (hoverScore || score) ? 'active' : ''}`}
+          onMouseEnter={() => !submitted && setHoverScore(star)}
+          onMouseLeave={() => !submitted && setHoverScore(0)}
+          onClick={() => handleRating(star)}
+        >
+          ★
+        </span>
+      ))}
+      {submitted && <span className="thank-you">感谢您的评分!</span>}
+    </div>
+  );
+};
+
+export default SeedRating;