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