2025-09-03 13:26:13 +08:00
|
|
|
|
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";
|
2025-09-08 05:24:00 +08:00
|
|
|
|
import InterviewStatusDropdown from "./components/InterviewStatusDropdown";
|
2025-09-03 13:26:13 +08:00
|
|
|
|
import {
|
|
|
|
|
|
getCompanyJobsPageData,
|
|
|
|
|
|
getJobsList,
|
|
|
|
|
|
getInterviewsList,
|
|
|
|
|
|
} from "@/services";
|
|
|
|
|
|
|
|
|
|
|
|
import "./index.css";
|
|
|
|
|
|
const PAGE_SIZE = 10;
|
|
|
|
|
|
|
|
|
|
|
|
const CompanyJobsPage = () => {
|
|
|
|
|
|
const studentInfo = useSelector((state) => state.student.studentInfo);
|
|
|
|
|
|
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);
|
2025-09-08 05:24:00 +08:00
|
|
|
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
|
|
|
|
const [selectedInterview, setSelectedInterview] = useState(null);
|
|
|
|
|
|
const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 });
|
2025-09-03 13:26:13 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-08 05:24:00 +08:00
|
|
|
|
// 处理面试状态点击
|
|
|
|
|
|
const handleStatusClick = (e, item) => {
|
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
|
|
|
|
setDropdownPosition({
|
|
|
|
|
|
top: rect.bottom + 5,
|
|
|
|
|
|
left: rect.left
|
|
|
|
|
|
});
|
|
|
|
|
|
setSelectedInterview(item);
|
|
|
|
|
|
setDropdownOpen(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭下拉栏
|
|
|
|
|
|
const handleCloseDropdown = () => {
|
|
|
|
|
|
setDropdownOpen(false);
|
|
|
|
|
|
setSelectedInterview(null);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-03 13:26:13 +08:00
|
|
|
|
// 获取企业内推岗位 - 用于分页加载更多
|
|
|
|
|
|
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"
|
|
|
|
|
|
>
|
2025-09-05 20:46:03 +08:00
|
|
|
|
<div className="company-jobs-page-header">
|
|
|
|
|
|
<p className="company-jobs-page-title">
|
|
|
|
|
|
<img
|
|
|
|
|
|
src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuUY5w4Kcw4H.png"
|
|
|
|
|
|
alt="icon"
|
|
|
|
|
|
style={{
|
|
|
|
|
|
width: '24px',
|
|
|
|
|
|
height: '24px',
|
|
|
|
|
|
marginRight: '8px',
|
|
|
|
|
|
verticalAlign: 'middle'
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
企业内推岗位库
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="view-all-jobs-btn"
|
|
|
|
|
|
onClick={handleJobWrapperClick}
|
|
|
|
|
|
>
|
|
|
|
|
|
查看全部岗位
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2025-09-03 13:26:13 +08:00
|
|
|
|
<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
|
2025-09-05 20:46:03 +08:00
|
|
|
|
className="company-jobs-page-interview"
|
2025-09-03 13:26:13 +08:00
|
|
|
|
>
|
2025-09-05 20:46:03 +08:00
|
|
|
|
<p className="company-jobs-page-title">
|
|
|
|
|
|
<img
|
|
|
|
|
|
src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuUY5wqNngw9.png"
|
|
|
|
|
|
alt="icon"
|
|
|
|
|
|
style={{
|
|
|
|
|
|
width: '24px',
|
|
|
|
|
|
height: '24px',
|
|
|
|
|
|
marginRight: '8px',
|
|
|
|
|
|
verticalAlign: 'middle'
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
岗位面试状态
|
|
|
|
|
|
</p>
|
2025-09-03 13:26:13 +08:00
|
|
|
|
<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"
|
|
|
|
|
|
}`}
|
2025-09-08 05:24:00 +08:00
|
|
|
|
onClick={(e) => handleStatusClick(e, item)}
|
|
|
|
|
|
style={{ cursor: 'pointer' }}
|
2025-09-03 13:26:13 +08:00
|
|
|
|
>
|
|
|
|
|
|
{item.statusText}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</InfiniteScroll>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2025-09-08 05:24:00 +08:00
|
|
|
|
{/* 面试状态下拉栏 */}
|
|
|
|
|
|
<InterviewStatusDropdown
|
|
|
|
|
|
status={selectedInterview?.status}
|
|
|
|
|
|
statusText={selectedInterview?.statusText}
|
|
|
|
|
|
isOpen={dropdownOpen}
|
|
|
|
|
|
onClose={handleCloseDropdown}
|
|
|
|
|
|
position={dropdownPosition}
|
|
|
|
|
|
/>
|
2025-09-03 13:26:13 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default CompanyJobsPage;
|