add css module compile rule and a login guard

Change-Id: I5c99e236f92d3b6c6d0060b36cf90a252df93a95
diff --git a/mock/user.js b/mock/user.js
deleted file mode 100644
index f2fcacd..0000000
--- a/mock/user.js
+++ /dev/null
@@ -1,39 +0,0 @@
-const accessTokens = {
-    admin: 'admin-accessToken',
-    editor: 'editor-accessToken',
-    test: 'test-accessToken'
-  };
-  
-  export default [
-    {
-      url: '/api/login',
-      method: 'post',
-      response: (config) => {
-        const { username } = config.body;
-        const accessToken = accessTokens[username];
-        if (!accessToken) {
-          return {
-            code: 500,
-            msg: '帐户或密码不正确。'
-          };
-        }
-        return {
-          code: 200,
-          msg: 'success',
-          data: {
-            accessToken
-          }
-        };
-      }
-    },
-    {
-      url: '/api/logout',
-      type: 'post',
-      response() {
-        return {
-          code: 200,
-          msg: 'success'
-        };
-      }
-    }
-  ];
diff --git a/public/avatar.jpg b/public/avatar.jpg
new file mode 100644
index 0000000..f1bd07d
--- /dev/null
+++ b/public/avatar.jpg
Binary files differ
diff --git a/scripts/webpack.base.js b/scripts/webpack.base.js
index 0bd9084..aeb4b79 100644
--- a/scripts/webpack.base.js
+++ b/scripts/webpack.base.js
@@ -6,7 +6,9 @@
 const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
 const webpack = require('webpack');
 const CircularDependencyPlugin = require('circular-dependency-plugin')
+const { isNamedExports } = require('typescript')
 
+const isDev = process.env.NODE_ENV === 'development' // 判断是否是开发环境
 
 module.exports = {
   entry: path.resolve(__dirname, '../src/index.tsx'),
@@ -69,12 +71,38 @@
         generator: {
           filename: 'assets/fonts/[name].[hash:8][ext]',
         },
-      },
-
-      {
+    },
+    {
+      test: /\.module\.css$/,
+      use: [
+        isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
+        {
+          loader: 'css-loader',
+          options: {
+            modules: {
+              namedExport: false,
+              localIdentName: isDev
+                ? '[name]__[local]__[hash:base64:5]'
+                : '[hash:base64:8]'
+            },
+            importLoaders: 1
+          },
+        },
+        {
+          loader: 'postcss-loader',
+          options: {
+            postcssOptions: {
+              plugins: ['postcss-preset-env'],
+            },
+          },
+        },
+      ],
+    },
+    {
         test: /\.(css|less)$/,
+        exclude: /\.module\.css$/,
         use: [
-          MiniCssExtractPlugin.loader, // 使用 MiniCssExtractPlugin.loader 代替 style-loader
+          isDev?'style-loader':MiniCssExtractPlugin.loader, // 使用 MiniCssExtractPlugin.loader 代替 style-loader
           'css-loader',
           {
             loader: 'postcss-loader',
@@ -88,9 +116,7 @@
           },
           'less-loader',
         ],
-        // 排除 node_modules 目录
-        exclude: /node_modules/,
-      },
+    },
 
     ],
   },
