Merge "fix : file url" into main
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 941167b..5076a0a 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -93,9 +93,9 @@
 }
 // 主页
 export default function Home() {
-    const user = useLocalStorage<User>('user');
-    const userId: number = user?.Id ?? -1;
-
+  const user = useLocalStorage<User>('user');
+  const userId: number = user?.Id ?? -1;
+  console.log(userId)
   // 模组列表
   const [mods, setMods] = useState<Mod[]>([]);
   // 悬赏列表
@@ -174,7 +174,7 @@
   const fetchRecommendMods = async () => {
     try {
       const response = await axios.get<ModList>(process.env.PUBLIC_URL + `/resource/recommend`, {
-        params: { userId, pageNumber: 1, rows: 3, type: '模组' }
+        params: { userId, pageNumber: 1, rows: 3, classify: '模组', searchValue: ''  }
       });
       console.log('获取模组列表:', response.data.records);
       setMods(response.data.records);
@@ -186,7 +186,7 @@
   const fetchRecommendMaps = async () => {
     try {
       const response = await axios.get<MapList>(process.env.PUBLIC_URL + `/resource/recommend`, {
-        params: { userId, pageNumber: 1, rows: 3, type: '地图' }
+        params: { userId, pageNumber: 1, rows: 3, classify: '地图', searchValue: '' }
       });
       console.log('获取模组列表:', response.data.records);
       setMaps(response.data.records);
@@ -198,7 +198,7 @@
   const fetchRecommendModpacks = async () => {
     try {
       const response = await axios.get<ModpackList>(process.env.PUBLIC_URL + `/resource/recommend`, {
-        params: { userId, pageNumber: 1, rows: 3, type: '整合包' }
+        params: { userId, pageNumber: 1, rows: 3, classify: '整合包', searchValue: ''  }
       });
       console.log('获取模组列表:', response.data.records);
       setModpacks(response.data.records);
@@ -210,7 +210,7 @@
   const fetchRecommendTextures = async () => {
     try {
       const response = await axios.get<TextureList>(process.env.PUBLIC_URL + `/resource/recommend`, {
-        params: { userId, pageNumber: 1, rows: 3, type: '材质包' }
+        params: { userId, pageNumber: 1, rows: 3, classify: '材质包', searchValue: ''  }
       });
       console.log('获取模组列表:', response.data.records);
       setTextures(response.data.records);
diff --git a/src/app/resource/classification/page.tsx b/src/app/resource/classification/page.tsx
index 32648da..f147ed2 100644
--- a/src/app/resource/classification/page.tsx
+++ b/src/app/resource/classification/page.tsx
@@ -35,7 +35,7 @@
   resourceSummary: string;
   lastUpdateTime: string;
   hot: number;
-  gamePlayList: { gameplayName: string }[];
+  gameplayList: string[];
 }
 
 interface HotResourceList {
@@ -120,8 +120,8 @@
               <div className="resource-content">
                 <h3>{hotResource.resourceName}</h3>
                 <div className="tags">
-                  {hotResource.gamePlayList.map((tag, index) => (
-                    <Tag key={index} value={tag.gameplayName} />
+                  {hotResource.gameplayList.map((tag, index) => (
+                    <Tag key={index} value={tag} />
                   ))}
                 </div>
               </div>
diff --git a/src/app/user/login/login.scss b/src/app/user/login/login.scss
new file mode 100644
index 0000000..b028689
--- /dev/null
+++ b/src/app/user/login/login.scss
@@ -0,0 +1,53 @@
+//全局容器样式
+.login-container {
+  max-width: 1200px;
+  margin: 0 auto;
+  padding: 0 2rem;
+  //display: flex;
+  //flex-direction: column;
+  //justify-content: center;
+
+  .form-header {
+    //margin: auto;
+    display: flex;
+    justify-content: center;
+    font-size: 50px;
+    margin-bottom: 60px;
+  }
+
+  .form-field {
+    display: flex;
+    justify-content: center;
+    gap: 0.5rem;
+    margin-bottom: 50px;
+
+    .form-field-header {
+      width: 90px;
+      font-size: 30px;
+      color: rgba(63, 74, 73, 1);
+      margin-right: 50px;
+    }
+
+    input {
+      width: 400px;
+      padding: 0.75rem 1rem;
+      border-radius: 8px;
+      font-size: 1rem;
+      color: #2d3748;
+    }
+  }
+
+  .form-operation {
+    display: flex;
+    justify-content: center;
+    margin-top: 80px;
+
+    .form-operation-button {
+      width: 150px;
+      background: rgba(82, 102, 101, 1);
+      border: 1px solid rgba(82, 102, 101, 1);
+      border-radius: 20px;
+      margin-right: 25px;
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/app/user/login/page.tsx b/src/app/user/login/page.tsx
index c48b626..a22cee0 100644
--- a/src/app/user/login/page.tsx
+++ b/src/app/user/login/page.tsx
@@ -1,12 +1,113 @@
 'use client';
-import React from 'react';
+import React, { useRef, useState } from 'react';
+import { Button } from "primereact/button";
+// 输入框
+import { InputText } from "primereact/inputtext";
+// 消息提醒
+import { Toast } from 'primereact/toast';
+// 接口传输
+import axios from "axios";
+// 页面跳转
+import { useRouter } from "next/navigation";
+// 密码
+import { Password } from 'primereact/password';
+// 样式
+import './login.scss';
 
-const EmptyPage: React.FC = () => {
-  return (
-    <div className="p-d-flex p-jc-center p-ai-center" style={{ height: '100vh' }}>
-      {"一个空页面"}
-    </div>
-  );
+export default function LoginPage() {
+    // 路由
+    const router = useRouter();
+    // 消息提醒
+    const toast = useRef<Toast>(null);
+
+    const [loginFormData, setLoginFormData] = useState({
+        username: '',
+        password: '',
+    })
+
+    // 获取userId
+    const fetchUserId = async () => {
+        try {
+            const response = await axios.get(process.env.PUBLIC_URL + `/user/getId`, {
+                params: { username: loginFormData.username , password: loginFormData.password }
+            });
+            console.log('用户id:', response.data)
+            localStorage.setItem("user", JSON.stringify({ Id: response.data }));
+        } catch (error) {
+            console.error('获取用户id失败:', error);
+            toast.current?.show({ severity: 'error', summary: 'Error', detail: '获取用户id失败' });
+        }
+    }
+
+    // 处理用户登录
+    const handleLogin = async () => {
+        try {
+            const postData = {
+                username: loginFormData.username,
+                password: loginFormData.password,
+            }
+            const response = await axios.post(process.env.PUBLIC_URL + `/user/login`, postData, {
+                // eslint-disable-next-line @typescript-eslint/no-unused-vars
+                validateStatus: (status) => true // 接受所有状态码
+            });
+
+            if (response.status === 200) {
+                console.log('用户登录成功');
+                toast.current?.show({ severity: 'success', summary: 'Success', detail: '登录成功' });
+                // 设置userId
+                fetchUserId()
+                router.push('/');
+            } else if (response.status === 406) {
+                console.log('用户名不存在');
+                toast.current?.show({ severity: 'error', summary: 'Error', detail: '用户名不存在' });
+            } else if (response.status === 408) {
+                console.error('密码错误');
+                toast.current?.show({ severity: 'error', summary: 'Error', detail: '密码错误' });
+            }
+
+        } catch (error) {
+            console.error('登录失败:', error);
+            toast.current?.show({ severity: 'error', summary: 'Error', detail: '登录失败' });
+        }
+    }
+
+    return (
+        <div className='login-container'>
+            <Toast ref={toast}></Toast>
+            <h1 className="form-header">登录</h1>
+            <div className="form-field">
+                <div className="form-field-header">
+                    <label htmlFor="username">用户名</label>
+                </div>
+                <InputText
+                    id="username"
+                    value={loginFormData.username}
+                    onChange={(e) => setLoginFormData(prev => ({
+                        ...prev, // 复制顶层所有属性
+                        username: e.target.value
+                    }))}
+                    className="w-full"
+                />
+            </div>
+            <div className="form-field">
+                <div className="form-field-header">
+                    <label htmlFor="password">密码</label>
+                </div>
+                <Password
+                    id="password"
+                    value={loginFormData.password}
+                    onChange={(e) => setLoginFormData(prev => ({
+                        ...prev,
+                        password: e.target.value
+                    }))}
+                    className="w-full"
+                    toggleMask
+                />
+            </div>
+            <div className="form-operation">
+                <Button label="确定" onClick={handleLogin} className='form-operation-button'/>
+                <Button label="注册" onClick={() => router.push("/user/register")} className='form-operation-button'/>
+            </div>
+        </div>
+    )
 };
-
-export default EmptyPage;
diff --git a/src/app/user/page.tsx b/src/app/user/page.tsx
index 4baef17..d534cd5 100644
--- a/src/app/user/page.tsx
+++ b/src/app/user/page.tsx
@@ -259,7 +259,7 @@
     const fetchHomePageThread = async () => {
         try {
             const response = await axios.get<ThreadList>(process.env.PUBLIC_URL + `/user/thread`, {
-                params: { userId: 22301010,  pageNumber: 1, rows: 3 }
+                params: { userId: userId,  pageNumber: 1, rows: 3 }
             })
             console.log('获取主页发布帖子:', response.data);
             setHomePageThread(response.data);
@@ -279,7 +279,7 @@
         try {
             const pageNumber = threadFirst / threadRows + 1;
             const response = await axios.get<ThreadList>(process.env.PUBLIC_URL + `/user/thread`, {
-                params: { userId: 22301010,  pageNumber: pageNumber, rows: threadRows }
+                params: { userId: userId,  pageNumber: pageNumber, rows: threadRows }
             })
             console.log('获取我的帖子:', response.data);
             setThreadList(response.data.records);
@@ -299,7 +299,7 @@
         try {
             const pageNumber = rewardFirst / rewardRows + 1;
             const response = await axios.get<RewardList>(process.env.PUBLIC_URL + `/user/reward`, {
-                params: { userId: 22301010,  pageNumber: pageNumber, rows: rewardRows }
+                params: { userId: userId,  pageNumber: pageNumber, rows: rewardRows }
             })
             console.log('获取我的悬赏:', response.data);
             setRewardList(response.data.rewardList);
@@ -329,7 +329,7 @@
         },
         {
             template: () => (
-                <Button label="编辑悬赏" onClick={() => router.push(`/user/manage/resources/`)} />
+                <Button label="编辑悬赏" />
             )
         }
     ];
@@ -364,7 +364,7 @@
             const response = await axios.delete(process.env.PUBLIC_URL + `/reward`, {
                 params: {rewardId: deleteRewardId},
             });
-            console.log("用户" + 22301010 + "要删除" + deleteRewardId + "号悬赏");
+            console.log("用户" + userId + "要删除" + deleteRewardId + "号悬赏");
 
             if (response.status === 204) {
                 console.log("用户成功删除悬赏");
@@ -495,8 +495,6 @@
                 setSelectedGameplay([]);
                 // 重置资源封面
                 setResourcePictureUrl('');
-                // 可以刷新资源列表
-                // fetchResourceList();
             }
         } catch (error) {
             console.error('资源上传失败:', error);
@@ -611,10 +609,6 @@
                     </div>
                 </TabPanel>
 
-                {/*<TabPanel header="发布">*/}
-
-                {/*</TabPanel>*/}
-
                 <TabPanel header="帖子">
                     {/*我的帖子*/}
                     <div className="homepage-item">
@@ -863,7 +857,6 @@
                                         }));
                                         setIngredient(e.value);
                                     }}
-                                    // onChange={(e: RadioButtonChangeEvent) => setIngredient(e.value)}
                                     checked={ingredient === 'modPack'}
                                 />
                                 <label htmlFor="ingredient2" className="ml-2">整合包</label>
@@ -883,7 +876,6 @@
                                         }));
                                         setIngredient(e.value);
                                     }}
-                                    // onChange={(e: RadioButtonChangeEvent) => setIngredient(e.value)}
                                     checked={ingredient === 'mod'}
                                 />
                                 <label htmlFor="ingredient3" className="ml-2">模组</label>
@@ -903,7 +895,6 @@
                                         }));
                                         setIngredient(e.value);
                                     }}
-                                    // onChange={(e: RadioButtonChangeEvent) => setIngredient(e.value)}
                                     checked={ingredient === 'map'}
                                 />
                                 <label htmlFor="ingredient4" className="ml-2">地图</label>
@@ -932,7 +923,6 @@
                             display="chip"
                             optionLabel="name"
                             placeholder="请选择资源标签"
-                            // maxSelectedLabels={3}
                             className="w-full md:w-20rem"
                         />
                     </div>
@@ -964,12 +954,6 @@
                             auto
                             accept="image/*"
                             chooseLabel="上传资源封面"
-
-                            // accept="image/*"
-                            // maxFileSize={10000000000}
-                            // chooseLabel="选择资源封面"
-                            // className="w-full"
-                            // onUpload={onUpload}
                         />
                     </div>
                 </div>
@@ -990,25 +974,9 @@
                 }
             >
                 <div className="dialog-form">
-                    <span>
+                    <span style={{marginBottom: "10px"}}>
                         确认是否删除该悬赏?
                     </span>
-                    {/*<div className="form-field">*/}
-                    {/*    <div className="form-field-header">*/}
-                    {/*        <label htmlFor="name">密码</label>*/}
-                    {/*    </div>*/}
-                    {/*    <Password*/}
-                    {/*        id="passwrod"*/}
-                    {/*        value={deleteResourceFormData.password}*/}
-                    {/*        onChange={(e) => setDeleteResourceFormData(prev => ({*/}
-                    {/*            ...prev,*/}
-                    {/*            password: e.target.value*/}
-                    {/*        }))}*/}
-                    {/*        placeholder="请输入密码"*/}
-                    {/*        className="w-full"*/}
-                    {/*        toggleMask*/}
-                    {/*    />*/}
-                    {/*</div>*/}
                 </div>
             </Dialog>
 
diff --git a/src/app/user/register/page.tsx b/src/app/user/register/page.tsx
index c48b626..25f2bd0 100644
--- a/src/app/user/register/page.tsx
+++ b/src/app/user/register/page.tsx
@@ -1,12 +1,140 @@
 'use client';
-import React from 'react';
+import React, { useRef, useState } from 'react';
+import { Button } from "primereact/button";
+// 输入框
+import { InputText } from "primereact/inputtext";
+// 消息提醒
+import { Toast } from 'primereact/toast';
+// 接口传输
+import axios from "axios";
+// 页面跳转
+import { useRouter } from "next/navigation";
+// 密码
+import { Password } from 'primereact/password';
+// 样式
+import './register.scss';
 
-const EmptyPage: React.FC = () => {
+export default function RegisterPage() {
+    // 路由
+    const router = useRouter();
+    // 消息提醒
+    const toast = useRef<Toast>(null);
+
+    const [registerFormData, setRegisterFormData] = useState({
+        username: '',
+        password: '',
+        confirmPassword: '',
+        invitationCode: '',
+    })
+
+
+    // 处理用户注册
+    const handleRegister = async () => {
+        try {
+            // 判断用户两次输入的密码是否一致
+            if (registerFormData.password != registerFormData.confirmPassword) {
+                console.error("两次输入的密码不一致,请重新输入");
+                toast.current?.show({severity: 'error', summary: 'Error', detail: '两次输入的密码不一致,请重新输入'});
+                return;
+            }
+
+            const postData = {
+                username: registerFormData.username,
+                password: registerFormData.password,
+                invitationCode:  registerFormData.invitationCode,
+            }
+            const response = await axios.post(process.env.PUBLIC_URL + `/user/register`, postData, {
+                // eslint-disable-next-line @typescript-eslint/no-unused-vars
+                validateStatus: (status) => true // 接受所有状态码
+            });
+
+            if (response.status === 200) {
+                console.log('用户注册成功');
+                toast.current?.show({severity: 'success', summary: 'Success', detail: '注册成功'});
+                router.push('/user/login');
+            } else if (response.status === 412) {
+                console.log('用户名重复');
+                toast.current?.show({severity: 'error', summary: 'Error', detail: '用户名重复'});
+            } else if (response.status === 409) {
+                console.error('邀请码不存在');
+                toast.current?.show({severity: 'error', summary: 'Error', detail: '邀请码不存在'});
+            } else if (response.status === 410) {
+                console.error('邀请码已被使用');
+                toast.current?.show({severity: 'error', summary: 'Error', detail: '邀请码已被使用'});
+            }
+
+        } catch (error) {
+            console.error('注册失败:', error);
+            toast.current?.show({severity: 'error', summary: 'Error', detail: '注册失败'});
+        }
+    }
+
   return (
-    <div className="p-d-flex p-jc-center p-ai-center" style={{ height: '100vh' }}>
-      {"一个空页面"}
-    </div>
-  );
+      <div className='register-container'>
+          <Toast ref={toast}></Toast>
+          <h1 className="form-header">注册</h1>
+          <div className="form-field">
+              <div className="form-field-header">
+                  <label htmlFor="username">用户名</label>
+              </div>
+              <InputText
+                  id="username"
+                  value={registerFormData.username}
+                  onChange={(e) => setRegisterFormData(prev => ({
+                      ...prev, // 复制顶层所有属性
+                      username: e.target.value
+                  }))}
+                  className="w-full"
+              />
+          </div>
+          <div className="form-field">
+              <div className="form-field-header">
+                  <label htmlFor="password">设置密码</label>
+              </div>
+              <Password
+                  id="password"
+                  value={registerFormData.password}
+                  onChange={(e) => setRegisterFormData(prev => ({
+                      ...prev,
+                      password: e.target.value
+                  }))}
+                  className="w-full"
+                  toggleMask
+              />
+          </div>
+          <div className="form-field">
+              <div className="form-field-header">
+                  <label htmlFor="confirmPassword">确认密码</label>
+              </div>
+              <Password
+                  id="confirmPassword"
+                  value={registerFormData.confirmPassword}
+                  onChange={(e) => setRegisterFormData(prev => ({
+                      ...prev,
+                      confirmPassword: e.target.value
+                  }))}
+                  className="w-full"
+                  toggleMask
+              />
+          </div>
+          <div className="form-field">
+              <div className="form-field-header">
+                  <label htmlFor="invitationCode">邀请码</label>
+              </div>
+              <InputText
+                  id="invitationCode"
+                  value={registerFormData.invitationCode}
+                  onChange={(e) => setRegisterFormData(prev => ({
+                      ...prev, // 复制顶层所有属性
+                      invitationCode: e.target.value
+                  }))}
+                  className="w-full"
+              />
+          </div>
+          <div className="form-operation">
+              <Button label="确定" onClick={handleRegister} className='form-operation-button'/>
+              <Button label="返回" onClick={() => router.push("/user/login")} className='form-operation-button'/>
+          </div>
+      </div>
+  )
 };
-
-export default EmptyPage;
diff --git a/src/app/user/register/register.scss b/src/app/user/register/register.scss
new file mode 100644
index 0000000..9c9a77d
--- /dev/null
+++ b/src/app/user/register/register.scss
@@ -0,0 +1,53 @@
+//全局容器样式
+.register-container {
+  max-width: 1200px;
+  margin: 0 auto;
+  padding: 0 2rem;
+  //display: flex;
+  //flex-direction: column;
+  //justify-content: center;
+
+  .form-header {
+    //margin: auto;
+    display: flex;
+    justify-content: center;
+    font-size: 50px;
+    margin-bottom: 60px;
+  }
+
+  .form-field {
+    display: flex;
+    justify-content: center;
+    gap: 0.5rem;
+    margin-bottom: 50px;
+
+    .form-field-header {
+      width: 120px;
+      font-size: 30px;
+      color: rgba(63, 74, 73, 1);
+      margin-right: 50px;
+    }
+
+    input {
+      width: 400px;
+      padding: 0.75rem 1rem;
+      border-radius: 8px;
+      font-size: 1rem;
+      color: #2d3748;
+    }
+  }
+
+  .form-operation {
+    display: flex;
+    justify-content: center;
+    margin-top: 80px;
+
+    .form-operation-button {
+      width: 150px;
+      background: rgba(82, 102, 101, 1);
+      border: 1px solid rgba(82, 102, 101, 1);
+      border-radius: 20px;
+      margin-right: 25px;
+    }
+  }
+}
\ No newline at end of file