完整的教务系统前端项目 - 包含所有修复和9月份数据
This commit is contained in:
286
src/pages/CompanyJobsPage/index.jsx
Normal file
286
src/pages/CompanyJobsPage/index.jsx
Normal file
@@ -0,0 +1,286 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Spin, Empty } from "@arco-design/web-react";
|
||||
// import { mapJobList, mapInterviewList } from "@/utils/dataMapper"; // 不再需要映射器,mock数据已经是前端格式
|
||||
import InfiniteScroll from "@/components/InfiniteScroll";
|
||||
import toast from "@/components/Toast";
|
||||
import JobList from "./components/JobList";
|
||||
import {
|
||||
getCompanyJobsPageData,
|
||||
getJobsList,
|
||||
getInterviewsList,
|
||||
} from "@/services";
|
||||
|
||||
import "./index.css";
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
const CompanyJobsPage = () => {
|
||||
const studentInfo = useSelector((state) => state.student.studentInfo);
|
||||
const [isExpand, setIsExpand] = useState(false); // 是否展开
|
||||
const [jobs, setJobs] = useState([]);
|
||||
const [jobsListPage, setJobsListPage] = useState(1);
|
||||
const [jobsListHasMore, setJobsListHasMore] = useState(true);
|
||||
const [interviews, setInterviews] = useState([]);
|
||||
const [interviewsPage, setInterviewsPage] = useState(1);
|
||||
const [interviewsHasMore, setInterviewsHasMore] = useState(true);
|
||||
const [initialDataLoaded, setInitialDataLoaded] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 初始化页面数据 - 使用聚合接口
|
||||
useEffect(() => {
|
||||
const fetchInitialData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const res = await getCompanyJobsPageData({
|
||||
studentId: studentInfo?.id,
|
||||
});
|
||||
|
||||
if (res?.success) {
|
||||
// 设置岗位数据
|
||||
if (res.data?.jobs) {
|
||||
// Mock数据已经是前端格式,直接使用不需要映射
|
||||
const jobs = res.data.jobs.list || [];
|
||||
setJobs(jobs);
|
||||
setJobsListHasMore(res.data.jobs.hasMore);
|
||||
if (jobs.length > 0) {
|
||||
setJobsListPage(2); // 下次从第2页开始
|
||||
}
|
||||
}
|
||||
|
||||
// 设置面试数据
|
||||
if (res.data?.interviews) {
|
||||
// Mock数据已经是前端格式,直接使用不需要映射
|
||||
const interviews = res.data.interviews.list || [];
|
||||
setInterviews(interviews);
|
||||
setInterviewsHasMore(res.data.interviews.hasMore);
|
||||
if (interviews.length > 0) {
|
||||
setInterviewsPage(2); // 下次从第2页开始
|
||||
}
|
||||
}
|
||||
|
||||
setInitialDataLoaded(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch initial page data:", error);
|
||||
// 如果聚合接口失败,回退到原来的方式
|
||||
setInitialDataLoaded(true);
|
||||
// 显示错误信息给用户
|
||||
if (toast && toast.error) {
|
||||
toast.error("加载数据失败,请刷新重试");
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchInitialData();
|
||||
}, [studentInfo?.id]);
|
||||
|
||||
// 获取面试信息 - 用于分页加载更多
|
||||
const fetchInterviewsData = async () => {
|
||||
// 如果初始数据还没加载完成,或者是第一页且已有初始数据,则跳过
|
||||
if (!initialDataLoaded || (interviewsPage === 1 && interviews.length > 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (studentInfo?.id) {
|
||||
const res = await getInterviewsList({
|
||||
page: interviewsPage,
|
||||
pageSize: PAGE_SIZE,
|
||||
studentId: studentInfo?.id,
|
||||
status: "SCHEDULED",
|
||||
});
|
||||
if (res.success) {
|
||||
// Mock数据已经是前端格式,直接使用
|
||||
const interviews = res.data || [];
|
||||
setInterviews((prevList) => {
|
||||
// 去重处理:过滤掉已存在的数据
|
||||
const existingIds = new Set(
|
||||
prevList.map((interview) => interview.id)
|
||||
);
|
||||
const newInterviews = interviews.filter(
|
||||
(interview) => !existingIds.has(interview.id)
|
||||
);
|
||||
|
||||
const newList = [...prevList, ...newInterviews];
|
||||
if (res.total <= newList?.length) {
|
||||
setInterviewsHasMore(false);
|
||||
} else {
|
||||
setInterviewsPage((prevPage) => prevPage + 1);
|
||||
}
|
||||
return newList;
|
||||
});
|
||||
} else {
|
||||
if (interviewsPage === 1) {
|
||||
setInterviews([]);
|
||||
}
|
||||
toast.error(res.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 获取企业内推岗位 - 用于分页加载更多
|
||||
const fetchJobsList = async () => {
|
||||
// 如果初始数据还没加载完成,或者是第一页且已有初始数据,则跳过
|
||||
if (!initialDataLoaded || (jobsListPage === 1 && jobs.length > 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 防止重复请求
|
||||
if (jobsListPage === 1 && jobs.length === 0) {
|
||||
return; // 初始数据应该通过聚合接口加载
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await getJobsList({
|
||||
page: jobsListPage,
|
||||
pageSize: PAGE_SIZE,
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
if (res?.success) {
|
||||
// Mock数据已经是前端格式,直接使用
|
||||
const jobs = res.data;
|
||||
setJobs((prevList) => {
|
||||
// 去重处理:过滤掉已存在的数据
|
||||
const existingIds = new Set(prevList.map((job) => job.id));
|
||||
const newJobs = jobs.filter((job) => !existingIds.has(job.id));
|
||||
|
||||
const newList = [...prevList, ...newJobs];
|
||||
if (res.total <= newList?.length) {
|
||||
setJobsListHasMore(false);
|
||||
} else {
|
||||
setJobsListPage((prevPage) => prevPage + 1);
|
||||
}
|
||||
return newList;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch data:", error);
|
||||
if (jobsListPage === 1) {
|
||||
setJobs([]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleJobWrapperClick = () => {
|
||||
navigate("/company-jobs-list");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="company-jobs-page-wrapper">
|
||||
<div className="company-jobs-page">
|
||||
{loading ? (
|
||||
<Spin size={80} className="company-jobs-page-spin" />
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
className="company-jobs-page-left"
|
||||
onClick={handleJobWrapperClick}
|
||||
>
|
||||
<p className="company-jobs-page-title">企业内推岗位库</p>
|
||||
<InfiniteScroll
|
||||
loadMore={fetchJobsList}
|
||||
hasMore={jobsListHasMore}
|
||||
className="company-jobs-page-left-list-wrapper"
|
||||
>
|
||||
<JobList data={jobs} />
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
<div className="company-jobs-page-interview-wrapper">
|
||||
<div
|
||||
className={`${
|
||||
isExpand
|
||||
? "company-jobs-page-interview"
|
||||
: "company-jobs-page-interview company-jobs-page-interview-expand"
|
||||
}`}
|
||||
>
|
||||
<p className="company-jobs-page-title">内推岗位面试</p>
|
||||
<InfiniteScroll
|
||||
loadMore={fetchInterviewsData}
|
||||
hasMore={interviewsHasMore}
|
||||
empty={interviews.length === 0}
|
||||
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}
|
||||
</p>
|
||||
{item.job?.tags?.length > 0 ? (
|
||||
<ul className="company-jobs-page-interview-item-info-tags">
|
||||
{item.job.tags.map((tag) => (
|
||||
<li
|
||||
className="company-jobs-page-interview-item-info-tag"
|
||||
key={tag}
|
||||
>
|
||||
{tag}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
<span className="company-jobs-page-interview-item-info-salary">
|
||||
{item.job?.salary || "面议"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="company-jobs-page-interview-item-btn-wrapper">
|
||||
<span>{item.interviewTime}</span>
|
||||
<div
|
||||
className={`company-jobs-page-interview-item-btn ${
|
||||
item.status !== "COMPLETED" &&
|
||||
"company-jobs-page-interview-item-btn-active"
|
||||
}`}
|
||||
>
|
||||
{item.statusText}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setIsExpand(!isExpand)}
|
||||
className={
|
||||
isExpand
|
||||
? "company-jobs-page-process-wrapper-expand"
|
||||
: "company-jobs-page-process-wrapper-close"
|
||||
}
|
||||
>
|
||||
<div className="company-jobs-page-process-wrapper-title">
|
||||
岗位陪跑流程
|
||||
</div>
|
||||
<div className="company-jobs-page-process-content">
|
||||
<div className="company-jobs-page-process-item-icon icon1">
|
||||
<p>内推岗位简历投递</p>
|
||||
</div>
|
||||
<div className="company-jobs-page-process-item-round-dot icon2" />
|
||||
<div className="company-jobs-page-process-item-icon icon3">
|
||||
<p>岗位简历接收</p>
|
||||
</div>
|
||||
<div className="company-jobs-page-process-item-icon icon4">
|
||||
<p>面试时间地点确定</p>
|
||||
</div>
|
||||
<div className="company-jobs-page-process-item-icon icon5">
|
||||
<p>参与企业面试</p>
|
||||
</div>
|
||||
<div className="company-jobs-page-process-item-round-dot icon6" />
|
||||
<div className="company-jobs-page-process-item-icon icon7">
|
||||
<p>企业offer发送</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompanyJobsPage;
|
||||
Reference in New Issue
Block a user