init

Change-Id: I62d8e17fdc3103133b9ddaff22c27ddd9ea9f6ac
diff --git a/src/api/user.ts b/src/api/user.ts
new file mode 100644
index 0000000..2c5e46c
--- /dev/null
+++ b/src/api/user.ts
@@ -0,0 +1,5 @@
+export const queryUserName='/user/${id}';
+export const getUserInfo= '/user/info';
+export const userLogin= '/user/login';
+export const userLogout= '/user/logout';
+export const userRegister= '/user/register';
\ No newline at end of file
diff --git a/src/components/selfStatus/selfStatus.tsx b/src/components/selfStatus/selfStatus.tsx
new file mode 100644
index 0000000..e954852
--- /dev/null
+++ b/src/components/selfStatus/selfStatus.tsx
@@ -0,0 +1,28 @@
+import React from "react";
+import { useAppSelector } from "../../hooks/store";
+import "./style.css"
+
+const SelfStatus :React.FC = () => {
+
+    const userName = useAppSelector(state => state.user.userName)
+    const role = useAppSelector(state => state.user.role)
+    const uploadTraffic = useAppSelector(state => state.user.uploadTraffic)
+    const downloadTraffic = useAppSelector(state => state.user.downloadTraffic)
+    const downloadPoints = useAppSelector(state => state.user.downloadPoints)
+    const avatar = useAppSelector(state => state.user.avatar)
+    const islogin = useAppSelector(state => state.user.isLogin)
+    return <>
+            <div className="user">
+                <img className="avatar" src={avatar}></img>
+                <p className="userName">{userName},欢迎你</p>
+            </div>
+            <div className="info" >
+                <p className="uploadTraffic">上传量:{uploadTraffic}</p>
+                <p>下载量:{downloadTraffic}</p>
+                <p>下载积分:{downloadPoints}</p>
+            </div>
+
+    </>
+}
+
+export default SelfStatus
diff --git a/src/components/selfStatus/style.css b/src/components/selfStatus/style.css
new file mode 100644
index 0000000..afb50a0
--- /dev/null
+++ b/src/components/selfStatus/style.css
@@ -0,0 +1,19 @@
+
+.user{
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    margin-top: 20px;
+}
+.user img {
+    width:40%;
+    border-radius: 20px;
+    margin-top: 20px;
+}
+.info p{
+    font-size: 20px;
+    font-weight: bold;
+    margin-top: 20px;
+}
+
diff --git a/src/hooks/request.ts b/src/hooks/request.ts
new file mode 100644
index 0000000..8594ac2
--- /dev/null
+++ b/src/hooks/request.ts
@@ -0,0 +1,43 @@
+import { useState, useEffect, useCallback } from 'react'
+import { data } from 'react-router'
+
+type RequestFunction<T> = () => Promise<T>
+
+interface UseApiResult<T> {
+  data: T | null
+  loading: boolean
+  error: Error | null
+  refresh: () => void
+}
+
+export function useApi<T>(
+    requestFn: RequestFunction<T>,
+    immediate = true
+  ): UseApiResult<T> {
+    const [data, setData] = useState<T | null>(null)
+    const [loading, setLoading] = useState(false)
+    const [error, setError] = useState<Error | null>(null)
+  
+    const execute = useCallback(async () => {
+      try {
+        setLoading(true)
+        const result = await requestFn()
+        setData(result)
+        setError(null)
+        return result  // 返回请求结果
+      } catch (err) {
+        setError(err as Error)
+        throw err
+      } finally {
+        setLoading(false)
+      }
+    }, [requestFn])
+  
+    useEffect(() => {
+      if (immediate) {
+        execute()
+      }
+    }, [execute, immediate])
+  
+    return { data, loading, error, refresh: execute }
+  }
\ No newline at end of file
diff --git a/src/hooks/store.ts b/src/hooks/store.ts
new file mode 100644
index 0000000..f9f22a6
--- /dev/null
+++ b/src/hooks/store.ts
@@ -0,0 +1,6 @@
+import { useDispatch, useSelector } from 'react-redux'
+import type { RootState, AppDispatch } from '../store'
+
+// Use throughout your app instead of plain `useDispatch` and `useSelector`
+export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
+export const useAppSelector = useSelector.withTypes<RootState>()
\ No newline at end of file
diff --git a/src/index.tsx b/src/index.tsx
new file mode 100644
index 0000000..e66ba06
--- /dev/null
+++ b/src/index.tsx
@@ -0,0 +1,13 @@
+import React 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";
+
+const root = createRoot(document.getElementById('root')!)
+root.render(
+    <Provider store={store}>
+        <RouterProvider router={router}/>
+    </Provider>
+)
diff --git a/src/mock/index.ts b/src/mock/index.ts
new file mode 100644
index 0000000..9bb29d3
--- /dev/null
+++ b/src/mock/index.ts
@@ -0,0 +1,21 @@
+import MockAdapter from 'axios-mock-adapter';
+import instance from '@/utils/axios'
+import {setupUserMock}  from './user'
+
+// 创建 Mock 实例
+export const mock = new MockAdapter(instance, { 
+  delayResponse: process.env.NODE_ENV === 'test' ? 0 : 500 
+})
+
+// 聚合所有 Mock 模块
+export function setupMock() {
+  // 开发环境启用 Mock
+  if (process.env.NODE_ENV !== 'development') return
+
+  // 加载各模块 Mock
+  setupUserMock(mock)
+  console.log('Mock 模块已加载')
+}
+
+// 自动执行
+setupMock()
\ No newline at end of file
diff --git a/src/mock/user.d.ts b/src/mock/user.d.ts
new file mode 100644
index 0000000..0f12476
--- /dev/null
+++ b/src/mock/user.d.ts
@@ -0,0 +1,3 @@
+import type MockAdapter from 'axios-mock-adapter';
+
+export declare function setupUserMock(mock: MockAdapter): void;
\ No newline at end of file
diff --git a/src/mock/user.js b/src/mock/user.js
new file mode 100644
index 0000000..2ffb8c7
--- /dev/null
+++ b/src/mock/user.js
@@ -0,0 +1,24 @@
+import Mock from 'mockjs';
+import MockAdapter from 'axios-mock-adapter';
+
+import { userLogin } from '@/api/user'; // Import the API endpoint
+
+/**
+ * 设置用户相关的 Mock 接口
+ * @param {MockAdapter} mock 
+ */
+export function setupUserMock(mock){
+    mock.onPost(userLogin).reply(config => {
+        let data = Mock.mock({
+          "userId": 0,
+          "userName": '',
+          "role": '',
+          "isLogin": false,
+          "uploadTraffic": 0,
+          "downloadTraffic": 0,
+          "downloadPoints": 0,
+          "avatar": ''
+        });
+        return [200, data];
+      });
+}    
diff --git a/src/route/index.tsx b/src/route/index.tsx
new file mode 100644
index 0000000..da0720e
--- /dev/null
+++ b/src/route/index.tsx
@@ -0,0 +1,19 @@
+import { createBrowserRouter } from 'react-router-dom'
+import PrivateRoute from './privateRoute'
+import Login from '../views/login'
+import React from 'react'
+import Forum from '../views/forum'
+
+const router = createBrowserRouter([
+    {
+        path: '/',
+        element: <Forum /> // 论坛主页面
+    },
+    {
+        path: '/login',
+        element: <Login /> // 登录页作为独立路由
+    }
+]
+)
+
+export default router
\ No newline at end of file
diff --git a/src/route/privateRoute.tsx b/src/route/privateRoute.tsx
new file mode 100644
index 0000000..8391f90
--- /dev/null
+++ b/src/route/privateRoute.tsx
@@ -0,0 +1,22 @@
+import { Navigate, Outlet } from 'react-router-dom'
+import React from 'react'
+
+interface PrivateRouteProps {
+  isAllowed: boolean
+  redirectPath?: string
+  children?: React.ReactNode
+}
+
+const PrivateRoute = ({
+  isAllowed,
+  redirectPath = '/login',
+  children
+}: PrivateRouteProps) => {
+  if (!isAllowed) {
+    return <Navigate to={redirectPath} replace />
+  }
+
+  return children ? children : <Outlet />
+}
+
+export default PrivateRoute
\ No newline at end of file
diff --git a/src/store/index.ts b/src/store/index.ts
new file mode 100644
index 0000000..5501ef3
--- /dev/null
+++ b/src/store/index.ts
@@ -0,0 +1,12 @@
+import {configureStore} from '@reduxjs/toolkit';
+import userReducer from './userReducer';
+const store = configureStore({
+  reducer: {
+    user: userReducer,
+  }
+});
+
+export default store;
+export type RootState = ReturnType<typeof store.getState>
+
+export type AppDispatch = typeof store.dispatch
\ No newline at end of file
diff --git a/src/store/userReducer.ts b/src/store/userReducer.ts
new file mode 100644
index 0000000..f335c25
--- /dev/null
+++ b/src/store/userReducer.ts
@@ -0,0 +1,66 @@
+import { createSlice } from '@reduxjs/toolkit';
+
+interface UserState {
+    userId: number;
+    userName: string;
+    role: string;
+    isLogin: boolean;
+    uploadTraffic: number;
+    downloadTraffic: number;
+    downloadPoints: number;
+    avatar: string;
+}
+
+const initialState: UserState = {
+    userId: 0,
+    userName: '',
+    role: '',
+    isLogin: false,
+    uploadTraffic: 0,
+    downloadTraffic: 0,
+    downloadPoints: 0,
+    avatar: '',
+};
+
+
+export const userSlice = createSlice({
+    name: 'user',
+    initialState,
+    reducers: {
+        login: (state, action) => {
+                
+            state.userId = action.payload.userId;
+            state.userName = action.payload.userName;
+            state.role = action.payload.role;
+            state.isLogin = true;
+            state.uploadTraffic = action.payload.uploadTraffic;
+            state.downloadTraffic = action.payload.downloadTraffic;
+            state.downloadPoints = action.payload.downloadPoints;
+            state.avatar = action.payload.avatar;
+
+            console.log('userId', state.userId);
+        },
+        logout: (state) => {
+            state.userId = 0;
+            state.userName = '';
+            state.role = '';
+            state.isLogin = false;
+            state.uploadTraffic = 0;
+            state.downloadTraffic = 0;
+            state.downloadPoints = 0;
+            state.avatar = '';
+        },
+        updateTraffic: (state, action) => {
+            state.uploadTraffic = action.payload.uploadTraffic;
+            state.downloadTraffic = action.payload.downloadTraffic;
+            state.downloadPoints = action.payload.downloadPoints;
+        },
+        updateAvatar: (state, action) => {
+            state.avatar = action.payload.avatar;
+        }
+    },
+
+});
+
+export const { login, logout, updateTraffic } = userSlice.actions;
+export default userSlice.reducer;
\ No newline at end of file
diff --git a/src/utils/axios.ts b/src/utils/axios.ts
new file mode 100644
index 0000000..c7639b0
--- /dev/null
+++ b/src/utils/axios.ts
@@ -0,0 +1,44 @@
+import axios from 'axios'
+import type { AxiosRequestConfig, AxiosResponse } from 'axios'
+
+const instance = axios.create({
+    baseURL: process.env.API_BASE_URL,
+    timeout: 10000,
+    headers: {
+      'Content-Type': 'application/json'
+    }
+  })
+  
+  // 请求拦截器
+  instance.interceptors.request.use(
+    (config) => {
+      // 添加认证 token
+      const token = localStorage.getItem('access_token')
+      if (token) {
+        config.headers.Authorization = `Bearer ${token}`
+      }
+      return config
+    },
+    (error) => {
+      return Promise.reject(error)
+    }
+  )
+  
+  // 响应拦截器
+  instance.interceptors.response.use(
+    (response) => {
+      console.log('Response:', response)
+      // 统一处理响应数据格式
+      if (response.status === 200) {
+        return response.data
+      }
+      return Promise.reject(response.data)
+    },
+    (error) => {
+      // 统一错误处理
+      console.error('API Error:', error.response?.status, error.message)
+      return Promise.reject(error)
+    }
+  )
+
+export default instance
\ No newline at end of file
diff --git a/src/utils/request.ts b/src/utils/request.ts
new file mode 100644
index 0000000..7701fc0
--- /dev/null
+++ b/src/utils/request.ts
@@ -0,0 +1,33 @@
+import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'
+import instance from './axios'
+
+// 封装基础请求方法
+const request = {
+  get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
+    return instance.get(url, config)
+  },
+
+  post<T = any>(
+    url: string,
+    data?: any,
+    config?: AxiosRequestConfig
+  ): Promise<T> {
+    return instance.post(url, data, config)
+  },
+
+  put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
+    return instance.put(url, data, config)
+  },
+
+  delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
+    return instance.delete(url, config)
+  }
+}
+
+// 开发环境启用 Mock
+if (process.env.NODE_ENV === 'development') {
+  require('../mock') // 你的 Mock 配置文件
+}
+
+
+export default request
\ No newline at end of file
diff --git a/src/views/forum/index.tsx b/src/views/forum/index.tsx
new file mode 100644
index 0000000..6090868
--- /dev/null
+++ b/src/views/forum/index.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+import { Navigate } from "react-router";
+
+export default function Forum() {
+
+
+    return (
+        <div>
+            <h1>Forum</h1>
+            <p>Welcome to the forum!</p>
+        </div>
+    );
+}
\ No newline at end of file
diff --git a/src/views/login/index.tsx b/src/views/login/index.tsx
new file mode 100644
index 0000000..9c1abc2
--- /dev/null
+++ b/src/views/login/index.tsx
@@ -0,0 +1,29 @@
+import React, { useEffect } from 'react';
+import { useApi } from '@/hooks/request';
+import request from '@/utils/request';
+import {userLogin} from '@/api/user';
+import { useAppDispatch } from '@/hooks/store';
+
+const Login: React.FC = () => {
+
+    const dispatch = useAppDispatch();
+    const { data, loading, error, refresh } = useApi(() => request.post(userLogin), false);
+
+    const handleLogin = async () => {
+        // 点击时调用 execute 发起请求
+        const res = await refresh();
+        console.log(res);
+        // 请求完成后可以使用返回的数据进行分发
+        dispatch({ type: "user/login", payload: res });
+    };
+    return (
+        <div>
+            <input type='email'></input>
+            <input></input>
+            <button onClick={handleLogin}>Login</button>
+            <button>Register</button>
+        </div>
+    );
+}
+
+export default Login;
\ No newline at end of file