feat: 🎸 企业内推岗位列表

This commit is contained in:
2025-08-20 18:05:50 +08:00
parent 3f590f21b2
commit f1a2a24939
9 changed files with 168 additions and 139 deletions

View File

@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react";
import { Empty } from "@arco-design/web-react"; import { Empty } from "@arco-design/web-react";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { setLoadingTrue, setLoadingFalse } from "@/store/slices/loadingSlice"; import { setLoadingFalse } from "@/store/slices/loadingSlice";
const InfiniteScroll = ({ const InfiniteScroll = ({
loadMore, loadMore,
@@ -13,22 +13,25 @@ const InfiniteScroll = ({
}) => { }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const containerRef = useRef(null); const containerRef = useRef(null);
const loading = useSelector((state) => state.loading.value); const globalLoading = useSelector((state) => state.loading.value);
const [loading, setLoading] = useState(false);
useEffect(() => {
const handleScroll = () => { const handleScroll = () => {
if (!containerRef.current || loading || !hasMore) return; if (loading) return;
setLoading(true);
if (!containerRef.current || globalLoading || !hasMore) return;
const { scrollTop, scrollHeight, clientHeight } = containerRef.current; const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
if (scrollTop + clientHeight >= scrollHeight - threshold) { if (scrollTop + clientHeight >= scrollHeight - threshold) {
dispatch(setLoadingTrue());
loadMore().finally(() => { loadMore().finally(() => {
dispatch(setLoadingFalse()); dispatch(setLoadingFalse());
setLoading(false);
}); });
} }
}; };
useEffect(() => {
const container = containerRef.current; const container = containerRef.current;
if (container) { if (container) {
container.addEventListener("scroll", handleScroll); container.addEventListener("scroll", handleScroll);
@@ -41,7 +44,7 @@ const InfiniteScroll = ({
container.removeEventListener("scroll", handleScroll); container.removeEventListener("scroll", handleScroll);
} }
}; };
}, [loadMore, hasMore, threshold, loading]); }, [loadMore, hasMore, threshold, globalLoading]);
return ( return (
<div <div
@@ -49,7 +52,7 @@ const InfiniteScroll = ({
className={`infinite-scroll-container ${className}`} className={`infinite-scroll-container ${className}`}
> >
{children} {children}
{!hasMore && !loading && <Empty description="没有更多了" />} {!hasMore && !globalLoading && <Empty description="没有更多了" />}
</div> </div>
); );
}; };

View File

@@ -1,13 +1,11 @@
import { StrictMode } from "react"; // import { StrictMode } from "react"; // 重复请求只会在 开发环境 下发生生产环境中StrictMode不会导致重复渲染
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { Provider } from "react-redux"; import { Provider } from "react-redux";
import store from "./store"; import store from "./store";
import App from "./App.jsx"; import App from "./App.jsx";
createRoot(document.getElementById("root")).render( createRoot(document.getElementById("root")).render(
<StrictMode>
<Provider store={store}> <Provider store={store}>
<App /> <App />
</Provider> </Provider>
</StrictMode>
); );

View File

@@ -40,7 +40,7 @@ export default ({ className = "", data = [], backgroundColor = "#FFFFFF" }) => {
))} ))}
</ul> </ul>
<p className="company-jobs-info-position-count"> <p className="company-jobs-info-position-count">
岗位招聘数量仅剩9 岗位招聘数量仅剩{item?.remainingPositions}
</p> </p>
</div> </div>
<div className="company-jobs-btn-wrapper"> <div className="company-jobs-btn-wrapper">

View File

@@ -11,6 +11,7 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
position: relative; position: relative;
.company-jobs-page-title { .company-jobs-page-title {
width: 100%; width: 100%;
height: 42px; height: 42px;
@@ -51,6 +52,7 @@
.company-jobs-page-left-list-wrapper { .company-jobs-page-left-list-wrapper {
width: 496px; width: 496px;
height: 760px; height: 760px;
overflow: auto;
} }
} }

View File

