帖子分类

Change-Id: I17bafbfe3c1c8fd26c1e12499cb3c17cd1738e23
diff --git a/nginx.conf b/nginx.conf
new file mode 100644
index 0000000..f15b3e1
--- /dev/null
+++ b/nginx.conf
@@ -0,0 +1,15 @@
+server {
+    listen 80;
+    server_name localhost;
+    
+    location / {
+        root   /usr/share/nginx/html;
+        index  index.html;
+        try_files $uri $uri/ /index.html;  # 关键:支持前端路由
+    }
+
+    error_page 500 502 503 504 /50x.html;
+    location = /50x.html {
+        root /usr/share/nginx/html;
+    }
+}
\ No newline at end of file
diff --git a/src/api/auth.ts b/src/api/auth.ts
index bc6d413..cba34b3 100644
--- a/src/api/auth.ts
+++ b/src/api/auth.ts
@@ -1,3 +1,4 @@
 export const postUserLogin= '/auth/login';
 export const postUserRegister= '/auth/register';
-export const postVerifivateCode="/auth/send_verification_code";
\ No newline at end of file
+export const postVerifivateCode="/auth/send_verification_code";
+export const getRefreshToken="/auth/refresh_token";
\ No newline at end of file
diff --git a/src/api/comment.ts b/src/api/comment.ts
new file mode 100644
index 0000000..1809ad2
--- /dev/null
+++ b/src/api/comment.ts
@@ -0,0 +1 @@
+export const  getPostComments = '/api/comments/post'
\ No newline at end of file
diff --git a/src/api/post.ts b/src/api/post.ts
index 99dc8fa..b211bdd 100644
--- a/src/api/post.ts
+++ b/src/api/post.ts
@@ -1 +1,6 @@
-export const hotPosts='/post/hot'
\ No newline at end of file
+export const getHotPosts='/post/recommended'
+export const getLikePosts='/post/recommended-by-tags'
+export const getPosts='/post/search'
+export const getPostDetail='/post'
+export const getPostComments='/comments/post'
+export const unknownAPI='/api/unknown'
\ No newline at end of file
diff --git a/src/api/user.ts b/src/api/user.ts
index 30fbfff..ecea4d8 100644
--- a/src/api/user.ts
+++ b/src/api/user.ts
@@ -1 +1,3 @@
-export const getUserInfo="/user"
\ No newline at end of file
+export const getUserInfo="/user"
+
+export const postFollowUser="/user/follow"
\ No newline at end of file
diff --git a/src/components/navbar/navbar.tsx b/src/components/navbar/navbar.tsx
index a6c1fe7..a305521 100644
--- a/src/components/navbar/navbar.tsx
+++ b/src/components/navbar/navbar.tsx
@@ -1,4 +1,4 @@
-import React, { use } from "react";
+import React, { use, useEffect } from "react";
 import Icon, {
     HomeOutlined,
 } from "@ant-design/icons";
@@ -8,12 +8,12 @@
 import { Menu } from 'antd';
 import { useState } from "react";
 import style from './navbar.module.css'
-
+import { set } from "lodash";
+import { useNavigate } from "react-router";
 
 type CustomIconComponentProps = GetProps<typeof Icon>;
 type MenuItem = Required<MenuProps>['items'][number];
 const web_base_url = process.env.WEB_BASE_URL || 'http://localhost:3000';
-
 const VideoSvg = () => (
     <svg width="1em" height="1em" fill="currentColor" xmlns="http://www.w3.org/2000/svg" p-id="7331" viewBox="0 0 1024 1024">
       <title>video icon</title>
@@ -63,7 +63,6 @@
     <Icon component={ChatSvg} {...props} />
 );
 
-
 const items: MenuItem[] = [
 {
     key: 'home',
@@ -78,7 +77,7 @@
     key: 'video',
     icon: <VideoIcon />,
     label: (
-    <a href={{web_base_url}+'/posts?type=video'}>
+    <a href={'/posts?type=video'}>
         影视
     </a>
     ),
@@ -87,7 +86,7 @@
     key: 'music',
     icon: <MusicIcon />,
     label: (
-    <a href={{web_base_url}+'/posts?type=music'}>
+    <a href={'/posts?type=music'}>
         音乐
     </a>
     ),
@@ -96,7 +95,7 @@
     key: 'game',
     icon: <GameIcon />,
     label: (
-    <a href={{web_base_url}+'/posts?type=game'}>
+    <a href={'/posts?type=game'}>
         游戏
     </a>
     ),
@@ -105,7 +104,7 @@
     key: 'software',
     icon: <SoftwareIcon />,
     label: (
-    <a href={{web_base_url}+'/posts?type=software'}>
+    <a href={'/posts?type=software'}>
         软件
     </a>
     ),
@@ -114,20 +113,27 @@
     key: 'chat',
     icon: <ChatIcon />,
     label: (
-    <a href={{web_base_url}+'/posts?type=chat'}>
+    <a href={'/posts?type=chat'}>
         聊天
     </a>
     ),
 },
 ];
 
+interface NavbarProps {
+    current?: string;
+}
 
 
 
