add download, upload

Change-Id: I8830fe9f86608b5c3df6d3720b625db797f3069a
diff --git a/src/app/user/page.tsx b/src/app/user/page.tsx
index 986f6ec..6f29b02 100644
--- a/src/app/user/page.tsx
+++ b/src/app/user/page.tsx
@@ -24,7 +24,7 @@
 // 页面跳转
 import { useRouter } from "next/navigation";
 // 类型转换
-import {toNumber} from "lodash";
+import { toNumber } from "lodash";
 // 分页
 import { Paginator, type PaginatorPageChangeEvent } from 'primereact/paginator';
 
@@ -86,11 +86,11 @@
 // 帖子
 interface Thread {
     threadId: number;
-    userId:  number;
-    threadPicture:   string;
+    userId: number;
+    threadPicture: string;
     title: string;
-    likes:  number;
-    createAt:  string;
+    likes: number;
+    createAt: string;
 }
 
 // 发布帖子列表
@@ -176,6 +176,8 @@
         setThreadFirst(event.first);
         setThreadRows(event.rows);
     };
+    const [torrentUrl, setTorrentUrl] = useState<string>(''); // 上传 .torrent 后得到的 URL
+
     // 悬赏分页
     const [rewardFirst, setRewardFirst] = useState(0);
     const [rewardRows, setRewardRows] = useState(5);
