From 95a099f613d28e07291d0356f5a6e409ce8f75e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=89=8D=E7=AB=AF=E4=BA=BA=E7=BB=9D=E4=B8=8D=E4=B8=BA?= =?UTF-8?q?=E5=A5=B4?= Date: Wed, 20 Aug 2025 11:43:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E4=BA=86=E4=B8=80=E4=B8=AA=E5=85=A8=E5=B1=80=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 5 +- pnpm-lock.yaml | 101 ++++++++++++++++++++++++++++ src/components/Layout/index.css | 6 +- src/components/Layout/index.jsx | 10 +-- src/main.jsx | 6 +- src/pages/CompanyJobsPage/index.jsx | 35 ++++------ src/store/index.js | 10 +++ src/store/slices/loadingSlice.js | 25 +++++++ src/store/slices/userSlice.js | 38 +++++++++++ vite.config.js | 6 +- 10 files changed, 207 insertions(+), 35 deletions(-) create mode 100644 src/store/index.js create mode 100644 src/store/slices/loadingSlice.js create mode 100644 src/store/slices/userSlice.js diff --git a/package.json b/package.json index 1d056c0..123018d 100644 --- a/package.json +++ b/package.json @@ -16,12 +16,15 @@ }, "dependencies": { "@arco-design/web-react": "^2.66.4", + "@reduxjs/toolkit": "^2.8.2", "axios": "^1.11.0", "echarts": "^6.0.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-echarts": "^0.1.1", - "react-router-dom": "^7.7.1" + "react-redux": "^9.2.0", + "react-router-dom": "^7.7.1", + "redux": "^5.0.1" }, "devDependencies": { "@eslint/js": "^9.30.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 100b47b..24344dc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@arco-design/web-react': specifier: ^2.66.4 version: 2.66.4(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@reduxjs/toolkit': + specifier: ^2.8.2 + version: 2.8.2(react-redux@9.2.0(@types/react@19.1.10)(react@19.1.1)(redux@5.0.1))(react@19.1.1) axios: specifier: ^1.11.0 version: 1.11.0 @@ -26,9 +29,15 @@ importers: react-echarts: specifier: ^0.1.1 version: 0.1.1(react@19.1.1) + react-redux: + specifier: ^9.2.0 + version: 9.2.0(@types/react@19.1.10)(react@19.1.1)(redux@5.0.1) react-router-dom: specifier: ^7.7.1 version: 7.8.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + redux: + specifier: ^5.0.1 + version: 5.0.1 devDependencies: '@eslint/js': specifier: ^9.30.1 @@ -396,6 +405,17 @@ packages: '@jridgewell/trace-mapping@0.3.30': resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + '@reduxjs/toolkit@2.8.2': + resolution: {integrity: sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -510,6 +530,12 @@ packages: cpu: [x64] os: [win32] + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -536,6 +562,9 @@ packages: '@types/react@19.1.10': resolution: {integrity: sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==} + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@vitejs/plugin-react@4.7.0': resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -878,6 +907,9 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + immer@10.1.1: + resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -1075,6 +1107,18 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-redux@9.2.0: + resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} @@ -1106,6 +1150,17 @@ packages: resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} engines: {node: '>=0.10.0'} + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + resize-observer-polyfill@1.5.1: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} @@ -1200,6 +1255,11 @@ packages: '@types/react': optional: true + use-sync-external-store@1.5.0: + resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + vite@7.1.2: resolution: {integrity: sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1557,6 +1617,18 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@reduxjs/toolkit@2.8.2(react-redux@9.2.0(@types/react@19.1.10)(react@19.1.1)(redux@5.0.1))(react@19.1.1)': + dependencies: + '@standard-schema/spec': 1.0.0 + '@standard-schema/utils': 0.3.0 + immer: 10.1.1 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.1.1 + optionalDependencies: + react: 19.1.1 + react-redux: 9.2.0(@types/react@19.1.10)(react@19.1.1)(redux@5.0.1) + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/rollup-android-arm-eabi@4.46.2': @@ -1619,6 +1691,10 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.46.2': optional: true + '@standard-schema/spec@1.0.0': {} + + '@standard-schema/utils@0.3.0': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.0 @@ -1652,6 +1728,8 @@ snapshots: dependencies: csstype: 3.1.3 + '@types/use-sync-external-store@0.0.6': {} + '@vitejs/plugin-react@4.7.0(vite@7.1.2)': dependencies: '@babel/core': 7.28.0 @@ -2025,6 +2103,8 @@ snapshots: ignore@5.3.2: {} + immer@10.1.1: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -2194,6 +2274,15 @@ snapshots: react-is@18.3.1: {} + react-redux@9.2.0(@types/react@19.1.10)(react@19.1.1)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 19.1.1 + use-sync-external-store: 1.5.0(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.10 + redux: 5.0.1 + react-refresh@0.17.0: {} react-router-dom@7.8.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): @@ -2221,6 +2310,14 @@ snapshots: react@19.1.1: {} + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + + reselect@5.1.1: {} + resize-observer-polyfill@1.5.1: {} resolve-from@4.0.0: {} @@ -2319,6 +2416,10 @@ snapshots: optionalDependencies: '@types/react': 19.1.10 + use-sync-external-store@1.5.0(react@19.1.1): + dependencies: + react: 19.1.1 + vite@7.1.2: dependencies: esbuild: 0.25.9 diff --git a/src/components/Layout/index.css b/src/components/Layout/index.css index be47ae4..ac8e516 100644 --- a/src/components/Layout/index.css +++ b/src/components/Layout/index.css @@ -5,11 +5,9 @@ width: 100%; background-color: #f2f3f5; - .app-layout-skeleton { + .app-layout-spin { flex: 1; - box-sizing: border-box; - padding: 100px; - background-color: aqua; + position: relative; } } /* 主内容区域 */ diff --git a/src/components/Layout/index.jsx b/src/components/Layout/index.jsx index 4b68264..98952e6 100644 --- a/src/components/Layout/index.jsx +++ b/src/components/Layout/index.jsx @@ -1,17 +1,19 @@ -import { useState, useEffect } from "react"; -import { Skeleton } from "@arco-design/web-react"; +import { useState } from "react"; +import { Spin } from "@arco-design/web-react"; +import { useSelector } from "react-redux"; import Sidebar from "../Sidebar"; import "./index.css"; const Layout = ({ children }) => { + const loading = useSelector((state) => state.loading.value); const [isCollapsed, setIsCollapsed] = useState(true); return (
- +
{children}
-
+
); }; diff --git a/src/main.jsx b/src/main.jsx index 0daebe8..e44155d 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,9 +1,13 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; +import { Provider } from "react-redux"; +import store from "./store"; import App from "./App.jsx"; createRoot(document.getElementById("root")).render( - + + + ); diff --git a/src/pages/CompanyJobsPage/index.jsx b/src/pages/CompanyJobsPage/index.jsx index 5b3e069..c18c583 100644 --- a/src/pages/CompanyJobsPage/index.jsx +++ b/src/pages/CompanyJobsPage/index.jsx @@ -1,27 +1,25 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import { useNavigate } from "react-router-dom"; import { jobAPI, interviewAPI, studentAPI } from "@/services/api"; import { mapJobList, mapInterviewList } from "@/utils/dataMapper"; import JobList from "./components/JobList"; - +import { useDispatch } from "react-redux"; +import { setLoadingTrue, setLoadingFalse } from "@/store/slices/loadingSlice"; +// 从Redux store中获取loading状态 import "./index.css"; const CompanyJobsPage = () => { + const dispatch = useDispatch(); + const [jobs, setJobs] = useState([]); const [interviews, setInterviews] = useState([]); - const [loading, setLoading] = useState(true); const [isExpand, setIsExpand] = useState(false); // 是否展开 const navigate = useNavigate(); - useEffect(() => { - fetchData(); - }, []); - const fetchData = async () => { try { - setLoading(true); - + dispatch(setLoadingTrue()); // Get current user's student ID from API let studentId = null; try { @@ -59,12 +57,14 @@ const CompanyJobsPage = () => { setJobs(mappedJobs); setInterviews(mappedInterviews); } catch (error) { + dispatch(setLoadingFalse()); + console.error("Failed to fetch data:", error); // Fallback to empty data setJobs([]); setInterviews([]); } finally { - setLoading(false); + dispatch(setLoadingFalse()); } }; @@ -72,18 +72,9 @@ const CompanyJobsPage = () => { navigate("/company-jobs-list"); }; - if (loading) { - return ( -
-
-

