Docker

Change-Id: I2aefd96a43bcf3a3c41c079ecfc04a3fee48bed6
diff --git a/src/api/homepage.ts b/src/api/homepage.ts
new file mode 100644
index 0000000..697d0d0
--- /dev/null
+++ b/src/api/homepage.ts
@@ -0,0 +1 @@
+export const getUserMessage="/user/message"
\ No newline at end of file
diff --git a/src/api/upload.ts b/src/api/upload.ts
new file mode 100644
index 0000000..cb768bb
--- /dev/null
+++ b/src/api/upload.ts
@@ -0,0 +1 @@
+export const Upload= '/user/Io';
\ No newline at end of file
diff --git a/src/components/selfStatus/selfStatus.tsx b/src/components/selfStatus/selfStatus.tsx
index a5156f4..3745736 100644
--- a/src/components/selfStatus/selfStatus.tsx
+++ b/src/components/selfStatus/selfStatus.tsx
@@ -1,12 +1,15 @@
 import React from "react";
 import { useAppSelector } from "../../hooks/store";
 import style from "./style.module.css"
+import { useNavigate } from "react-router";
 
 interface SelfStatusProps {
     className?: string;
 }
 
 const SelfStatus: React.FC<SelfStatusProps> = () => {
+
+    const nav = useNavigate()
     const userName = useAppSelector(state => state.user.userName);
     const role = useAppSelector(state => state.user.role);
     const uploadTraffic = useAppSelector(state => state.user.uploadTraffic);
@@ -14,10 +17,14 @@
     const downloadPoints = useAppSelector(state => state.user.downloadPoints);
     const avatar = useAppSelector(state => state.user.avatar);
 
+    function handleAvatarClick(){
+        nav('/homepage')
+    }
+
     return (
         <div className={style.container}>
             <div className={style.left}>
-                <img className={style.avatar} src={avatar} alt="User Avatar" />
+                <img className={style.avatar} onClick={handleAvatarClick} src={avatar} alt="User Avatar" />
             </div>
             <div className={style.right}>
                 <div className={style.info}>
diff --git a/src/components/upload/upload.module.css b/src/components/upload/upload.module.css
new file mode 100644
index 0000000..2fb6278
--- /dev/null
+++ b/src/components/upload/upload.module.css
@@ -0,0 +1,39 @@
+.uploadContainer {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  border: 2px dashed #1890ff;
+  padding: 40px;
+  border-radius: 8px;
+  background-color: #f0f5ff;
+  transition: border-color 0.3s;
+}
+
+.uploadContainer:hover {
+  border-color: #40a9ff;
+}
+
+.uploadLabel {
+  font-size: 16px;
+  margin-bottom: 10px;
+  color: #333;
+}
+
+.uploadInput {
+  display: none;
+}
+
+.uploadButton {
+  padding: 8px 16px;
+  background-color: #1890ff;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+}
+
+.uploadButton:hover {
+  background-color: #40a9ff;
+}
diff --git a/src/components/upload/upload.tsx b/src/components/upload/upload.tsx
new file mode 100644
index 0000000..708e9d1
--- /dev/null
+++ b/src/components/upload/upload.tsx
@@ -0,0 +1,44 @@
+// src/component/upload/upload.tsx
+import React, { useState } from 'react'
+import './upload.css'
+
+const UploadComponent: React.FC = () => {
+  const [selectedFile, setSelectedFile] = useState<File | null>(null)
+
+  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+    if (e.target.files && e.target.files.length > 0) {
+      setSelectedFile(e.target.files[0])
+    }
+  }
+
+  const handleUpload = () => {
+    if (selectedFile) {
+      console.log('上传文件:', selectedFile)
+      // 此处可以添加实际上传逻辑
+    }
+  }
+
+  return (
+    <div className="uploadContainer">
+      <label className="uploadLabel" htmlFor="upload-input">请选择文件上传:</label>
+      <input
+        type="file"
+        id="upload-input"
+        className="uploadInput"
+        data-testid="upload-input"
+        onChange={handleFileChange}
+      />
+      <label htmlFor="upload-input">
+        <div
+          className="uploadButton"
+          data-testid="upload-button"
+          onClick={handleUpload}
+        >
+          点击上传
+        </div>
+      </label>
+    </div>
+  )
+}
+
+export default UploadComponent
diff --git a/src/global.css b/src/global.css
index b49490a..81a6670 100644
--- a/src/global.css
+++ b/src/global.css
@@ -28,6 +28,9 @@
     --text-color: #000000;
     --card-bg: #ffffff;
     --border-color: #e0e0e0;
+    --primary-color: #3498db; 
+    --primary-hover: #2980b9;
+    --primary-card: #a3d1f0;
   }
   
 body.dark {
@@ -35,5 +38,8 @@
     --text-color: #f1f1f1;
     --card-bg: #1e1e1e;
     --border-color: #444444;
+    --primary-color: #3498db; 
+    --primary-hover: #2980b9;
+    --primary-card:#280202;
   }
   
\ No newline at end of file
diff --git a/src/hooks/request.ts b/src/hooks/request.ts
index 8594ac2..b53da73 100644
--- a/src/hooks/request.ts
+++ b/src/hooks/request.ts
@@ -1,7 +1,8 @@
 import { useState, useEffect, useCallback } from 'react'
 import { data } from 'react-router'
 
-type RequestFunction<T> = () => Promise<T>
+// type RequestFunction<T> = () => Promise<T>
+type RequestFunction<T, P = any> = (params?: P) => Promise<T>;
 
 interface UseApiResult<T> {
   data: T | null
@@ -10,18 +11,22 @@
   refresh: () => void
 }
 
-export function useApi<T>(
-    requestFn: RequestFunction<T>,
-    immediate = true
-  ): UseApiResult<T> {
+// export function useApi<T>(
+//     requestFn: RequestFunction<T>,
+//     immediate = true
+//   ): UseApiResult<T> {
+export function useApi<T, P = any>(
+  requestFn: RequestFunction<T, P>,  // 接收参数
+  immediate = true
+) {
     const [data, setData] = useState<T | null>(null)
     const [loading, setLoading] = useState(false)
     const [error, setError] = useState<Error | null>(null)
   
-    const execute = useCallback(async () => {
+    const execute = useCallback(async (params?: P) => { // 添加参数
       try {
         setLoading(true)
-        const result = await requestFn()
+        const result = await requestFn(params);//传参
         setData(result)
         setError(null)
         return result  // 返回请求结果
diff --git a/src/mock/homepage.d.ts b/src/mock/homepage.d.ts
new file mode 100644
index 0000000..ba70a8a
--- /dev/null
+++ b/src/mock/homepage.d.ts
@@ -0,0 +1,3 @@
+import type MockAdapter from 'axios-mock-adapter';
+
+export declare function setupUserMessageMock(mock: MockAdapter): void;
\ No newline at end of file
diff --git a/src/mock/homepage.js b/src/mock/homepage.js
new file mode 100644
index 0000000..41ca3c7
--- /dev/null
+++ b/src/mock/homepage.js
@@ -0,0 +1,46 @@
+import Mock from 'mockjs';
+import MockAdapter from 'axios-mock-adapter';
+import {getUserMessage} from '@/api/homepage';
+
+/**
+ * 设置用户相关的 Mock 接口
+ * @param {MockAdapter} mock 
+ */
+export function setupUserMessageMock(mock) {
+    mock.onGet(getUserMessage).reply((config) => {
+        console.log("visited")
+        let data = Mock.mock({
+            'username': '阳菜,放睛!',
+            'inviteCode': '1314520',
+            'stats': {
+                'likes': 0,
+                'following': 25,
+                'followers': 276,
+                'mutualFollows': 52
+            },
+            'upload': '5.2 ',
+            'level': '荣耀会员',
+            // 'works': [{
+            //     'id': 1,
+            //     'title': '【PC】【ARPG】【开放世界】刺客信条影破解版',
+            //     'publishTime': '2025-3-21',
+            //     'downloadCount': 0,
+            //     'seedCount': 1
+            // }],
+            'works': [{
+                'postId': 0,
+                'userId': 0,
+                'postTitle': '阳菜',
+                "postContent": "",
+                "createdAt": 0,
+                "postType": "",
+                "viewCount": 0,
+                "hotScore": 0.0,
+                "lastCalculated": 0
+                }],
+            'petImage': '/assets/pet-blue-star.png',
+            'trafficImage': '/assets/duck-computer.png'
+        });
+        return [200, data];
+    });
+}
\ No newline at end of file
diff --git a/src/mock/index.ts b/src/mock/index.ts
index fabb34e..f9c6a64 100644
--- a/src/mock/index.ts
+++ b/src/mock/index.ts
@@ -1,8 +1,10 @@
 import MockAdapter from 'axios-mock-adapter';
 import instance from '@/utils/axios'
-import {setupAuthMock}  from './auth'
+import { setupAuthMock }  from './auth'
 import { setupUserMock } from './user';
 import { setupPostMock } from './post';
+import {setupUserMessageMock} from './homepage';
+import { setupUploadMock } from './upload';
 
 // 创建 Mock 实例
 export const mock = new MockAdapter(instance, { 
@@ -18,7 +20,9 @@
   setupAuthMock(mock)
   setupUserMock(mock)
   setupPostMock(mock)
-  
+  setupUserMessageMock(mock)
+  setupUploadMock(mock)
+
   console.log('Mock 模块已加载')
 }
 
diff --git a/src/mock/upload.d.ts b/src/mock/upload.d.ts
new file mode 100644
index 0000000..3ddb4b9
--- /dev/null
+++ b/src/mock/upload.d.ts
@@ -0,0 +1,3 @@
+import type MockAdapter from 'axios-mock-adapter';
+
+export declare function setupUploadMock(mock: MockAdapter): void;
\ No newline at end of file
diff --git a/src/mock/upload.js b/src/mock/upload.js
new file mode 100644
index 0000000..c3f2705
--- /dev/null
+++ b/src/mock/upload.js
@@ -0,0 +1,26 @@
+import Mock from 'mockjs';
+import MockAdapter from 'axios-mock-adapter';
+import {Upload} from '@/api/upload'; 
+
+/**
+ * 设置上传种子的 Mock 接口
+ * @param {MockAdapter} mock 
+ */
+export function setupUploadMock(mock) {
+  mock.onPost(Upload).reply((config) => {
+    const body = JSON.parse(config.data);
+
+    console.log('收到上传请求,内容如下:');
+    console.log(body);
+
+
+    return [
+      200,
+      {
+        code: 0,
+        message: '',
+        data: null
+      }
+    ];
+  });
+}
diff --git a/src/route/index.tsx b/src/route/index.tsx
index c56ada3..62bb7a5 100644
--- a/src/route/index.tsx
+++ b/src/route/index.tsx
@@ -2,6 +2,9 @@
 import PrivateRoute from './privateRoute'
 import { useSelector } from 'react-redux'
 import Login from '../views/login/login'
+import Homepage from '../views/homepage/homepage'
+import Upload from '../views/upload/upload'
+import Search from '../views/search/search'
 import Frame from '../views/frame/frame'
 import React from 'react'
 import Forum from '../views/forum'
@@ -24,6 +27,18 @@
                         element:<Forum/>
 
                     },
+                    {
+                        path:'/homepage',
+                        element: <Homepage/>
+                    },
+                    {
+                        path:'/postDetails',
+                        element: <Upload/>
+                    },
+                    {
+                        path:'/search',
+                        element:<Search/>
+                    }
                 ]
             },
         ]
diff --git a/src/utils/axios.ts b/src/utils/axios.ts
index 1eaad87..8c60d4b 100644
--- a/src/utils/axios.ts
+++ b/src/utils/axios.ts
@@ -2,7 +2,8 @@
 import type { AxiosRequestConfig, AxiosResponse } from 'axios'
 
 const instance = axios.create({
-    baseURL: process.env.API_BASE_URL,
+    // baseURL: process.env.API_BASE_URL,
+    baseURL: 'http://localhost:8080',
     timeout: 10000,
     headers: {
       'Content-Type': 'application/json'
diff --git a/src/views/frame/frame.tsx b/src/views/frame/frame.tsx
index c0c2e0e..c22a528 100644
--- a/src/views/frame/frame.tsx
+++ b/src/views/frame/frame.tsx
@@ -12,12 +12,21 @@
 import logo from "&/assets/logo.png";
 import { useAppDispatch } from "@/hooks/store";
 import { useSelector } from "react-redux";
+
+import { useNavigate } from "react-router-dom";
+
+
 const Frame:React.FC = () => {
 
     const dispatch = useAppDispatch();
 
     const showSearch = useSelector((state: any) => state.setting.showSearch); 
     const theme= useSelector((state: any) => state.setting.theme);
+    
+    const navigate = useNavigate(); // ✅ 用于跳转
+    const [searchText, setSearchText] = useState(""); // ✅ 存储搜索输入内容
+
+
     const toggleSearch = () => {
         dispatch({ type: "setting/toggleSearch" });
     }
@@ -30,12 +39,27 @@
         dispatch({ type: "setting/toggleTheme" });
     };
 
+    // ✅ 用于跳转
+    const handleSearch = (e: React.KeyboardEvent<HTMLInputElement>) => {
+        if (e.key === "Enter" && searchText.trim() !== "") {
+            navigate(`/search?keyword=${encodeURIComponent(searchText.trim())}`);
+        }
+    };
+
 
     return (
         <div style={{ display: 'block', height: '100vh' }}>
             <header className={style.header}>
                 <img className={style.logo} src={logo} alt="website logo"></img>
-                {showSearch && (<input className={style.searchInput} placeholder="输入关键词进行搜索"/>)}
+                {showSearch && (
+                    // <input className={style.searchInput} placeholder="输入关键词进行搜索"/>
+                    <input
+                        className={style.searchInput}
+                        placeholder="输入关键词进行搜索"
+                        value={searchText}
+                        onChange={(e) => setSearchText(e.target.value)}
+                        onKeyDown={handleSearch} // ⌨️ 按下回车时执行跳转
+                    />)}
                 <div className={style.toollist}>
                     <SearchOutlined onClick={toggleSearch}/>
                     <FontSizeOutlined onClick={toggleFontSize}/>
diff --git a/src/views/homepage/homepage.module.css b/src/views/homepage/homepage.module.css
new file mode 100644
index 0000000..fdcc060
--- /dev/null
+++ b/src/views/homepage/homepage.module.css
@@ -0,0 +1,249 @@
+/* 主题色变量 */
+/* :root {
+    --primary-color: #3498db; 
+    --primary-hover: #2980b9;
+    --secondary-color: #f1c40f; 
+    --dark-color: #2c3e50; 
+    --light-color: #ecf0f1;
+    --text-color: #333;
+    --text-secondary: #7f8c8d;
+    --border-color: #ddd;
+  } */
+  
+    /* --bg-color: #2b2b2b;
+    --text-color: #f1f1f1;
+    --card-bg: #1e1e1e;
+    --border-color: #444444; */
+
+  .container {
+    min-height: 100vh;
+    background-color: var(--bg-color);
+    color: var(--text-color);
+    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+  }
+  
+  /* 顶部导航栏 */
+  .header {
+    display: flex;
+    align-items: center;
+    padding: 15px 30px;
+    background-color: solid var(--card-bg);
+    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+  }
+  
+  .logo {
+    height: 40px;
+    cursor: pointer;
+    transition: transform 0.3s;
+  }
+  
+  .logo:hover {
+    transform: scale(1.05);
+  }
+  
+  .siteTitle {
+    margin: 0 0 0 15px;
+    color: var(--text-color);
+    font-size: 24px;
+  }
+  
+  /* 主内容区 */
+  .mainContent {
+    display: flex;
+    min-height: calc(100vh - 70px);
+    padding: 20px;
+    gap: 20px;
+  }
+  
+  /* 左侧用户信息区 */
+  .userProfile {
+    flex: 2;
+    background-color: var(--card-bg);
+    border-radius: 10px;
+    padding: 25px;
+    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
+  }
+  
+  .userHeader {
+    display: flex;
+    align-items: center;
+    margin-bottom: 20px;
+  }
+  
+  .userAvatar {
+    width: 80px;
+    height: 80px;
+    border-radius: 50%;
+    object-fit: cover;
+    border: 3px solid var(--primary-color);
+    margin-right: 20px;
+  }
+  
+  .userInfo {
+    flex: 1;
+  }
+  
+  .username {
+    margin: 0 0 5px 0;
+    color: var(--text-color);
+    font-size: 22px;
+  }
+  
+  .inviteCode {
+    color: var(--text-color);
+    font-size: 14px;
+    margin-bottom: 10px;
+  }
+  
+  .editButton {
+    padding: 8px 20px;
+    background-color: var(--primary-color);
+    color: white;
+    border: none;
+    border-radius: 20px;
+    cursor: pointer;
+    font-size: 14px;
+    transition: background-color 0.3s;
+  }
+  
+  .editButton:hover {
+    background-color: var(--primary-hover);
+  }
+  
+  /* 用户统计 */
+  .userStats {
+    display: flex;
+    justify-content: space-between;
+    margin: 25px 0;
+    padding: 15px 0;
+    border-top: 1px solid var(--border-color);
+    border-bottom: 1px solid var(--border-color);
+  }
+  
+  .statItem {
+    text-align: center;
+    padding: 0 10px;
+  }
+  
+  .statNumber {
+    font-size: 22px;
+    font-weight: bold;
+    color: var(--primary-color);
+  }
+  
+  .statLabel {
+    font-size: 14px;
+    color: var(--text-secondary);
+    margin-top: 5px;
+  }
+  
+  /* 用户数据 */
+  .userData {
+    background-color: var(--light-color);
+    padding: 15px;
+    border-radius: 8px;
+    margin-bottom: 25px;
+  }
+  
+  .dataItem {
+    margin-bottom: 8px;
+    font-size: 15px;
+  }
+  
+  .dataItem strong {
+    color: var(--primary-color);
+  }
+  
+  /* 作品区 */
+  .worksSection {
+    margin-top: 30px;
+  }
+  
+  .sectionTitle {
+    margin: 0 0 20px 0;
+    color: var(--dark-color);
+    font-size: 18px;
+    padding-bottom: 10px;
+    border-bottom: 2px solid var(--primary-color);
+  }
+  
+  .workItem {
+    padding: 15px;
+    margin-bottom: 15px;
+    background-color: var(--primary-card);
+    border-radius: 8px;
+    border-left: 4px solid var(--primary-color);
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+    transition: transform 0.3s, box-shadow 0.3s;
+  }
+  
+  .workItem:hover {
+    transform: translateY(-3px);
+    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
+  }
+  
+  .workTitle {
+    margin: 0 0 10px 0;
+    color: var(--text-color);
+    font-size: 16px;
+  }
+  
+  .workMeta {
+    display: flex;
+    justify-content: space-between;
+    font-size: 14px;
+    color: var(--text-color);
+  }
+  
+  /* 右侧内容区 */
+  .rightContent {
+    flex: 1;
+    min-width: 300px;
+    display: flex;
+    flex-direction: column;
+    gap: 20px;
+  }
+  
+  .petSection, .trafficSection {
+    background-color: var(--card-bg);
+    border-radius: 10px;
+    padding: 20px;
+    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
+  }
+  
+  .petContainer {
+    background-color: var(--card-bg);
+    border-radius: 8px;
+    padding: 15px;
+    text-align: center;
+  }
+  
+  .petImage {
+    max-width: 100%;
+    height: auto;
+    border-radius: 5px;
+  }
+  
+  .trafficContainer {
+    background-color: white;
+    border-radius: 8px;
+    padding: 10px;
+    text-align: center;
+    border: 1px solid var(--border-color);
+  }
+  
+  .trafficImage {
+    max-width: 100%;
+    height: auto;
+  }
+  
+  /* 加载和错误状态 */
+  .loading, .error {
+    padding: 20px;
+    text-align: center;
+    color: var(--text-color);
+  }
+  
+  .error {
+    color: #e74c3c;
+  }
\ No newline at end of file
diff --git a/src/views/homepage/homepage.tsx b/src/views/homepage/homepage.tsx
new file mode 100644
index 0000000..58c910f
--- /dev/null
+++ b/src/views/homepage/homepage.tsx
@@ -0,0 +1,164 @@
+import React, { useCallback, useEffect } from 'react';
+import styles from './homepage.module.css';
+import { useApi } from '@/hooks/request';
+import { useSelector } from 'react-redux';
+import { RootState } from '@/store';
+import { useNavigate } from 'react-router';
+import logo from '&/assets/logo.png';
+import { getUserMessage } from '@/api/homepage';
+import request from '@/utils/request'
+import { hotPosts } from '@/api/post';
+import { postUserLogin } from '@/api/auth';
+
+interface WorkItem {  
+  postId: number,
+  userId: number,
+  postTitle: string,
+  postContent: string,
+  createdAt: number,
+  postType: string,
+  viewCount: number,
+  hotScore: number,
+  lastCalculated: number
+
+}
+
+interface UserStats {
+  likes: number;
+  following: number;
+  followers: number;
+  mutualFollows: number;
+}
+
+interface UserResponse {
+  username: string;
+  inviteCode: string;
+  stats: UserStats;
+  upload: string;
+  level: string;
+  works: WorkItem[];
+  petImage: string;
+  trafficImage: string;
+}
+
+
+const Homepage: React.FC =() => {
+  const navigate = useNavigate();
+  const userInfo = useSelector((state: RootState) => state.user);
+  
+  // 获取用户作品数据
+  // const {data: response, loading, error, refresh} =useApi(()=>request.get(getUserMessage), false);
+  const { data: response, loading, error, refresh } = useApi<UserResponse>(() => request.get(getUserMessage), false);
+  useEffect(() => {
+    refresh(); // 页面首次加载时触发请求
+  }, []);
+  // 用户统计数据
+  const userStats = {
+    likes: response?.stats?.likes ?? 0,
+    following: response?.stats?.following ?? 0,
+    followers: response?.stats?.followers ?? 0,
+    mutualFollows: response?.stats?.mutualFollows ?? 0,
+    uploadAmount: response?.upload ?? '--',
+    level: response?.level ?? '--'
+  };
+  
+  const handleLogoClick = () => {
+    navigate('/');
+  };
+
+  return (
+    <div className={styles.container}>
+
+
+      {/* 用户信息主区域 */}
+      <div className={styles.mainContent}>
+        {/* 左侧用户信息区 */}
+        <div className={styles.userProfile}>
+          <div className={styles.userHeader}>
+            <img 
+              src={userInfo.avatar || '/default-avatar.png'} 
+              alt="用户头像" 
+              className={styles.userAvatar}
+            />
+            <div className={styles.userInfo}>
+              <h2 className={styles.username}>阳菜,放睛!</h2>
+              <div className={styles.inviteCode}>邀请码:1314520</div>
+              <button className={styles.editButton}>编辑主页</button>
+               <button 
+                className={styles.editButton}
+                onClick={() => navigate('/postDetails', {
+                  state: { isNewPost: true }
+                })}
+              >
+                发布种子
+              </button>
+            </div>
+          </div>
+
+          <div className={styles.userStats}>
+            <div className={styles.statItem}>
+              <div className={styles.statNumber}>{userStats.likes}</div>
+              <div className={styles.statLabel}>获赞</div>
+            </div>
+            <div className={styles.statItem}>
+              <div className={styles.statNumber}>{userStats.following}</div>
+              <div className={styles.statLabel}>关注</div>
+            </div>
+            <div className={styles.statItem}>
+              <div className={styles.statNumber}>{userStats.followers}</div>
+              <div className={styles.statLabel}>粉丝</div>
+            </div>
+            <div className={styles.statItem}>
+              <div className={styles.statNumber}>{userStats.mutualFollows}</div>
+              <div className={styles.statLabel}>互关</div>
+            </div>
+          </div>
+
+          <div className={styles.userData}>
+            <div className={styles.dataItem}>
+              <span>您的总上传量为:</span>
+              <strong>{userStats.uploadAmount}</strong>
+            </div>
+            <div className={styles.dataItem}>
+              <span>您的用户等级为:</span>
+              <strong>{userStats.level}</strong>
+            </div>
+          </div>
+
+          <div className={styles.worksSection}>
+            <h3 className={styles.sectionTitle}>我的作品</h3>
+            {loading && <div className={styles.loading}>加载中...</div>}
+            {error && <div className={styles.error}>{error.message}</div>}
+            
+            {response && response.works.map(work => (
+              <div key={work.postId} className={styles.workItem}>
+                <h4 className={styles.workTitle}>{work.postTitle}</h4>
+                <div className={styles.workMeta}>
+                  <span>发布时间:{work.createdAt}</span>
+                  <span>下载量:{work.viewCount} 做种数:{'待定'}</span>
+                </div>
+              </div>
+            )) }
+          </div>
+        </div>
+
+        {/* 右侧内容区 */}
+        <div className={styles.rightContent}>
+          <div className={styles.petSection}>
+            <h3 className={styles.sectionTitle}>宠物图</h3>
+            <div className={styles.petContainer}>
+              <img 
+                src="/assets/pet-blue-star.png" 
+                alt="蓝色星星宠物" 
+                className={styles.petImage}
+              />
+            </div>
+          </div>
+
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default Homepage;
\ No newline at end of file
diff --git a/src/views/login/login.tsx b/src/views/login/login.tsx
index bd429c7..96c5485 100644
--- a/src/views/login/login.tsx
+++ b/src/views/login/login.tsx
@@ -16,13 +16,17 @@
     const [email, setEmail] = useState('');
     const [password, setPassword] = useState('');
     const dispatch = useAppDispatch();
-    const { refresh: postUserLoginRefresh } = useApi(() => request.post(postUserLogin, {}), false);
-    const { refresh: getUserInfoRefresh } = useApi(() => request.get(getUserInfo), false);
+
+    const { refresh: postUserLoginRefresh } = useApi(
+        () => request.post(postUserLogin, { email, password}), false);
+    const { refresh: getUserInfoRefresh } = useApi(
+        () => request.get(getUserInfo), false);
 
     const nav = useNavigate();
     const handleLogin = debounce(async () => {
         try {
-            const res =await postUserLoginRefresh();
+            const res =await postUserLoginRefresh({email, password});
+            console.log("res", res);
             if (res==null ||(res as any).error) {
                 alert('Login failed. Please check your credentials.');
                 return;
diff --git a/src/views/pet/pet.module.css b/src/views/pet/pet.module.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/views/pet/pet.module.css
diff --git a/src/views/pet/pet.tsx b/src/views/pet/pet.tsx
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/views/pet/pet.tsx
diff --git a/src/views/search/search.module.css b/src/views/search/search.module.css
new file mode 100644
index 0000000..2e1f40a
--- /dev/null
+++ b/src/views/search/search.module.css
@@ -0,0 +1,132 @@
+:root {
+  --primary-color: #3498db;
+  --primary-hover: #2980b9;
+  --secondary-color: #f1c40f;
+  --dark-color: #2c3e50;
+  --light-color: #ecf0f1;
+  --text-color: #333;
+  --text-secondary: #7f8c8d;
+  --border-color: #ddd;
+  --bg-color: #2b2b2b;
+  --card-bg: #1e1e1e;
+}
+
+.container {
+  min-height: 100vh;
+  background-color: var(--bg-color);
+  color: var(--text-color);
+  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+  padding: 20px;
+}
+
+.secondaryHeader {
+  background-color: var(--card-bg);
+  padding: 15px 20px;
+  margin-bottom: 20px;
+  border-radius: 8px;
+  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
+  display: flex;
+  flex-wrap: wrap; /* 可换成 nowrap + overflow-x: auto 实现强制一行 + 横向滚动 */
+  gap: 15px;
+  align-items: center;
+  justify-content: flex-start;
+}
+
+.selectBox {
+  background-color: var(--light-color);
+  color: var(--text-color);
+  border: 1px solid var(--border-color);
+  border-radius: 5px;
+  padding: 8px 10px;
+  font-size: 14px;
+}
+
+.selectBox:focus {
+  outline: none;
+  border-color: var(--primary-color);
+}
+
+.tagFilters {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 10px;
+  background-color: var(--light-color);
+  color: var(--text-color);
+  border: 1px solid var(--border-color);
+  border-radius: 5px;
+  padding: 8px 10px;
+  font-size: 14px;
+}
+
+.tagFilters label {
+  margin: 0;
+  color: var(--text-color);
+}
+
+.filterButton {
+  padding: 8px 16px;
+  background-color: var(--primary-color);
+  color: white;
+  border: none;
+  border-radius: 5px;
+  font-size: 14px;
+  cursor: pointer;
+  transition: background-color 0.3s;
+}
+
+.filterButton:hover {
+  background-color: var(--primary-hover);
+}
+
+.results {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+}
+
+.postItem {
+  background-color: var(--card-bg);
+  padding: 20px;
+  border-radius: 8px;
+  border-left: 4px solid var(--primary-color);
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
+}
+
+.postItem h3 {
+  margin: 0 0 10px 0;
+  color: var(--text-color);
+}
+
+.postItem p {
+  margin: 4px 0;
+  color: var(--text-secondary);
+  font-size: 14px;
+}
+
+.secondaryHeader {
+  background-color: var(--card-bg);
+  padding: 15px 20px;
+  margin-bottom: 20px;
+  border-radius: 8px;
+  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.leftSection {
+  display: flex;
+  gap: 10px;
+}
+
+.centerSection {
+  flex: 1;
+  display: flex;
+  justify-content: center;
+}
+
+.rightSection {
+  display: flex;
+  justify-content: flex-end;
+}
diff --git a/src/views/search/search.tsx b/src/views/search/search.tsx
new file mode 100644
index 0000000..cd9db91
--- /dev/null
+++ b/src/views/search/search.tsx
@@ -0,0 +1,131 @@
+// search.tsx
+import React, { useState, useEffect } from 'react';
+import styles from './search.module.css';
+import { useLocation } from "react-router-dom";
+
+interface PostItem {
+  postId: number;
+  userId: number;
+  postTitle: string;
+  postContent: string;
+  createdAt: number;
+  postType: string;
+  viewCount: number;
+  hotScore: number;
+  lastCalculated: number;
+  tags?: number[];
+}
+
+const tagMap: Record<number, string> = {
+  1: "搞笑",
+  2: "悬疑",
+  3: "教育",
+  4: "动作",
+  5: "剧情"
+};
+
+const SearchPage: React.FC = () => {
+  const [posts, setPosts] = useState<PostItem[]>([]);
+  const [filteredPosts, setFilteredPosts] = useState<PostItem[]>([]);
+  const [selectedPostType, setSelectedPostType] = useState<string>('all');
+  const [selectedRating, setSelectedRating] = useState<number | null>(null);
+  const [selectedTags, setSelectedTags] = useState<number[]>([]);
+
+  const location = useLocation();
+  const params = new URLSearchParams(location.search);
+  const keyword = params.get("keyword");
+
+  useEffect(() => {
+    fetch('/api/posts')
+      .then((res) => res.json())
+      .then((data) => {
+        setPosts(data);
+        setFilteredPosts(data);
+      });
+  }, []);
+
+  const applyFilters = () => {
+    let filtered = posts;
+
+    if (selectedPostType !== 'all') {
+      filtered = filtered.filter((post) => post.postType === selectedPostType);
+    }
+
+    if (selectedRating !== null) {
+      filtered = filtered.filter((post) => post.hotScore >= selectedRating);
+    }
+
+    if (selectedTags.length > 0) {
+      filtered = filtered.filter((post) =>
+        post.tags?.some((tag) => selectedTags.includes(tag))
+      );
+    }
+
+    setFilteredPosts(filtered);
+  };
+
+  return (
+    <div className={styles.secondaryHeader}>
+    <div className={styles.leftSection}>
+        <select
+        value={selectedPostType}
+        onChange={(e) => setSelectedPostType(e.target.value)}
+        className={styles.selectBox}
+        >
+        <option value="all">所有分区</option>
+        <option value="影视">影视</option>
+        <option value="音乐">音乐</option>
+        <option value="游戏">游戏</option>
+        <option value="软件">软件</option>
+        </select>
+
+        <select
+        value={selectedRating || ''}
+        onChange={(e) =>
+            setSelectedRating(e.target.value ? Number(e.target.value) : null)
+        }
+        className={styles.selectBox}
+        >
+        <option value="">所有评分</option>
+        <option value="1">1星及以上</option>
+        <option value="2">2星及以上</option>
+        <option value="3">3星及以上</option>
+        <option value="4">4星及以上</option>
+        <option value="5">5星</option>
+        </select>
+    </div>
+
+    <div className={styles.centerSection}>
+        <div className={styles.tagFilters}>
+        {Object.entries(tagMap).map(([tagId, tagName]) => (
+            <label key={tagId}>
+            <input
+                type="checkbox"
+                value={tagId}
+                checked={selectedTags.includes(Number(tagId))}
+                onChange={(e) => {
+                const value = Number(e.target.value);
+                setSelectedTags((prev) =>
+                    prev.includes(value)
+                    ? prev.filter((t) => t !== value)
+                    : [...prev, value]
+                );
+                }}
+            />
+            {tagName}
+            </label>
+        ))}
+        </div>
+    </div>
+
+    <div className={styles.rightSection}>
+        <button className={styles.filterButton} onClick={applyFilters}>
+        筛选
+        </button>
+    </div>
+    </div>
+
+  );
+};
+
+export default SearchPage;
diff --git a/src/views/upload/upload.module.css b/src/views/upload/upload.module.css
new file mode 100644
index 0000000..5a9952a
--- /dev/null
+++ b/src/views/upload/upload.module.css
@@ -0,0 +1,150 @@
+.container {
+  background-color: var(--card-bg);
+  padding: 32px;
+  border-radius: 12px;
+  width: 100%;
+  height: 100%;
+  margin: auto;
+  border: 1px solid var(--border-color);
+  color: var(--text-color);
+}
+
+.formGroup {
+  margin-bottom: 20px;
+}
+
+.input,
+.select,
+.textarea {
+  width: 100%;
+  padding: 8px 12px;
+  margin-top: 4px;
+  border: 1px solid var(--border-color);
+  border-radius: 8px;
+  background-color: var(--bg-color);
+  color: var(--text-color);
+}
+
+.upload {
+  margin-top: 8px;
+}
+
+.textarea {
+  height: 100px;
+  resize: none;
+}
+
+.charCount {
+  text-align: right;
+  font-size: 12px;
+  color: var(--text-color);
+}
+
+.requirement {
+  font-size: 14px;
+  color: var(--primary-color);
+  margin-bottom: 12px;
+}
+
+.checkbox {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 20px;
+}
+
+.submitBtn {
+  background-color: var(--primary-color);
+  color: white;
+  border: none;
+  padding: 10px 20px;
+  border-radius: 8px;
+  cursor: pointer;
+  transition: background-color 0.3s ease;
+}
+
+.submitBtn:hover {
+  background-color: var(--primary-hover);
+}
+
+
+.wrapper {
+  display: flex;
+  justify-content: center;
+  margin-top: 50px;
+}
+
+.form {
+  width: 400px;
+  background: #ffffff;
+  border: 1px solid #ddd;
+  border-radius: 12px;
+  padding: 24px;
+  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
+}
+
+.title {
+  font-size: 22px;
+  margin-bottom: 20px;
+  text-align: center;
+}
+
+.input,
+.textarea {
+  width: 100%;
+  padding: 10px;
+  margin-bottom: 16px;
+  border-radius: 6px;
+  border: 1px solid #ccc;
+  font-size: 14px;
+}
+
+.textarea {
+  resize: vertical;
+  height: 100px;
+}
+
+.uploadArea {
+  padding: 12px;
+  border: 2px dashed #999;
+  border-radius: 8px;
+  text-align: center;
+  cursor: pointer;
+  background: #f9f9f9;
+  margin-bottom: 16px;
+  transition: all 0.2s;
+}
+
+.uploadArea:hover {
+  background: #f0f0f0;
+  border-color: #666;
+}
+
+.fileName {
+  margin-top: 8px;
+  font-size: 14px;
+  color: #333;
+}
+
+.error {
+  color: red;
+  margin-bottom: 10px;
+  font-size: 14px;
+  text-align: center;
+}
+
+.uploadButton {
+  width: 100%;
+  padding: 10px;
+  background-color: #409eff;
+  color: white;
+  border: none;
+  border-radius: 6px;
+  font-size: 16px;
+  cursor: pointer;
+  transition: background-color 0.3s;
+}
+
+.uploadButton:hover {
+  background-color: #317ee7;
+}
diff --git a/src/views/upload/upload.tsx b/src/views/upload/upload.tsx
new file mode 100644
index 0000000..4a60330
--- /dev/null
+++ b/src/views/upload/upload.tsx
@@ -0,0 +1,123 @@
+import instance from '@/utils/axios';
+import React, { useState } from 'react';
+import styles from './upload.module.css';
+import { Upload } from '@/api/upload';
+import { useNavigate } from 'react-router-dom'; // 用于跳转
+
+const PostDetails = () => {
+  const [postTitle, setPostTitle] = useState('');
+  const [postType, setPostType] = useState('');
+  const [postContent, setPostContent] = useState('');
+  const [isChecked, setIsChecked] = useState(false);
+
+  const navigate = useNavigate();
+
+  const handleSubmit = async () => {
+    if (!postTitle.trim() || !postType || !postContent.trim()) {
+      alert('请填写完整内容(资源名、类型、内容介绍)');
+      return;
+    }
+
+    if (!isChecked) {
+      alert('请先确认您已知晓以上内容');
+      return;
+    }
+
+    const payload = {
+      post: {
+        postId: 0,
+        userId: 0,
+        postTitle,
+        postContent,
+        createdAt: Date.now(),
+        postType,
+        viewCount: 0,
+        hotScore: 5,
+        lastCalculated: Date.now()
+      },
+      tagIds: [0]
+    };
+
+    try {
+      const res = await instance.post(Upload, payload);
+
+      console.log('mock返回内容:', res.code);
+
+      // 判断返回内容是否成功(根据你 mock 接口返回的 code 字段)
+      if (res.code !== 0) throw new Error('发布失败');
+
+      alert('发布成功!');
+      navigate(-1); // 返回上一页(homepage)
+    } catch (error) {
+      alert('发布失败,请稍后重试');
+      console.error(error);
+    }
+  };
+
+  return (
+    <div className={styles.container}>
+      <div className={styles.formGroup}>
+        <label>资源名:</label>
+        <input
+          type="text"
+          value={postTitle}
+          placeholder="请输入文本"
+          onChange={(e) => setPostTitle(e.target.value)}
+          className={styles.input}
+        />
+      </div>
+
+      <div className={styles.formGroup}>
+        <label>类型选择:</label>
+        <select
+          value={postType}
+          onChange={(e) => setPostType(e.target.value)}
+          className={styles.select}
+        >
+          <option value="">下拉选择</option>
+          <option value="type1">类型一</option>
+          <option value="type2">类型二</option>
+        </select>
+      </div>
+
+      {/* 暂时移除上传文件表单 */}
+      {/* <div className={styles.formGroup}>
+        <label>上传资源:</label>
+        <input
+          type="file"
+          onChange={(e) => setFile(e.target.files?.[0] || null)}
+          className={styles.upload}
+        />
+      </div> */}
+
+      <div className={styles.formGroup}>
+        <label>内容介绍:</label>
+        <textarea
+          placeholder="请输入内容介绍"
+          value={postContent}
+          onChange={(e) => setPostContent(e.target.value)}
+          maxLength={200}
+          className={styles.textarea}
+        />
+        <div className={styles.charCount}>{postContent.length}/200</div>
+      </div>
+
+      <div className={styles.requirement}>【发布内容要求】</div>
+
+      <div className={styles.checkbox}>
+        <input
+          type="checkbox"
+          checked={isChecked}
+          onChange={() => setIsChecked(!isChecked)}
+        />
+        <span>我已知晓以上内容</span>
+      </div>
+
+      <button onClick={handleSubmit} className={styles.submitBtn}>
+        我已知晓
+      </button>
+    </div>
+  );
+};
+
+export default PostDetails;