@@ -1,70 +1,78 @@
import { useState, useEffect, useCallback } from "react"; import { useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { jobAPI, interviewAPI, studentAPI } from "@/services/api";
import { mapJobList, mapInterviewList } from "@/utils/dataMapper"; import { mapJobList, mapInterviewList } from "@/utils/dataMapper";
import JobList from "./components/JobList"; import JobList from "./components/JobList";
import { useDispatch } from "react-redux"; import { getJobsList, getInterviewsList, getCurrentStudent } from "@/services";
import { setLoadingTrue, setLoadingFalse } from "@/store/slices/loadingSlice"; import InfiniteScroll from "@/components/InfiniteScroll";
// 从Redux store中获取loading状态
import "./index.css"; import "./index.css";
const PAGE_SIZE = 10;
const CompanyJobsPage = () => { const CompanyJobsPage = () => {
const dispatch = useDispatch();
const [jobs, setJobs] = useState([]);
const [interviews, setInterviews] = useState([]);
const [isExpand, setIsExpand] = useState(false); // 是否展开 const [isExpand, setIsExpand] = useState(false); // 是否展开
const [jobs, setJobs] = useState([]);
const [jobsListPage, setJobsListPage] = useState(1);
const [joblistHasMore, setJobsListHasMore] = useState(true);
const [interviews, setInterviews] = useState([]);
const [interviewsPage, setInterviewsPage] = useState(1);
const [interviewsHasMore, setInterviewsHasMore] = useState(true);
const navigate = useNavigate(); const navigate = useNavigate();
const fetchData = async () => { // 获取面试信息
const fetchInterviewsData = async () => {
try { try {
dispatch(setLoadingTrue());
// Get current user's student ID from API
let studentId = null; let studentId = null;
try { const currentStudent = await getCurrentStudent();
const currentStudent = await studentAPI.getCurrentStudent();
studentId = currentStudent?.id; studentId = currentStudent?.id;
} catch (err) {
console.log("Could not get current student:", err);
}
// Fetch jobs (and interviews if we have a student ID)
const jobsData = await jobAPI.getList({
page: 1,
pageSize: 10,
isActive: true,
});
let interviewsData = { data: [] };
if (studentId) { if (studentId) {
try { const res = await getInterviewsList({
interviewsData = await interviewAPI.getList({ page: interviewsPage,
pageSize: PAGE_SIZE,
studentId: studentId, studentId: studentId,
status: "SCHEDULED", status: "SCHEDULED",
}); });
} catch (err) { if (res.success) {
console.log("No interviews found or API error"); const mappedInterviews = mapInterviewList(res.data || []);
setInterviews((prevList) => {
const newList = [...prevList, ...mappedInterviews];
if (res.total === newList?.length) {
setInterviewsHasMore(false);
} else {
setInterviewsPage((prevPage) => prevPage + 1);
}
return newList;
});
} }
} }
// Map data to frontend format
const mappedJobs = mapJobList(jobsData.data || jobsData);
const mappedInterviews = mapInterviewList(
interviewsData.data || interviewsData
);
setJobs(mappedJobs);
setInterviews(mappedInterviews);
} catch (error) { } catch (error) {
dispatch(setLoadingFalse());
console.error("Failed to fetch data:", error); console.error("Failed to fetch data:", error);
// Fallback to empty data
setJobs([]);
setInterviews([]); setInterviews([]);
} finally { }
dispatch(setLoadingFalse()); };
// 获取企业内推岗位
const fetchJobsList = async () => {
try {
const res = await getJobsList({
page: jobsListPage,
pageSize: PAGE_SIZE,
isActive: true,
});
if (res.success) {
const mappedJobs = mapJobList(res.data);
setJobs((prevList) => {
const newList = [...prevList, ...mappedJobs];
if (res.total === newList?.length) {
setJobsListHasMore(false);
} else {
setJobsListPage((prevPage) => prevPage + 1);
}
return newList;
});
}
} catch (error) {
console.error("Failed to fetch data:", error);
setJobs([]);
} }
}; };
@@ -72,18 +80,18 @@ const CompanyJobsPage = () => {
navigate("/company-jobs-list"); navigate("/company-jobs-list");
}; };
useEffect(() => {
fetchData();
}, []);
return ( return (
<div className="company-jobs-page-wrapper"> <div className="company-jobs-page-wrapper">
<div className="company-jobs-page"> <div className="company-jobs-page">
<div className="company-jobs-page-left" onClick={handleJobWrapperClick}> <div className="company-jobs-page-left" onClick={handleJobWrapperClick}>
<p className="company-jobs-page-title">企业内推岗位库</p> <p className="company-jobs-page-title">企业内推岗位库</p>
<div className="company-jobs-page-left-list-wrapper"> <InfiniteScroll
loadMore={fetchJobsList}
hasMore={joblistHasMore}
className="company-jobs-page-left-list-wrapper"
>
<JobList data={jobs} backgroundColor="#F7F8FA" /> <JobList data={jobs} backgroundColor="#F7F8FA" />
</div> </InfiniteScroll>
</div> </div>
<div className="company-jobs-page-interview-wrapper"> <div className="company-jobs-page-interview-wrapper">
<div <div
@@ -94,13 +102,13 @@ const CompanyJobsPage = () => {
}`} }`}
> >
<p className="company-jobs-page-title">内推岗位面试</p> <p className="company-jobs-page-title">内推岗位面试</p>
<ul className="company-jobs-page-interview-list"> <InfiniteScroll
{interviews.length > 0 ? ( loadMore={fetchInterviewsData}
interviews.map((item) => ( hasMore={interviewsHasMore}
<li className="company-jobs-page-interview-list"
className="company-jobs-page-interview-item"
key={item.id}
> >
{interviews.map((item) => (
<li className="company-jobs-page-interview-item" key={item.id}>
<div className="company-jobs-page-interview-item-info"> <div className="company-jobs-page-interview-item-info">
<p className="company-jobs-page-interview-item-info-position"> <p className="company-jobs-page-interview-item-info-position">
{item.position} {item.position}
@@ -133,21 +141,8 @@ const CompanyJobsPage = () => {
</div> </div>
</div> </div>
</li> </li>
)) ))}
) : ( </InfiniteScroll>
<li className="company-jobs-page-interview-item">
<p
style={{
color: "#999",
textAlign: "center",
width: "100%",
}}
>
暂无面试安排
</p>
</li>
)}
</ul>
</div> </div>
</div> </div>
<div <div

View File

@@ -1,5 +1,5 @@
import { useState, useEffect } from "react"; import { useState } from "react";
import { Input, Empty } from "@arco-design/web-react"; import { Input } from "@arco-design/web-react";
import InfiniteScroll from "@/components/InfiniteScroll"; import InfiniteScroll from "@/components/InfiniteScroll";
import ProjectCasesModal from "./components/ProjectCasesModal"; import ProjectCasesModal from "./components/ProjectCasesModal";
import { getProjectsList } from "@/services/projectLibrary"; import { getProjectsList } from "@/services/projectLibrary";
@@ -37,16 +37,22 @@ const ProjectLibrary = () => {
const fetchProjects = async (searchValue = "", pageNum) => { const fetchProjects = async (searchValue = "", pageNum) => {
try { try {
// 这里使用真实API替换模拟数据 // 这里使用真实API替换模拟数据
const response = await getProjectsList({ const res = await getProjectsList({
search: searchValue, search: searchValue,
page: pageNum ?? page, page: pageNum ?? page,
pageSize: PAGE_SIZE, pageSize: PAGE_SIZE,
}); });
if (response.success) { if (res.success) {
setProjectList((prevList) => [...prevList, ...response.data]); setProjectList((prevList) => {
setHasMore(response?.data.length === PAGE_SIZE); const newList = [...prevList, ...res.data];
if (res.total === newList?.length) {
setHasMore(false);
} else {
setPage((prevPage) => prevPage + 1); setPage((prevPage) => prevPage + 1);
} }
return newList;
});
}
} catch (error) { } catch (error) {
console.error("Failed to fetch projects:", error); console.error("Failed to fetch projects:", error);
} }

View File

@@ -0,0 +1,10 @@
import request from "@/utils/request";
// 获取企业内推岗位
export async function getJobsList(params) {
return request.get("/api/jobs", { params });
}
// 获取企业内推岗位面试
export async function getInterviewsList(params) {
return request.get("/api/interviews", { params });
}

6
src/services/global.js Normal file
View File

@@ -0,0 +1,6 @@
import request from "@/utils/request";
// 获取学生信息
export async function getCurrentStudent() {
return request.get("/api/students/me");
}

View File

@@ -3,5 +3,14 @@ import {
getLearningProgressSummary, getLearningProgressSummary,
} from "./dashboard"; } from "./dashboard";
import { getProjectsList } from "./projectLibrary"; import { getProjectsList } from "./projectLibrary";
import { getJobsList, getInterviewsList } from "./companyJobs";
import { getCurrentStudent } from "./global";
export { getDashboardStatistics, getLearningProgressSummary, getProjectsList }; export {
getDashboardStatistics,
getLearningProgressSummary,
getProjectsList,
getJobsList,
getInterviewsList,
getCurrentStudent,
};