@@ -110,7 +136,8 @@
       template:path.resolve(__dirname, '../public/index.html')
     }),
     new MiniCssExtractPlugin({
-      filename: 'assets/css/[hash:8].css', // 将css单独提测出来放在assets/css 下
+      filename: 'assets/css/[name].[contenthash:8].css',
+      chunkFilename: 'assets/css/[id].[contenthash:8].css',
     }),
     new CircularDependencyPlugin({
       exclude: /node_modules/,
diff --git a/src/components/selfStatus/selfStatus.tsx b/src/components/selfStatus/selfStatus.tsx
index e954852..40d3ac3 100644
--- a/src/components/selfStatus/selfStatus.tsx
+++ b/src/components/selfStatus/selfStatus.tsx
@@ -1,28 +1,36 @@
 import React from "react";
 import { useAppSelector } from "../../hooks/store";
-import "./style.css"
+import style from "./style.module.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>
+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);
+    console.log(avatar)
+
+    return (
+        <div className={style.container}>
+            <div className={style.left}>
+                <img className={style.avatar} src={avatar} alt="User Avatar" />
             </div>
-            <div className="info" >
-                <p className="uploadTraffic">上传量:{uploadTraffic}</p>
-                <p>下载量:{downloadTraffic}</p>
-                <p>下载积分:{downloadPoints}</p>
+            <div className={style.right}>
+                <div className={style.info}>
+                    <p className={style.userName}>{userName}</p>
+                    <p className={style.role}>角色: {role}</p>
+                    <p className={style.uploadTraffic}>上传量: {uploadTraffic}</p>
+                    <p className={style.downloadTraffic}>下载量: {downloadTraffic}</p>
+                    <p className={style.shareRatio}>
+                        分享率: {uploadTraffic && downloadTraffic ? (uploadTraffic / downloadTraffic).toFixed(2) : "N/A"}
+                    </p>
+                </div>
+                <button className={style.signInButton}>签到</button>
             </div>
+        </div>
+    );
+};
 
-    </>
-}
-
-export default SelfStatus
+export default SelfStatus;
diff --git a/src/components/selfStatus/style.css b/src/components/selfStatus/style.css
deleted file mode 100644
index afb50a0..0000000
--- a/src/components/selfStatus/style.css
+++ /dev/null
@@ -1,19 +0,0 @@
-
-.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/components/selfStatus/style.module.css b/src/components/selfStatus/style.module.css
new file mode 100644
index 0000000..4e80ece
--- /dev/null
+++ b/src/components/selfStatus/style.module.css
@@ -0,0 +1,71 @@
+.container {
+    display: flex;
+    flex-direction: row;
+    align-items: flex-start;
+    justify-content: space-between;
+    padding: 20px;
+    border: 1px solid #ccc;
+    border-radius: 10px;
+    background-color: #f9f9f9;
+    width: 100%;
+    height: 200px; /* Adjust height as needed */
+    box-sizing: border-box;
+}
+
+.left {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: flex-start;
+    width: 20%; /* Adjust width for avatar section */
+}
+
+.avatar {
+    width: 80px;
+    height: 80px;
+    border-radius: 50%;
+    object-fit: cover;
+    margin-bottom: 10px;
+}
+.right {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    align-items: flex-start;
+    width: 75%; /* Adjust width for info and button section */
+}
+
+.info {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+    margin-bottom: 10px;
+}
+
+.userName {
+    font-size: 18px;
+    font-weight: bold;
+    margin-bottom: 5px;
+}
+.role,
+.uploadTraffic,
+.downloadTraffic,
+.shareRatio {
+    font-size: 14px;
+    margin-bottom: 5px;
+}
+
+.signInButton {
+    align-self: flex-end;
+    padding: 10px 20px;
+    font-size: 14px;
+    background-color: #4CAF50;
+    color: white;
+    border: none;
+    border-radius: 5px;
+    cursor: pointer;
+}
+
+.signInButton:hover {
+    background-color: #45a049;
+}
\ No newline at end of file
diff --git a/src/global.css b/src/global.css
new file mode 100644
index 0000000..7dfec7b
--- /dev/null
+++ b/src/global.css
@@ -0,0 +1,12 @@
+body {
+    margin: 0;
+    padding: 0;
+    height: 100vh;
+    width: 100vw;
+    background-color: #e6f7ff; /* Light blue background */
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    font-family: Arial, sans-serif; /* Optional: Set a global font */
+    box-sizing: border-box;
+}
\ No newline at end of file
diff --git a/src/index.tsx b/src/index.tsx
index e66ba06..4bd29bd 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -4,6 +4,7 @@
 import router from "./route";
 import store from "./store/index";
 import { RouterProvider } from "react-router";
+import './global.css';
 
 const root = createRoot(document.getElementById('root')!)
 root.render(
diff --git a/src/mock/index.ts b/src/mock/index.ts
index 9bb29d3..1b2b722 100644
--- a/src/mock/index.ts
+++ b/src/mock/index.ts
@@ -14,6 +14,7 @@
 
   // 加载各模块 Mock
   setupUserMock(mock)
+  
   console.log('Mock 模块已加载')
 }
 
