Merge "fix reward publish" into main
diff --git a/Dockerfile b/Dockerfile
index af2bef1..8be866d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -30,10 +30,10 @@
# 设置环境变量
ENV NODE_ENV=production
-ENV PORT=3000
+ENV PORT=3009
# 暴露端口
-EXPOSE 3000
+EXPOSE 3009
# 启动命令
CMD ["node", "server.js"]
\ No newline at end of file
diff --git a/src/app/reward/page.tsx b/src/app/reward/page.tsx
index 99d00ec..77d11a6 100644
--- a/src/app/reward/page.tsx
+++ b/src/app/reward/page.tsx
@@ -1,268 +1,392 @@
-'use client';
+"use client";
import React, { useEffect, useState, useRef } from "react";
-import { InputText } from 'primereact/inputtext';
-import { Button } from 'primereact/button';
-import { Card } from 'primereact/card';
-import { Image } from 'primereact/image';
+import { InputText } from "primereact/inputtext";
+import { Button } from "primereact/button";
+import { Card } from "primereact/card";
+import { Image } from "primereact/image";
// 页面跳转
-import { useRouter } from 'next/navigation';
+import { useRouter } from "next/navigation";
// 分页
-import { Paginator, type PaginatorPageChangeEvent } from 'primereact/paginator';
+import { Paginator, type PaginatorPageChangeEvent } from "primereact/paginator";
// 消息提醒
-import { Toast } from 'primereact/toast';
+import { Toast } from "primereact/toast";
// 发布帖子
-import { Dialog } from 'primereact/dialog';
-import { FileUpload } from 'primereact/fileupload';
-import { InputTextarea } from 'primereact/inputtextarea';
+import { Dialog } from "primereact/dialog";
+import { FileUpload } from "primereact/fileupload";
+import { InputTextarea } from "primereact/inputtextarea";
// 接口传输
-import axios from 'axios';
+import axios from "axios";
// 防抖函数
-import { debounce } from 'lodash';
-import { TabView, TabPanel } from 'primereact/tabview';
-import { useLocalStorage } from '../hook/useLocalStorage';
+import { debounce } from "lodash";
+import { TabView, TabPanel } from "primereact/tabview";
+import { useLocalStorage } from "../hook/useLocalStorage";
// 样式
-import './reward.scss';
+import "./reward.scss";
interface User {
- Id: number;
+ Id: number;
}
// 悬赏列表数据
interface Reward {
- rewardId: number;
- userId: number;
- rewardPicture: string;
- rewardName: string;
- createAt: string;
- rewardDescription: string;
- price: number;
+ rewardId: number;
+ userId: number;
+ rewardPicture: string;
+ rewardName: string;
+ createAt: string;
+ rewardDescription: string;
+ price: number;
}
interface RewardList {
- total: number; // 总记录数
- records: Reward[];
+ total: number; // 总记录数
+ records: Reward[];
}
-
// 社区详情页面
export default function RewardDetailPage() {
- const user = useLocalStorage<User>('user');
- const userId: number = user?.Id ?? -1;
- // 页面跳转
- const router = useRouter();
- // 帖子列表数据
- const [rewards, setRewards] = useState<Reward[]>([]);
- const [totalRewards, setTotalRewards] = useState<number>(0);
- const [activeOption, setActiveOption] = useState<number>(0); // 0表示"赏金最高",1表示"最新发布"
- const options = ["赏金最高", "最新发布"];
- // 搜索框
- const [searchValue, setSearchValue] = useState("");
- const debouncedSearch = useRef(
- debounce((value: string) => {
- setSearchValue(value);
- }, 600)
- ).current;
- // 消息提醒
- const toast = useRef<Toast>(null);
- // 分页
- const [first, setFirst] = useState(0);
- const [rows, setRows] = useState(5);
- const onPageChange = (event: PaginatorPageChangeEvent) => {
- setFirst(event.first);
- setRows(event.rows);
- };
+ const user = useLocalStorage<User>("user");
+ const userId: number = user?.Id ?? -1;
+ // 页面跳转
+ const router = useRouter();
+ // 帖子列表数据
+ const [rewards, setRewards] = useState<Reward[]>([]);
+ const [totalRewards, setTotalRewards] = useState<number>(0);
+ const [activeOption, setActiveOption] = useState<number>(0); // 0表示"赏金最高",1表示"最新发布"
+ const options = ["赏金最高", "最新发布"];
+ // 搜索框
+ const [searchValue, setSearchValue] = useState("");
+ const debouncedSearch = useRef(
+ debounce((value: string) => {
+ setSearchValue(value);
+ }, 600)
+ ).current;
+ // 消息提醒
+ const toast = useRef<Toast>(null);
+ // 分页
+ const [first, setFirst] = useState(0);
+ const [rows, setRows] = useState(5);
+ const onPageChange = (event: PaginatorPageChangeEvent) => {
+ setFirst(event.first);
+ setRows(event.rows);
+ };
- // 获取悬赏列表
- useEffect(() => {
+ // 获取悬赏列表
+ useEffect(() => {
+ fetchRewards();
+ }, [first, rows, searchValue, activeOption]);
+
+ const fetchRewards = async () => {
+ try {
+ const pageNumber = first / rows + 1;
+ console.log(
+ "当前页" +
+ pageNumber +
+ "size" +
+ rows +
+ "搜索内容" +
+ searchValue +
+ "排序方式" +
+ activeOption
+ );
+ const response = await axios.get<RewardList>(
+ process.env.PUBLIC_URL + `/reward`,
+ {
+ params: {
+ pageNumber,
+ rows,
+ searchValue,
+ option: options[activeOption],
+ },
+ }
+ );
+ console.log("获取悬赏列表:", response.data.records);
+ setRewards(response.data.records);
+ setTotalRewards(response.data.total); // 假设返回的总数
+ } catch (err) {
+ console.error("获取悬赏失败", err);
+ toast.current?.show({
+ severity: "error",
+ summary: "error",
+ detail: "获取悬赏失败",
+ });
+ }
+ };
+
+ // 发布悬赏弹窗
+ const [visible, setVisible] = useState(false);
+ const [formData, setFormData] = useState({
+ rewardName: "",
+ rewardDescription: "",
+ rewardPicture: "",
+ price: "",
+ });
+
+ // 发布悬赏接口
+ const handleSubmit = async () => {
+ try {
+ const currentDate = new Date().toISOString();
+ const postData = {
+ userId, // 记得用户登录状态获取
+ rewardPicture: formData.rewardPicture,
+ rewardName: formData.rewardName,
+ rewardDescription: formData.rewardDescription,
+ createAt: currentDate.slice(0, 10),
+ lastUpdateAt: currentDate.slice(0, 10),
+ price: Number(formData.price),
+ };
+ // 发送POST请求
+ const response = await axios.post(
+ process.env.PUBLIC_URL + "/reward",
+ postData
+ );
+
+ if (response.status === 200) {
+ toast.current?.show({
+ severity: "success",
+ summary: "Success",
+ detail: "悬赏发布成功",
+ });
+ // 发帖成功
+ setVisible(false);
+ // 重置表单
+ setFormData({
+ rewardName: "",
+ rewardDescription: "",
+ rewardPicture: "",
+ price: "",
+ });
+ // 可以刷新帖子列表
fetchRewards();
- }, [first, rows, searchValue, activeOption]);
-
- const fetchRewards = async () => {
- try {
- const pageNumber = first / rows + 1;
- console.log("当前页" + pageNumber + "size" + rows + "搜索内容" + searchValue + "排序方式" + activeOption);
- const response = await axios.get<RewardList>(
- process.env.PUBLIC_URL + `/reward`, {
- params: { pageNumber, rows, searchValue, option: options[activeOption] }
- }
- );
- console.log('获取悬赏列表:', response.data.records);
- setRewards(response.data.records);
- setTotalRewards(response.data.total); // 假设返回的总数
- } catch (err) {
- console.error('获取悬赏失败', err);
- toast.current?.show({ severity: 'error', summary: 'error', detail: '获取悬赏失败' });
- }
- };
-
- // 发布悬赏弹窗
- const [visible, setVisible] = useState(false);
- const [formData, setFormData] = useState({
- rewardName: '',
- rewardDescription: '',
- rewardPicture: '',
- price: ''
- });
- // 图片上传消息通知
- const onUpload = () => {
- toast.current?.show({ severity: 'info', summary: 'Success', detail: 'File Uploaded' });
- };
-
- // 发布悬赏接口
- const handleSubmit = async () => {
- try {
- const currentDate = new Date().toISOString();
- const postData = {
- userId, // 记得用户登录状态获取
- rewardPicture: formData.rewardPicture,
- rewardName: formData.rewardName,
- rewardDescription: formData.rewardDescription,
- createdAt: currentDate,
- price: formData.price
- };
- // 发送POST请求
- const response = await axios.post(process.env.PUBLIC_URL + '/reward', postData);
-
- if (response.status === 200) {
- toast.current?.show({ severity: 'success', summary: 'Success', detail: '悬赏发布成功' });
- // 发帖成功
- setVisible(false);
- // 重置表单
- setFormData({
- rewardName: '',
- rewardDescription: '',
- rewardPicture: '',
- price: ''
- });
- // 可以刷新帖子列表
- fetchRewards();
- }
- } catch (error) {
- console.error('发布悬赏失败:', error);
- toast.current?.show({ severity: 'error', summary: 'error', detail: '悬赏发布失败' });
- }
- };
- return (
- <div className="reward">
- <Toast ref={toast}></Toast>
- {/* 悬赏标题和介绍 */}
- <div className="reward-header">
- <div className="title-section">
- <h1>悬赏排行</h1>
- </div>
- <div className="input">
- <div className="searchBar">
- <i className="pi pi-search" />
- <InputText type="search" className="search-helper" placeholder="搜索你的目标悬赏" onChange={(e) => { const target = e.target as HTMLInputElement; debouncedSearch(target.value); }} />
- </div>
- <div className="reward-buttons">
- <Button label="我的悬赏" onClick={() => router.push(`/user/悬赏`)} />
- <Button label="发布悬赏" onClick={() => setVisible(true)} />
- </div>
- </div>
- </div>
-
- {/* 悬赏列表 */}
- <TabView activeIndex={activeOption} onTabChange={(e) => setActiveOption(e.index)}>
- <TabPanel header={options[0]} >
- <div className="rewards-list">
- {rewards.map((reward) => (
- <Card key={reward.rewardId} className="rewards-list-card" onClick={() => router.push(`/reward/reward-detail/${reward.rewardId}`)}>
- <Image alt="avatar" src={ "rewards/" + reward.rewardPicture} className="reward-avatar" width="250" height="140" />
- <div className="reward-header">
- <div className="reward-content">
- <h3>{reward.rewardName}</h3>
- </div>
- <div className="reward-states">
- <span className="price">$: {reward.price}</span>
- <Button label="提交悬赏" />
- </div>
- </div>
- </Card>
- ))}
- {totalRewards > 5 && <Paginator className="Paginator" first={first} rows={rows} totalRecords={totalRewards} rowsPerPageOptions={[5, 10]} onPageChange={onPageChange} />}
- </div>
- </TabPanel>
- <TabPanel header={options[1]}>
- <div className="rewards-list">
- {rewards.map((reward) => (
- <Card key={reward.rewardId} className="rewards-list-card" onClick={() => router.push(`/reward/reward-detail/${reward.rewardId}`)}>
- <Image alt="avatar" src={ reward.rewardPicture} className="reward-avatar" width="250" height="140" />
- <div className="reward-header">
- <div className="reward-content">
- <h3>{reward.rewardName}</h3>
- </div>
- <div className="reward-states">
- <span className="price">$: {reward.price}</span>
- <Button label="提交悬赏" />
- </div>
- </div>
- </Card>
- ))}
- {totalRewards > 5 && <Paginator className="Paginator" first={first} rows={rows} totalRecords={totalRewards} rowsPerPageOptions={[5, 10]} onPageChange={onPageChange} />}
- </div>
- </TabPanel>
- </TabView>
- {/* 发布悬赏弹窗 */}
- <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">
- <label htmlFor="title">标题</label>
- <InputText
- id="title"
- value={formData.rewardName}
- onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
- placeholder="请输入悬赏标题"
- className="w-full"
- />
- </div>
-
- <div className="form-field">
- <label htmlFor="content">内容</label>
- <InputTextarea
- id="content"
- value={formData.rewardDescription}
- onChange={(e) => setFormData(prev => ({ ...prev, content: e.target.value }))}
- rows={5}
- placeholder="请输入悬赏需求"
- className="w-full"
- />
- </div>
- <div className="form-field">
- <label htmlFor="price">赏金</label>
- <InputText
- id="price"
- value={formData.price}
- onChange={(e) => setFormData(prev => ({ ...prev, price: e.target.value }))}
- placeholder="请输入赏金金额"
- className="w-full"
- />
- </div>
- <div className="form-field">
- <label>封面图片</label>
- <FileUpload
- mode="basic"
- name="thread-image"
- url={process.env.PUBLIC_URL + "/file"} // 与后端交互的URL
- accept="image/*"
- maxFileSize={10000000000}
- chooseLabel="选择悬赏封面"
- className="w-full"
- onUpload={onUpload}
- />
- </div>
- </div>
- </Dialog>
+ }
+ } catch (error) {
+ console.error("发布悬赏失败:", error);
+ toast.current?.show({
+ severity: "error",
+ summary: "error",
+ detail: "悬赏发布失败",
+ });
+ }
+ };
+ return (
+ <div className="reward">
+ <Toast ref={toast}></Toast>
+ {/* 悬赏标题和介绍 */}
+ <div className="reward-header">
+ <div className="title-section">
+ <h1>悬赏排行</h1>
</div>
- );
-}
\ No newline at end of file
+ <div className="input">
+ <div className="searchBar">
+ <i className="pi pi-search" />
+ <InputText
+ type="search"
+ className="search-helper"
+ placeholder="搜索你的目标悬赏"
+ onChange={(e) => {
+ const target = e.target as HTMLInputElement;
+ debouncedSearch(target.value);
+ }}
+ />
+ </div>
+ <div className="reward-buttons">
+ <Button
+ label="我的悬赏"
+ onClick={() => router.push(`/user/悬赏`)}
+ />
+ <Button label="发布悬赏" onClick={() => setVisible(true)} />
+ </div>
+ </div>
+ </div>
+
+ {/* 悬赏列表 */}
+ <TabView
+ activeIndex={activeOption}
+ onTabChange={(e) => setActiveOption(e.index)}
+ >
+ <TabPanel header={options[0]}>
+ <div className="rewards-list">
+ {rewards.map((reward) => (
+ <Card
+ key={reward.rewardId}
+ className="rewards-list-card"
+ onClick={() =>
+ router.push(`/reward/reward-detail/${reward.rewardId}`)
+ }
+ >
+ <Image
+ alt="avatar"
+ src={reward.rewardPicture}
+ className="reward-avatar"
+ width="250"
+ height="140"
+ />
+ <div className="reward-header">
+ <div className="reward-content">
+ <h3>{reward.rewardName}</h3>
+ </div>
+ <div className="reward-states">
+ <span className="price">$: {reward.price}</span>
+ <Button label="提交悬赏" />
+ </div>
+ </div>
+ </Card>
+ ))}
+ {totalRewards > 5 && (
+ <Paginator
+ className="Paginator"
+ first={first}
+ rows={rows}
+ totalRecords={totalRewards}
+ rowsPerPageOptions={[5, 10]}
+ onPageChange={onPageChange}
+ />
+ )}
+ </div>
+ </TabPanel>
+ <TabPanel header={options[1]}>
+ <div className="rewards-list">
+ {rewards.map((reward) => (
+ <Card
+ key={reward.rewardId}
+ className="rewards-list-card"
+ onClick={() =>
+ router.push(`/reward/reward-detail/${reward.rewardId}`)
+ }
+ >
+ <Image
+ alt="avatar"
+ src={reward.rewardPicture}
+ className="reward-avatar"
+ width="250"
+ height="140"
+ />
+ <div className="reward-header">
+ <div className="reward-content">
+ <h3>{reward.rewardName}</h3>
+ </div>
+ <div className="reward-states">
+ <span className="price">$: {reward.price}</span>
+ <Button label="提交悬赏" />
+ </div>
+ </div>
+ </Card>
+ ))}
+ {totalRewards > 5 && (
+ <Paginator
+ className="Paginator"
+ first={first}
+ rows={rows}
+ totalRecords={totalRewards}
+ rowsPerPageOptions={[5, 10]}
+ onPageChange={onPageChange}
+ />
+ )}
+ </div>
+ </TabPanel>
+ </TabView>
+ {/* 发布悬赏弹窗 */}
+ <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">
+ <label htmlFor="title">标题</label>
+ <InputText
+ id="title"
+ value={formData.rewardName}
+ onChange={(e) =>
+ setFormData((prev) => ({ ...prev, rewardName: e.target.value }))
+ }
+ placeholder="请输入悬赏标题"
+ className="w-full"
+ />
+ </div>
+
+ <div className="form-field">
+ <label htmlFor="content">内容</label>
+ <InputTextarea
+ id="content"
+ value={formData.rewardDescription}
+ onChange={(e) =>
+ setFormData((prev) => ({
+ ...prev,
+ rewardDescription: e.target.value,
+ }))
+ }
+ rows={5}
+ placeholder="请输入悬赏需求"
+ className="w-full"
+ />
+ </div>
+ <div className="form-field">
+ <label htmlFor="price">赏金</label>
+ <InputText
+ id="price"
+ value={formData.price}
+ onChange={(e) =>
+ setFormData((prev) => ({ ...prev, price: e.target.value }))
+ }
+ placeholder="请输入赏金金额"
+ className="w-full"
+ />
+ </div>
+ <div className="form-field">
+ <label>封面图片</label>
+ <FileUpload
+ mode="advanced"
+ name="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(fileUrl);
+ setFormData((prev) => ({ ...prev, rewardPicture: 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>
+ );
+}
diff --git "a/src/app/reward/reward-detail/\133rewardId\135/page.tsx" "b/src/app/reward/reward-detail/\133rewardId\135/page.tsx"
index 6c7b5bd..b6c60d4 100644
--- "a/src/app/reward/reward-detail/\133rewardId\135/page.tsx"
+++ "b/src/app/reward/reward-detail/\133rewardId\135/page.tsx"
@@ -293,7 +293,7 @@
{/* 右侧图片+价格+按钮 */}
<div className="reward-media">
<Image
- src={ "rewards/" + rewardInfo.rewardPicture}
+ src={ rewardInfo.rewardPicture}
alt={rewardInfo.rewardName}
width="500"
height="400"