blob: 04020598ba5f5df54f45c8e4a154ac01e9883675 [file] [log] [blame]
"use client";
import React, { useEffect, useState, useRef } from "react";
import { TabView, TabPanel } from "primereact/tabview";
import { Avatar } from "primereact/avatar";
import { Button } from "primereact/button";
import { Card } from "primereact/card";
import { Image } from "primereact/image";
// 发布资源
import { Dialog } from "primereact/dialog";
import { InputText } from "primereact/inputtext";
import { InputTextarea } from "primereact/inputtextarea";
import { FileUpload } from "primereact/fileupload";
// 资源分类
import { RadioButton, RadioButtonChangeEvent } from "primereact/radiobutton";
// 资源标签
import { MultiSelect, MultiSelectChangeEvent } from "primereact/multiselect";
// 浮动按钮
import { SpeedDial } from "primereact/speeddial";
// 评分图标
import { Fire } from "@icon-park/react";
// 消息提醒
import { Toast } from "primereact/toast";
// 页面跳转
import { useRouter } from "next/navigation";
// 类型转换
import { toNumber } from "lodash";
// 分页
import { Paginator, type PaginatorPageChangeEvent } from "primereact/paginator";
// 接口传输
import axios from "axios";
import { useLocalStorage } from "../hook/useLocalStorage";
// 样式
import "./user.scss";
interface User {
Id: number;
}
// 用户信息
interface UserInfo {
userId: number;
username: string;
password: string;
avatar: string;
followerCount: number; // 粉丝数
subscriberCount: number; // 关注数
signature: string; // 个性签名
uploadAmount: number;
purchaseAmount: number;
credits: number;
}
// 用户数据
interface UserData {
subscriberCount: number; // 关注数
uploadAmount: number; // 上传量(资源个数)
beDownloadedAmount: number; // 上传资源被下载量
seedPercentageList: number[]; // 上传资源类型百分比列表,按材质包、模组、整合包、地图的顺序返回
}
// 用户发布过的资源
interface Resource {
resourceId: number;
resourceName: string;
resourcePicture: string;
resourceSummary: string; // 资源简介(一句话)
resourceDetail: string; // 资源介绍
uploadTime: string; // 上传时间
lastUpdateTime: string; // 最近更新时间
price: number;
downloads: number;
likes: number;
collections: number;
comments: number;
seeds: number; // 种子数
classify: string; // 资源分类(材质包:resourcePack,模组:mod,整合包:modPack ,地图:map
}
// 用户发布过的资源列表
interface ResourceList {
records: Resource[];
}
// 帖子
interface Thread {
threadId: number;
userId: number;
threadPicture: string;
title: string;
likes: number;
createAt: string;
}
// 发布帖子列表
interface ThreadList {
records: Thread[];
total: number;
pages: number;
current: number;
size: number;
}
// 悬赏
interface Reward {
rewardId: number;
userId: number;
rewardPicture: string;
rewardName: string;
createAt: string;
rewardDescription: string;
price: number;
}
// 我的悬赏列表
interface RewardList {
rewardList: Reward[];
total: number; // 总记录数
}
// 资源标签
interface GameplayOption {
name: string;
code: number;
}
// 资源标签选项
const gameplayOptions: GameplayOption[] = [
{ name: "科技", code: 1 },
{ name: "魔法", code: 2 },
{ name: "建筑", code: 3 },
{ name: "风景", code: 4 },
{ name: "竞技", code: 5 },
{ name: "生存", code: 6 },
{ name: "冒险", code: 7 },
{ name: "跑酷", code: 8 },
{ name: "艺术", code: 9 },
{ name: "剧情", code: 10 },
{ name: "社交", code: 11 },
{ name: "策略", code: 12 },
{ name: "极限", code: 13 },
];
export default function UserPage() {
const user = useLocalStorage<User>("user");
const userId: number = user?.Id ?? -1;
// 路由
const router = useRouter();
// 发布资源列表
const [resourceList, setResourceList] = useState<Resource[]>([]);
// 用户信息
const [userInfo, setUserInfo] = useState<UserInfo>();
// 用户数据
const [userData, setUserData] = useState<UserData>();
// 消息提醒
const toast = useRef<Toast>(null);
// 资源标签
const [selectedGameplay, setSelectedGameplay] = useState<GameplayOption[]>(
[]
);
// 资源封面路径
const [resourcePictureUrl, setResourcePictureUrl] = useState<string>("");
// 主页发布帖子列表
const [homePageThread, setHomePageThread] = useState<ThreadList>();
// 我的帖子列表
const [threadList, setThreadList] = useState<Thread[]>([]);
// 我的悬赏列表
const [rewardList, setRewardList] = useState<Reward[]>([]);
// 控制Tab切换
const [activeIndex, setActiveIndex] = useState(0);
// 帖子分页
const [threadFirst, setThreadFirst] = useState(0);
const [threadRows, setThreadRows] = useState(6);
const [totalThreads, setTotalThreads] = useState<number>(0);
const onThreadPageChange = (event: PaginatorPageChangeEvent) => {
setThreadFirst(event.first);
setThreadRows(event.rows);
};
const [torrentUrl, setTorrentUrl] = useState<string>(""); // 上传 .torrent 后得到的 URL
// 悬赏分页
const [rewardFirst, setRewardFirst] = useState(0);
const [rewardRows, setRewardRows] = useState(5);
const [totalRewards, setTotalRewards] = useState<number>(0);
const onRewardPageChange = (event: PaginatorPageChangeEvent) => {
setRewardFirst(event.first);
setRewardRows(event.rows);
};
useEffect(() => {
fetchUserInfo();
fetchUserData();
fetchResourceList();
}, []);
// 获取用户信息
const fetchUserInfo = async () => {
try {
const response = await axios.get<UserInfo>(
process.env.PUBLIC_URL + `/user/info`,
{
params: { userId },
}
);
console.log("获取用户信息:", response.data);
setUserInfo(response.data);
} catch (err) {
console.error("获取用户信息失败", err);
toast.current?.show({
severity: "error",
summary: "error",
detail: "获取用户信息失败",
});
}
};
// 获取用户数据
const fetchUserData = async () => {
try {
const response = await axios.get<UserData>(
process.env.PUBLIC_URL + `/user/data`,
{
params: { userId },
}
);
console.log("获取用户数据:", response.data);
setUserData(response.data);
} catch (err) {
console.error("获取用户数据失败", err);
toast.current?.show({
severity: "error",
summary: "error",
detail: "获取用户数据失败",
});
}
};
// 格式化数字显示 (3000 -> 3k)
const formatCount = (count?: number): string => {
if (count == null) return "0"; // 同时处理 undefined/null
const absCount = Math.abs(count); // 处理负数
const format = (num: number, suffix: string) => {
const fixed = num.toFixed(1);
return fixed.endsWith(".0")
? `${Math.floor(num)}${suffix}`
: `${fixed}${suffix}`;
};
if (absCount >= 1e6) return format(count / 1e6, "m");
if (absCount >= 1e3) return format(count / 1e3, "k");
return count.toString();
};
// 获取发布资源
const fetchResourceList = async () => {
try {
const response = await axios.get<ResourceList>(
process.env.PUBLIC_URL + `/user/upload`,
{
params: { userId, pageNumber: 1, rows: 3 },
}
);
console.log("获取发布资源列表:", response.data.records);
setResourceList(response.data.records);
} catch (err) {
console.error("获取发布资源失败", err);
toast.current?.show({
severity: "error",
summary: "error",
detail: "获取发布资源失败",
});
}
};
useEffect(() => {
fetchHomePageThread();
}, []);
// 获取主页部分的发布帖子
const fetchHomePageThread = async () => {
try {
const response = await axios.get<ThreadList>(
process.env.PUBLIC_URL + `/user/thread`,
{
params: { userId: userId, pageNumber: 1, rows: 3 },
}
);
console.log("获取主页发布帖子:", response.data);
setHomePageThread(response.data);
} catch (err) {
console.error("获取主页发布帖子失败", err);
toast.current?.show({
severity: "error",
summary: "error",
detail: "获取主页发布帖子失败",
});
}
};
useEffect(() => {
fetchThreadList();
}, [threadFirst, threadRows]);
// 获取用户发布的所有帖子
const fetchThreadList = async () => {
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 },
}
);
console.log("获取我的帖子:", response.data);
setThreadList(response.data.records);
setTotalThreads(response.data.total);
} catch (err) {
console.error("获取我的帖子失败", err);
toast.current?.show({
severity: "error",
summary: "error",
detail: "获取我的帖子失败",
});
}
};
useEffect(() => {
fetchRewardList();
}, [rewardFirst, rewardRows]);
// 获取用户发布的所有悬赏
const fetchRewardList = async () => {
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 },
}
);
console.log("获取我的悬赏:", response.data);
setRewardList(response.data.rewardList);
setTotalRewards(response.data.total);
} catch (err) {
console.error("获取我的悬赏失败", err);
toast.current?.show({
severity: "error",
summary: "error",
detail: "获取我的悬赏失败",
});
}
};
// 浮动按钮的子模块
const actions = [
{
template: () => (
<Button
label="管理资源"
onClick={() => router.push(`/user/manage/resources/`)}
/>
),
},
{
template: () => (
<Button
label="已购资源"
onClick={() => router.push(`/user/purchased-resources/`)}
/>
),
},
{
template: () => (
<Button label="发布资源" onClick={() => setVisible(true)} />
),
},
{
template: () => <Button label="编辑悬赏" />,
},
];
// 发布资源弹窗
const [visible, setVisible] = useState(false);
const [resourceFormData, setResourceFormData] = useState({
resource: {
resourceName: "",
resourcePicture: "",
resourceSummary: "",
resourceDetail: "",
uploadTime: "",
lastUpdateTime: "",
price: "",
classify: "",
},
resourceVersionName: "",
gameplayList: [""],
completeRewardId: null,
userId: 0,
});
const [ingredient, setIngredient] = useState<string>("");
// 删除悬赏弹窗
const [deleteVisible, setDeleteVisible] = useState(false);
// 要删除悬赏的id
const [deleteRewardId, setDeleteResourceId] = useState<number>(0);
// 处理删除悬赏接口
const handleDeleteSubmit = async () => {
try {
// 发送DELETE请求
const response = await axios.delete(process.env.PUBLIC_URL + `/reward`, {
params: { rewardId: deleteRewardId },
});
console.log("用户" + userId + "要删除" + deleteRewardId + "号悬赏");
if (response.status === 204) {
console.log("用户成功删除悬赏");
toast.current?.show({
severity: "success",
summary: "Success",
detail: "删除悬赏成功",
});
setDeleteVisible(false);
// 重新拉取资源列表
fetchRewardList();
}
} catch (error) {
console.error("资源删除失败:", error);
toast.current?.show({
severity: "error",
summary: "error",
detail: "密码错误,资源删除失败",
});
}
};
// 编辑悬赏弹窗
const [editVisible, setEditVisible] = useState(false);
// 悬赏封面路径
const [rewardPictureUrl, setRewardPictureUrl] = useState<string>("");
const [editRewardFormData, setEditRewardFormData] = useState({
rewardId: 0,
rewardName: "",
price: "",
rewardDescription: "",
});
// 处理编辑资源接口
const handleEditSubmit = async () => {
try {
const postData = {
rewardId: editRewardFormData.rewardId,
rewardName: editRewardFormData.rewardName,
rewardPicture: rewardPictureUrl,
price: toNumber(editRewardFormData.price),
rewardDescription: editRewardFormData.rewardDescription,
};
// 发送POST请求
const response = await axios.put(
process.env.PUBLIC_URL + "/reward/info",
postData
);
console.log("编辑悬赏的信息:", postData);
if (response.status === 200) {
toast.current?.show({
severity: "success",
summary: "Success",
detail: "悬赏编辑成功",
});
// 编辑成功
setEditVisible(false);
fetchRewardList();
}
} catch (error) {
console.error("悬赏编辑失败:", error);
toast.current?.show({
severity: "error",
summary: "error",
detail: "悬赏编辑失败",
});
}
};
// 上传资源接口
const handleSubmit = async () => {
try {
// 规定用户必须输入的内容
if (resourceFormData.resource.resourceName == "") {
toast.current?.show({
severity: "info",
summary: "error",
detail: "缺少资源名称",
});
return;
}
if (resourceFormData.resource.resourceSummary == "") {
toast.current?.show({
severity: "info",
summary: "error",
detail: "缺少资源简介",
});
return;
}
if (resourceFormData.resourceVersionName == "") {
toast.current?.show({
severity: "info",
summary: "error",
detail: "缺少资源版本名",
});
return;
}
if (resourceFormData.resource.price == "") {
toast.current?.show({
severity: "info",
summary: "error",
detail: "缺少资源价格",
});
return;
}
if (resourceFormData.resource.classify == "") {
toast.current?.show({
severity: "info",
summary: "error",
detail: "缺少资源分类",
});
return;
}
if (
resourceFormData.gameplayList.length === 1 &&
resourceFormData.gameplayList[0] === ""
) {
toast.current?.show({
severity: "info",
summary: "error",
detail: "缺少资源标签",
});
return;
}
if (resourcePictureUrl === "") {
toast.current?.show({
severity: "info",
summary: "error",
detail: "缺少资源封面",
});
return;
}
const currentDate = new Date().toISOString().split("T")[0];
const postData = {
resource: {
resourceName: resourceFormData.resource.resourceName,
resourcePicture: resourcePictureUrl,
resourceSummary: resourceFormData.resource.resourceSummary,
resourceDetail: resourceFormData.resource.resourceDetail,
uploadTime: currentDate,
lastUpdateTime: currentDate,
price: toNumber(resourceFormData.resource.price),
classify: resourceFormData.resource.classify,
},
gameplayList: resourceFormData.gameplayList,
resourceVersionName: resourceFormData.resourceVersionName,
completeRewardId: null,
userId, // 记得用户登录状态获取
};
// 发送POST请求
const response = await axios.post(
process.env.PUBLIC_URL + "/resource",
postData
);
console.log("上传资源的信息:", postData);
if (response.status === 200) {
toast.current?.show({
severity: "success",
summary: "Success",
detail: "资源上传成功",
});
console.log(torrentUrl);
try {
const versionResponse = await axios.post(
`${process.env.PUBLIC_URL}/resource/version`,
{
resourceVersionName: postData.resourceVersionName,
resourceId: Number(response.data),
}
);
// 上传资源文件
const btPayload = {
torrentUrl: torrentUrl,
infoHash: "8",
uploadTime: currentDate,
uploaderUserId: userId,
resourceVersionId: Number(versionResponse.data),
};
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);
// 重置表单
setResourceFormData({
resource: {
resourceName: "",
resourcePicture: "",
resourceSummary: "",
resourceDetail: "",
uploadTime: "",
lastUpdateTime: "",
price: "",
classify: "",
},
resourceVersionName: "",
gameplayList: [],
completeRewardId: null,
userId: 0,
});
// 重置资源分类
setIngredient("");
// 重置资源标签
setSelectedGameplay([]);
// 重置资源封面
setResourcePictureUrl("");
setTorrentUrl("");
}
} catch (error) {
console.error("资源上传失败:", error);
toast.current?.show({
severity: "error",
summary: "error",
detail: "资源上传失败",
});
}
};
return (
<div className="user-container">
<Toast ref={toast}></Toast>
{/*个人信息*/}
<div className="user-profile-card">
<Avatar
image={userInfo?.avatar}
className="user-avatar"
shape="circle"
/>
<div className="user-info">
<div className="user-detail-info">
<div className="name-container">
<h2 className="name">{userInfo?.username}</h2>
<span className="signature">{userInfo?.signature}</span>
</div>
<div className="stats-container">
<div className="stats">
<span className="stats-label">粉丝:</span>
<span className="stats-value">{userInfo?.followerCount}</span>
</div>
<div className="stats">
<span className="stats-label">累计上传量:</span>
<span className="stats-value">
{formatCount(userData?.uploadAmount)}
</span>
</div>
<div className="stats">
<span className="stats-label">关注:</span>
<span className="stats-value">{userInfo?.subscriberCount}</span>
</div>
<div className="stats">
<span className="stats-label">累计被下载量:</span>
<span className="stats-value">
{formatCount(userData?.beDownloadedAmount)}
</span>
</div>
</div>
</div>
<Button label="关注" className="action-button" />
</div>
</div>
{/*个人内容*/}
<TabView
activeIndex={activeIndex}
onTabChange={(e) => setActiveIndex(e.index)}
>
<TabPanel header="主页">
{/*发布资源*/}
<div className="homepage-item">
<div className="section-header">
<h1>发布资源</h1>
<Button
label="显示更多"
link
onClick={() => router.push("/user/manage/resources/")}
/>
</div>
<div className="resource-grid">
{resourceList.map((resourceList) => (
<Card
key={resourceList.resourceId}
className="resource-card"
onClick={() =>
router.push(
`/resource/resource-detail/${resourceList.resourceId}`
)
}
>
<Image
src={resourceList.resourcePicture}
alt={resourceList.resourceName}
width="368"
height="200"
/>
<div className="card-content">
<h3>{resourceList.resourceName}</h3>
<div className="view-count">
<Fire theme="outline" size="16" fill="#FF8D1A" />
<span>{resourceList.likes}</span>
</div>
</div>
</Card>
))}
</div>
</div>
{/*发布帖子*/}
<div className="homepage-item">
<div className="section-header">
<h1>发布帖子</h1>
<Button label="显示更多" link onClick={() => setActiveIndex(1)} />
</div>
<div className="resource-grid">
{homePageThread?.records.map((homePageThread) => (
<Card
key={homePageThread.threadId}
className="resource-card"
onClick={() =>
router.push(
`/community/thread-detail/${homePageThread.threadId}`
)
}
>
<Image
src={homePageThread.threadPicture}
alt={homePageThread.title}
width="368"
height="200"
/>
<div className="card-content">
<h3>{homePageThread.title}</h3>
<div className="view-count">
<Fire theme="outline" size="16" fill="#FF8D1A" />
<span>{homePageThread.likes}</span>
</div>
</div>
</Card>
))}
</div>
</div>
</TabPanel>
<TabPanel header="帖子">
{/*我的帖子*/}
<div className="homepage-item">
<div className="section-header">
<h1>我的帖子</h1>
</div>
<div className="resource-grid">
{threadList.map((threadList) => (
<Card
key={threadList.threadId}
className="resource-card"
onClick={() =>
router.push(
`/community/thread-detail/${threadList.threadId}`
)
}
>
<Image
src={threadList.threadPicture}
alt={threadList.title}
width="368"
height="200"
/>
<div className="card-content">
<h3>{threadList.title}</h3>
<div className="view-count">
<Fire theme="outline" size="16" fill="#FF8D1A" />
<span>{threadList.likes}</span>
</div>
</div>
</Card>
))}
</div>
{totalThreads > 6 && (
<Paginator
className="Paginator"
first={threadFirst}
rows={threadRows}
totalRecords={totalThreads}
rowsPerPageOptions={[6, 12]}
onPageChange={onThreadPageChange}
/>
)}
</div>
</TabPanel>
<TabPanel header="收藏"></TabPanel>
<TabPanel header="数据"></TabPanel>
<TabPanel header="悬赏">
<div className="section-header">
<h1>我的悬赏</h1>
</div>
<div className="resource-list">
{rewardList.map((rewardItem) => (
<Card
key={rewardItem.rewardId}
className="resources-list-card"
onClick={() =>
router.push(`/reward/reward-detail/${rewardItem.rewardId}`)
}
>
<Image
alt="avatar"
src={"rewards/" + rewardItem.rewardPicture}
className="resource-avatar"
width="250"
height="140"
/>
<div className="resource-header">
<div className="resource-content">
<h3>{rewardItem.rewardName}</h3>
</div>
<div className="resource-operation">
<Button
label="编辑"
onClick={(e) => {
e.stopPropagation(); // 关键修复:阻止事件冒泡,避免触发Card的点击事件
setEditVisible(true);
setEditRewardFormData({
rewardId: rewardItem.rewardId,
rewardName: rewardItem.rewardName,
price: rewardItem.price.toString(),
rewardDescription: rewardItem.rewardDescription,
});
console.log("用户编辑悬赏");
}}
/>
<Button
label="删除"
onClick={(e) => {
e.stopPropagation(); // 关键修复:阻止事件冒泡,避免触发Card的点击事件
setDeleteResourceId(rewardItem.rewardId);
setDeleteVisible(true);
}}
style={{ backgroundColor: "rgba(255, 87, 51, 1)" }}
/>
</div>
</div>
</Card>
))}
{totalRewards > 5 && (
<Paginator
className="Paginator"
first={rewardFirst}
rows={rewardRows}
totalRecords={totalRewards}
rowsPerPageOptions={[5, 10]}
onPageChange={onRewardPageChange}
/>
)}
</div>
</TabPanel>
</TabView>
{/*浮动按钮*/}
<div className="card">
<SpeedDial
model={actions}
direction="up"
style={{ position: "fixed", bottom: "2rem", right: "2rem" }}
showIcon="pi pi-plus"
hideIcon="pi pi-times"
buttonClassName="custom-speeddial-button"
/>
</div>
{/*发布资源弹窗*/}
<Dialog
header="发布资源"
visible={visible}
onHide={() => setVisible(false)}
className="publish-dialog"
modal
footer={
<div className="dialog-footer">
<Button
label="发布"
icon="pi pi-check"
onClick={handleSubmit}
autoFocus
/>
<Button
label="取消"
icon="pi pi-times"
onClick={() => setVisible(false)}
className="p-button-text"
/>
</div>
}
>
<div className="publish-form">
<div className="form-field">
<div className="form-field-header">
<span className="form-field-sign">*</span>
<label htmlFor="name">资源名称</label>
</div>
<InputText
id="name"
value={resourceFormData.resource.resourceName}
onChange={(e) =>
setResourceFormData((prev) => ({
...prev, // 复制顶层所有属性
resource: {
...prev.resource, // 复制resource对象的所有属性
resourceName: e.target.value, // 只更新resourceName
},
}))
}
placeholder="请输入资源名称"
className="w-full"
/>
</div>
<div className="form-field">
<div className="form-field-header">
<span className="form-field-sign">*</span>
<label htmlFor="summary">资源简介</label>
</div>
<InputText
id="summary"
value={resourceFormData.resource.resourceSummary}
onChange={(e) =>
setResourceFormData((prev) => ({
...prev, // 复制顶层所有属性
resource: {
...prev.resource, // 复制resource对象的所有属性
resourceSummary: e.target.value,
},
}))
}
placeholder="请输入资源简介(一句话)"
className="w-full"
/>
</div>
<div className="form-field">
<div className="form-field-header">
<span className="form-field-sign">*</span>
<label htmlFor="summary">资源版本名</label>
</div>
<InputText
id="summary"
value={resourceFormData.resourceVersionName}
onChange={(e) =>
setResourceFormData((prev) => ({
...prev, // 复制顶层所有属性
resourceVersionName: e.target.value,
}))
}
placeholder="请输入资源版本名"
className="w-full"
/>
</div>
<div className="form-field">
<div className="form-field-header">
<label htmlFor="detail">资源介绍</label>
</div>
<InputTextarea
id="detail"
value={resourceFormData.resource.resourceDetail}
onChange={(e) =>
setResourceFormData((prev) => ({
...prev, // 复制顶层所有属性
resource: {
...prev.resource, // 复制resource对象的所有属性
resourceDetail: e.target.value,
},
}))
}
rows={5}
placeholder="请输入资源介绍"
className="w-full"
/>
</div>
<div className="form-field">
<div className="form-field-header">
<span className="form-field-sign">*</span>
<label htmlFor="price">价格</label>
</div>
<InputText
id="price"
value={resourceFormData.resource.price}
onChange={(e) =>
setResourceFormData((prev) => ({
...prev, // 复制顶层所有属性
resource: {
...prev.resource, // 复制resource对象的所有属性
price: e.target.value,
},
}))
}
placeholder="请输入资源价格"
className="w-full"
/>
</div>
<div className="form-field">
<div className="form-field-header">
<span className="form-field-sign">*</span>
<label htmlFor="classify">资源分类(请选择一项)</label>
</div>
<div className="form-field-classify">
<div className="flex align-items-center">
<RadioButton
inputId="ingredient1"
name="pizza"
value="resourcePack"
onChange={(e: RadioButtonChangeEvent) => {
setResourceFormData((prev) => ({
...prev, // 复制顶层所有属性
resource: {
...prev.resource, // 复制resource对象的所有属性
classify: e.target.value,
},
}));
setIngredient(e.value);
console.log(ingredient);
// console.log(resourceFormData.resource.classify);
}}
checked={ingredient === "resourcePack"}
/>
<label htmlFor="ingredient1" className="ml-2">
材质包
</label>
</div>
<div className="flex align-items-center">
<RadioButton
inputId="ingredient2"
name="pizza"
value="modPack"
onChange={(e: RadioButtonChangeEvent) => {
setResourceFormData((prev) => ({
...prev, // 复制顶层所有属性
resource: {
...prev.resource, // 复制resource对象的所有属性
classify: e.target.value,
},
}));
setIngredient(e.value);
}}
checked={ingredient === "modPack"}
/>
<label htmlFor="ingredient2" className="ml-2">
整合包
</label>
</div>
<div className="flex align-items-center">
<RadioButton
inputId="ingredient3"
name="pizza"
value="mod"
onChange={(e: RadioButtonChangeEvent) => {
setResourceFormData((prev) => ({
...prev, // 复制顶层所有属性
resource: {
...prev.resource, // 复制resource对象的所有属性
classify: e.target.value,
},
}));
setIngredient(e.value);
}}
checked={ingredient === "mod"}
/>
<label htmlFor="ingredient3" className="ml-2">
模组
</label>
</div>
<div className="flex align-items-center">
<RadioButton
inputId="ingredient4"
name="pizza"
value="map"
onChange={(e: RadioButtonChangeEvent) => {
setResourceFormData((prev) => ({
...prev, // 复制顶层所有属性
resource: {
...prev.resource, // 复制resource对象的所有属性
classify: e.target.value,
},
}));
setIngredient(e.value);
}}
checked={ingredient === "map"}
/>
<label htmlFor="ingredient4" className="ml-2">
地图
</label>
</div>
</div>
</div>
<div className="form-field">
<div className="form-field-header">
<span className="form-field-sign">*</span>
<label htmlFor="gameplayList">资源标签</label>
</div>
<MultiSelect
value={selectedGameplay}
onChange={(e: MultiSelectChangeEvent) => {
const selectedOptions = e.value as GameplayOption[];
// 提取选中项的 name 属性组成字符串数组
const selectedNames = selectedOptions.map((item) => item.name);
setResourceFormData((prev) => ({
...prev,
gameplayList: selectedNames,
}));
setSelectedGameplay(selectedOptions);
}}
options={gameplayOptions}
display="chip"
optionLabel="name"
placeholder="请选择资源标签"
className="w-full md:w-20rem"
/>
</div>
<div className="form-field">
<div className="form-field-header">
<span className="form-field-sign">*</span>
<label>封面图片</label>
</div>
<FileUpload
mode="advanced"
name="resource-image"
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(fileUrl);
setResourcePictureUrl(fileUrl);
toast.current?.show({
severity: "success",
summary: "上传成功",
});
} catch (error) {
console.log(error);
toast.current?.show({
severity: "error",
summary: "上传失败",
});
}
}}
auto
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;
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>
{/*删除悬赏弹窗*/}
<Dialog
header="删除悬赏"
visible={deleteVisible}
onHide={() => setDeleteVisible(false)}
className="resource-delete-dialog"
modal
footer={
<div className="dialog-footer">
<Button
label="确认"
icon="pi pi-check"
onClick={handleDeleteSubmit}
autoFocus
/>
<Button
label="取消"
icon="pi pi-times"
onClick={() => setDeleteVisible(false)}
className="p-button-text"
/>
</div>
}
>
<div className="dialog-form">
<span style={{ marginBottom: "10px" }}>确认是否删除该悬赏?</span>
</div>
</Dialog>
{/*编辑资源弹窗*/}
<Dialog
header="编辑资源"
visible={editVisible}
onHide={() => setEditVisible(false)}
className="resource-edit-dialog"
modal
footer={
<div className="dialog-footer">
<Button
label="确认"
icon="pi pi-check"
onClick={handleEditSubmit}
autoFocus
/>
<Button
label="取消"
icon="pi pi-times"
onClick={() => setEditVisible(false)}
className="p-button-text"
/>
</div>
}
>
<div className="dialog-form">
<div className="form-field">
<div className="form-field-header">
<label htmlFor="name">更改标题</label>
</div>
<InputText
id="name"
value={editRewardFormData.rewardName}
onChange={(e) =>
setEditRewardFormData((prev) => ({
...prev,
rewardName: e.target.value,
}))
}
className="w-full"
/>
</div>
<div className="form-field">
<div className="form-field-header">
<label htmlFor="price">更改定价</label>
</div>
<InputText
id="price"
value={editRewardFormData.price}
onChange={(e) =>
setEditRewardFormData((prev) => ({
...prev,
price: e.target.value,
}))
}
className="w-full"
/>
</div>
<div className="form-field">
<div className="form-field-header">
<label htmlFor="description">更改需求</label>
</div>
<InputTextarea
id="description"
value={editRewardFormData.rewardDescription}
onChange={(e) =>
setEditRewardFormData((prev) => ({
...prev,
rewardDescription: e.target.value,
}))
}
rows={5}
className="w-full"
/>
</div>
<div className="form-field">
<div className="form-field-header">
<span className="form-field-sign">*</span>
<label>封面图片</label>
</div>
<FileUpload
mode="advanced"
name="reward-image"
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(fileUrl);
setRewardPictureUrl(fileUrl);
toast.current?.show({
severity: "success",
summary: "上传成功",
});
} catch (error) {
console.log(error);
toast.current?.show({
severity: "error",
summary: "上传失败",
});
}
}}
auto
accept="image/*"
chooseLabel="选择悬赏封面"
/>
</div>
</div>
</Dialog>
</div>
);
}