新增求种和个人中心接口

Change-Id: Ibf3eef5b91a45a0ccf7b99d08afa29960884a8cf
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",