修改提示框样式、完成付费片单、推荐跳转

Change-Id: Ie84c53d4e306435144b1f26ceb39cc182e99d57a
diff --git a/src/pages/SeedList/Recommend/CreatePlaylistModal.css b/src/pages/SeedList/Recommend/CreatePlaylistModal.css
new file mode 100644
index 0000000..022e190
--- /dev/null
+++ b/src/pages/SeedList/Recommend/CreatePlaylistModal.css
@@ -0,0 +1,153 @@
+/* CreatePlaylistModal.css */
+
+.create-playlist-modal.modal-overlay {
+  position: fixed;
+  inset: 0;
+  background-color: rgba(0, 0, 0, 0.65);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 9999;
+  padding: 16px;
+}
+
+.create-playlist-modal .modal {
+  background: #fff;
+  border-radius: 14px;
+  width: 420px;
+  max-width: 100%;
+  padding: 32px 40px;
+  box-shadow:
+    0 10px 15px rgba(0, 0, 0, 0.1),
+    0 4px 6px rgba(0, 0, 0, 0.05);
+  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.create-playlist-modal .modal:hover {
+  transform: translateY(-6px);
+}
+
+.create-playlist-modal .modal h3 {
+  margin: 0 0 28px 0;
+  font-weight: 700;
+  font-size: 1.9rem;
+  color: #222;
+  text-align: center;
+  letter-spacing: 0.02em;
+}
+
+/* Label纵向排列 */
+.create-playlist-modal .modal label {
+  display: flex !important;
+  flex-direction: column !important;
+  margin-bottom: 20px;
+  font-weight: 600;
+  color: #444;
+  font-size: 1.05rem;
+  user-select: none;
+  cursor: default; /* 加上避免label内容被选中时光标变形 */
+}
+
+.create-playlist-modal .modal input[type="text"],
+.create-playlist-modal .modal input[type="number"],
+.create-playlist-modal .modal textarea {
+  margin-top: 8px;
+  padding: 12px 16px;
+  border: 1.6px solid #bbb;
+  border-radius: 8px;
+  font-size: 1rem;
+  color: #333;
+  box-sizing: border-box;
+  resize: vertical;
+  transition: border-color 0.25s ease, box-shadow 0.25s ease;
+  font-family: inherit;
+}
+
+.create-playlist-modal .modal input[type="text"]:focus,
+.create-playlist-modal .modal input[type="number"]:focus,
+.create-playlist-modal .modal textarea:focus {
+  outline: none;
+  border-color: #4a69bd;
+  box-shadow: 0 0 10px rgba(74, 105, 189, 0.5);
+}
+
+.create-playlist-modal .modal textarea {
+  min-height: 90px;
+  line-height: 1.5;
+  font-family: inherit;
+}
+
+.create-playlist-modal .modal-actions {
+  display: flex;
+  justify-content: flex-end;
+  gap: 16px;
+  margin-top: 32px;
+}
+
+.create-playlist-modal .modal-actions button {
+  padding: 12px 26px;
+  font-size: 1.15rem;
+  font-weight: 700;
+  border: none;
+  border-radius: 10px;
+  cursor: pointer;
+  user-select: none;
+  transition:
+    background-color 0.35s ease,
+    box-shadow 0.35s ease,
+    transform 0.2s ease;
+  font-family: inherit;
+  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+}
+
+.create-playlist-modal .modal-actions button:first-child {
+  background: linear-gradient(135deg, #4a69bd 0%, #3f51b5 100%);
+  color: #fff;
+  box-shadow: 0 6px 12px rgba(63, 81, 181, 0.5);
+}
+
+.create-playlist-modal .modal-actions button:first-child:hover {
+  background: linear-gradient(135deg, #3f51b5 0%, #2c3e9f 100%);
+  box-shadow: 0 8px 18px rgba(44, 62, 159, 0.75);
+  transform: translateY(-2px);
+}
+
+.create-playlist-modal .modal-actions button:first-child:disabled {
+  background: #a0a5b8;
+  box-shadow: none;
+  cursor: not-allowed;
+  transform: none;
+}
+
+.create-playlist-modal .modal-actions button:last-child {
+  background-color: #f3f4f6;
+  color: #555;
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
+  transition: background-color 0.25s ease;
+}
+
+.create-playlist-modal .modal-actions button:last-child:hover {
+  background-color: #d9dade;
+  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.12);
+}
+.torrent-selector {
+  border: 1px solid #ccc;
+  padding: 8px;
+  max-height: 200px;
+  overflow-y: auto;
+  margin-top: 8px;
+}
+.torrent-list {
+  list-style: none;
+  padding: 0;
+  margin: 0;
+}
+.torrent-list li {
+  margin: 4px 0;
+}
+.selected-torrents {
+  margin-top: 6px;
+  font-size: 0.9em;
+  color: #555;
+}
diff --git a/src/pages/SeedList/Recommend/CreatePlaylistModal.jsx b/src/pages/SeedList/Recommend/CreatePlaylistModal.jsx
new file mode 100644
index 0000000..60a5b76
--- /dev/null
+++ b/src/pages/SeedList/Recommend/CreatePlaylistModal.jsx
@@ -0,0 +1,153 @@
+import React, { useState } from 'react';
+import axios from 'axios';
+import toast from 'react-hot-toast';
+import TorrentSelector from './TorrentSelector'; // 引入种子选择组件
+import './CreatePlaylistModal.css';
+import { uploadFile } from '../../../api/file';
+
+const CreatePlaylistModal = ({ onClose, onSuccess }) => {
+  const [newPlaylist, setNewPlaylist] = useState({
+    title: '',
+    price: '',
+    description: '',
+    torrentList: [], // 选中的种子ID数组
+  });
+
+  const [coverFile, setCoverFile] = useState(null); // 新增封面文件状态
+  const [previewUrl, setPreviewUrl] = useState(''); // 封面预览URL
+
+  const handleSelect = (id, checked) => {
+    setNewPlaylist(prev => {
+      let newList = [...prev.torrentList];
+      if (checked) {
+        if (!newList.includes(id)) newList.push(id);
+      } else {
+        newList = newList.filter(i => i !== id);
+      }
+      return { ...prev, torrentList: newList };
+    });
+  };
+
+  const handleCoverChange = (e) => {
+    const file = e.target.files?.[0];
+    if (file) {
+      setCoverFile(file);
+      setPreviewUrl(URL.createObjectURL(file)); // 生成预览图
+    } else {
+      setCoverFile(null);
+      setPreviewUrl('');
+    }
+  };
+
+  const handleCreate = async () => {
+    const { title, price, description, torrentList } = newPlaylist;
+
+    if (!title.trim()) {
+      toast.error('标题不能为空');
+      return;
+    }
+    if (!price || isNaN(Number(price)) || Number(price) < 0) {
+      toast.error('请输入合法的价格');
+      return;
+    }
+    if (torrentList.length === 0) {
+      toast.error('请至少选择一个种子');
+      return;
+    }
+
+    const toastId = toast.loading('正在创建...');
+    const playlistData = { title, price, description, torrentList }; // 构建片单数据
+    
+    try {
+
+      if (coverFile) {
+        const { data } = await uploadFile(coverFile);
+        if (data.code === 0) {
+          playlistData['coverUrl'] = data.data;
+        } else {
+          toast.error('封面上传失败: ' + data.msg, { id: toastId});
+          return;
+        }
+      }
+
+      const res = await axios.post('/playlist', playlistData, {
+      });
+
+      if (res.data.code === 0) {
+        toast.success('片单创建成功', { id: toastId });
+        onSuccess(res.data.data);
+        onClose();
+      } else {
+        toast.error(`创建失败:${res.data.msg}`, { id: toastId });
+      }
+    } catch (err) {
+      console.error('创建片单失败', err);
+      toast.error('创建失败,请稍后重试', { id: toastId });
+    }
+  };
+
+  return (
+    <div className="modal-overlay create-playlist-modal">
+      <div className="modal">
+        <h3>创建新片单</h3>
+
+        <label>
+          标题:
+          <input
+            type="text"
+            value={newPlaylist.title}
+            onChange={(e) => setNewPlaylist({ ...newPlaylist, title: e.target.value })}
+          />
+        </label>
+
+        <label>
+          封面图:
+          <input
+            type="file"
+            accept="image/*"
+            onChange={handleCoverChange}
+          />
+          {previewUrl && (
+            <div style={{ marginTop: '10px' }}>
+              <img src={previewUrl} alt="封面预览" style={{ maxWidth: '100%', maxHeight: '150px' }} />
+            </div>
+          )}
+        </label>
+
+        <label>
+          价格(元):
+          <input
+            type="number"
+            step="0.01"
+            min="0"
+            value={newPlaylist.price}
+            onChange={(e) => setNewPlaylist({ ...newPlaylist, price: e.target.value })}
+          />
+        </label>
+
+        <label>
+          片单描述:
+          <textarea
+            value={newPlaylist.description}
+            onChange={(e) => setNewPlaylist({ ...newPlaylist, description: e.target.value })}
+          />
+        </label>
+
+        <label>
+          关联种子选择:
+          <TorrentSelector
+            selectedIds={newPlaylist.torrentList}
+            onSelect={handleSelect}
+          />
+        </label>
+
+        <div className="modal-actions">
+          <button onClick={handleCreate}>提交</button>
+          <button onClick={onClose}>取消</button>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default CreatePlaylistModal;
diff --git a/src/pages/SeedList/Recommend/PlaylistDetailPage.css b/src/pages/SeedList/Recommend/PlaylistDetailPage.css
new file mode 100644
index 0000000..78e7068
--- /dev/null
+++ b/src/pages/SeedList/Recommend/PlaylistDetailPage.css
@@ -0,0 +1,83 @@
+.playlist-detail {
+  background-color: #fffaf7; /* 和上一页主背景色保持一致 */
+  border-radius: 12px; /* 统一圆角 */
+  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); /* 统一阴影 */
+  color: #6b4f3b; /* 主文字颜色,棕色系 */
+}
+
+.playlist-detail h1 {
+  font-size: 2rem;
+  margin-bottom: 16px;
+  color: #4e342e; /* 深棕色标题 */
+  font-weight: 700;
+}
+
+.cover-image {
+  width: 100%;
+  max-height: 400px;
+  object-fit: cover;
+  border-radius: 12px; /* 与容器圆角匹配 */
+  margin-bottom: 20px;
+  box-shadow: 0 4px 12px rgb(107 79 59 / 0.2); /* 柔和阴影 */
+}
+
+.playlist-detail p {
+  font-size: 1rem;
+  line-height: 1.6;
+  margin-bottom: 20px;
+  color: #6b4f3b;
+}
+
+.playlist-detail h3 {
+  font-size: 1.4rem;
+  margin-bottom: 12px;
+  color: #4e342e;
+  font-weight: 600;
+}
+
+.playlist-detail ul {
+  padding-left: 20px;
+  list-style: disc;
+  color: #6b4f3b;
+}
+
+.playlist-detail li {
+  padding: 6px 0;
+  border-bottom: 1px solid #e2cfc3;
+  font-weight: 500;
+  color: #5d4037;
+}
+
+.playlist-detail li:last-child {
+  border-bottom: none;
+}
+
+/* 错误提示 */
+.error {
+  max-width: 600px;
+  margin: 40px auto;
+  padding: 20px;
+  background: #f8d7da; /* 柔和粉棕 */
+  color: #a94442;
+  border: 1px solid #a94442;
+  border-radius: 12px;
+  text-align: center;
+  font-weight: 600;
+}
+
+/* 重试按钮 */
+.retry-button {
+  margin-top: 12px;
+  padding: 8px 16px;
+  background-color: #6b4f3b;
+  border: none;
+  border-radius: 8px;
+  color: #fffaf7;
+  font-weight: 600;
+  cursor: pointer;
+  transition: background-color 0.25s ease;
+}
+
+.retry-button:hover {
+  background-color: #4b3325;
+}
diff --git a/src/pages/SeedList/Recommend/PlaylistDetailPage.jsx b/src/pages/SeedList/Recommend/PlaylistDetailPage.jsx
new file mode 100644
index 0000000..07558d6
--- /dev/null
+++ b/src/pages/SeedList/Recommend/PlaylistDetailPage.jsx
@@ -0,0 +1,92 @@
+import React, { useEffect, useState } from 'react';
+import { useRoute } from 'wouter'; // ✅ 修改这里
+import axios from 'axios';
+import Header from '../../../components/Header';
+import './PlaylistDetailPage.css';
+import { useUser } from '../../../context/UserContext';
+import toast from 'react-hot-toast';
+import { confirmAlert } from 'react-confirm-alert';
+import 'react-confirm-alert/src/react-confirm-alert.css';
+
+const PlaylistDetailPage = () => {
+  const [match, params] = useRoute('/playlist/:id'); // ✅ 使用 useRoute
+  const id = params?.id;
+  const { user } = useUser();
+  const userId = user?.userId;
+
+  const [detail, setDetail] = useState(null);
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState('');
+
+  const fetchDetail = async () => {
+    if (!id) {
+      setError('无效的片单ID');
+      setLoading(false);
+      return;
+    }
+    if (!userId) {
+      setError('请先登录');
+      setLoading(false);
+      return;
+    }
+
+    try {
+      setLoading(true);
+      setError('');
+      const res = await axios.get(`/playlist/${id}`, {
+        params: { userId }
+      });
+      if (res.data.code === 0) {
+        setDetail(res.data.data);
+      } else {
+        setError(res.data.msg || '加载失败');
+      }
+    } catch (err) {
+      console.error(err);
+      setError('请求失败,请检查网络');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  useEffect(() => {
+    if (id && userId) fetchDetail(); // ✅ 防止空 ID 反复触发
+  }, [id, userId]);
+
+  if (loading) return <div>加载中...</div>;
+
+  if (error) {
+    return (
+      <div className="error">
+        {error}
+        <button onClick={fetchDetail} className="retry-button">重试</button>
+      </div>
+    );
+  }
+
+return (
+  <div className="playlist-detail">
+    <Header />
+    <h1>{detail.title}</h1>
+    <img
+      src={detail.coverUrl || '/default-cover.jpg'}
+      alt={detail.title}
+      onError={e => { e.target.src = '/default-cover.jpg'; }}
+      className="cover-image"
+    />
+    <p>{detail.description}</p>
+
+    <h3>包含的种子:</h3>
+    {detail.torrentList && detail.torrentList.length > 0 ? (
+      <ul>
+        {detail.torrentList.map(seed => (
+          <li key={seed.id}>{seed.title}</li>
+        ))}
+      </ul>
+    ) : (
+      <p>暂无种子数据</p>
+    )}
+  </div>
+);
+};
+export default PlaylistDetailPage;
diff --git a/src/pages/SeedList/Recommend/Recommend.css b/src/pages/SeedList/Recommend/Recommend.css
index cf46b86..f1e34da 100644
--- a/src/pages/SeedList/Recommend/Recommend.css
+++ b/src/pages/SeedList/Recommend/Recommend.css
@@ -1,90 +1,153 @@
-/* .recommend-wrapper {
-  padding: 20px;
-  background-color: #fff;
+/* 推荐页整体容器 */
+.recommendation-page {
+  padding: 24px 32px;
+  background-color: #f8f3ef; /* 米棕底色 */
+  min-height: 100vh;
+  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+  color: #4e342e; /* 深米棕文字 */
 }
 
-.recommend-section-title {
-  font-size: 20px;
-  font-weight: bold;
-  margin-bottom: 15px;
+/* 各个版块标题 */
+.recommendation-page h2 {
+  font-size: 1.75rem;
+  font-weight: 700;
+  margin-bottom: 18px;
+  border-left: 6px solid #eecfc1; /* 粉米色强调条 */
+  padding-left: 12px;
+  color: #4e342e; /* 标题米棕色 */
+  user-select: none;
 }
 
+/* 付费片单 - 横向滚动行 */
 .recommend-paid-row {
   display: flex;
   gap: 20px;
-  flex-wrap: nowrap;
   overflow-x: auto;
+  padding-bottom: 12px;
+  width: 100%;
+  scroll-behavior: smooth;
 }
 
+.recommend-paid-row::-webkit-scrollbar {
+  height: 8px;
+}
+.recommend-paid-row::-webkit-scrollbar-thumb {
+  background-color: rgba(0, 0, 0, 0.3);
+  border-radius: 4px;
+}
+
+/* 付费片单卡片 */
 .paid-card {
-  width: 200px;
-  flex-shrink: 0;
-  border: 1px solid #eee;
-  border-radius: 8px;
-  overflow: hidden;
-  background-color: #f9f9f9;
-  text-align: center;
+  width: 450px; /* 固定宽度 */
+  flex-shrink: 0; /* 不允许缩小 */
+  border-radius: 10px;
+  /* overflow: hidden; */
+    overflow: visible;
+  background: #fffaf7; /* 粉米背景 */
+  box-shadow: 0 10px 20px rgba(78, 52, 46, 0.1); /* 柔和阴影 */
+  cursor: pointer;
+  transition: transform 0.25s ease, box-shadow 0.25s ease;
+}
+
+.paid-card:hover {
+  transform: translateY(-6px);
+  box-shadow: 0 14px 28px rgba(78, 52, 46, 0.2);
 }
 
 .paid-cover {
   width: 100%;
-  height: 120px;
+  height: 130px;
   object-fit: cover;
+  border-bottom: 1px solid #e2cfc3; /* 米棕分割线 */
+  border-radius: 10px 10px 0 0;
 }
 
 .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);
+  font-size: 1.1rem;
+  font-weight: 600;
+  padding: 12px 10px;
+  color: #6b4f3b; /* 深米棕文字 */
+  white-space: nowrap;
   overflow: hidden;
-  transition: transform 0.2s ease;
-  cursor: pointer;
+  text-overflow: ellipsis;
 }
 
+/* 热门资源 - 一行横向滚动 */
+.seed-list.popular-row {
+  display: flex;
+  overflow-x: auto;
+  white-space: nowrap;
+  gap: 16px;
+  padding-bottom: 12px;
+  scrollbar-width: none;
+}
+.seed-list.popular-row::-webkit-scrollbar {
+  display: none;
+}
+
+/* 热门资源卡片 */
+.seed-card {
+  width: 150px;
+  flex-shrink: 0;
+  background: #fffaf7; /* 粉米背景 */
+  border-radius: 12px;
+  box-shadow: 0 8px 16px rgba(78, 52, 46, 0.08);
+  overflow: hidden;
+  transition: transform 0.3s ease, box-shadow 0.3s ease;
+  cursor: pointer;
+  user-select: none;
+}
 .seed-card:hover {
-  transform: translateY(-4px);
+  transform: translateY(-6px);
+  box-shadow: 0 12px 24px rgba(78, 52, 46, 0.15);
 }
 
 .seed-card img {
   width: 100%;
-  height: 220px;
+  height: 210px;
   object-fit: cover;
+  border-radius: 12px 12px 0 0;
+  border-bottom: 1px solid #e2cfc3;
 }
 
 .seed-card .title {
-  padding: 8px;
-  font-size: 0.95rem;
+  padding: 10px 8px;
+  font-size: 1rem;
+  color: #6b4f3b; /* 深米棕文字 */
   text-align: center;
-  color: #333;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
 }
 
+/* 猜你喜欢 - 多行换行排列 */
+.seed-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 18px;
+  margin-bottom: 32px;
+  user-select: none;
+}
+
+/* 登录提醒 */
 .login-reminder {
-  font-size: 1rem;
-  color: #888;
-  padding: 10px;
+  font-size: 1.1rem;
+  color: #a1887f; /* 柔和棕色 */
+  padding: 14px 0;
+  text-align: center;
+  font-style: italic;
+  user-select: none;
+}
+
+/* 统一h2的上下间距 */
+.recommendation-page h2 {
+  margin-top: 36px;
+  margin-bottom: 18px;
+}
+
+
+/* 给滚动区域内种子卡片添加最小宽度避免缩得太小 */
+.seed-list.popular-row .seed-card,
+.recommend-paid-row .paid-card {
+  min-width: 150px;
 }
diff --git a/src/pages/SeedList/Recommend/Recommend.jsx b/src/pages/SeedList/Recommend/Recommend.jsx
index 8270145..f58c73d 100644
--- a/src/pages/SeedList/Recommend/Recommend.jsx
+++ b/src/pages/SeedList/Recommend/Recommend.jsx
@@ -2,48 +2,205 @@
 import axios from 'axios';
 import './Recommend.css';
 import { useUser } from '../../../context/UserContext';
+import { useLocation } from 'wouter';
+import toast from 'react-hot-toast';
+import CreatePlaylistModal from './CreatePlaylistModal';
+import { confirmAlert } from 'react-confirm-alert';
+import 'react-confirm-alert/src/react-confirm-alert.css';
 
 const Recommend = () => {
   const { user } = useUser();
+  const [paidLists, setPaidLists] = useState([]);
   const [popularSeeds, setPopularSeeds] = useState([]);
-  const [recommendedSeeds, setRecommendedSeeds] = useState([]);
+  const [recommendedSeeds, setRecommendedSeeds] = useState({
+    movie: [],
+    tv: [],
+    anime: []
+  });
+
+  const [showModal, setShowModal] = useState(false);
+  const [, navigate] = useLocation();
 
   useEffect(() => {
-    // 获取热门资源
+    axios
+      .get('/playlist/page', { params: { page: 1, size: 8 } })
+      .then((res) => {
+        if (res.data.code === 0) {
+          setPaidLists(res.data.data);
+        } else {
+          toast.error(`获取片单失败:${res.data.msg}`);
+        }
+      })
+      .catch((err) => {
+        console.error('请求片单失败', err);
+        toast.error('请求片单失败,请稍后重试');
+      });
+
     axios
       .get('/echo/recommendation/popular', { params: { limit: 16 } })
       .then((res) => setPopularSeeds(res.data))
-      .catch((err) => console.error('获取热门资源失败', err));
+      .catch((err) => {
+        console.error('获取热门资源失败', err);
+        toast.error('获取热门资源失败');
+      });
   }, []);
 
   useEffect(() => {
-    // 获取个性化推荐
-    if (user && user.userId) {
+    if (user?.userId) {
       axios
         .get(`/echo/recommendation/seeds/${user.userId}`)
-        .then((res) => setRecommendedSeeds(res.data))
-        .catch((err) => console.error('获取个性化推荐失败', err));
+        .then((res) => {
+          const categorized = { movie: [], tv: [], anime: [] };
+          res.data.forEach((seed) => {
+            if (seed.category === 'movie') categorized.movie.push(seed);
+            else if (seed.category === 'tv') categorized.tv.push(seed);
+            else if (seed.category === 'anime') categorized.anime.push(seed);
+          });
+          setRecommendedSeeds(categorized);
+        })
+        .catch((err) => {
+          console.error('获取个性化推荐失败', err);
+          toast.error('获取个性化推荐失败');
+        });
     }
   }, [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>
+  const handleDelete = (id) => {
+    confirmAlert({
+      title: '确认删除',
+      message: '确定删除此片单吗?',
+      buttons: [
+        {
+          label: '确定',
+          onClick: async () => {
+            const toastId = toast.loading('正在删除...');
+            try {
+              await axios.delete('/playlist', {
+                params: { ids: id },
+                paramsSerializer: (params) =>
+                  `ids=${Array.isArray(params.ids) ? params.ids.join(',') : params.ids}`
+              });
+              setPaidLists(paidLists.filter((list) => list.id !== id));
+              toast.success('删除成功', { id: toastId });
+            } catch (error) {
+              console.error('删除失败', error);
+              toast.error('删除失败,请稍后重试', { id: toastId });
+            }
+          }
+        },
+        { label: '取消' }
+      ]
+    });
+  };
+
+  const handlePurchase = (id) => {
+    confirmAlert({
+      title: '确认购买',
+      message: '确定支付该片单?',
+      buttons: [
+        {
+          label: '确定',
+          onClick: async () => {
+            const toastId = toast.loading('购买中...');
+            try {
+              const res = await axios.post(`/playlist/${id}/pay`);
+              if (res.data.code === 0) {
+                toast.success('购买成功', { id: toastId });
+                navigate(`/playlist/${id}`);
+              } else {
+                toast.error(`购买失败:${res.data.msg}`, { id: toastId });
+              }
+            } catch (err) {
+              console.error('支付失败', err);
+              toast.error('购买失败,请稍后重试', { id: toastId });
+            }
+          }
+        },
+        { label: '取消' }
+      ]
+    });
+  };
+
+const renderSeedCard = (seed) => (
+  <div
+    className="seed-card"
+    key={seed.id}
+    onClick={() => navigate(`/seed/${seed.id}`)}
+    style={{ cursor: 'pointer' }}
+  >
+    <img src={seed.imageUrl || '/default-cover.jpg'} alt={seed.title} />
+    <div className="title">{seed.title}</div>
+  </div>
+);
+
+
+  const renderSection = (title, seeds) => (
+    <>
+      <h2>{title}</h2>
+      <div className="seed-list">{seeds.map(renderSeedCard)}</div>
+    </>
   );
 
   return (
     <div className="recommendation-page">
-      <h2>🎬 正在热映</h2>
-      <div className="seed-list">{popularSeeds.map(renderSeedCard)}</div>
+      <h2>💰 付费片单</h2>
+      {user && user.role === 'admin' && (
+        <button className="create-button" onClick={() => setShowModal(true)}>
+          ➕ 创建片单
+        </button>
+      )}
 
-      <h2>🎯 个性化推荐</h2>
+      <div className="recommend-paid-row">
+        {paidLists.map((list) => (
+          <div className="paid-card" key={list.id}>
+            <img
+              className="paid-cover"
+              src={list.coverUrl || '/default-cover.jpg'}
+              alt={list.title}
+            />
+            <div className="paid-title">{list.title}</div>
+
+            {user && user.role === 'admin' ? (
+              <div className="admin-actions">
+                <button onClick={() => handleDelete(list.id)}>删除</button>
+              </div>
+            ) : list.isPaid ? (
+              <button onClick={() => navigate(`/playlist/${list.id}`)}>详情</button>
+            ) : (
+              <button onClick={() => handlePurchase(list.id)}>购买</button>
+            )}
+          </div>
+        ))}
+      </div>
+
+      <h2>🎬 正在热映</h2>
+      <div className="seed-list popular-row">
+        {popularSeeds.slice(0, 8).map(renderSeedCard)}
+      </div>
+
+      <h2>🎯 猜你喜欢</h2>
       {user ? (
-        <div className="seed-list">{recommendedSeeds.map(renderSeedCard)}</div>
+        <>
+          {recommendedSeeds.movie.length > 0 &&
+            renderSection('🎞️ 电影推荐', recommendedSeeds.movie)}
+          {recommendedSeeds.tv.length > 0 &&
+            renderSection('📺 电视剧推荐', recommendedSeeds.tv)}
+          {recommendedSeeds.anime.length > 0 &&
+            renderSection('🎌 动漫推荐', recommendedSeeds.anime)}
+        </>
       ) : (
         <div className="login-reminder">请登录以获取个性化推荐</div>
       )}
+
+      {showModal && (
+        <CreatePlaylistModal
+          onClose={() => setShowModal(false)}
+          onSuccess={(newPlaylist) => {
+            setPaidLists([newPlaylist, ...paidLists]);
+            setShowModal(false);
+          }}
+        />
+      )}
     </div>
   );
 };
diff --git a/src/pages/SeedList/Recommend/TorrentSelector.css b/src/pages/SeedList/Recommend/TorrentSelector.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pages/SeedList/Recommend/TorrentSelector.css
diff --git a/src/pages/SeedList/Recommend/TorrentSelector.jsx b/src/pages/SeedList/Recommend/TorrentSelector.jsx
new file mode 100644
index 0000000..abe2806
--- /dev/null
+++ b/src/pages/SeedList/Recommend/TorrentSelector.jsx
@@ -0,0 +1,84 @@
+import React, { useState, useEffect } from 'react';
+import axios from 'axios';
+import toast from 'react-hot-toast';
+
+const TorrentSelector = ({ selectedIds, onSelect }) => {
+  const [query, setQuery] = useState('');
+  const [torrents, setTorrents] = useState([]);
+  const [loading, setLoading] = useState(false);
+
+  useEffect(() => {
+    const timer = setTimeout(() => {
+      if (query.trim()) {
+        fetchTorrents(query.trim());
+      } else {
+        setTorrents([]);
+      }
+    }, 500);
+
+    return () => clearTimeout(timer);
+  }, [query]);
+
+  const fetchTorrents = async (keyword) => {
+    setLoading(true);
+    try {
+      const res = await axios.get('/seeds/list', {
+        params: {
+          title: keyword,  // 与 SeedList 统一参数名
+          page: 1,
+          size: 20,
+          orderKey: 'upload_time',
+          orderDesc: true,
+        }
+      });
+      if (res.data.code === 0) {
+        // 如果接口返回 data.list 格式
+        setTorrents(res.data.data.list || res.data.data || []);
+      } else {
+        setTorrents([]);
+      }
+    } catch (error) {
+      console.error('请求失败', error);
+      setTorrents([]);
+    }
+    setLoading(false);
+  };
+
+  const toggleSelect = (id, checked) => {
+    onSelect(id, checked);
+  };
+
+  return (
+    <div className="torrent-selector">
+      <input
+        placeholder="搜索种子名称"
+        value={query}
+        onChange={(e) => setQuery(e.target.value)}
+      />
+      {loading && <div>加载中...</div>}
+      <ul>
+        {torrents.map(t => (
+          <li key={t.id}>
+            <label>
+              <input
+                type="checkbox"
+                value={t.id}
+                checked={selectedIds.includes(t.id)}
+                onChange={(e) => toggleSelect(t.id, e.target.checked)}
+              />
+              {t.title || t.name}
+            </label>
+          </li>
+        ))}
+      </ul>
+      {selectedIds.length > 0 && (
+        <div className="selected-torrents">
+          <b>已选种子:</b>
+          {selectedIds.join(', ')}
+        </div>
+      )}
+    </div>
+  );
+};
+
+export default TorrentSelector;