diff --git a/src/mock/user.js b/src/mock/user.js
index 2ffb8c7..4bca1f8 100644
--- a/src/mock/user.js
+++ b/src/mock/user.js
@@ -17,7 +17,7 @@
           "uploadTraffic": 0,
           "downloadTraffic": 0,
           "downloadPoints": 0,
-          "avatar": ''
+          "avatar": 'D:/Code/projects/react/G8Frontend/public/avatar.jpg',
         });
         return [200, data];
       });
diff --git a/src/route/index.tsx b/src/route/index.tsx
index da0720e..5c69c6c 100644
--- a/src/route/index.tsx
+++ b/src/route/index.tsx
@@ -1,18 +1,31 @@
 import { createBrowserRouter } from 'react-router-dom'
 import PrivateRoute from './privateRoute'
+import { useSelector } from 'react-redux'
 import Login from '../views/login'
 import React from 'react'
 import Forum from '../views/forum'
+import { RootState } from '@/store'
 
 const router = createBrowserRouter([
     {
         path: '/',
-        element: <Forum /> // 论坛主页面
+        element:<PrivateRoute
+        role={0} // 判断是否登录
+        redirectPath="/login"/>, 
+        children: [
+            {
+                index: true,
+                element: <Forum /> // 论坛主页面
+            },
+        ]
     },
     {
         path: '/login',
         element: <Login /> // 登录页作为独立路由
     }
+
+
+    
 ]
 )
 
diff --git a/src/route/privateRoute.tsx b/src/route/privateRoute.tsx
index 8391f90..992f372 100644
--- a/src/route/privateRoute.tsx
+++ b/src/route/privateRoute.tsx
@@ -1,22 +1,35 @@
 import { Navigate, Outlet } from 'react-router-dom'
 import React from 'react'
