Files
jiaowu-test/src/pages/CompanyJobsPage/index.jsx
KQL 321da26eb2 feat: 为岗位面试状态添加可点击下拉栏功能
- 创建InterviewStatusDropdown组件,显示面试状态动画
- 集成Lottie动画播放器,加载对应状态的动画文件
- 点击面试状态按钮后弹出下拉栏,展示动画和状态说明
- 添加11种不同面试状态的动画映射和描述文字
- 下拉栏支持点击遮罩层关闭

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-08 05:24:00 +08:00

314 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 InterviewStatusDropdown from "./components/InterviewStatusDropdown";
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);
const [dropdownOpen, setDropdownOpen] = useState(false);
const [selectedInterview, setSelectedInterview] = useState(null);
const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 });
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 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);
};
// 获取企业内推岗位 - 用于分页加载更多
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"
>
<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>
<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="company-jobs-page-interview"
>
<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>
<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"
}`}
onClick={(e) => handleStatusClick(e, item)}
style={{ cursor: 'pointer' }}
>
{item.statusText}
</div>
</div>
</li>
))}
</InfiniteScroll>
</div>
</div>
</>
)}
</div>
{/* 面试状态下拉栏 */}
<InterviewStatusDropdown
status={selectedInterview?.status}
statusText={selectedInterview?.statusText}
isOpen={dropdownOpen}
onClose={handleCloseDropdown}
position={dropdownPosition}
/>
</div>
);
};
export default CompanyJobsPage;