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"