+import { useSelector } from 'react-redux'
 
 interface PrivateRouteProps {
-  isAllowed: boolean
+  role: number
   redirectPath?: string
   children?: React.ReactNode
 }
 
 const PrivateRoute = ({
-  isAllowed,
+  role,
   redirectPath = '/login',
   children
 }: PrivateRouteProps) => {
-  if (!isAllowed) {
+  const isLogin = useSelector((state: any) => state.user.isLogin)
+  const userRole = useSelector((state: any) => state.user.role)
+
+  if (!isLogin) {
     return <Navigate to={redirectPath} replace />
   }
 
-  return children ? children : <Outlet />
+  if (role && role >= userRole) {
+    return <Navigate to={redirectPath} replace />
+  }
+
+  return children ? (
+    <>{children}</>
+  ) : (
+    <Outlet />
+  )
+
 }
 
 export default PrivateRoute
\ No newline at end of file
diff --git a/src/views/forum/index.tsx b/src/views/forum/index.tsx
index 6090868..6e8ad59 100644
--- a/src/views/forum/index.tsx
+++ b/src/views/forum/index.tsx
@@ -1,11 +1,13 @@
 import React from "react";
 import { Navigate } from "react-router";
+import SelfStatus from "@/components/selfStatus/selfStatus";
 
 export default function Forum() {
 
 
     return (
         <div>
+            <SelfStatus/>
             <h1>Forum</h1>
             <p>Welcome to the forum!</p>
         </div>
diff --git a/src/views/login/index.tsx b/src/views/login/index.tsx
index 9c1abc2..4cb66a5 100644
--- a/src/views/login/index.tsx
+++ b/src/views/login/index.tsx
@@ -1,29 +1,37 @@
 import React, { useEffect } from 'react';
 import { useApi } from '@/hooks/request';
 import request from '@/utils/request';
-import {userLogin} from '@/api/user';
+import { userLogin } from '@/api/user';
 import { useAppDispatch } from '@/hooks/store';
+import { RootState } from '@/store';
+import style from './login.module.css';
+import { useSelector } from 'react-redux';
+import { useNavigate } from 'react-router';
 
 const Login: React.FC = () => {
-
     const dispatch = useAppDispatch();
     const { data, loading, error, refresh } = useApi(() => request.post(userLogin), false);
 
+    const nav = useNavigate();
     const handleLogin = async () => {
-        // 点击时调用 execute 发起请求
         const res = await refresh();
         console.log(res);
-        // 请求完成后可以使用返回的数据进行分发
         dispatch({ type: "user/login", payload: res });
+        nav('/');
+        
     };
+    const userName = useSelector((state: RootState) => state.user.userName);
+
     return (
-        <div>
-            <input type='email'></input>
-            <input></input>
-            <button onClick={handleLogin}>Login</button>
+
+        <div className={style.form}>
+            <img alt = "logo"></img>
+            <input type="email" className={style.email} placeholder="Enter your email" />
+            <input type="password" className={style.password} placeholder="Enter your password" />
+            <button className={style.submit} onClick={handleLogin}>Login</button>
             <button>Register</button>
         </div>
     );
-}
+};
 
 export default Login;
\ No newline at end of file
diff --git a/src/views/login/login.module.css b/src/views/login/login.module.css
new file mode 100644
index 0000000..7571160
--- /dev/null
+++ b/src/views/login/login.module.css
@@ -0,0 +1,75 @@
+.form {
+    display: flex;
+    flex-direction: column;
+    align-items: center; /* Center items horizontally */
+    justify-content: center; /* Center items vertically */
+    height: 40%; /* Occupy 40% of the viewport height */
+    width:25%;
+    background-color: #f0f8ff; /* Light blue background */
+    box-shadow: 3px 3px 5px 6px #ccc;
+    position: absolute; /* Position the form absolutely */
+    top: 50%; /* Move to the middle of the viewport */
+    left: 50%; /* Center horizontally */
+    transform: translate(-50%, -50%); /* Adjust for centering */
+    border-radius: 10px; /* Add rounded corners */
+    padding: 20px; /* Add padding for better spacing */
+    box-sizing: border-box; /* Include padding and border in width/height */
+    
+}
+
+
+.form input {
+    margin: 5px;
+    padding: 5px;
+    border-radius: 5px;
+    border: 1px solid #ccc;
+    font-size: 16px;
+    width: 80%; /* Adjust width for better appearance */
+    max-width: 300px; /* Limit maximum width */
+    box-sizing: border-box;
+}
+
+.form .email,
+.form .password {
+    margin: 10px;
+    padding: 10px;
+    border-radius: 5px;
+    border: 1px solid #ccc;
+    font-size: 16px;
+    width: 80%;
+    max-width: 300px;
+}
+
+.form .submit {
+    margin: 10px;
+    padding: 10px;
+    border-radius: 5px;
+    border: 1px solid #ccc;
+    font-size: 16px;
+    background-color: #4CAF50; /* Green */
+    color: white;
+    cursor: pointer;
+    width: 80%;
+    max-width: 300px;
+}
+
+.form .submit:hover {
+    background-color: #45a049; /* Darker green */
+}
+
+.form button {
+    margin: 10px;
+    padding: 10px;
+    border-radius: 5px;
+    border: none;
+    font-size: 16px;
+    background-color: #007BFF; /* Blue */
+    color: white;
+    cursor: pointer;
+    width: 80%;
+    max-width: 300px;
+}
+
+.form button:hover {
+    background-color: #0056b3; /* Darker blue */
+}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index cc211c6..39f819e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -28,6 +28,10 @@
     "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */
     /* Type Checking */
     "strict": true,                                      /* Enable all strict type-checking options. */
-    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
+    "skipLibCheck": true                   /* Skip type checking all .d.ts files. */
   },
+  "include": [
+      "src",
+      "typed-css.d.ts"//配置的.d.ts文件
+    ]        
 }
diff --git a/typed-css.d.ts b/typed-css.d.ts
new file mode 100644
index 0000000..e7b091b
--- /dev/null
+++ b/typed-css.d.ts
@@ -0,0 +1,14 @@
+declare module '*.module.css' {
+    const classes: { readonly [key: string]: string }
+    export default classes
+  }
+  
+  declare module '*.module.sass' {
+    const classes: { readonly [key: string]: string }
+    export default classes
+  }
+  
+  declare module '*.module.scss' {
+    const classes: { readonly [key: string]: string }
+    export default classes
+  }
\ No newline at end of file