加载中...

-
-
- ); - } + useEffect(() => { + fetchData(); + }, []); return (
diff --git a/src/store/index.js b/src/store/index.js new file mode 100644 index 0000000..564517f --- /dev/null +++ b/src/store/index.js @@ -0,0 +1,10 @@ +import { configureStore } from '@reduxjs/toolkit'; +import loadingReducer from './slices/loadingSlice'; +import userReducer from './slices/userSlice'; // 导入新的用户reducer + +export default configureStore({ + reducer: { + loading: loadingReducer, + user: userReducer // 添加用户reducer到store + } +}); diff --git a/src/store/slices/loadingSlice.js b/src/store/slices/loadingSlice.js new file mode 100644 index 0000000..0bab42c --- /dev/null +++ b/src/store/slices/loadingSlice.js @@ -0,0 +1,25 @@ +import { createSlice } from "@reduxjs/toolkit"; + +// 创建slice +const loadingSlice = createSlice({ + name: "loading", + initialState: { + value: false, + }, + reducers: { + // 设置loading状态为true + setLoadingTrue: (state) => { + state.value = true; + }, + // 设置loading状态为false + setLoadingFalse: (state) => { + state.value = false; + }, + }, +}); + +// 导出actions +export const { setLoadingTrue, setLoadingFalse } = loadingSlice.actions; + +// 导出reducer +export default loadingSlice.reducer; diff --git a/src/store/slices/userSlice.js b/src/store/slices/userSlice.js new file mode 100644 index 0000000..82f258a --- /dev/null +++ b/src/store/slices/userSlice.js @@ -0,0 +1,38 @@ +import { createSlice } from '@reduxjs/toolkit'; + +// 定义初始状态 +const initialState = { + userInfo: null, // 用户信息初始为null + isLoggedIn: false // 登录状态初始为false +}; + +const userSlice = createSlice({ + name: 'user', + initialState, + reducers: { + // 设置用户信息 + setUserInfo: (state, action) => { + state.userInfo = action.payload; + state.isLoggedIn = true; + }, + // 清除用户信息 + clearUserInfo: (state) => { + state.userInfo = null; + state.isLoggedIn = false; + }, + // 更新用户信息的部分字段 + updateUserInfo: (state, action) => { + if (state.userInfo) { + state.userInfo = { + ...state.userInfo, + ...action.payload + }; + } + } + } +}); + +// 导出actions +export const { setUserInfo, clearUserInfo, updateUserInfo } = userSlice.actions; + +export default userSlice.reducer; \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index 46e03b5..3516d09 100644 --- a/vite.config.js +++ b/vite.config.js @@ -22,7 +22,7 @@ export default defineConfig({ // 分割第三方库 vendor: ["react", "react-dom", "react-router-dom"], // 分割大型组件 - components: ["@/components/CoursesVideoPlayer"], + components: [], // 分割工具函数 utils: ["@/utils/LinePathGenerator", "@/utils/request"], }, @@ -38,12 +38,12 @@ export default defineConfig({ // 开发服务器配置 server: { host: "0.0.0.0", - port: 5173, // Frontend port, different from backend + port: 5173, // Frontend port, different from backend strictPort: true, // API代理配置 proxy: { "/api": { - target: "http://localhost:2025", // Backend server + target: "http://localhost:2025", // Backend server changeOrigin: true, rewrite: (path) => path, },