新增求种和个人中心接口

Change-Id: Ibf3eef5b91a45a0ccf7b99d08afa29960884a8cf
diff --git a/front/src/AdminPage.js b/front/src/AdminPage.js
index dac05cb..f063c09 100644
--- a/front/src/AdminPage.js
+++ b/front/src/AdminPage.js
@@ -51,11 +51,6 @@
         }
     };
 
-    const handleConfigChange = (e) => {
-        const { name, value } = e.target;
-        setConfig({ ...config, [name]: value });
-    };
-
     const handleBan = (user) => {
         const adminId = getUserIdFromCookie();
         if (!adminId) {
@@ -97,28 +92,6 @@
             .catch((err) => console.error('解封用户失败:', err));
     };
 
-    // 保存系统参数到后端
-    const handleSave = () => {
-        const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
-        const userId = match ? match[2] : null;
-        if (!userId) {
-            alert('无法获取用户ID');
-            return;
-        }
-        fetch(`${API_BASE_URL}/api/save-config`, {
-            method: 'POST',
-            // credentials: 'include',
-            headers: { 'Content-Type': 'application/json' },
-            body: JSON.stringify({ userid: userId, ...config }),
-        })
-            .then((res) => {
-                if (!res.ok) throw new Error('Network response was not ok');
-                return res.json();
-            })
-            .then(() => alert('系统参数已保存'))
-            .catch((err) => console.error('保存系统参数失败:', err));
-    };
-
     // 初始化时向后端请求系统参数及用户列表
     useEffect(() => {
         const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
@@ -149,37 +122,10 @@
             {/* 参数设置 */}
             <div style={{ marginBottom: 32, padding: 18, background: "#f7faff", borderRadius: 12, display: "flex", gap: 24, alignItems: "center", justifyContent: "space-between" }}>
                 <b>系统参数:</b>
-                <label>
-                    FarmNumber:
-                    <input type="number" name="FarmNumber" value={config.FarmNumber} onChange={handleConfigChange} disabled style={{ width: 60, margin: "0 12px" }} />
-                </label>
-                <label>
-                    FakeTime:
-                    <input type="number" name="FakeTime" value={config.FakeTime} onChange={handleConfigChange} disabled style={{ width: 60, margin: "0 12px" }} />
-                </label>
-                <label>
-                    BegVote:
-                    <input type="number" name="BegVote" value={config.BegVote} onChange={handleConfigChange} disabled style={{ width: 60, margin: "0 12px" }} />
-                </label>
-                <label>
-                    CheatTime:
-                    <input type="number" name="CheatTime" value={config.CheatTime} onChange={handleConfigChange} disabled style={{ width: 60, margin: "0 12px" }} />
-                </label>
-                {/* <button
-                    onClick={handleSave}
-                    style={{
-                        background: '#1976d2',
-                        color: '#fff',
-                        border: 'none',
-                        borderRadius: 6,
-                        padding: '6px 18px',
-                        cursor: 'pointer',
-                        writingMode: 'horizontal-tb',
-                        whiteSpace: 'nowrap'
-                    }}
-                >
-                    保存
-                </button> */}
+                <span>FarmNumber: {config.FarmNumber}</span>
+                <span>FakeTime: {config.FakeTime}</span>
+                <span>BegVote: {config.BegVote}</span>
+                <span>CheatTime: {config.CheatTime}</span>
             </div>
             {/* 作弊用户 */}
             <div style={{ marginBottom: 32 }}>
@@ -201,7 +147,7 @@
                                 <td>{u.email}</td>
                                 <td>{u.username}</td>
                                 <td style={{ color: "#e53935" }}>
-                                    {"封禁" }
+                                    {"封禁"}
                                 </td>
                                 <td>
                                     <button
@@ -265,6 +211,12 @@
                 >
                     用户迁移
                 </button>
+                <button
+                    style={{ background: "#ff9800", color: "#fff", border: "none", borderRadius: 8, padding: "10px 28px", fontWeight: 600, fontSize: 16, cursor: "pointer" }}
+                    onClick={() => navigate("/seed-promotion")}
+                >
+                    促销管理
+                </button>
             </div>
         </div>
     );
diff --git a/front/src/App.js b/front/src/App.js
index 58fb58c..ad42aff 100644
--- a/front/src/App.js
+++ b/front/src/App.js
@@ -1,5 +1,6 @@
 import React from "react";
 import { BrowserRouter as Router, Routes, Route, useNavigate, Link, Navigate } from "react-router-dom";
+import HomeIcon from "@mui/icons-material/Home";
 import MovieIcon from "@mui/icons-material/Movie";
 import EmailIcon from "@mui/icons-material/Email";
 import MusicNoteIcon from "@mui/icons-material/MusicNote";
@@ -31,6 +32,8 @@
 import MigrationPage from './MigrationPage';
 import BegSeedPage from "./BegSeedPage";
 import BegInfo from "./BegInfo";
+import SeedPromotionPage from "./SeedPromotionPage";
+import HomePage from "./HomePage";
 
 
 const navItems = [
@@ -160,6 +163,7 @@
         <Route path="/" element={<Navigate to="/login" replace />} />
         {/* Protected routes */}
         <Route element={<RequireAuth />}>
+          <Route path="/home" element={<HomePage />} />
           <Route path="/movie" element={<MoviePage />} />
           <Route path="/tv" element={<TVPage />} />
           <Route path="/music" element={<MusicPage />} />
@@ -175,6 +179,7 @@
           <Route path="/admin" element={<AdminPage />} />
           <Route path="/appeal-review" element={<AppealPage />} />
           <Route path="/migration-review" element={<MigrationPage />} />
+          <Route path="/seed-promotion" element={<SeedPromotionPage />} />
           <Route path="/begseed" element={<BegSeedPage />} />
           <Route path="/begseed/:begid" element={<BegInfo />} />
         </Route>
diff --git a/front/src/BegInfo.js b/front/src/BegInfo.js
index 1b3e2af..8ea3f6b 100644
--- a/front/src/BegInfo.js
+++ b/front/src/BegInfo.js
@@ -1,7 +1,8 @@
-import React, { useState } from "react";
+import React, { useState, useEffect } from "react";
 import { useParams } from "react-router-dom";
+import { API_BASE_URL } from "./config";
 
-// 求种任务示例数据
+// 求种任务示例数据(作为后备数据)
 const begSeedList = [
     {
         beg_id: "beg001",
@@ -45,61 +46,311 @@
 
 export default function BegInfo() {
     const { begid } = useParams();
-    const beg = begSeedList.find((b) => b.beg_id === begid);
-    const [seeds, setSeeds] = useState(
-        submitSeedList.filter((s) => s.beg_id === begid)
-    );
+    const [beg, setBeg] = useState(null);
+    const [loading, setLoading] = useState(true);
+    const [error, setError] = useState(null);
+    const [seeds, setSeeds] = useState([]);
+    const [seedInfoMap, setSeedInfoMap] = useState({});
     const [showForm, setShowForm] = useState(false);
+    const [userSeeds, setUserSeeds] = useState([]);
+    const [loadingUserSeeds, setLoadingUserSeeds] = useState(false);
     const [formData, setFormData] = useState({
-        title: "",
-        subtitle: "",
-        torrentFile: null,
+        selectedSeedId: "",
     });
 
-    if (!beg) return <div style={{ padding: 40 }}>未找到该求种信息</div>;
+    // 从后端获取求种详情
+    const fetchBegSeedDetail = async () => {
+        setLoading(true);
+        try {
+            const response = await fetch(`${API_BASE_URL}/api/begseed-detail?begid=${begid}`);
+            if (!response.ok) {
+                throw new Error(`请求失败,状态码: ${response.status}`);
+            }
+            const data = await response.json();
+            
+            
+            // 格式化数据以匹配前端期望的格式
+            const formattedBeg = {
+                beg_id: data.beg_id || data.begid || data.id,
+                info: data.info || data.description || data.content,
+                beg_count: data.beg_count || data.begCount || 1,
+                reward_magic: data.reward_magic || data.rewardMagic || data.magic,
+                deadline: data.deadline || data.endtime,
+                has_match: data.has_match || data.hasMatch || data.completed || 0,
+            };
+            
+            setBeg(formattedBeg);
+            setError(null);
+        } catch (err) {
+            console.error('获取求种详情失败:', err);
+            setError(err.message);
+            // 如果API调用失败,使用默认数据
+            const fallbackBeg = begSeedList.find((b) => b.beg_id === begid);
+            setBeg(fallbackBeg || null);
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    // 从后端获取已提交的种子列表
+    const fetchSubmittedSeeds = async () => {
+        try {
+            const response = await fetch(`${API_BASE_URL}/api/begseed-submissions?begid=${begid}`);
+            if (!response.ok) {
+                throw new Error(`请求失败,状态码: ${response.status}`);
+            }
+            const data = await response.json();
+            console.log('获取到的种子提交数据:', data);
+            
+            // 新的数据结构:数组,每个元素包含seed对象和votes字段
+            const submissions = Array.isArray(data) ? data : [];
+            
+            // 格式化种子数据
+            const formattedSeeds = submissions.map(item => ({
+                seed_id: item.seed?.seedid || item.seedid,
+                beg_id: begid,
+                votes: item.votes || 0, // 每个种子单独的投票数
+                title: item.seed?.title || item.title || "未知标题",
+                subtitle: item.seed?.subtitle || item.subtitle || "无简介",
+                seedsize: item.seed?.seedsize || item.seedsize,
+                downloadtimes: item.seed?.downloadtimes || item.downloadtimes || 0,
+                url: item.seed?.url || item.url,
+                user: item.seed?.user || item.user
+            }));
+            
+            // 构建种子信息映射
+            const newSeedInfoMap = {};
+            submissions.forEach(item => {
+                const seedId = item.seed?.seedid || item.seedid;
+                if (seedId) {
+                    newSeedInfoMap[seedId] = {
+                        title: item.seed?.title || item.title || "未知标题",
+                        subtitle: item.seed?.subtitle || item.subtitle || "无简介",
+                    };
+                }
+            });
+            
+            setSeeds(formattedSeeds);
+            setSeedInfoMap(newSeedInfoMap);
+        } catch (err) {
+            console.error('获取种子提交列表失败:', err);
+            // 如果API调用失败,使用默认数据
+            const fallbackSeeds = submitSeedList.filter((s) => s.beg_id === begid);
+            setSeeds(fallbackSeeds);
+            setSeedInfoMap(seedInfoMap);
+        }
+    };
+
+    // 组件挂载时获取数据
+    useEffect(() => {
+        fetchBegSeedDetail();
+        fetchSubmittedSeeds();
+    }, [begid]);
+
+    // 加载状态
+    if (loading) {
+        return (
+            <div className="container">
+                <div style={{ textAlign: "center", margin: "40px 0", color: "#666" }}>
+                    正在加载求种详情...
+                </div>
+            </div>
+        );
+    }
+
+    // 未找到求种信息
+    if (!beg) {
+        return (
+            <div className="container">
+                <div style={{ padding: 40, textAlign: "center", color: "#666" }}>
+                    未找到该求种信息
+                </div>
+            </div>
+        );
+    }
 
     const isExpired = new Date(beg.deadline) < new Date();
     const isFinished = beg.has_match === 1;
     const isActive = !isExpired && !isFinished;
 
-    // 投票功能(前端+1)
-    const handleVote = (seed_id) => {
-        setSeeds((prev) =>
-            prev.map((s) =>
-                s.seed_id === seed_id ? { ...s, votes: s.votes + 1 } : s
-            )
-        );
+    // 获取用户的所有种子
+    const fetchUserSeeds = async () => {
+        setLoadingUserSeeds(true);
+        try {
+            // 获取用户ID
+            const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
+            const userId = match ? match[2] : null;
+            
+            if (!userId) {
+                alert("请先登录后再获取种子列表");
+                setLoadingUserSeeds(false);
+                return;
+            }
+
+            const response = await fetch(`${API_BASE_URL}/api/user-seeds?userid=${userId}`);
+            if (!response.ok) {
+                throw new Error(`请求失败,状态码: ${response.status}`);
+            }
+            const data = await response.json();
+            
+            // 格式化种子数据
+            const formattedSeeds = Array.isArray(data) ? data.map(seed => ({
+                seedid: seed.seedid || seed.id,
+                title: seed.title || "未知标题",
+                subtitle: seed.subtitle || "无简介",
+                seedsize: seed.seedsize,
+                downloadtimes: seed.downloadtimes || 0,
+                url: seed.url
+            })) : [];
+            
+            setUserSeeds(formattedSeeds);
+        } catch (err) {
+            console.error('获取用户种子失败:', err);
+            alert(`获取种子列表失败: ${err.message}`);
+        } finally {
+            setLoadingUserSeeds(false);
+        }
+    };
+
+    // 投票功能(发送到后端)
+    const handleVote = async (seed_id) => {
+        try {
+            // 获取用户ID
+            const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
+            const userId = match ? match[2] : null;
+            
+            if (!userId) {
+                alert("请先登录后再投票");
+                return;
+            }
+
+            const response = await fetch(`${API_BASE_URL}/api/vote-seed`, {
+                method: 'POST',
+                headers: {
+                    'Content-Type': 'application/json',
+                },
+                body: JSON.stringify({
+                    userid: userId,
+                    seedid: seed_id,
+                    begid: begid,
+                }),
+            });
+
+            
+            if (response.ok) {
+                // 投票成功,重新获取数据以更新投票计数
+                await fetchSubmittedSeeds();
+                alert("投票成功!");
+            } else if (response.status === 409) {
+                alert("您已投过票,不能重复投票");
+            }
+            else {
+                const errorData = await response.json();
+                alert(`投票失败: ${errorData.message || '未知错误'}`);
+            }
+        } catch (err) {
+            console.error('投票失败:', err);
+            // 如果后端调用失败,更新本地状态作为后备
+            setSeeds((prev) =>
+                prev.map((s) => 
+                    s.seed_id === seed_id ? { ...s, votes: s.votes + 1 } : s
+                )
+            );
+            alert("投票成功(前端演示)");
+        }
     };
 
     // 上传表单处理
     const handleFormChange = (e) => {
-        const { name, value, files } = e.target;
-        if (name === "torrentFile") {
-            setFormData((f) => ({ ...f, torrentFile: files[0] }));
-        } else {
-            setFormData((f) => ({ ...f, [name]: value }));
-        }
+        const { name, value } = e.target;
+        setFormData((f) => ({ ...f, [name]: value }));
     };
 
-    const handleSubmitSeed = (e) => {
+    const handleSubmitSeed = async (e) => {
         e.preventDefault();
-        // 这里只做前端演示,实际应上传到后端
-        const newSeedId = "seed" + Math.floor(Math.random() * 10000);
-        setSeeds((prev) => [
-            ...prev,
-            {
-                beg_id: begid,
-                seed_id: newSeedId,
-                votes: 0,
-            },
-        ]);
-        seedInfoMap[newSeedId] = {
-            title: formData.title,
-            subtitle: formData.subtitle,
-        };
-        setShowForm(false);
-        setFormData({ title: "", subtitle: "", torrentFile: null });
-        alert("提交成功(前端演示)");
+        
+        if (!formData.selectedSeedId) {
+            alert("请选择一个种子");
+            return;
+        }
+        
+        try {
+            // 获取用户ID
+            const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
+            const userId = match ? match[2] : null;
+            
+            if (!userId) {
+                alert("请先登录后再提交种子");
+                return;
+            }
+            // console.log('提交种子数据:', {
+            //     userid: userId,
+            //     begid: begid,
+            //     seedid: formData.selectedSeedId,
+            // });
+
+            const response = await fetch(`${API_BASE_URL}/api/submit-seed`, {
+                method: 'POST',
+                headers: {
+                    'Content-Type': 'application/json',
+                },
+                body: JSON.stringify({
+                    userid: userId,
+                    begid: begid,
+                    seedid: formData.selectedSeedId,
+                }),
+            });
+
+            if (response.ok) {
+                // 提交成功,重新获取所有数据以刷新页面
+                await Promise.all([
+                    fetchBegSeedDetail(),
+                    fetchSubmittedSeeds()
+                ]);
+                setShowForm(false);
+                setFormData({ selectedSeedId: "" });
+                setUserSeeds([]);
+                alert("提交成功!");
+            } else {
+                const errorData = await response.json();
+                alert(`提交失败: ${errorData.message || '未知错误'}`);
+            }
+        } catch (err) {
+            console.error('提交种子失败:', err);
+            // 如果后端调用失败,使用前端演示逻辑
+            const newSeedId = "seed" + Math.floor(Math.random() * 10000);
+            
+            // 从用户种子列表中找到选中种子的信息
+            const selectedSeed = userSeeds.find(seed => seed.seedid === formData.selectedSeedId);
+            
+            setSeeds((prev) => [
+                ...prev,
+                {
+                    beg_id: begid,
+                    seed_id: newSeedId,
+                    votes: 0,
+                    title: selectedSeed?.title || "未知标题",
+                    subtitle: selectedSeed?.subtitle || "无简介",
+                    seedsize: selectedSeed?.seedsize,
+                    downloadtimes: selectedSeed?.downloadtimes || 0,
+                    url: selectedSeed?.url,
+                    user: { username: "当前用户" }
+                },
+            ]);
+            
+            setSeedInfoMap(prev => ({
+                ...prev,
+                [newSeedId]: {
+                    title: selectedSeed?.title || "未知标题",
+                    subtitle: selectedSeed?.subtitle || "无简介",
+                }
+            }));
+            
+            setShowForm(false);
+            setFormData({ selectedSeedId: "" });
+            setUserSeeds([]);
+            alert("提交成功(前端演示)");
+        }
     };
 
     return (
@@ -107,6 +358,21 @@
             <h1 style={{ margin: "24px 0 32px 0", color: "#1976d2" }}>
                 求种详情
             </h1>
+            
+            {/* 错误状态 */}
+            {error && (
+                <div style={{ 
+                    textAlign: "center", 
+                    margin: "20px 0", 
+                    padding: "10px", 
+                    background: "#ffebee", 
+                    color: "#c62828", 
+                    borderRadius: "4px" 
+                }}>
+                    加载失败: {error} (已显示默认数据)
+                </div>
+            )}
+            
             <div
                 style={{
                     background: "#e3f7e7",
@@ -135,26 +401,36 @@
             </div>
 
             <h2 style={{ margin: "24px 0 12px 0" }}>已提交种子</h2>
-            <table className="movie-table" style={{ maxWidth: 700, margin: "0 auto" }}>
+            <table className="movie-table" style={{ maxWidth: 1000, margin: "0 auto" }}>
                 <thead>
                     <tr>
                         <th>标题</th>
                         <th>简介</th>
+                        <th>文件大小</th>
+                        <th>下载次数</th>
                         <th>投票数</th>
+                        <th>上传者</th>
                         <th>操作</th>
                     </tr>
                 </thead>
                 <tbody>
                     {seeds.length === 0 ? (
                         <tr>
-                            <td colSpan={4} style={{ textAlign: "center" }}>暂无提交的种子</td>
+                            <td colSpan={7} style={{ textAlign: "center" }}>暂无提交的种子</td>
                         </tr>
                     ) : (
                         seeds.map((s) => (
                             <tr key={s.seed_id}>
-                                <td>{seedInfoMap[s.seed_id]?.title || "未知标题"}</td>
-                                <td>{seedInfoMap[s.seed_id]?.subtitle || "无简介"}</td>
-                                <td>{s.votes}</td>
+                                <td>
+                                    <a href={`/torrent/${s.seed_id}`} style={{ color: '#1a237e', textDecoration: 'none' }}>
+                                        {s.title}
+                                    </a>
+                                </td>
+                                <td>{s.subtitle || "无简介"}</td>
+                                <td>{s.seedsize ? `${s.seedsize} MB` : "未知"}</td>
+                                <td>{s.downloadtimes || 0}</td>
+                                <td style={{ fontWeight: 'bold', color: '#1976d2' }}>{s.votes || 0}</td>
+                                <td>{s.user?.username || "未知用户"}</td>
                                 <td>
                                     {isActive ? (
                                         <button
@@ -182,10 +458,20 @@
                 </tbody>
             </table>
 
+            {/* 显示总投票数 */}
+            {seeds.length > 0 && (
+                <div style={{ textAlign: "center", margin: "16px 0", color: "#666" }}>
+                    总投票数: {seeds.reduce((total, seed) => total + (seed.votes || 0), 0)}
+                </div>
+            )}
+
             {isActive && (
                 <div style={{ margin: "32px 0", textAlign: "center" }}>
                     <button
-                        onClick={() => setShowForm(true)}
+                        onClick={() => {
+                            setShowForm(true);
+                            fetchUserSeeds();
+                        }}
                         style={{
                             fontSize: 18,
                             padding: "12px 36px",
@@ -199,7 +485,7 @@
                             transition: "background 0.2s",
                         }}
                     >
-                        提交悬赏种子
+                        提交种子
                     </button>
                 </div>
             )}
@@ -216,60 +502,76 @@
                         boxShadow: "0 2px 8px #e0e7ff",
                     }}
                 >
-                    <h3 style={{ color: "#1976d2", marginBottom: 18 }}>上传种子</h3>
+                    <h3 style={{ color: "#1976d2", marginBottom: 18 }}>选择种子</h3>
+                    
+                    {/* 加载用户种子状态 */}
+                    {loadingUserSeeds && (
+                        <div style={{ textAlign: "center", margin: "16px 0", color: "#666" }}>
+                            正在加载您的种子列表...
+                        </div>
+                    )}
+                    
+                    {/* 选择已有种子 */}
+                    {userSeeds.length > 0 ? (
+                        <div style={{ marginBottom: 24 }}>
+                            <div style={{ marginBottom: 16 }}>
+                                <label style={{ display: "inline-block", width: 80, fontWeight: 500 }}>选择种子:</label>
+                                <select
+                                    name="selectedSeedId"
+                                    value={formData.selectedSeedId}
+                                    onChange={handleFormChange}
+                                    style={{
+                                        padding: "8px 12px",
+                                        borderRadius: 6,
+                                        border: "1px solid #b2d8ea",
+                                        width: 300,
+                                        background: "#fff",
+                                        fontSize: 14,
+                                    }}
+                                >
+                                    <option value="">请选择一个种子</option>
+                                    {userSeeds.map((seed) => (
+                                        <option key={seed.seedid} value={seed.seedid}>
+                                            {seed.title} - {seed.subtitle || "无简介"} ({seed.seedsize ? `${seed.seedsize} MB` : "未知大小"})
+                                        </option>
+                                    ))}
+                                </select>
+                            </div>
+                            {formData.selectedSeedId && (
+                                <div style={{ 
+                                    padding: 12, 
+                                    background: "#e8f5e8", 
+                                    borderRadius: 6, 
+                                    border: "1px solid #4caf50",
+                                    color: "#2e7d32"
+                                }}>
+                                    ✓ 已选择种子,点击提交即可使用此种子
+                                </div>
+                            )}
+                        </div>
+                    ) : (
+                        !loadingUserSeeds && (
+                            <div style={{ 
+                                textAlign: "center", 
+                                margin: "20px 0", 
+                                padding: "16px",
+                                background: "#fff3cd",
+                                color: "#856404",
+                                border: "1px solid #ffeaa7",
+                                borderRadius: 6
+                            }}>
+                                您还没有上传过种子,无法参与悬赏
+                            </div>
+                        )
+                    )}
+                    
                     <form onSubmit={handleSubmitSeed}>
-                        <div style={{ marginBottom: 16 }}>
-                            <label style={{ display: "inline-block", width: 60 }}>标题:</label>
-                            <input
-                                type="text"
-                                name="title"
-                                value={formData.title}
-                                onChange={handleFormChange}
-                                required
-                                style={{
-                                    padding: "6px 12px",
-                                    borderRadius: 6,
-                                    border: "1px solid #b2d8ea",
-                                    width: 280,
-                                }}
-                            />
-                        </div>
-                        <div style={{ marginBottom: 16 }}>
-                            <label style={{ display: "inline-block", width: 60 }}>简介:</label>
-                            <input
-                                type="text"
-                                name="subtitle"
-                                value={formData.subtitle}
-                                onChange={handleFormChange}
-                                style={{
-                                    padding: "6px 12px",
-                                    borderRadius: 6,
-                                    border: "1px solid #b2d8ea",
-                                    width: 280,
-                                }}
-                            />
-                        </div>
-                        <div style={{ marginBottom: 16 }}>
-                            <label style={{ display: "inline-block", width: 60 }}>种子文件:</label>
-                            <input
-                                type="file"
-                                name="torrentFile"
-                                accept=".torrent"
-                                onChange={handleFormChange}
-                                required
-                                style={{
-                                    padding: "6px 0",
-                                    borderRadius: 6,
-                                    border: "1px solid #b2d8ea",
-                                    width: 280,
-                                }}
-                            />
-                        </div>
                         <div style={{ marginTop: 18 }}>
                             <button
                                 type="submit"
+                                disabled={!formData.selectedSeedId}
                                 style={{
-                                    background: "#1976d2",
+                                    background: formData.selectedSeedId ? "#1976d2" : "#b0b0b0",
                                     color: "#fff",
                                     border: "none",
                                     borderRadius: 6,
@@ -277,14 +579,18 @@
                                     fontWeight: 500,
                                     fontSize: 16,
                                     marginRight: 18,
-                                    cursor: "pointer",
+                                    cursor: formData.selectedSeedId ? "pointer" : "not-allowed",
                                 }}
                             >
-                                提交
+                                提交种子
                             </button>
                             <button
                                 type="button"
-                                onClick={() => setShowForm(false)}
+                                onClick={() => {
+                                    setShowForm(false);
+                                    setFormData({ selectedSeedId: "" });
+                                    setUserSeeds([]);
+                                }}
                                 style={{
                                     background: "#b0b0b0",
                                     color: "#fff",
diff --git a/front/src/BegSeedPage.js b/front/src/BegSeedPage.js
index a456147..0854535 100644
--- a/front/src/BegSeedPage.js
+++ b/front/src/BegSeedPage.js
@@ -1,46 +1,85 @@
-import React, { useState } from "react";
+import React, { useState, useEffect } from "react";
 import HelpIcon from "@mui/icons-material/Help";
 import { useNavigate } from "react-router-dom";
-
-// 初始硬编码数据
-const initialBegSeedList = [
-    {
-        beg_id: "beg001",
-        info: "求《三体》高清资源",
-        beg_count: 5,
-        reward_magic: 100,
-        deadline: "2025-06-10T23:59:59",
-        has_match: 0,
-    },
-    {
-        beg_id: "beg002",
-        info: "求《灌篮高手》国语配音版",
-        beg_count: 3,
-        reward_magic: 50,
-        deadline: "2024-05-01T23:59:59",
-        has_match: 1,
-    },
-    {
-        beg_id: "beg003",
-        info: "求《黑暗之魂3》PC版种子",
-        beg_count: 2,
-        reward_magic: 80,
-        deadline: "2024-04-01T23:59:59",
-        has_match: 0,
-    },
-];
+import { API_BASE_URL } from "./config";
 
 export default function BegSeedPage() {
     const navigate = useNavigate();
     const now = new Date();
-    const [begSeedList, setBegSeedList] = useState(initialBegSeedList);
+    const [begSeedList, setBegSeedList] = useState([]);
+    const [loading, setLoading] = useState(true);
+    const [error, setError] = useState(null);
     const [showForm, setShowForm] = useState(false);
+    const [refreshKey, setRefreshKey] = useState(0); // 用于强制重新渲染
     const [formData, setFormData] = useState({
         info: "",
         reward_magic: "",
         deadline: "",
     });
 
+    // 从后端获取求种列表
+    const fetchBegSeedList = async () => {
+        setLoading(true);
+        try {
+            const response = await fetch(`${API_BASE_URL}/api/begseed-list`);
+            if (!response.ok) {
+                throw new Error(`请求失败,状态码: ${response.status}`);
+            }
+            const data = await response.json();
+            console.log("获取到的求种列表数据:", data);
+            
+            // 格式化数据以匹配前端期望的格式
+            const formattedData = data.map(item => ({
+                beg_id: item.beg_id || item.begid || item.id,
+                info: item.info || item.description || item.content,
+                beg_count: item.beg_count || item.begCount || 1,
+                reward_magic: item.reward_magic || item.rewardMagic || item.magic,
+                deadline: item.deadline || item.endtime,
+                has_match: item.has_match || item.hasMatch || item.completed || 0,
+            }));
+            
+            setBegSeedList(formattedData);
+            setError(null);
+        } catch (err) {
+            console.error('获取求种列表失败:', err);
+            setError(err.message);
+            // 如果API调用失败,使用默认数据
+            setBegSeedList([
+                {
+                    beg_id: "beg001",
+                    info: "求《三体》高清资源",
+                    beg_count: 5,
+                    reward_magic: 100,
+                    deadline: "2025-06-10T23:59:59",
+                    has_match: 0,
+                },
+                {
+                    beg_id: "beg002",
+                    info: "求《灌篮高手》国语配音版",
+                    beg_count: 3,
+                    reward_magic: 50,
+                    deadline: "2024-05-01T23:59:59",
+                    has_match: 1,
+                },
+                {
+                    beg_id: "beg003",
+                    info: "求《黑暗之魂3》PC版种子",
+                    beg_count: 2,
+                    reward_magic: 80,
+                    deadline: "2024-04-01T23:59:59",
+                    has_match: 0,
+                },
+            ]);
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    // 组件挂载时获取数据
+    useEffect(() => {
+        fetchBegSeedList();
+    }, []);
+
     // 表单输入处理
     const handleFormChange = (e) => {
         const { name, value } = e.target;
@@ -51,23 +90,66 @@
     };
 
     // 提交新求种任务
-    const handleSubmit = (e) => {
+    const handleSubmit = async (e) => {
         e.preventDefault();
-        const newBegId = "beg" + Math.floor(Math.random() * 10000);
-        setBegSeedList([
-            {
-                beg_id: newBegId,
-                info: formData.info,
-                beg_count: 1,
-                reward_magic: Number(formData.reward_magic),
-                deadline: formData.deadline,
-                has_match: 0,
-            },
-            ...begSeedList,
-        ]);
-        setShowForm(false);
-        setFormData({ info: "", reward_magic: "", deadline: "" });
-        alert("发布成功(前端演示)");
+        
+        // 获取用户ID
+        const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
+        const userId = match ? match[2] : null;
+        
+        if (!userId) {
+            alert("请先登录后再发布求种任务");
+            return;
+        }
+
+        try {
+            const response = await fetch(`${API_BASE_URL}/api/create-begseed`, {
+                method: 'POST',
+                headers: {
+                    'Content-Type': 'application/json',
+                },
+                body: JSON.stringify({
+                    userid: userId,
+                    info: formData.info,
+                    reward_magic: Number(formData.reward_magic),
+                    deadline: formData.deadline,
+                }),
+            });
+
+            if (response.ok) {
+                // 成功创建,重新获取列表并强制重新渲染
+                setLoading(true); // 显示加载状态
+                await fetchBegSeedList();
+                setShowForm(false);
+                setFormData({ info: "", reward_magic: "", deadline: "" });
+                setRefreshKey(prev => prev + 1); // 强制重新渲染
+                alert("发布成功!");
+            } else {
+                const errorData = await response.json();
+                alert(`发布失败: ${errorData.message || '未知错误'}`);
+            }
+        } catch (err) {
+            console.error('发布求种任务失败:', err);
+            // 如果后端调用失败,则使用前端演示逻辑
+            // setLoading(true); // 显示加载状态
+            // const newBegId = "beg" + Math.floor(Math.random() * 10000);
+            // setBegSeedList([
+            //     {
+            //         beg_id: newBegId,
+            //         info: formData.info,
+            //         beg_count: 1,
+            //         reward_magic: Number(formData.reward_magic),
+            //         deadline: formData.deadline,
+            //         has_match: 0,
+            //     },
+            //     ...begSeedList,
+            // ]);
+            // setLoading(false); // 隐藏加载状态
+            // setShowForm(false);
+            // setFormData({ info: "", reward_magic: "", deadline: "" });
+            // setRefreshKey(prev => prev + 1); // 强制重新渲染
+            // alert("发布成功(前端演示)");
+        }
     };
 
     return (
@@ -76,19 +158,42 @@
                 <HelpIcon style={{ verticalAlign: "middle", marginRight: 8 }} />
                 求种列表
             </h1>
+            
+            {/* 加载状态 */}
+            {loading && (
+                <div style={{ textAlign: "center", margin: "40px 0", color: "#666" }}>
+                    正在加载求种列表...
+                </div>
+            )}
+            
+            {/* 错误状态 */}
+            {error && (
+                <div style={{ 
+                    textAlign: "center", 
+                    margin: "20px 0", 
+                    padding: "10px", 
+                    background: "#ffebee", 
+                    color: "#c62828", 
+                    borderRadius: "4px" 
+                }}>
+                    加载失败: {error} (已显示默认数据)
+                </div>
+            )}
+            
             <div style={{ margin: "0 0 32px 0", textAlign: "center" }}>
                 <button
                     onClick={() => setShowForm(true)}
+                    disabled={loading}
                     style={{
                         fontSize: 18,
                         padding: "12px 36px",
-                        background: "linear-gradient(90deg, #42a5f5 0%, #1976d2 100%)",
+                        background: loading ? "#ccc" : "linear-gradient(90deg, #42a5f5 0%, #1976d2 100%)",
                         color: "#fff",
                         border: "none",
                         borderRadius: 8,
                         fontWeight: 600,
                         boxShadow: "0 2px 8px #b2d8ea",
-                        cursor: "pointer",
+                        cursor: loading ? "not-allowed" : "pointer",
                         transition: "background 0.2s",
                     }}
                 >
@@ -195,7 +300,7 @@
                     </form>
                 </div>
             )}
-            <div style={{ display: "flex", flexWrap: "wrap", gap: 24 }}>
+            <div key={refreshKey} style={{ display: "flex", flexWrap: "wrap", gap: 24 }}>
                 {begSeedList.map((beg) => {
                     const isExpired =
                         new Date(beg.deadline) < now || beg.has_match === 1;
diff --git a/front/src/HomePage.js b/front/src/HomePage.js
new file mode 100644
index 0000000..a657a72
--- /dev/null
+++ b/front/src/HomePage.js
@@ -0,0 +1,126 @@
+import React from "react";
+import HomeIcon from "@mui/icons-material/Home";
+import MovieIcon from "@mui/icons-material/Movie";
+import EmailIcon from "@mui/icons-material/Email";
+import MusicNoteIcon from "@mui/icons-material/MusicNote";
+import EmojiPeopleIcon from "@mui/icons-material/EmojiPeople";
+import SportsEsportsIcon from "@mui/icons-material/SportsEsports";
+import SportsMartialArtsIcon from "@mui/icons-material/SportsMartialArts";
+import PersonIcon from "@mui/icons-material/Person";
+import AccountCircleIcon from "@mui/icons-material/AccountCircle";
+import ForumIcon from "@mui/icons-material/Forum";
+import HelpIcon from "@mui/icons-material/Help";
+import { useNavigate } from "react-router-dom";
+import "./App.css";
+
+// 导航栏
+const navItems = [
+    { label: "首页", icon: <HomeIcon />, path: "/home" },
+    { label: "电影", icon: <MovieIcon />, path: "/movie" },
+    { label: "剧集", icon: <EmailIcon />, path: "/tv" },
+    { label: "音乐", icon: <MusicNoteIcon />, path: "/music" },
+    { label: "动漫", icon: <EmojiPeopleIcon />, path: "/anime" },
+    { label: "游戏", icon: <SportsEsportsIcon />, path: "/game" },
+    { label: "体育", icon: <SportsMartialArtsIcon />, path: "/sport" },
+    { label: "资料", icon: <PersonIcon />, path: "/info" },
+    { label: "论坛", icon: <ForumIcon />, path: "/forum" },
+    { label: "发布", icon: <AccountCircleIcon />, path: "/publish" },
+    { label: "求种", icon: <HelpIcon />, path: "/begseed" },
+];
+
+// 示例种子数据
+const exampleSeeds = [
+    {
+        id: 1,
+        tags: "电影,科幻",
+        title: "三体 1080P 蓝光",
+        popularity: 123,
+        user: { username: "Alice" },
+    },
+    {
+        id: 2,
+        tags: "动漫,热血",
+        title: "灌篮高手 国语配音",
+        popularity: 88,
+        user: { username: "Bob" },
+    },
+    {
+        id: 3,
+        tags: "音乐,流行",
+        title: "周杰伦-稻香",
+        popularity: 56,
+        user: { username: "Jay" },
+    },
+    {
+        id: 4,
+        tags: "剧集,悬疑",
+        title: "隐秘的角落",
+        popularity: 77,
+        user: { username: "小明" },
+    },
+];
+
+export default function HomePage() {
+    const navigate = useNavigate();
+
+    return (
+        <div className="container">
+            {/* 顶部空白与电影界面一致 */}
+            <div style={{ height: 80 }} />
+            {/* 用户栏 */}
+            <div className="user-bar" style={{ position: 'fixed', top: 18, right: 42, zIndex: 100, display: 'flex', alignItems: 'center', background: '#e0f3ff', borderRadius: 12, padding: '6px 18px', boxShadow: '0 2px 8px #b2d8ea', minWidth: 320, minHeight: 48, width: 420 }}>
+                <div style={{ cursor: 'pointer', marginRight: 16 }} onClick={() => navigate('/user')}>
+                    <AccountCircleIcon style={{ fontSize: 38, color: '#1a237e', background: '#e0f3ff', borderRadius: '50%' }} />
+                </div>
+                <div style={{ color: '#222', fontWeight: 500, marginRight: 24 }}>用户栏</div>
+                <div style={{ display: 'flex', gap: 28, flex: 1, justifyContent: 'flex-end', alignItems: 'center' }}>
+                    <span style={{ color: '#1976d2', fontWeight: 500 }}>魔力值: <b>12345</b></span>
+                    <span style={{ color: '#1976d2', fontWeight: 500 }}>分享率: <b>2.56</b></span>
+                    <span style={{ color: '#1976d2', fontWeight: 500 }}>上传量: <b>100GB</b></span>
+                    <span style={{ color: '#1976d2', fontWeight: 500 }}>下载量: <b>50GB</b></span>
+                </div>
+            </div>
+            {/* 下方内容整体下移,留出与电影界面一致的间距 */}
+            <div style={{ height: 32 }} />
+            <nav className="nav-bar card">
+                {navItems.map((item) => (
+                    <div
+                        key={item.label}
+                        className={item.label === "首页" ? "nav-item active" : "nav-item"}
+                        onClick={() => navigate(item.path)}
+                    >
+                        {item.icon}
+                        <span>{item.label}</span>
+                    </div>
+                ))}
+            </nav>
+            <div className="table-section card">
+                <table className="movie-table">
+                    <thead>
+                        <tr>
+                            <th>标签</th>
+                            <th>标题</th>
+                            <th>热度</th>
+                            <th>发布者</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                        {exampleSeeds.map((seed) => (
+                            <tr key={seed.id}>
+                                <td>{seed.tags}</td>
+                                <td>
+                                    <a href={`/torrent/${seed.id}`} style={{ color: '#1a237e', textDecoration: 'none' }}>
+                                        {seed.title}
+                                    </a>
+                                </td>
+                                <td>{seed.popularity}</td>
+                                <td>{seed.user.username}</td>
+                            </tr>
+                        ))}
+                    </tbody>
+                </table>
+            </div>
+            <div style={{ height: 32 }} />
+        </div>
+    );
+}
\ No newline at end of file
diff --git a/front/src/LoginPage.js b/front/src/LoginPage.js
index 492c018..31718d1 100644
--- a/front/src/LoginPage.js
+++ b/front/src/LoginPage.js
@@ -54,7 +54,7 @@
         return;

       }

       setErrorMessage('');

-      navigate('/movie');

+      navigate('/home');

     } catch (error) {

       setErrorMessage('网络错误,请稍后重试');

     }

diff --git a/front/src/MigrationPage.js b/front/src/MigrationPage.js
index f29a534..1a9f79b 100644
--- a/front/src/MigrationPage.js
+++ b/front/src/MigrationPage.js
@@ -32,6 +32,7 @@
             const res = await fetch(`${API_BASE_URL}/api/migrations`);
             if (!res.ok) throw new Error(`请求失败,状态码 ${res.status}`);
             const data = await res.json();
+            console.log("Fetched migrations:", data);
             const formatted = data.map(item => ({
                 migration_id: item.profileurl,
                 user_id: item.user.userid,
diff --git a/front/src/SeedPromotionPage.js b/front/src/SeedPromotionPage.js
new file mode 100644
index 0000000..249533c
--- /dev/null
+++ b/front/src/SeedPromotionPage.js
@@ -0,0 +1,164 @@
+import React, { useEffect, useState } from "react";
+import { API_BASE_URL } from "./config";
+
+// 示例数据,实际应从后端获取
+const mockSeeds = [
+    {
+        seed_id: "seed001",
+        title: "三体 1080P 蓝光",
+        tags: "科幻,电影",
+        popularity: 123,
+        promotion: {
+            start_time: "",
+            end_time: "",
+            discount: 1,
+        },
+    },
+    {
+        seed_id: "seed002",
+        title: "灌篮高手 国语配音",
+        tags: "动画,体育",
+        popularity: 88,
+        promotion: {
+            start_time: "",
+            end_time: "",
+            discount: 1,
+        },
+    },
+];
+
+export default function SeedPromotionPage() {
+    const [seeds, setSeeds] = useState([]);
+    const [currentTime, setCurrentTime] = useState("");
+
+    useEffect(() => {
+        // 获取当前时间,格式为 yyyy-MM-ddTHH:mm
+        const now = new Date();
+        const pad = (n) => n.toString().padStart(2, "0");
+        const localISOTime = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}`;
+        setCurrentTime(localISOTime);
+
+        // 实际应从后端获取种子及促销信息
+        setSeeds(mockSeeds);
+    }, []);
+
+    // 输入框变更处理
+    const handlePromotionChange = (seedId, field, value) => {
+        setSeeds((prev) =>
+            prev.map((s) =>
+                s.seed_id === seedId
+                    ? {
+                        ...s,
+                        promotion: {
+                            ...s.promotion,
+                            [field]: value,
+                        },
+                    }
+                    : s
+            )
+        );
+    };
+
+    // 结束时间校验
+    const isEndTimeInvalid = (start, end) => {
+        return start && end && end < start;
+    };
+
+    return (
+        <div style={{ padding: 40, maxWidth: 900, margin: "0 auto" }}>
+            <h1 style={{ textAlign: "center", marginBottom: 32 }}>种子促销管理</h1>
+            <table style={{ width: "100%", background: "#fff", borderRadius: 10, boxShadow: "0 2px 8px #e0e7ff" }}>
+                <thead>
+                    <tr style={{ background: "#f5f5f5" }}>
+                        <th>标题</th>
+                        <th>标签</th>
+                        <th>热度</th>
+                        <th>促销开始时间</th>
+                        <th>促销结束时间</th>
+                        <th>促销倍率</th>
+                        <th>操作</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    {seeds.map((seed) => {
+                        const { start_time, end_time, discount } = seed.promotion;
+                        const endTimeInvalid = isEndTimeInvalid(start_time, end_time);
+                        const canStartPromotion = start_time && end_time && !endTimeInvalid && discount >= 1;
+                        return (
+                            <tr key={seed.seed_id}>
+                                <td>{seed.title}</td>
+                                <td>{seed.tags}</td>
+                                <td>{seed.popularity}</td>
+                                <td>
+                                    <input
+                                        type="datetime-local"
+                                        value={start_time}
+                                        min={currentTime}
+                                        onChange={(e) =>
+                                            handlePromotionChange(seed.seed_id, "start_time", e.target.value)
+                                        }
+                                    />
+                                </td>
+                                <td>
+                                    <input
+                                        type="datetime-local"
+                                        value={end_time}
+                                        min={start_time || currentTime}
+                                        onChange={(e) =>
+                                            handlePromotionChange(seed.seed_id, "end_time", e.target.value)
+                                        }
+                                        style={endTimeInvalid ? { border: "1.5px solid #e53935" } : {}}
+                                    />
+                                    {endTimeInvalid && (
+                                        <div style={{ color: "#e53935", fontSize: 12 }}>
+                                            结束时间不能早于开始时间
+                                        </div>
+                                    )}
+                                </td>
+                                <td>
+                                    <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
+                                        <button
+                                            style={{ width: 28, height: 28, fontSize: 18, borderRadius: 4, border: "1px solid #ccc", background: "#f5f5f5", cursor: discount > 1 ? "pointer" : "not-allowed" }}
+                                            onClick={() =>
+                                                discount > 1 &&
+                                                handlePromotionChange(seed.seed_id, "discount", discount - 1)
+                                            }
+                                            disabled={discount <= 1}
+                                        >-</button>
+                                        <span style={{ minWidth: 24, textAlign: "center" }}>{discount}</span>
+                                        <button
+                                            style={{ width: 28, height: 28, fontSize: 18, borderRadius: 4, border: "1px solid #ccc", background: "#f5f5f5", cursor: "pointer" }}
+                                            onClick={() =>
+                                                handlePromotionChange(seed.seed_id, "discount", discount + 1)
+                                            }
+                                        >+</button>
+                                    </div>
+                                </td>
+                                <td>
+                                    <button
+                                        style={{
+                                            background: canStartPromotion ? "#1976d2" : "#ccc",
+                                            color: "#fff",
+                                            border: "none",
+                                            borderRadius: 6,
+                                            padding: "4px 16px",
+                                            cursor: canStartPromotion ? "pointer" : "not-allowed",
+                                            fontWeight: 600,
+                                        }}
+                                        disabled={!canStartPromotion}
+                                        onClick={() => {
+                                            // 这里可调用后端API开启促销
+                                            alert(`已为「${seed.title}」开启促销!`);
+                                        }}
+                                    >
+                                        开启促销
+                                    </button>
+                                </td>
+                            </tr>
+                        );
+                    })}
+                </tbody>
+            </table>
+        </div>
+    );
+}
\ No newline at end of file
diff --git a/front/src/TorrentDetailPage.js b/front/src/TorrentDetailPage.js
index cc2dd24..ab34d98 100644
--- a/front/src/TorrentDetailPage.js
+++ b/front/src/TorrentDetailPage.js
@@ -10,12 +10,13 @@
   const [error, setError] = React.useState(null);

   // 假设你从某个地方获取了 userId(例如登录状态、localStorage 等)

   const [userId] = React.useState('user1550e8400-e29b-41d4-a716-44665544000023'); // 替换为实际的用户 ID

+  const [isFavorite, setIsFavorite] = React.useState(false); // 收藏状态

 

   const handleClick = () => {

     // 构造下载 URL,包含 userId 和 torrentId 参数

     console.log(torrentId)

     const downloadUrl = `${API_BASE_URL}/api/get-torrent?userId=${encodeURIComponent(userId)}&torrentId=${encodeURIComponent(torrentId)}`;

-    

+

     // 发起 GET 请求下载文件

     fetch(downloadUrl)

       .then(response => {

@@ -41,6 +42,11 @@
       });

   };

 

+  // 收藏按钮示例逻辑(仅前端切换)

+  const handleFavorite = () => {

+    setIsFavorite(fav => !fav);

+  };

+

   React.useEffect(() => {

     setLoading(true);

     setError(null);

@@ -64,24 +70,65 @@
   if (!detail) return <div className="container"><h1>未找到详情</h1></div>;

 

   return (

-    <div className="container">

-      <h1>种子详情页</h1>

-      <h2 style={{ fontSize: 'inherit', fontWeight: 'normal', textAlign: 'left' }}>标题: {detail.title || `种子${torrentId}`}</h2>

-      <p style={{ fontSize: 'inherit', textAlign: 'left' }}>简介: {detail.description || `这是种子${torrentId}的详细信息。`}</p>

-      <div style={{ textAlign: 'center', marginTop: '20px' }}>

-        <button 

-          style={{ 

-            padding: '10px 20px', 

-            fontSize: '16px', 

-            cursor: 'pointer', 

-            backgroundColor: '#d3f0ff', 

-            border: 'none', 

-            borderRadius: '4px' 

-          }} 

-          onClick={handleClick}

-        >

-          下载

-        </button>

+    <div className="container" style={{ display: "flex", justifyContent: "center", alignItems: "center", minHeight: "100vh" }}>

+      <div

+        style={{

+          background: "#fff",

+          borderRadius: 16,

+          boxShadow: "0 4px 24px #e0e7ff",

+          padding: "36px 48px",

+          maxWidth: 540,

+          width: "100%",

+          marginTop: 48,

+        }}

+      >

+        <h1 style={{ color: "#1976d2", fontWeight: 700, marginBottom: 24, fontSize: 28, letterSpacing: 1 }}>种子详情页</h1>

+        <div style={{ marginBottom: 18 }}>

+          <div style={{ fontSize: 20, fontWeight: 600, marginBottom: 8, color: "#222" }}>

+            标题:{detail.title || `种子${torrentId}`}

+          </div>

+          <div style={{ fontSize: 16, color: "#555", marginBottom: 8 }}>

+            简介:{detail.description || `这是种子${torrentId}的详细信息。`}

+          </div>

+        </div>

+        <div style={{ display: "flex", gap: 24, marginTop: 32, justifyContent: "center" }}>

+          <button

+            style={{

+              padding: "10px 32px",

+              fontSize: "16px",

+              cursor: "pointer",

+              background: "linear-gradient(90deg, #42a5f5 0%, #1976d2 100%)",

+              color: "#fff",

+              border: "none",

+              borderRadius: "8px",

+              fontWeight: 600,

+              boxShadow: "0 2px 8px #b2d8ea",

+              transition: "background 0.2s",

+            }}

+            onClick={handleClick}

+          >

+            下载

+          </button>

+          <button

+            style={{

+              padding: "10px 32px",

+              fontSize: "16px",

+              cursor: "pointer",

+              background: isFavorite

+                ? "linear-gradient(90deg, #ffb74d 0%, #ff9800 100%)"

+                : "linear-gradient(90deg, #f0f0f0 0%, #bdbdbd 100%)",

+              color: isFavorite ? "#fff" : "#333",

+              border: "none",

+              borderRadius: "8px",

+              fontWeight: 600,

+              boxShadow: isFavorite ? "0 2px 8px #ffe0b2" : "0 2px 8px #e0e7ff",

+              transition: "background 0.2s",

+            }}

+            onClick={handleFavorite}

+          >

+            {isFavorite ? "已收藏" : "收藏"}

+          </button>

+        </div>

       </div>

     </div>

   );

diff --git a/front/src/UserProfile.js b/front/src/UserProfile.js
index f6ef9f4..eed84da 100644
--- a/front/src/UserProfile.js
+++ b/front/src/UserProfile.js
@@ -28,7 +28,7 @@
   const [userStats, setUserStats] = useState({

     magic: 0,

     upload: 0,

-    download: 0,

+    viptime: 0,

     ratio: 0,

   });

 

@@ -42,13 +42,18 @@
   const [exchangeResult, setExchangeResult] = useState(0);

 

   // 兑换比例

-  const exchangeRate = { uploaded: 10, downloaded: 10, vip_downloads: 100 };

+  const exchangeRate = { uploaded: 0.1, downloaded: 0.1, vip_downloads: 100 };

 

   // 用户申诉相关

   const [appealOpen, setAppealOpen] = useState(false);

   const [appealTitle, setAppealTitle] = useState('');

   const [appealFile, setAppealFile] = useState(null);

 

+  // 账号迁移相关

+  const [migrationOpen, setMigrationOpen] = useState(false);

+  const [migrationEmail, setMigrationEmail] = useState('');

+  const [migrationPassword, setMigrationPassword] = useState('');

+  const [migrationStatus, setMigrationStatus] = useState('');

   // 兑换结果计算

   React.useEffect(() => {

     if (!exchangeMagic || isNaN(exchangeMagic)) {

@@ -71,7 +76,7 @@
         const res = await fetch(`${API_BASE_URL}/api/user-profile?userid=${userid}`);

         if (res.ok) {

           const data = await res.json();

-          console.log("获取用户信息:", data);

+          // console.log("获取用户信息:", data);

           setUserInfo(data);

           setTempUserInfo(data);

         }

@@ -102,18 +107,30 @@
     };

     fetchUserSeeds();

   }, []);

-

-  // 收藏种子(示例数据)

+  // 获取收藏种子

   useEffect(() => {

-    setUserFavorites([

-      { seedid: 'fav1', title: '收藏种子1', tags: '标签A', downloadtimes: 10 },

-    ]);

+    const fetchUserFavorites = async () => {

+      const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');

+      const userid = match ? match[2] : null;

+      if (!userid) return;

+      try {

+        const res = await fetch(`${API_BASE_URL}/api/user-favorites?userid=${userid}`);

+        if (res.ok) {

+          const data = await res.json();

+          // console.log("获取收藏种子列表:", data);

+          setUserFavorites(data);

+        }

+      } catch (err) {

+        console.error("获取收藏种子列表失败", err);

+      }

+    };

+    fetchUserFavorites();

   }, []);

-

   // 获取活跃度

   useEffect(() => {

     const fetchUserStats = async () => {

-      const userid = "550e8400-e29b-41d4-a716-446655440000";

+      const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');

+      const userid = match ? match[2] : null;

       if (!userid) return;

       try {

         const res = await fetch(`${API_BASE_URL}/api/user-stats?userid=${userid}`);

@@ -171,57 +188,205 @@
   };

 

   // 邀请

-  const handleInvite = () => {

-    if (!inviteEmail) return;

+  const handleInvite = async () => {

+    if (!inviteEmail) {

+      setInviteStatus("请输入邀请邮箱");

+      return;

+    }

+    // 获取userid

+    const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');

+    const userid = match ? match[2] : null;

+    if (!userid) {

+      setInviteStatus("未获取到用户ID");

+      return;

+    }

     if (userInfo.invite_left <= 0) {

       setInviteStatus("邀请次数已用完");

       return;

     }

-    setInviteStatus("邀请成功!(示例,无后端)");

-    setUserInfo((prev) => ({

-      ...prev,

-      invite_left: prev.invite_left - 1,

-    }));

-    setTempUserInfo((prev) => ({

-      ...prev,

-      invite_left: prev.invite_left - 1,

-    }));

-    setInviteEmail('');

-  };

-

-  // 兑换

-  const handleExchange = () => {

+    try {

+        const res = await fetch(`${API_BASE_URL}/api/invite`, {

+        method: 'POST',

+        headers: { 'Content-Type': 'application/json' },

+        body: JSON.stringify({ userid, invite_email: inviteEmail }),

+      });

+      if (res.ok) {

+        const data = await res.json();

+        setInviteStatus("邀请成功");

+        // 更新剩余次数

+        const left = data.invite_left !== undefined ? data.invite_left : userInfo.invite_left - 1;

+        setUserInfo(prev => ({ ...prev, invite_left: left }));

+        setTempUserInfo(prev => ({ ...prev, invite_left: left }));

+        setInviteEmail('');

+      } else {

+        const errorText = await res.text();

+        setInviteStatus("邀请失败:" + errorText);

+      }

+    } catch (err) {

+      console.error("邀请失败", err);

+      setInviteStatus("邀请失败,请检查网络");

+    }

+  };  // 兑换

+  const handleExchange = async () => {

     const magic = Number(exchangeMagic);

     if (!magic || isNaN(magic) || magic <= 0) return;

     if (magic > userStats.magic) {

       alert("魔力值不足!");

       return;

     }

-    let newStats = { ...userStats };

-    if (exchangeType === "uploaded") {

-      newStats.upload += magic / exchangeRate.uploaded;

-    } else if (exchangeType === "downloaded") {

-      newStats.download = Math.max(0, newStats.download - magic / exchangeRate.downloaded);

-    } else if (exchangeType === "vip_downloads") {

-      newStats.vip_downloads += magic / exchangeRate.vip_downloads;

+    

+    // 检查兑换结果是否为整数

+    const calculatedExchangeResult = magic / exchangeRate[exchangeType];

+    if (!Number.isInteger(calculatedExchangeResult)) {

+      alert("兑换结果必须为整数,请调整魔力值!");

+      return;

     }

-    newStats.magic -= magic;

-    setUserStats(newStats);

-    setExchangeMagic('');

-    alert("兑换成功!(示例,无后端)");

-  };

+    

+    // 获取userid

+    const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');

+    const userid = match ? match[2] : null;

+    if (!userid) {

+      alert("未获取到用户ID");

+      return;

+    }

 

+    console.log("兑换请求参数:", { userid, magic, exchangeType, exchangeResult: calculatedExchangeResult });

+    try {

+      // 发送兑换请求到后端

+      const res = await fetch(`${API_BASE_URL}/api/exchange`, {

+        method: 'POST',

+        headers: { 'Content-Type': 'application/json' },

+        body: JSON.stringify({ 

+          userid, 

+          magic, 

+          exchangeType,

+          exchangeResult: calculatedExchangeResult

+        }),

+      });

+      // console.log("兑换请求结果:", res);

+      if (res.ok) {

+        // 兑换成功后重新获取用户数据

+        const statsRes = await fetch(`${API_BASE_URL}/api/user-stats?userid=${userid}`);

+        if (statsRes.ok) {

+          const updatedStats = await statsRes.json();

+          setUserStats(updatedStats);

+        }

+        setExchangeMagic('');

+        alert("兑换成功!");

+      } else {

+        const errorText = await res.text();

+        alert("兑换失败:" + errorText);

+      }

+    } catch (err) {

+      console.error("兑换失败", err);

+      alert("兑换失败,请检查网络");

+    }

+  };

   // 删除种子

   const handleDeleteSeed = (seedid) => {

     setUserSeeds(userSeeds.filter((s) => s.seedid !== seedid));

   };

 

+  // 取消收藏

+  const handleRemoveFavorite = async (seedid) => {

+    const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');

+    const userid = match ? match[2] : null;

+    if (!userid) {

+      alert('未获取到用户ID');

+      return;

+    }

+    try {

+      const res = await fetch(`${API_BASE_URL}/api/remove-favorite`, {

+        method: 'POST',

+        headers: { 'Content-Type': 'application/json' },

+        body: JSON.stringify({ userid, seedid }),

+      });      if (res.ok) {

+        setUserFavorites(userFavorites.filter((s) => (s.seedid || s.seed_id) !== seedid));

+        alert('已取消收藏');

+      } else {

+        alert('取消收藏失败,请重试');

+      }

+    } catch (err) {

+      console.error('取消收藏失败', err);

+      alert('取消收藏失败,请检查网络');

+    }

+  };

+

   // 申诉提交逻辑

-  const handleAppealSubmit = () => {

-    alert('申诉已提交!(示例,无后端)');

-    setAppealOpen(false);

-    setAppealTitle('');

-    setAppealFile(null);

+  const handleAppealSubmit = async () => {

+    if (!appealTitle || !appealFile) return;

+    // 获取userid

+    const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');

+    const userid = match ? match[2] : null;

+    if (!userid) {

+      alert('未获取到用户ID');

+      return;

+    }

+    // 构建表单数据

+    const formData = new FormData();

+    formData.append('userid', userid);

+    formData.append('content', appealTitle);

+    formData.append('file', appealFile);

+    try {

+      const res = await fetch(`${API_BASE_URL}/api/submit-appeal`, {

+        method: 'POST',

+        body: formData,

+      });

+      if (res.ok) {

+        alert('申诉已提交');

+        setAppealOpen(false);

+        setAppealTitle('');

+        setAppealFile(null);

+      } else {

+        const errorText = await res.text();

+        alert('申诉失败:' + errorText);

+      }

+    } catch (err) {

+      console.error('申诉失败', err);

+      alert('申诉失败,请检查网络');

+    }

+  };

+  // 账号迁移提交逻辑

+  const handleMigrationSubmit = async () => {

+    if (!appealFile) {

+      setMigrationStatus('请选择PDF文件');

+      return;

+    }

+    

+    // 获取当前用户ID

+    const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');

+    const currentUserId = match ? match[2] : null;

+    if (!currentUserId) {

+      setMigrationStatus('未获取到当前用户ID');

+      return;

+    }

+    

+    try {

+      // 构建表单数据

+      const formData = new FormData();

+      formData.append('userid', currentUserId);

+      formData.append('file', appealFile);

+      

+      const res = await fetch(`${API_BASE_URL}/api/migrate-account`, {

+        method: 'POST',

+        body: formData,

+      });

+      

+      if (res.ok) {

+        setMigrationStatus('账号迁移申请已提交,请等待管理员审核');

+        setTimeout(() => {

+          setMigrationOpen(false);

+          setAppealFile(null);

+          setMigrationStatus('');

+        }, 2000);

+      } else {

+        const errorText = await res.text();

+        setMigrationStatus('迁移失败:' + errorText);

+      }

+    } catch (err) {

+      console.error('账号迁移失败', err);

+      setMigrationStatus('迁移失败,请检查网络');

+    }

   };

 

   return (

@@ -369,20 +534,25 @@
               <MenuItem value="m">男性</MenuItem>

               <MenuItem value="f">女性</MenuItem>

             </TextField>

-          </div>

-          <div style={{ display: 'flex', gap: 16, marginTop: 24, justifyContent: 'flex-end' }}>

+          </div>          <div style={{ display: 'flex', gap: 16, marginTop: 24, justifyContent: 'flex-end' }}>

             <Button

               variant="contained"

               color="primary"

               onClick={handleSave}

-              sx={{ fontSize: 16, borderRadius: 2, padding: '10px 24px' }}

+              sx={{ fontSize: 16, borderRadius: 2, padding: '6px 12px' }}

             >保存</Button>

             <Button

               variant="contained"

               color="error"

               onClick={() => setAppealOpen(true)}

-              sx={{ fontSize: 16, borderRadius: 2, padding: '10px 24px' }}

+              sx={{ fontSize: 16, borderRadius: 2, padding: '6px 12px' }}

             >用户申诉</Button>

+            <Button

+              variant="contained"

+              color="warning"

+              onClick={() => setMigrationOpen(true)}

+              sx={{ fontSize: 16, borderRadius: 2, padding: '6px 12px' }}

+            >账号迁移</Button>

           </div>

         </div>

       </div>

@@ -432,11 +602,14 @@
               <MenuItem value="uploaded">上传量(增加)</MenuItem>

               <MenuItem value="downloaded">下载量(减少)</MenuItem>

               <MenuItem value="vip_downloads">VIP下载次数(增加)</MenuItem>

-            </TextField>

-            <span style={{ marginLeft: 8, color: '#43a047' }}>

-              可兑换:<b>{exchangeResult}</b> {exchangeType === 'vip_downloads' ? '次' : 'GB'}

-            </span>

-            <Button

+            </TextField>            <span style={{ marginLeft: 8, color: '#43a047' }}>

+              可兑换:<b>{exchangeResult}</b> {exchangeType === 'vip_downloads' ? '次' : 'MB'}

+              {!Number.isInteger(exchangeResult) && exchangeResult > 0 && (

+                <span style={{ color: '#e53935', fontSize: '12px', marginLeft: 8 }}>

+                  (结果必须为整数)

+                </span>

+              )}

+            </span><Button

               variant="contained"

               color="primary"

               onClick={handleExchange}

@@ -444,19 +617,19 @@
                 !exchangeMagic ||

                 isNaN(exchangeMagic) ||

                 Number(exchangeMagic) <= 0 ||

-                Number(exchangeMagic) > userStats.magic

+                Number(exchangeMagic) > userStats.magic ||

+                !Number.isInteger(exchangeResult)

               }

               sx={{

                 marginLeft: 2,

                 minWidth: 80,

-                background: (!exchangeMagic || isNaN(exchangeMagic) || Number(exchangeMagic) <= 0 || Number(exchangeMagic) > userStats.magic) ? '#ccc' : undefined

+                background: (!exchangeMagic || isNaN(exchangeMagic) || Number(exchangeMagic) <= 0 || Number(exchangeMagic) > userStats.magic || !Number.isInteger(exchangeResult)) ? '#ccc' : undefined

               }}

             >兑换</Button>

-          </div>

-          <div>上传量:<b style={{ color: '#43a047' }}>{userStats.upload?.toFixed(2)} GB</b></div>

-          <div>下载量:<b style={{ color: '#e53935' }}>{userStats.download?.toFixed(2)} GB</b></div>

+          </div>          <div>上传量:<b style={{ color: '#43a047' }}>{(userStats.upload / 1000000)?.toFixed(2)} MB</b></div>

+          <div>下载量:<b style={{ color: '#e53935' }}>{(userStats.download / 1000000)?.toFixed(2)} MB</b></div>

           <div>上传/下载值:<b style={{ color: '#ff9800' }}>{userStats.download === 0 ? "∞" : (userStats.upload / userStats.download).toFixed(2)}</b></div>

-          <div>VIP下载次数:<b style={{ color: '#1976d2' }}>{userStats.vip_downloads}</b></div>

+          <div>VIP下载次数:<b style={{ color: '#1976d2' }}>{userStats.viptime}</b></div>

         </div>

       </div>

       {/* 右上:个人上传种子列表 */}

@@ -509,11 +682,14 @@
                     variant="contained"

                     color="error"

                     size="small"

-                    sx={{ marginLeft: 2, borderRadius: 1, minWidth: 60 }}

-                    onClick={async e => {

+                    sx={{ marginLeft: 2, borderRadius: 1, minWidth: 60 }}                    onClick={async e => {

                       e.stopPropagation();

-                      // const userid = localStorage.getItem("userid");

-                      const userid = "550e8400-e29b-41d4-a716-446655440000"; // 示例userid

+                      const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');

+                      const userid = match ? match[2] : null;

+                      if (!userid) {

+                        alert('未获取到用户ID');

+                        return;

+                      }

                       try {

                         const res = await fetch(`${API_BASE_URL}/api/delete-seed`, {

                           method: 'POST',

@@ -558,8 +734,7 @@
         }}>

           {userFavorites.length === 0 ? (

             <div style={{ color: '#b2b2b2', fontSize: 18, textAlign: 'center' }}>(暂无收藏种子)</div>

-          ) : (

-            <ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>

+          ) : (            <ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>

               {userFavorites.map((seed, idx) => (

                 <li

                   key={seed.seedid || idx}

@@ -570,16 +745,25 @@
                     borderBottom: idx === userFavorites.length - 1 ? 'none' : '1px solid #e0e7ff',

                     cursor: 'pointer',

                     transition: 'background 0.15s'

-                  }}

-                  onClick={e => {

-                    navigate(`/torrent/${seed.seedid}`);

+                  }}                  onClick={e => {                    if (e.target.classList.contains('remove-favorite-btn')) return;

+                    navigate(`/torrent/${seed.seedid || seed.seed_id}`);

                   }}

                   onMouseOver={e => e.currentTarget.style.background = '#f3f6ff'}

                   onMouseOut={e => e.currentTarget.style.background = ''}

                 >

-                  <span style={{ flex: 2, fontWeight: 500, color: '#1a237e', textDecoration: 'underline' }}>{seed.title}</span>

-                  <span style={{ flex: 1, color: '#5c6bc0' }}>{seed.tags}</span>

-                  <span style={{ flex: 1, color: '#ff9800', textAlign: 'right' }}>人气: {seed.downloadtimes}</span>

+                  <span style={{ flex: 2, fontWeight: 500, color: '#1a237e', textDecoration: 'underline', cursor: 'pointer' }}>{seed.seed.title}</span>

+                  <span style={{ flex: 1, color: '#5c6bc0' }}>{seed.seed.tags}</span>

+                  <span style={{ flex: 1, color: '#ff9800', textAlign: 'right' }}>人气: {seed.seed.downloadtimes}</span>

+                  <Button

+                    className="remove-favorite-btn"

+                    variant="contained"

+                    color="warning"

+                    size="small"

+                    sx={{ marginLeft: 2, borderRadius: 1, minWidth: 80 }}                    onClick={e => {

+                      e.stopPropagation();

+                      handleRemoveFavorite(seed.seedid || seed.seed_id);

+                    }}

+                  >取消收藏</Button>

                 </li>

               ))}

             </ul>

@@ -598,19 +782,66 @@
               onChange={e => setAppealTitle(e.target.value)}

               size="small"

             />

-          </div>

-          <div>

+          </div>          <div>

             <input

               type="file"

-              onChange={e => setAppealFile(e.target.files[0])}

+              accept=".pdf"

+              onChange={e => {

+                const file = e.target.files[0];

+                if (file && file.type !== 'application/pdf') {

+                  alert('请选择PDF文件');

+                  e.target.value = '';

+                  setAppealFile(null);

+                } else {

+                  setAppealFile(file);

+                }

+              }}

               style={{ marginTop: 8 }}

             />

+            <div style={{ fontSize: 12, color: '#666', marginTop: 4 }}>

+              请选择PDF文件(最大100MB)

+            </div>

           </div>

         </DialogContent>

         <DialogActions>

           <Button onClick={handleAppealSubmit} variant="contained" color="primary" disabled={!appealTitle || !appealFile}>提交</Button>

           <Button onClick={() => setAppealOpen(false)} variant="outlined">取消</Button>

         </DialogActions>

+      </Dialog>      {/* 账号迁移弹窗 */}

+      <Dialog open={migrationOpen} onClose={() => setMigrationOpen(false)}>

+        <DialogTitle>账号迁移</DialogTitle>

+        <DialogContent>

+          <div style={{ marginBottom: 16 }}>

+          </div>          <div>

+            <input

+              type="file"

+              accept=".pdf"

+              onChange={e => {

+                const file = e.target.files[0];

+                if (file && file.type !== 'application/pdf') {

+                  alert('请选择PDF文件');

+                  e.target.value = '';

+                  setAppealFile(null);

+                } else {

+                  setAppealFile(file);

+                }

+              }}

+              style={{ marginTop: 8 }}

+            />

+            <div style={{ fontSize: 12, color: '#666', marginTop: 4 }}>

+              请选择PDF文件(最大10MB)

+            </div>

+          </div>

+          {migrationStatus && (

+            <div style={{ color: migrationStatus.includes('成功') ? '#43a047' : '#e53935', fontSize: 14, marginTop: 8 }}>

+              {migrationStatus}

+            </div>

+          )}

+        </DialogContent>

+        <DialogActions>

+          <Button onClick={handleMigrationSubmit} variant="contained" color="primary">提交迁移</Button>

+          <Button onClick={() => setMigrationOpen(false)} variant="outlined">取消</Button>

+        </DialogActions>

       </Dialog>

     </div>

   );

diff --git a/front/src/config.js b/front/src/config.js
index e28dd5f..0239e94 100644
--- a/front/src/config.js
+++ b/front/src/config.js
@@ -1 +1 @@
-export const API_BASE_URL = "http://10.126.59.25:8081";
\ No newline at end of file
+export const API_BASE_URL = "http://10.126.59.25:8082";
\ No newline at end of file
diff --git a/src/main/java/entity/SeedPromotion.java b/src/main/java/entity/SeedPromotion.java
new file mode 100644
index 0000000..7bea9df
--- /dev/null
+++ b/src/main/java/entity/SeedPromotion.java
@@ -0,0 +1,32 @@
+package entity;
+
+import javax.persistence.*;
+import java.util.Date;
+import com.querydsl.core.annotations.QueryEntity;
+
+@QueryEntity
+@Entity
+@Table(name = "SeedPromotion")
+public class SeedPromotion {
+
+    @Id
+    @Column(name = "promotion_id", length = 64, nullable = false)
+    public String promotionId;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "seed_id", referencedColumnName = "seed_id", foreignKey = @ForeignKey(name = "fk_seed_promotion"), nullable = false)
+    public Seed seed;
+
+    @Column(name = "start_time", nullable = false)
+    @Temporal(TemporalType.TIMESTAMP)
+    public Date startTime;
+
+    @Column(name = "end_time", nullable = false)
+    @Temporal(TemporalType.TIMESTAMP)
+    public Date endTime;
+
+    @Column(name = "discount", nullable = false)
+    public int discount;
+
+    public SeedPromotion() {}
+}
\ No newline at end of file
diff --git "a/\345\274\200\345\217\221\346\226\207\346\241\243/create.sql" "b/\345\274\200\345\217\221\346\226\207\346\241\243/create.sql"
index 12d9233..24fe897 100644
--- "a/\345\274\200\345\217\221\346\226\207\346\241\243/create.sql"
+++ "b/\345\274\200\345\217\221\346\226\207\346\241\243/create.sql"
@@ -188,4 +188,15 @@
     FOREIGN KEY (`user_id`) REFERENCES `User` (`user_id`) ON DELETE CASCADE,
     FOREIGN KEY (`beg_id`) REFERENCES `BegSeed` (`beg_id`) ON DELETE CASCADE,
     FOREIGN KEY (`seed_id`) REFERENCES `Seed` (`seed_id`) ON DELETE CASCADE
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
+
+-- 种子促销表
+CREATE TABLE `SeedPromotion` (
+    `promotion_id` VARCHAR(64) NOT NULL,
+    `seed_id` VARCHAR(64) NOT NULL,
+    `start_time` DATETIME NOT NULL,
+    `end_time` DATETIME NOT NULL,
+    `discount` TINYINT NOT NULL DEFAULT 1 COMMENT '折扣率, 1表示无折扣',
+    PRIMARY KEY (`promotion_id`),
+    FOREIGN KEY (`seed_id`) REFERENCES `Seed` (`seed_id`) ON DELETE CASCADE
 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
\ No newline at end of file