add download, upload
Change-Id: I8830fe9f86608b5c3df6d3720b625db797f3069a
diff --git a/src/app/resource/classification/page.tsx b/src/app/resource/classification/page.tsx
index ce9d43e..6b41969 100644
--- a/src/app/resource/classification/page.tsx
+++ b/src/app/resource/classification/page.tsx
@@ -61,6 +61,7 @@
const [selectedGameplay, setSelectedGameplay] = useState<string[]>([]);
const [selectedVersions, setSelectedVersions] = useState<string[]>([]);
const [searchValue, setSearchValue] = useState<string>('');
+
const debouncedSearch = useRef(
debounce((value: string) => {
setSearchValue(value);
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