@@ -259,7 +261,7 @@
     const fetchHomePageThread = async () => {
         try {
             const response = await axios.get<ThreadList>(process.env.PUBLIC_URL + `/user/thread`, {
-                params: { userId: userId,  pageNumber: 1, rows: 3 }
+                params: { userId: userId, pageNumber: 1, rows: 3 }
             })
             console.log('获取主页发布帖子:', response.data);
             setHomePageThread(response.data);
@@ -279,12 +281,12 @@
         try {
             const pageNumber = threadFirst / threadRows + 1;
             const response = await axios.get<ThreadList>(process.env.PUBLIC_URL + `/user/thread`, {
-                params: { userId: userId,  pageNumber: pageNumber, rows: threadRows }
+                params: { userId: userId, pageNumber: pageNumber, rows: threadRows }
             })
             console.log('获取我的帖子:', response.data);
             setThreadList(response.data.records);
             setTotalThreads(response.data.total)
-        }catch (err) {
+        } catch (err) {
             console.error('获取我的帖子失败', err);
             toast.current?.show({ severity: 'error', summary: 'error', detail: '获取我的帖子失败' });
         }
@@ -299,12 +301,12 @@
         try {
             const pageNumber = rewardFirst / rewardRows + 1;
             const response = await axios.get<RewardList>(process.env.PUBLIC_URL + `/user/reward`, {
-                params: { userId: userId,  pageNumber: pageNumber, rows: rewardRows }
+                params: { userId: userId, pageNumber: pageNumber, rows: rewardRows }
             })
             console.log('获取我的悬赏:', response.data);
             setRewardList(response.data.rewardList);
             setTotalRewards(response.data.total)
-        }catch (err) {
+        } catch (err) {
             console.error('获取我的悬赏失败', err);
             toast.current?.show({ severity: 'error', summary: 'error', detail: '获取我的悬赏失败' });
         }
@@ -356,19 +358,19 @@
     // 删除悬赏弹窗
     const [deleteVisible, setDeleteVisible] = useState(false);
     // 要删除悬赏的id
-    const [deleteRewardId, setDeleteResourceId] =  useState<number>(0);
+    const [deleteRewardId, setDeleteResourceId] = useState<number>(0);
     // 处理删除悬赏接口
     const handleDeleteSubmit = async () => {
         try {
             // 发送DELETE请求
             const response = await axios.delete(process.env.PUBLIC_URL + `/reward`, {
-                params: {rewardId: deleteRewardId},
+                params: { rewardId: deleteRewardId },
             });
             console.log("用户" + userId + "要删除" + deleteRewardId + "号悬赏");
 
             if (response.status === 204) {
                 console.log("用户成功删除悬赏");
-                toast.current?.show({severity: 'success', summary: 'Success', detail: '删除悬赏成功'});
+                toast.current?.show({ severity: 'success', summary: 'Success', detail: '删除悬赏成功' });
                 setDeleteVisible(false);
                 // 重新拉取资源列表
                 fetchRewardList();
@@ -471,6 +473,24 @@
 
             if (response.status === 200) {
                 toast.current?.show({ severity: 'success', summary: 'Success', detail: '资源上传成功' });
+                console.log(torrentUrl)
+                // 上传资源文件
+                const btPayload = {
+                    torrentUrl: torrentUrl,
+                    infoHash: '8',
+                    uploadTime: currentDate,
+                    uploaderUserId: userId,
+                    resourceVersionId: 1
+                };
+
+                try {
+                    await axios.post(`${process.env.PUBLIC_URL}/file/bt`, btPayload);
+                    toast.current?.show({ severity: 'success', summary: '资源登记成功', detail: '已同步种子信息' });
+                } catch (btError) {
+                    console.error("种子登记失败:", btError);
+                    toast.current?.show({ severity: 'warn', summary: '资源已上传', detail: '但种子登记失败' });
+                }
+
                 // 上传成功
                 setVisible(false);
                 // 重置表单
@@ -495,6 +515,7 @@
                 setSelectedGameplay([]);
                 // 重置资源封面
                 setResourcePictureUrl('');
+                setTorrentUrl('');
             }
         } catch (error) {
             console.error('资源上传失败:', error);
@@ -661,7 +682,7 @@
                     <div className="resource-list">
                         {rewardList.map((rewardItem) => (
                             <Card key={rewardItem.rewardId} className="resources-list-card"
-                                  onClick={() => router.push(`/reward/reward-detail/${rewardItem.rewardId}`)}>
+                                onClick={() => router.push(`/reward/reward-detail/${rewardItem.rewardId}`)}>
                                 <Image alt="avatar"
                                        src={ "rewards/" + rewardItem.rewardPicture}
                                        className="resource-avatar" width="250" height="140"/>
@@ -693,7 +714,7 @@
                                                 setDeleteResourceId(rewardItem.rewardId);
                                                 setDeleteVisible(true);
                                             }}
-                                            style={{backgroundColor: "rgba(255, 87, 51, 1)"}}
+                                            style={{ backgroundColor: "rgba(255, 87, 51, 1)" }}
                                         />
                                     </div>
                                 </div>
@@ -952,10 +973,38 @@
                                 }
                             }}
                             auto
-                            accept="image/*"
                             chooseLabel="上传资源封面"
                         />
                     </div>
+                    <div className="form-field">
+                        <div className="form-field-header">
+                            <span className="form-field-sign">*</span>
+                            <label>上传资源文件</label>
+                        </div>
+                        <FileUpload
+                            mode="advanced"
+                            name="resource-file"
+                            customUpload
+                            uploadHandler={async (e) => {
+                                const formData = new FormData();
+                                formData.append("file", e.files[0]);
+
+                                try {
+                                    const res = await axios.post(`${process.env.PUBLIC_URL}/file`, formData);
+                                    const fileUrl = res.data.url;
+                                    console.log("上传的 torrent 文件 URL:", fileUrl);
+                                    setTorrentUrl(fileUrl);
+                                    toast.current?.show({ severity: 'success', summary: '上传成功', detail: '资源文件已上传' });
+                                } catch (error) {
+                                    console.error("上传资源文件失败:", error);
+                                    toast.current?.show({ severity: 'error', summary: '上传失败', detail: '资源文件上传失败' });
+                                }
+                            }}
+                            auto
+                            chooseLabel="上传资源文件(.torrent)"
+                        />
+                    </div>
+
                 </div>
             </Dialog>
 
@@ -974,7 +1023,7 @@
                 }
             >
                 <div className="dialog-form">
-                    <span style={{marginBottom: "10px"}}>
+                    <span style={{ marginBottom: "10px" }}>
                         确认是否删除该悬赏?
                     </span>
                 </div>
@@ -1004,7 +1053,7 @@
                             value={editRewardFormData.rewardName}
                             onChange={(e) => setEditRewardFormData(prev => ({
                                 ...prev,
-                                rewardName:  e.target.value
+                                rewardName: e.target.value
                             }))}
                             className="w-full"
                         />
@@ -1018,7 +1067,7 @@
                             value={editRewardFormData.price}
                             onChange={(e) => setEditRewardFormData(prev => ({
                                 ...prev,
-                                price:  e.target.value
+                                price: e.target.value
                             }))}
                             className="w-full"
                         />
@@ -1032,7 +1081,7 @@
                             value={editRewardFormData.rewardDescription}
                             onChange={(e) => setEditRewardFormData(prev => ({
                                 ...prev,
-                                rewardDescription:  e.target.value
+                                rewardDescription: e.target.value
                             }))}
                             rows={5}
                             className="w-full"
diff --git a/src/app/user/purchased-resources/page.tsx b/src/app/user/purchased-resources/page.tsx
index c48b626..11284ff 100644
--- a/src/app/user/purchased-resources/page.tsx
+++ b/src/app/user/purchased-resources/page.tsx
@@ -1,12 +1,169 @@
 'use client';
-import React from 'react';
+import React, { useEffect, useState, useRef } from "react";
+import { Card } from 'primereact/card';
+import { Image } from 'primereact/image';
 
-const EmptyPage: React.FC = () => {
+import { Dialog } from 'primereact/dialog';
+// 页面跳转
+import { useRouter } from 'next/navigation';
+// 消息提醒
+import { Toast } from 'primereact/toast';
+// 分页
+import { Paginator, type PaginatorPageChangeEvent } from 'primereact/paginator';
+
+import { Button } from 'primereact/button';
+// 接口传输
+import axios from 'axios';
+// 标签
+import { Tag } from 'primereact/tag';
+
+
+import { useLocalStorage } from '../../hook/useLocalStorage';
+
+import "./purchased-resource.scss"
+interface User {
+  Id: number;
+}
+// 热门资源数据
+interface Torrent {
+  torrentRecordId: number;
+  torrentUrl: string;
+  infoHash: string;
+  uploadTime: string;
+  uploaderUserId: number;
+}
+
+interface ResourceVersion {
+  resourceVersionId: number;
+  resourceVersionName: string;
+  compatibleVersions: string[];
+  torrentList: Torrent[];
+  seeds: number;
+}
+
+interface Resource {
+  resourceId: number;
+  resourceName: string;
+  resourcePicture: string;
+  resourceSummary: string;
+  lastUpdateTime: string;
+  hot: number;
+  gameplayList: string[];
+  resourceVersionList: ResourceVersion[];
+}
+interface ResourceList {
+  records: Resource[];
+  total: number;
+}
+
+export default function PurchasedResource() {
+  const user = useLocalStorage<User>('user');
+  const userId: number = user?.Id ?? -1;
+  // 热门资源列表
+  const [resources, setResources] = useState<Resource[]>([]);
+  const [totalResource, setTotalResource] = useState(0);
+  const [visible, setVisible] = useState<boolean>(false);
+  const [selectedResource, setSelectedResource] = useState<Resource | null>(null);
+  // 消息提醒
+  const toast = useRef<Toast>(null);
+  const router = useRouter();
+
+  // 分页
+  const [first, setFirst] = useState(0);
+  const [rows, setRows] = useState(5);
+  const onPageChange = (event: PaginatorPageChangeEvent) => {
+    setFirst(event.first);
+    setRows(event.rows);
+  };
+  // 获取帖子列表
+  useEffect(() => {
+    handleSearch();
+  }, [first, rows]);
+
+  const handleSearch = async () => {
+
+    try {
+      const pageNumber = first / rows + 1;
+      const response = await axios.get<ResourceList>(process.env.PUBLIC_URL + `/user/purchase`, {
+        params: {
+          userId,
+          pageNumber,
+          rows
+        }
+      });
+      console.log(response.data.records);
+      setResources(response.data.records);
+      setTotalResource(response.data.total);
+    } catch (err) {
+      console.error('搜索资源失败', err);
+      toast.current?.show({ severity: 'error', summary: 'error', detail: '搜索资源失败' });
+    }
+  };
+
+
   return (
-    <div className="p-d-flex p-jc-center p-ai-center" style={{ height: '100vh' }}>
-      {"一个空页面"}
+    <div className="PurchasedResource">
+      <div className="header">
+        <h1>我的资源库</h1>
+      </div>
+      {/* 全部社区 */}
+      <div className="all-resources-list">
+        {resources.map((resource) => (
+          <Card key={resource.resourceId} className="all-resources-card">
+            <Image alt="avatar" src={process.env.NEXT_PUBLIC_NGINX_URL + "Resource/" + resource.resourcePicture} className="resource-avatar" width="250" height="140" onClick={() => router.push(`/resource/resource-detail/${resource.resourceId}`)} />
+            <div className="resource-header">
+              <div className="resource-content">
+                <h3>{resource.resourceName}</h3>
+                <div className="tags">
+                  {resource.gameplayList.map((tag, index) => (
+                    <Tag key={index} value={tag} />
+                  ))}
+                </div>
+              </div>
+              <div className="resources-states">
+                <Button
+                  label="下载"
+                  className="classificationButton"
+                  onClick={() => {
+                    setSelectedResource(resource);
+                    setVisible(true);
+                  }}
+                />
+              </div>
+            </div>
+          </Card>
+        ))}
+      </div>
+      {totalResource > 5 && (<Paginator className="Paginator" first={first} rows={rows} totalRecords={totalResource} rowsPerPageOptions={[5, 10]} onPageChange={onPageChange} />)}
+      <Dialog
+        header="选择版本并下载"
+        visible={visible}
+        style={{ width: '50vw' }}
+        onHide={() => {
+          setVisible(false);
+          setSelectedResource(null);
+        }}
+      >
+        {selectedResource?.resourceVersionList.map((version) => (
+          <div key={version.resourceVersionId} style={{ marginBottom: '1.5rem' }}>
+            <h4>{version.resourceVersionName}</h4>
+            <p>兼容版本: {version.compatibleVersions.join(', ')}</p>
+            <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
+              {version.torrentList.map((torrent) => (
+                <Button
+                  key={torrent.torrentRecordId}
+                  label={`种子:${torrent.torrentUrl}`}
+                  onClick={() => {
+                    window.open(torrent.torrentUrl, '_blank');
+                  }}
+                  className="p-button-sm"
+                />
+              ))}
+            </div>
+          </div>
+        ))}
+      </Dialog>
     </div>
   );
 };
 
-export default EmptyPage;
diff --git a/src/app/user/purchased-resources/purchased-resource.scss b/src/app/user/purchased-resources/purchased-resource.scss
new file mode 100644
index 0000000..5e466b6
--- /dev/null
+++ b/src/app/user/purchased-resources/purchased-resource.scss
@@ -0,0 +1,5 @@
+.PurchasedResource {
+    max-width: 1200px;
+    margin: 0 auto;
+    padding: 0 2rem;
+}
\ No newline at end of file