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