-
-const Navbar: React.FC = () => {
+const Navbar: React.FC<NavbarProps> = (props) => {
     const [current, setCurrent] = useState('home');
-
+    useEffect(() => {
+        console.log('current:', props.current);
+        if (props.current) {
+            setCurrent(props.current);
+        }
+    },[props.current]);
     const onClick: MenuProps['onClick'] = (e) => {
         console.log('click ', e);
         setCurrent(e.key);
diff --git a/src/components/postsPanel/postsPanel.module.css b/src/components/postsPanel/postsPanel.module.css
index dc10683..b4b75ce 100644
--- a/src/components/postsPanel/postsPanel.module.css
+++ b/src/components/postsPanel/postsPanel.module.css
@@ -2,6 +2,7 @@
     background-color: var(--card-bg);
     height:100%;
     width:100%;
+    padding:0px 5px;
 }
 
 .header{
@@ -27,7 +28,16 @@
 .content .item{
     display:flex;
     justify-content:space-between;
+    align-items:center;
+    padding-top:10px;
+    padding-bottom:10px;
+    box-shadow: 0 2px 8px rgba(0,0,0,0.04); /* 添加边框阴影 */
+    border:1px solid rgba(0,0,0,0.04);
+}
 
+.content .item:hover {
+    background:var(--select-bg-color);
+    cursor:pointer
 }
 .content .item .text{
     width:70%;
diff --git a/src/components/postsPanel/postsPanel.tsx b/src/components/postsPanel/postsPanel.tsx
index e4f87c0..2916753 100644
--- a/src/components/postsPanel/postsPanel.tsx
+++ b/src/components/postsPanel/postsPanel.tsx
@@ -2,6 +2,7 @@
 import React, { useCallback } from  'react';
 import request from '@/utils/request'
 import style from './postsPanel.module.css'
+import { useNavigate } from 'react-router';
 
 
 interface panelProps{
@@ -11,23 +12,24 @@
 }
 
 const PostsPanel:React.FC<panelProps> = (props) => {
-    const fenchData = useCallback(() => request.get(props.url), [props.url])
+    const nav = useNavigate();
+    const fenchData = useCallback(() => request.get(`${props.url}?page=1&size=5`), [props.url])
     const {data} = useApi(fenchData, true);
-
-
-
+    const handlePostCheck =(postId:string) =>{
+        nav('/postDetail?postId=' + postId);
+    }
     return (
         <div className={style.panel}>
             <div className={style.header}>
                 <span className={style.title}>{props.name}</span>
                 <span className={style.more}>更多</span>
             </div>
-            <div className={style.content}>
+            <div className={style.content} >
                 {data && data.length > 0 ?
-                data?.map((item: { title: string; date: string }, index: number) => (
-                    <div key={index} className={style.item}>
-                        <span className={style.text}>{item.title}</span>
-                        <span>{item.date}</span>
+                data?.map((item: {postId:string, postTitle: string; createdAt: string }, index: number) => (
+                    <div key={index} className={style.item} onClick={()=> handlePostCheck(item.postId)} >
+                        <span className={style.text}>{item.postTitle}</span>
+                        <span>{item.createdAt}</span>
                     </div>
                 ))  :(
                     <div>未查询到相关记录</div>
diff --git a/src/components/selfStatus/selfStatus.tsx b/src/components/selfStatus/selfStatus.tsx
index a5156f4..834e841 100644
--- a/src/components/selfStatus/selfStatus.tsx
+++ b/src/components/selfStatus/selfStatus.tsx
@@ -1,7 +1,10 @@
-import React from "react";
+import React, { useEffect } from "react";
 import { useAppSelector } from "../../hooks/store";
 import style from "./style.module.css"
-
+import { useApi } from "@/hooks/request";
+import request from "@/utils/request";
+import { getUserInfo } from "@/api/user";
+import { useAppDispatch } from "@/hooks/store";
 interface SelfStatusProps {
     className?: string;
 }
@@ -13,11 +16,25 @@
     const downloadTraffic = useAppSelector(state => state.user.downloadTraffic);
     const downloadPoints = useAppSelector(state => state.user.downloadPoints);
     const avatar = useAppSelector(state => state.user.avatar);
+    const dispatch = useAppDispatch();
+    const { data, refresh } = useApi(() => request.get(getUserInfo), false);
+    useEffect(() => {
+        if (avatar.length === 0) {
+            refresh(); // 触发 API 请求
+        }
+    }, [avatar, refresh]);
+
+    useEffect(() => {
+        if (data) {
+            dispatch({ type: "user/getUserInfo", payload: data });
+        }
+    }, [data, dispatch]);
 
     return (
         <div className={style.container}>
             <div className={style.left}>
-                <img className={style.avatar} src={avatar} alt="User Avatar" />
+                {avatar && avatar.length > 0 ? (
+                <img className={style.avatar} src={avatar} alt="User Avatar" />):null}
             </div>
             <div className={style.right}>
                 <div className={style.info}>
diff --git a/src/components/selfStatus/style.module.css b/src/components/selfStatus/style.module.css
index 8ed86a7..858e88a 100644
--- a/src/components/selfStatus/style.module.css
+++ b/src/components/selfStatus/style.module.css
@@ -4,7 +4,6 @@
     align-items: flex-start;
     justify-content: space-between;
     padding: 20px;
-    border: 1px solid #ccc;
     border-radius: 10px;
     background-color: var(--card-bg);
     width: 100%;
diff --git a/src/global.css b/src/global.css
index b49490a..9c38334 100644
--- a/src/global.css
+++ b/src/global.css
@@ -28,6 +28,7 @@
     --text-color: #000000;
     --card-bg: #ffffff;
     --border-color: #e0e0e0;
+    --select-bg-color: #b3d8fd;
   }
   
 body.dark {
@@ -35,5 +36,6 @@
     --text-color: #f1f1f1;
     --card-bg: #1e1e1e;
     --border-color: #444444;
+    --select-bg-color:#3a466b;
   }
   
\ No newline at end of file
diff --git a/src/index.tsx b/src/index.tsx
index 69441df..9205b1b 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,10 +1,13 @@
-import React from "react";
+import React, { use } from "react";
 import {createRoot} from "react-dom/client"
 import { Provider } from "react-redux";
 import router from "./route";
 import store from "./store/index";
 import { RouterProvider } from "react-router";
 import './global.css';
+import { setupMock } from "./mock/index";
+import { useEffect } from "react";
+import { checkAndRefreshToken } from "./utils/jwt";
 
 if(localStorage.getItem("theme") === null) {
     localStorage.setItem("theme", "light");
@@ -13,7 +16,9 @@
     document.body.className=localStorage.getItem("theme")!;
 }
 
+setupMock()
 const root = createRoot(document.getElementById('root')!)
+
 root.render(
     <Provider store={store}>
         <RouterProvider router={router}/>
diff --git a/src/mock/auth.js b/src/mock/auth.js
index 3cc837b..272d8b7 100644
--- a/src/mock/auth.js
+++ b/src/mock/auth.js
@@ -1,16 +1,63 @@
 import Mock from 'mockjs';
 import MockAdapter from 'axios-mock-adapter';
-import {postUserLogin} from '@/api/auth'; // Import the API endpoint
+import {postUserLogin, getRefreshToken} from '@/api/auth'; // Import the API endpoint
 
+
+
+function generateToken(userId, role) {
+  const exp = Math.floor(Date.now() / 1000) + 60 * 60;
+
+  // 生成 JWT Header
+  const header = {
+      alg: 'HS256', // 签名算法
+      typ: 'JWT',   // 类型
+  };
+
+  // 生成 JWT Payload
+  const payload = {
+      userId,
+      role,
+      exp,
+  };
+
+  // Base64 编码 Header 和 Payload
+  const base64Header = btoa(JSON.stringify(header));
+  const base64Payload = btoa(JSON.stringify(payload));
+
+  // 模拟 Signature(实际应使用密钥进行 HMAC-SHA256 签名)
+  const signature = btoa('mock-signature'); // 简单模拟签名
+
+  // 拼接 JWT Token
+  const token = `${base64Header}.${base64Payload}.${signature}`;
+  return token;
+}
 /**
  * 设置用户相关的 Mock 接口
  * @param {MockAdapter} mock 
  */
-export function setupAuthMock(mock){
-    mock.onPost(postUserLogin).reply(config => {
-        let data = Mock.mock({
-          "token": '11111111111111111',
-        });
-        return [200, data];
+export function setupAuthMock(mock) {
+  mock.onPost(postUserLogin).reply(config => {
+    const data = JSON.parse(config.data);
+    if(data.email === 'admin@1' && data.password === '123456'){
+      // 模拟用户登录数据
+      const token = generateToken(1, 'admin'); 
+      
+      // 返回模拟的 Token
+      let data = Mock.mock({
+          token,
       });
-}    
+      return [200, data];
+    }else{
+      return [401, {message: '用户名或密码错误'}];
+    }
+  });
+  mock.onGet(getRefreshToken).reply(config => {
+      // 模拟用户登录数据
+      const  token = generateToken(1, 'admin'); // 这里的 1 和 'admin' 可以根据需要修改
+      // 返回模拟的 Token
+      let data = Mock.mock({
+          token,
+      });
+      return [200, data];
+  })
+}
diff --git a/src/mock/comment.d.ts b/src/mock/comment.d.ts
new file mode 100644
index 0000000..2ed6cca
--- /dev/null
+++ b/src/mock/comment.d.ts
@@ -0,0 +1,3 @@
+import type MockAdapter from 'axios-mock-adapter';
+
+export declare function setupCommentMock(mock: MockAdapter): void;
\ No newline at end of file
diff --git a/src/mock/comment.js b/src/mock/comment.js
new file mode 100644
index 0000000..695ccc4
--- /dev/null
+++ b/src/mock/comment.js
@@ -0,0 +1,34 @@
+import mockjs from "mockjs";
+import MockAdapter from "axios-mock-adapter";
+import { getPostComments} from "@/api/comment";
+
+export function setupCommentMock(mock) {
+    const getPostCommentsPattern = new RegExp(`^${getPostComments}/\\d+$`);
+    mock.onGet(getPostCommentsPattern).reply(config => {
+         let data = mockjs.mock({
+            [`list|5`]: [
+                {
+                    "commentId|+1": 1, // 自增评论 ID
+                    "content": "@cparagraph(1, 3)", // 随机生成 1-3 段评论内容
+                    "createdAt": "@datetime('T')", // 随机生成时间戳
+                    "parentCommentId": null, // 顶级评论的父评论 ID 为 null
+                    "postId|1-100": 1, // 随机生成帖子 ID
+                    "userId|1-100": 1, // 随机生成用户 ID
+                    "replies|0-3": [ // 随机生成 0-3 条子评论
+                        {
+                            "commentId|+1": 100, // 子评论的 ID 从 100 开始自增
+                            "content": "@cparagraph(1, 2)", // 随机生成 1-2 段子评论内容
+                            "createdAt": "@datetime('T')", // 随机生成时间戳
+                            "parentCommentId": "@increment(1)", // 父评论 ID
+                            "postId|1-100": 1, // 随机生成帖子 ID
+                            "userId|1-100": 1, // 随机生成用户 ID
+                            "replies": [] // 子评论的子评论为空
+                        }
+                    ]
+                },
+            ],
+        });
+        return [200, data.list];
+    });
+
+}
\ No newline at end of file
diff --git a/src/mock/index.ts b/src/mock/index.ts
index fabb34e..4429216 100644
--- a/src/mock/index.ts
+++ b/src/mock/index.ts
@@ -3,6 +3,7 @@
 import {setupAuthMock}  from './auth'
 import { setupUserMock } from './user';
 import { setupPostMock } from './post';
+import { setupCommentMock } from './comment';
 
 // 创建 Mock 实例
 export const mock = new MockAdapter(instance, { 
@@ -18,9 +19,8 @@
   setupAuthMock(mock)
   setupUserMock(mock)
   setupPostMock(mock)
+  setupCommentMock(mock)
   
   console.log('Mock 模块已加载')
 }
 
-// 自动执行
-setupMock()
\ No newline at end of file
diff --git a/src/mock/post.js b/src/mock/post.js
index abdfc6a..63f4413 100644
--- a/src/mock/post.js
+++ b/src/mock/post.js
@@ -1,17 +1,83 @@
 import Mock from 'mockjs';
 import MockAdapter from 'axios-mock-adapter';
-import {hotPosts} from '@/api/post'
+import {getHotPosts, getLikePosts, getPosts, getPostDetail} from '@/api/post'
 
 /**
  * 设置用户相关的 Mock 接口
  * @param {MockAdapter} mock 
  */
 export function setupPostMock(mock){
-    mock.onGet(hotPosts).reply(config => {
-        let data = Mock.mock([{
-          'title':'test title',
-          'date':'2025-4-20'
-        }]);
+    const hotPostsPattern = new RegExp(`^${getHotPosts}(\\?page=\\d+&size=\\d+)?$`);
+    const LikePostsPattern = new RegExp(`^${getLikePosts}(\\?page=\\d+&size=\\d+)?$`);
+    const searchPostsPattern = new RegExp(`^${getPosts}\\?((keyword=[^&]+&?)|(tags=[^&]+&?)|(author=[^&]+&?)|)+(page=\\d+)&(pageSize=\\d+)$`);
+    mock.onGet(hotPostsPattern).reply(config => {
+      const urlParams = new URLSearchParams(config.url.split('?')[1]);
+      const size = parseInt(urlParams.get('size')) || 10;
+      let data = Mock.mock({
+        [`list|${size}`]: [
+          {
+            'postId|+1': 1,
+            'postTitle': '@ctitle(5, 10)',
+            'postContent': '@cparagraph(1, 3)',
+            'author': '@cname()',
+            'createdAt': '@date("yyyy-MM-dd")',
+            'viewCount|1-100': 1,
+          },
+        ],
+      });
+      return [200, data.list];
+    });
+      mock.onGet(LikePostsPattern).reply(config => {
+        const urlParams = new URLSearchParams(config.url.split('?')[1]);
+        const size = parseInt(urlParams.get('size')) || 10;
+        let data = Mock.mock({
+          [`list|${size}`]: [
+            {
+              'postId|+1': 1,
+              'postTitle': '@ctitle(5, 10)',
+              'postContent': '@cparagraph(1, 3)',
+              'author': '@cname()',
+              'createdAt': '@date("yyyy-MM-dd")',
+              'viewCount|1-100': 1,
+            },
+          ],
+        });
+        return [200, data.list];
+      });
+      mock.onGet(searchPostsPattern).reply(config => {
+        const urlParams = new URLSearchParams(config.url.split('?')[1]);
+        const tags = urlParams.get('tags')?.split(',') || []; // 将 tags 参数解析为数组
+        const page = parseInt(urlParams.get('page')) || 1; // 默认 page 为 1
+        const size = parseInt(urlParams.get('size')) || 10; // 默认 size 为 10
+        let data = Mock.mock({
+          [`list|${size}`]: [
+            {
+              'postId|+1': 1,
+              'postTitle': '@ctitle(5, 10)',
+              'postContent': '@cparagraph(1, 3)',
+              'author': '@cname()',
+              'createdAt': '@date("yyyy-MM-dd")',
+              'viewCount|1-100': 1,
+            },
+          ],
+        });
+        return [200, data.list];
+      });
+
+
+      const getPostDetailPattern = new RegExp(`^${getPostDetail}/[0-9]+$`);
+      mock.onGet(getPostDetailPattern).reply(config => {
+        const postId = config.url.split('/').pop();
+        let data = Mock.mock({
+          'postId': postId,
+          'postTitle': '@ctitle(5, 10)',
+          'postContent': '@cparagraph(1, 3)',
+          'author': '@cname()',
+          'createdAt': '@date("yyyy-MM-dd")',
+          'viewCount|1-100': 1,
+        });
         return [200, data];
       });
+
+
 }    
diff --git a/src/route/index.tsx b/src/route/index.tsx
index c56ada3..93e6cf9 100644
--- a/src/route/index.tsx
+++ b/src/route/index.tsx
@@ -6,6 +6,8 @@
 import React from 'react'
 import Forum from '../views/forum'
 import { RootState } from '@/store'
+import PostList from '../views/postList/postList'
+import PostDetail from '../views/postDetail/postDetail'
 
 const router = createBrowserRouter([
     {
@@ -24,6 +26,14 @@
                         element:<Forum/>
 
                     },
+                    {
+                        path: '/posts',
+                        element: <PostList/>
+                    },
+                    {
+                        path: '/postsDetail',
+                        element: <PostDetail/>
+                    }
                 ]
             },
         ]
diff --git a/src/store/userReducer.ts b/src/store/userReducer.ts
index bbed95e..b677263 100644
--- a/src/store/userReducer.ts
+++ b/src/store/userReducer.ts
@@ -1,4 +1,5 @@
 import { createSlice } from '@reduxjs/toolkit';
+import { isTokenExpired } from '@/utils/jwt';
 
 interface UserState {
     userId: string;
@@ -15,7 +16,7 @@
     userId: '',
     userName: '',
     role: '',
-    isLogin: false,
+    isLogin: localStorage.getItem('token')&& isTokenExpired(localStorage.getItem('token') as string) ==='0'  ? true : false,
     uploadTraffic: 0,
     downloadTraffic: 0,
     downloadPoints: 0,
@@ -39,7 +40,6 @@
             state.downloadTraffic = action.payload.downloadTraffic;
             state.downloadPoints = action.payload.downloadPoints;
             state.avatar = action.payload.avatar;
-            console.log(state);
         },
         logout: (state) => {
             state.userId = '';
diff --git a/src/utils/axios.ts b/src/utils/axios.ts
index 1eaad87..273a9c4 100644
--- a/src/utils/axios.ts
+++ b/src/utils/axios.ts
@@ -12,6 +12,7 @@
   // 请求拦截器
   instance.interceptors.request.use(
     (config) => {
+      console.log('Request Config:', config)
       // 添加认证 token
       const token = localStorage.getItem('token')
       if (token) {
@@ -27,7 +28,6 @@
   // 响应拦截器
   instance.interceptors.response.use(
     (response) => {
-      console.log('Response:', response)
       // 统一处理响应数据格式
       if (response.status === 200) {
         return response.data
diff --git a/src/utils/jwt.ts b/src/utils/jwt.ts
new file mode 100644
index 0000000..42308dc
--- /dev/null
+++ b/src/utils/jwt.ts
@@ -0,0 +1,61 @@
+import { getRefreshToken } from '@/api/auth';
+import axios from 'axios';
+
+function parseJwt(token: string) {
+    console.log('Parsing JWT token:', token);
+    try {
+        const base64Url = token.split('.')[1];
+        const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
+        const jsonPayload = decodeURIComponent(
+            atob(base64)
+                .split('')
+                .map(c => `%${('00' + c.charCodeAt(0).toString(16)).slice(-2)}`)
+                .join('')
+        );
+        return JSON.parse(jsonPayload);
+    } catch (error) {
+        console.error('Invalid JWT token:', error);
+        return null;
+    }
+}
+
+/**
+ * 检查token是否过期
+ * @returns {string} -1: token无效 0: token未过期 1: token已过期
+**/
+export function isTokenExpired(token: string): string {
+    if (token === null || token === undefined) {
+        return '-1'; // 如果没有token,认为token无效
+    }
+    const decoded = parseJwt(token);
+    if (!decoded || !decoded.exp) {
+        return '-1'; // 如果解析失败或没有 `exp` 字段,认为token无效
+    }
+    const currentTime = Math.floor(Date.now() / 1000); // 当前时间(秒)
+    const bufferTime = 60 * 10; // 过期前的缓冲时间(秒)
+    return decoded.exp < currentTime ?decoded.exp < currentTime - bufferTime ? '-1' : '1'  :"0";
+}
+
+async function refreshToken() {
+    try {
+        const response = await axios.post(getRefreshToken, {
+            token: localStorage.getItem('token'),
+        });
+        const newToken = response.data.token;
+        localStorage.setItem('token', newToken);
+        return newToken;
+    } catch (error) {
+        console.error('Failed to refresh token:', error);
+        return null;
+    }
+}
+
+export async function checkAndRefreshToken() {
+    const token = localStorage.getItem('token');
+    if (token && isTokenExpired(token)=='1') {
+        const newToken = await refreshToken();
+        if (!newToken) {
+            localStorage.removeItem('token');
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/views/forum/index.tsx b/src/views/forum/index.tsx
index 345f880..83d791f 100644
--- a/src/views/forum/index.tsx
+++ b/src/views/forum/index.tsx
@@ -5,7 +5,7 @@
 import style from "./index.module.css";
 import Navbar from "@/components/navbar/navbar";
 import PostsPanel from "@/components/postsPanel/postsPanel";
-import { hotPosts } from "@/api/post";
+import { getHotPosts, getLikePosts} from "@/api/post";
 import { Carousel } from 'antd';
 import ad1 from '&/assets/ad1.png'
 import ad2 from '&/assets/ad2.png'
@@ -42,7 +42,7 @@
                         </Carousel>
                     </div>
                     <div className={style.hotPosts}>
-                        <PostsPanel name='热门种子' url={hotPosts} limit={5}/>
+                        <PostsPanel name='热门种子' url={getHotPosts} limit={5}/>
                     </div>
                 </div>
                </div>
@@ -54,13 +54,13 @@
             </div>
             <div className={style.down}>
                 <div className={style.newPost}>
-                    <PostsPanel name='最新发布' url={hotPosts} limit={5}/>
+                    <PostsPanel name='最新发布' url={getHotPosts} limit={5}/>
                 </div>
                 <div className={style.likePost}>
-                    <PostsPanel name='猜你喜欢' url={hotPosts} limit={5}/>
+                    <PostsPanel name='猜你喜欢' url={getLikePosts} limit={5}/>
                 </div>
                 <div className={style.forsalePost}>
-                    <PostsPanel name='促销种子' url={hotPosts} limit={5}/>
+                    <PostsPanel name='促销种子' url={getHotPosts} limit={5}/>
                 </div>
             </div>
         </div>
diff --git a/src/views/frame/frame.tsx b/src/views/frame/frame.tsx
index c0c2e0e..f41e53f 100644
--- a/src/views/frame/frame.tsx
+++ b/src/views/frame/frame.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { use } from "react";
 import { Outlet } from "react-router";
 import { useEffect, useState } from "react";
 import { 
@@ -12,8 +12,13 @@
 import logo from "&/assets/logo.png";
 import { useAppDispatch } from "@/hooks/store";
 import { useSelector } from "react-redux";
+
+import { checkAndRefreshToken } from "@/utils/jwt";
 const Frame:React.FC = () => {
 
+    useEffect(() => {
+        checkAndRefreshToken();
+    }, []);
     const dispatch = useAppDispatch();
 
     const showSearch = useSelector((state: any) => state.setting.showSearch); 
diff --git a/src/views/login/login.tsx b/src/views/login/login.tsx
index bd429c7..562f178 100644
--- a/src/views/login/login.tsx
+++ b/src/views/login/login.tsx
@@ -11,38 +11,39 @@
 import logo from '&/assets/logo.png';
 import { getUserInfo } from '@/api/user';
 import debounce from 'lodash/debounce';
+import {message} from 'antd';
+
 
 const Login: React.FC = () => {
     const [email, setEmail] = useState('');
     const [password, setPassword] = useState('');
     const dispatch = useAppDispatch();
-    const { refresh: postUserLoginRefresh } = useApi(() => request.post(postUserLogin, {}), false);
+    const { refresh: postUserLoginRefresh } = useApi(() => request.post(postUserLogin, {email, password}), false);
     const { refresh: getUserInfoRefresh } = useApi(() => request.get(getUserInfo), false);
+    const [messageApi, contextHolder] = message.useMessage();
 
     const nav = useNavigate();
+    const showErrorMessage = async (message: string) => {
+        messageApi.error(message);
+    };
     const handleLogin = debounce(async () => {
         try {
-            const res =await postUserLoginRefresh();
-            if (res==null ||(res as any).error) {
-                alert('Login failed. Please check your credentials.');
-                return;
+            const res = await postUserLoginRefresh();
+            console.log(res);
+            if (res == null || (res as any).error) {
+                throw new Error('登录失败');
             }
             dispatch({ type: "user/login", payload: res });
-            
+
             const userInfo = await getUserInfoRefresh();
-            if (userInfo==null || (userInfo as any).error) {
-                alert('Failed to fetch user information.');
-                return;
+            if (userInfo == null || (userInfo as any).error) {
+                throw new Error('获取用户信息失败');
             }
             dispatch({ type: "user/getUserInfo", payload: userInfo });
             nav('/');
         } catch (error) {
-            alert('An unexpected error occurred. Please try again later.');
-            if (error instanceof Error) {
-              console.error(error.message); // 明确访问 message 属性
-            } else {
-              console.error('Unknown error occurred');
-            }
+            // 将错误信息传递给一个异步函数
+            showErrorMessage('登录失败,请检查您的用户名和密码');
         }
     }, 1000) as () => void;
 
@@ -51,6 +52,7 @@
     }
     return (
         <div className={style.form}>
+            {contextHolder}
             <img className={style.logo} src={logo} alt="logo" onClick={handleLogoClick}></img>
             <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} className={style.email} placeholder="Enter your email" />
             <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} className={style.password} placeholder="Enter your password" />
diff --git a/src/views/postDetail/postDetail.module.css b/src/views/postDetail/postDetail.module.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/views/postDetail/postDetail.module.css
diff --git a/src/views/postDetail/postDetail.tsx b/src/views/postDetail/postDetail.tsx
new file mode 100644
index 0000000..a40fb68
--- /dev/null
+++ b/src/views/postDetail/postDetail.tsx
@@ -0,0 +1,114 @@
+import React, { useEffect, useState } from 'react';
+import { useParams } from 'react-router-dom';
+import styles from './PostDetail.module.css';
+import { Card, List, Typography, Button, Input, Spin, Empty } from 'antd';
+type CommentProps = {
+	children?: React.ReactNode;
+};
+import { getPostDetail } from '@/api/post';
+import { getPostComments } from '@/api/comment';
+import { useSearchParams } from 'react-router-dom';
+import request from '@/utils/request';
+import { useApi } from '@/hooks/request';
+import Navbar from '@/components/navbar/navbar';
+
+const { Title, Text, Paragraph } = Typography;
+const { TextArea } = Input;
+
+export interface PostResponse {
+	createdAt?: number;
+	hotScore?: number;
+	lastCalculated?: number;
+	postContent?: string;
+	postId?: number;
+	postTitle?: string;
+	postType?: string;
+	userId?: number;
+	viewCount?: number;
+	[property: string]: any;
+}
+
+export interface CommentResponse {
+	commentId?: number;
+	content?: string;
+	createdAt?: number;
+	parentCommentId?: number | null;
+	postId?: number;
+	replies?: CommentResponse[];
+	userId?: number;
+	[property: string]: any;
+}
+
+const PostDetail: React.FC = () => {
+	const [searchParams] = useSearchParams();
+	const postId = searchParams.get('postId');
+	const { refresh: getPostDetailRefresh } = useApi(() => request.get(getPostDetail + `/${postId}`), false);
+	const { refresh: getPostCommentsRefresh } = useApi(() => request.get(getPostComments + `/${postId}`), false);
+	const [post, setPost] = useState<PostResponse | null>(null);
+	const [comments, setComments] = useState<CommentResponse[]>([]);
+	const [newComment, setNewComment] = useState<string>('');
+	const [loading, setLoading] = useState<boolean>(true);
+
+	useEffect(() => {
+		console.log('postId', postId);
+		if (!postId) return;
+		const fetchData = async () => {
+			setLoading(true);
+			const res = await getPostDetailRefresh();
+			if (res == null || (res as any).error) {
+				setLoading(false);
+				return;
+			}
+			setPost(res as PostResponse);
+			await getPostCommentsRefresh();
+			setComments(res as CommentResponse[]);
+			setLoading(false);
+		};
+		fetchData();
+	}, [postId]);
+
+	if (loading) return <div className={styles.center}><Spin /></div>;
+	if (!post) return <div className={styles.center}><Empty description="未找到帖子" /></div>;
+
+	return (
+		<div className={styles.container}>
+			<div className={styles.nav}>
+				<Navbar current={post.postType} />
+			</div>
+			<div className={styles.content}>
+				<div className={styles.postDetail}>
+						
+				</div >
+				<Card title={post.postTitle} className={styles.card}>
+					<Paragraph>{post.postContent}</Paragraph>
+					<div className={styles.actions}>
+						<Button type="primary" onClick={() => setNewComment('')}>评论</Button>
+					</div>
+				</Card>
+
+				<List
+					className={styles.commentList}
+					header={<Title level={4}>评论区</Title>}
+					dataSource={comments}
+					renderItem={(item) => (
+						<List.Item key={item.commentId}>
+							<List.Item.Meta
+								title={<Text strong>{item.userId}</Text>}
+								description={<Text>{item.content}</Text>}
+							/>
+						</List.Item>
+					)}
+				/>
+
+				<TextArea
+					rows={4}
+					value={newComment}
+					onChange={(e) => setNewComment(e.target.value)}
+					placeholder="写下你的评论..."
+				/>
+				</div>
+		</div>
+	);
+};
+
+export default PostDetail;
\ No newline at end of file
diff --git a/src/views/postDetail/posterInfo.tsx b/src/views/postDetail/posterInfo.tsx
new file mode 100644
index 0000000..8fe381c
--- /dev/null
+++ b/src/views/postDetail/posterInfo.tsx
@@ -0,0 +1,37 @@
+import { PositionType } from 'antd/es/image/style';
+import request from '@/utils/request';
+import { useApi } from '@/hooks/request';
+import React from 'react';
+import { useNavigate } from 'react-router';
+import { postFollowUser } from '@/api/user';
+interface PosterInfoProps {
+    userId: number;
+    userName: string;
+    role:string;
+    avatar: string;
+}
+
+const PosterInfo: React.FC<PosterInfoProps> = (prop:PosterInfoProps) => {
+    const nav = useNavigate();
+    const { refresh } = useApi(()=>request.post(postFollowUser+`userId=${prop.userId}`), false);
+    const handleClick = () => {
+        nav(`/homepage?userId=${prop.userId}`);
+    }
+
+    const handleFollow = () => {
+        refresh()
+    }
+    const { userId, userName, role, avatar } = prop;
+    return (
+        <>
+            <div className="poster-info" onClick={handleClick}>
+                <img src={avatar}></img>
+                <p>{userName}</p>
+                <p>{role}</p>
+            </div>
+            <button className="poster-info-button" />关注
+
+        </>
+
+    );
+}
\ No newline at end of file
diff --git a/src/views/postList/postList.module.css b/src/views/postList/postList.module.css
new file mode 100644
index 0000000..6290106
--- /dev/null
+++ b/src/views/postList/postList.module.css
@@ -0,0 +1,88 @@
+.container {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: row;
+    position: relative;
+}
+
+.left {
+    flex: 3;
+    display: flex;
+    flex-direction: column;
+    margin: 5px;
+}
+
+.navbar {
+    height: 60px;
+    background-color: #f5f5f5;
+    border-bottom: 1px solid #ddd;
+    border-radius: 8px;
+}
+
+.content {
+    flex: 1;
+    overflow-y: auto; /* 允许垂直滚动 */
+    padding: 10px;
+    background-color: #fff;
+    border-radius: 8px;
+    border: 1px solid #ddd;
+}
+
+.contentItem {
+    position: relative;
+    margin-bottom: 15px;
+    padding: 10px;
+    border: 1px solid #ddd;
+    border-radius: 5px;
+    background-color: #f9f9f9;
+}
+
+.contentItem h3 {
+    margin: 0 0 5px;
+    font-size: 18px;
+    color: #333;
+}
+
+.contentItem p {
+    margin: 0;
+    font-size: 14px;
+    color: #666;
+}
+
+.contentItem .createDate {
+    position: absolute;
+    top: 10px;
+    right: 10px;
+    font-size: 12px;
+    color: #999;
+}
+
+.noData {
+    text-align: center;
+    color: #999;
+    margin-top: 20px;
+}
+
+.right {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    margin: 5px;
+}
+
+.selfStatus {
+    margin-bottom: 20px;
+    background-color: #fff;
+    border-radius: 8px;
+    border: 1px solid #ddd;
+    padding: 10px;
+}
+
+.filter {
+    flex: 1;
+    background-color: #fff;
+    border: 1px solid #ddd;
+    border-radius: 8px;
+    padding: 10px;
+}
\ No newline at end of file
diff --git a/src/views/postList/postList.tsx b/src/views/postList/postList.tsx
new file mode 100644
index 0000000..c08b7cf
--- /dev/null
+++ b/src/views/postList/postList.tsx
@@ -0,0 +1,82 @@
+import React from "react";
+import style from "./postList.module.css";
+import SelfStatus from "@/components/selfStatus/selfStatus";
+import Navbar from "@/components/navbar/navbar";
+import PostsPanel from "@/components/postsPanel/postsPanel";
+import { getPosts, unknownAPI } from "@/api/post";
+import { Form } from "antd"
+import { useApi } from "@/hooks/request";
+import request from "@/utils/request";
+import { Pagination, PaginationProps } from "antd";
+import { set } from "lodash";
+import { useEffect } from "react";
+import { useNavigate, useSearchParams } from "react-router";
+
+
+const PostList:React.FC = () => {
+    const [searchParams] = useSearchParams();
+    const type = searchParams.get("type") || ""; 
+    const nav = useNavigate();
+
+    if(type in ['video', 'music', 'game', 'software']) {
+        nav('/')
+    }
+
+    const {data:postList, refresh:getPostList} = useApi(() => request.get(getPosts + `?tags=${[type]}&page=${currentPage}&pageSize=${pageSize}`), false);
+    const [currentPage, setCurrentPage] = React.useState(1);
+    const [pageSize, setPageSize] = React.useState(10);
+    const handlePageChange = (page:number, size?:number) => {
+        setCurrentPage(page);
+        if(size) setPageSize(size);
+        console.log(page, size);
+    };
+
+    const handlePostClick = (postId:number) => {
+        nav(`/postsDetail?postId=${postId}`);
+    }
+
+    useEffect(() => {
+        getPostList();
+    },[currentPage, pageSize]);
+
+    return (
+        <div className={style.container}>
+            <div className={style.left}>
+                <div className={style.navbar}>
+                    <Navbar current={type}/>
+                </div>
+                <div className={style.content}>
+                    {postList && postList.length > 0 ? (
+                        postList.map((post: { postId: number; postTitle: string; postContent: string; createdAt:string }) => (
+                            <div key={post.postId} className={style.contentItem} onClick={() => handlePostClick(post.postId)}>
+                                <h3>{post.postTitle}</h3>
+                                <p>{post.postContent.substring(0, 20)}</p>
+                                <p className={style.createDate}>{post.createdAt}</p>
+                            </div>
+                        ))
+                    ) : (
+                        <div className={style.noData}>未查询到相关帖子</div>
+                    )}
+
+                    <Pagination
+                        showSizeChanger
+                        onShowSizeChange={handlePageChange}
+                        defaultCurrent={1}
+                        total={500}
+                        onChange={handlePageChange}
+                    />
+                </div>
+            </div>
+            <div className={style.right}>
+                <div className={style.selfStatus}>
+                    <SelfStatus/>
+                </div>
+                <div className={style.filter}>
+                    
+                </div> 
+            </div>
+        </div>
+    )
+}
+
+export default PostList;
\ No newline at end of file
diff --git a/test/login.test.tsx b/test/login.test.tsx
deleted file mode 100644
index b342387..0000000
--- a/test/login.test.tsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import React from 'react';
-import { render, screen, fireEvent, waitFor } from '@testing-library/react';
-import '@testing-library/jest-dom';
-import Login from '../src/views/login/login';
-import { useApi } from '@/hooks/request';
-import { useAppDispatch } from '@/hooks/store';
-import { useNavigate } from 'react-router';
-import { useSelector } from 'react-redux';
-
-
-jest.mock('@/hooks/request', () => ({
-  useApi: jest.fn(),
-}));
-// 模拟所有外部依赖
-jest.mock('@/hooks/store', () => ({
-  useAppDispatch: jest.fn(),
-}));
-
-jest.mock('react-router', () => ({
-  useNavigate: jest.fn(),
-}));
-
-jest.mock('react-redux', () => ({
-  useSelector: jest.fn(),
-}));
-
-// 类型安全:明确模拟函数的返回类型
-const mockUseApi = useApi as jest.MockedFunction<typeof useApi>;
-const mockUseAppDispatch = useAppDispatch as jest.MockedFunction<typeof useAppDispatch>;
-const mockUseNavigate = useNavigate as jest.MockedFunction<typeof useNavigate>;
-const mockUseSelector = useSelector as jest.MockedFunction<typeof useSelector>;
-
-describe('Login Component', () => {
-    const mockDispatch = jest.fn();
-    const mockNavigate = jest.fn();
-    const mockPostRefresh = jest.fn();
-    const mockGetRefresh = jest.fn();
-
-    beforeEach(() => {
-      // 初始化模拟函数返回值
-      mockUseAppDispatch.mockReturnValue(mockDispatch);
-      mockUseNavigate.mockReturnValue(mockNavigate);
-      mockUseSelector.mockImplementation((selector) => selector({ user: { userName: '' } }));
-      
-      // 默认模拟 useApi 返回正常状态
-      mockUseApi
-      .mockReturnValueOnce({
-        data: { token: 'mock-token' },
-        loading: false,
-        error: null,
-        refresh: mockPostRefresh,
-      })
-      .mockReturnValueOnce({
-        data:{
-            'userId' : '001',
-            'userName' : 'san3yuan',
-            'role' : 'manager',
-            'uploadTraffic' : 0,
-            'downloadTraffic': 0,
-            'downloadPoints' : 0,
-            'avatar' : 'https://www.w3school.com.cn/i/photo/tulip.jpg',
-        },
-        loading: false,
-        error: null,
-        refresh: mockGetRefresh,
-      })
-      ;
-    });
-  
-    afterEach(() => {
-      jest.clearAllMocks();
-    });
-  
-    // 测试 1: 基础渲染
-    it('渲染所有输入框和按钮', () => {
-      render(<Login />);
-      expect(screen.getByPlaceholderText('Enter your email')).toBeInTheDocument();
-      expect(screen.getByPlaceholderText('Enter your password')).toBeInTheDocument();
-      expect(screen.getByText('登录')).toBeInTheDocument();
-      expect(screen.getByText('注册')).toBeInTheDocument();
-      expect(screen.getByText('忘记密码')).toBeInTheDocument();
-    });
-  
-    // 测试 2: 登录成功流程
-    it('点击登录按钮触发API请求、Redux更新和导航', async () => {
-      // 模拟 API 返回有效数据
-      mockPostRefresh.mockResolvedValue({ token: 'mock-token' });
-      mockGetRefresh.mockResolvedValue({
-        'userId' : '001',
-        'userName' : 'san3yuan',
-        'role' : 'manager',
-        'uploadTraffic' : 0,
-        'downloadTraffic': 0,
-        'downloadPoints' : 0,
-        'avatar' : 'https://www.w3school.com.cn/i/photo/tulip.jpg',
-    });
-    
-      render(<Login />);
-      jest.useFakeTimers();
-      fireEvent.click(screen.getByText('登录'));
-      jest.runAllTimers();
-      await waitFor(() => {
-        // 验证 dispatch 调用
-        expect(mockPostRefresh).toHaveBeenCalled();
-      
-        // 验证Redux更新
-        expect(mockDispatch).toHaveBeenNthCalledWith(1, {
-          type: 'user/login',
-          payload: { token: 'mock-token' }
-        });
-
-        // 验证第二次API调用(用户信息)
-        expect(mockGetRefresh).toHaveBeenCalled();
-
-        // 验证用户信息更新
-        expect(mockDispatch).toHaveBeenNthCalledWith(2, {
-          type: 'user/getUserInfo',
-          payload: {
-            'userId' : '001',
-            'userName' : 'san3yuan',
-            'role' : 'manager',
-            'uploadTraffic' : 0,
-            'downloadTraffic': 0,
-            'downloadPoints' : 0,
-            'avatar' : 'https://www.w3school.com.cn/i/photo/tulip.jpg',
-        }
-        });
-      });
-    });  
-  });
\ No newline at end of file
diff --git a/test/postsPanel.test.tsx b/test/postsPanel.test.tsx
deleted file mode 100644
index cd753fc..0000000
--- a/test/postsPanel.test.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import React from 'react';
-import { render, screen, waitFor } from '@testing-library/react';
-import '@testing-library/jest-dom';
-import { useApi } from '@/hooks/request';
-import PostsPanel from '@/components/postsPanel/postsPanel';
-
-// 模拟 useApi
-jest.mock('@/hooks/request', () => ({
-    useApi: jest.fn(() => ({
-        data: [], // 默认返回空数组
-        loading: false,
-        error: null,
-    })),
-}));
-describe('PostsPanel Component', () => {
-    const mockUseApi = useApi as jest.MockedFunction<typeof useApi>;
-
-    it('renders the component with a title', () => {
-        // 渲染组件
-        render(<PostsPanel name="热门帖子" url="/api/posts" limit={5} />);
-
-        // 验证标题是否正确渲染
-        expect(screen.getByText('热门帖子')).toBeInTheDocument();
-        expect(screen.getByText('更多')).toBeInTheDocument();
-    });
-
-    it('renders posts when data is available', async () => {
-        // 模拟 API 返回数据
-        mockUseApi.mockReturnValue({
-            data: [
-                { title: 'Post 1', date: '2025-04-01' },
-                { title: 'Post 2', date: '2025-04-02' },
-            ],
-        });
-
-        // 渲染组件
-        render(<PostsPanel name="热门帖子" url="/api/posts" limit={5} />);
-
-        // 验证数据是否正确渲染
-        await waitFor(() => {
-            expect(screen.getByText('Post 1')).toBeInTheDocument();
-            expect(screen.getByText('2025-04-01')).toBeInTheDocument();
-            expect(screen.getByText('Post 2')).toBeInTheDocument();
-            expect(screen.getByText('2025-04-02')).toBeInTheDocument();
-        });
-    });
-
-    it('renders a message when no data is available', async () => {
-        // 模拟 API 返回空数据
-        mockUseApi.mockReturnValue({
-            data: [],
-        });
-
-        // 渲染组件
-        render(<PostsPanel name="热门帖子" url="/api/posts" limit={5} />);
-
-        // 验证无数据时的提示信息
-        await waitFor(() => {
-            expect(screen.getByText('未查询到相关记录')).toBeInTheDocument();
-        });
-    });
-
-    it('handles loading state', () => {
-        // 模拟加载状态
-        mockUseApi.mockReturnValue({
-            data: null,
-        });
-
-        // 渲染组件
-        render(<PostsPanel name="热门帖子" url="/api/posts" limit={5} />);
-
-        // 验证组件是否正确渲染(可以根据需求添加加载状态的测试)
-        expect(screen.getByText('未查询到相关记录')).toBeInTheDocument();
-    });
-});
\ No newline at end of file
diff --git a/test/selfStatus.test.tsx b/test/selfStatus.test.tsx
deleted file mode 100644
index 4bd690e..0000000
--- a/test/selfStatus.test.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import SelfStatus from '@/components/selfStatus/selfStatus';
-import { render, screen } from '@testing-library/react';
-import { useSelector } from 'react-redux';
-import React from 'react';
-import '@testing-library/jest-dom';
-import { useAppSelector } from '@/hooks/store';
-
-jest.mock('@/hooks/request', () => ({
-    useApi: jest.fn(),
-  }));
-  // 模拟所有外部依赖
-  jest.mock('@/hooks/store', () => ({
-    useAppDispatch: jest.fn(),
-    useAppSelector: jest.fn(),
-  }));
-  
-  jest.mock('react-router', () => ({
-    useNavigate: jest.fn(),
-  }));
-  
-  jest.mock('react-redux', () => ({
-    useSelector: jest.fn(),
-  }));
-
-
-describe('SelfStatus Component', () => {
-    it('renders correctly', () => {
-        (useAppSelector as jest.Mock).mockImplementation((selector) => selector({
-            user: {
-                userId: '001',
-                userName: 'san3yuan',
-                role: 'manager',
-                uploadTraffic: 0,
-                downloadTraffic: 0,
-                downloadPoints: 0,
-            },
-            setting: {
-                theme: 'light',
-            },
-        }));
-        
-        render(<SelfStatus />);
-        
-        expect(screen.getByText('san3yuan')).toBeInTheDocument();
-        expect(screen.getByText('用户组: manager')).toBeInTheDocument();
-        expect(screen.getByText('上传量: 0')).toBeInTheDocument();
-        expect(screen.getByText('下载量: 0')).toBeInTheDocument();
-        expect(screen.getByText('下载积分: 0')).toBeInTheDocument();
-    })
-    it('calculates and displays share ratio correctly', () => {
-        (useAppSelector as jest.Mock).mockImplementation((selector) => selector({
-            user: {
-                uploadTraffic: 100,
-                downloadTraffic: 50,
-            },
-        }));
-        render(<SelfStatus />);
-        expect(screen.getByText('分享率: 2.00')).toBeInTheDocument();
-    });
-    it('handles empty data gracefully', () => {
-        (useAppSelector as jest.Mock).mockImplementation((selector) => selector({
-            user: {
-                userName: '',
-                role: '',
-                uploadTraffic: null,
-                downloadTraffic: null,
-                downloadPoints: null,
-            },
-        }));
-        render(<SelfStatus />);
-        expect(screen.getByText('用户组: N/A')).toBeInTheDocument();
-        expect(screen.getByText('上传量: 0')).toBeInTheDocument();
-        expect(screen.getByText('下载量: 0')).toBeInTheDocument();        
-        expect(screen.getByText('分享率: N/A')).toBeInTheDocument();
-        expect(screen.getByText('下载积分: 0')).toBeInTheDocument();
-    });
-});
\ No newline at end of file