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

View File

@@ -1,13 +1,11 @@
import { StrictMode } from "react";
// import { StrictMode } from "react"; // 重复请求只会在 开发环境 下发生生产环境中StrictMode不会导致重复渲染
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(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>
);

View File

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

View File

@@ -11,6 +11,7 @@
justify-content: space-between;
align-items: center;
position: relative;
.company-jobs-page-title {
width: 100%;
height: 42px;
@@ -51,6 +52,7 @@
.company-jobs-page-left-list-wrapper {
width: 496px;
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 { 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 { getJobsList, getInterviewsList, getCurrentStudent } from "@/services";
import InfiniteScroll from "@/components/InfiniteScroll";
import "./index.css";
const PAGE_SIZE = 10;
const CompanyJobsPage = () => {
const dispatch = useDispatch();
const [jobs, setJobs] = useState([]);
const [interviews, setInterviews] = useState([]);
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 fetchData = async () => {
// 获取面试信息
const fetchInterviewsData = async () => {
try {
dispatch(setLoadingTrue());
// Get current user's student ID from API
let studentId = null;
try {
const currentStudent = await studentAPI.getCurrentStudent();
const currentStudent = await getCurrentStudent();
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) {
try {
interviewsData = await interviewAPI.getList({
const res = await getInterviewsList({
page: interviewsPage,
pageSize: PAGE_SIZE,
studentId: studentId,
status: "SCHEDULED",
});
} catch (err) {
console.log("No interviews found or API error");
if (res.success) {
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) {
dispatch(setLoadingFalse());
console.error("Failed to fetch data:", error);
// Fallback to empty data
setJobs([]);
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");
};
useEffect(() => {
fetchData();
}, []);
return (
<div className="company-jobs-page-wrapper">
<div className="company-jobs-page">
<div className="company-jobs-page-left" onClick={handleJobWrapperClick}>
<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" />
</div>
</InfiniteScroll>
</div>
<div className="company-jobs-page-interview-wrapper">
<div
@@ -94,13 +102,13 @@ const CompanyJobsPage = () => {
}`}
>
<p className="company-jobs-page-title">内推岗位面试</p>
<ul className="company-jobs-page-interview-list">
{interviews.length > 0 ? (
interviews.map((item) => (
<li
className="company-jobs-page-interview-item"
key={item.id}
<InfiniteScroll
loadMore={fetchInterviewsData}
hasMore={interviewsHasMore}
className="company-jobs-page-interview-list"
>
{interviews.map((item) => (
<li className="company-jobs-page-interview-item" key={item.id}>
<div className="company-jobs-page-interview-item-info">
<p className="company-jobs-page-interview-item-info-position">
{item.position}
@@ -133,21 +141,8 @@ const CompanyJobsPage = () => {
</div>
</div>
</li>
))
) : (
<li className="company-jobs-page-interview-item">
<p
style={{
color: "#999",
textAlign: "center",
width: "100%",
}}
>
暂无面试安排
</p>
</li>
)}
</ul>
))}
</InfiniteScroll>
</div>
</div>
<div

View File

@@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import { Input, Empty } from "@arco-design/web-react";
import { useState } from "react";
import { Input } from "@arco-design/web-react";
import InfiniteScroll from "@/components/InfiniteScroll";
import ProjectCasesModal from "./components/ProjectCasesModal";
import { getProjectsList } from "@/services/projectLibrary";
@@ -37,16 +37,22 @@ const ProjectLibrary = () => {
const fetchProjects = async (searchValue = "", pageNum) => {
try {
// 这里使用真实API替换模拟数据
const response = await getProjectsList({
const res = await getProjectsList({
search: searchValue,
page: pageNum ?? page,
pageSize: PAGE_SIZE,
});
if (response.success) {
setProjectList((prevList) => [...prevList, ...response.data]);
setHasMore(response?.data.length === PAGE_SIZE);
if (res.success) {
setProjectList((prevList) => {
const newList = [...prevList, ...res.data];
if (res.total === newList?.length) {
setHasMore(false);
} else {
setPage((prevPage) => prevPage + 1);
}
return newList;
});
}
} catch (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,
} from "./dashboard";
import { getProjectsList } from "./projectLibrary";
import { getJobsList, getInterviewsList } from "./companyJobs";
import { getCurrentStudent } from "./global";
export { getDashboardStatistics, getLearningProgressSummary, getProjectsList };
export {
getDashboardStatistics,
getLearningProgressSummary,
getProjectsList,
getJobsList,
getInterviewsList,
getCurrentStudent,
};