Files
jiaowu-test/src/pages/ResumeInterviewPage/index.jsx
KQL efae5a15d9 feat: 更新简历详情页面教育经历为苏州信息职业技术学院
- 统一所有岗位简历的教育经历显示为苏州信息职业技术学院
- 更新简历详情页面组件,确保教育经历信息一致性
- 优化简历信息展示格式和样式
- 添加新的面试题库和项目库数据
- 完善文旅产业相关简历模板
2025-09-08 12:59:17 +08:00

385 lines
12 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 { useRef, useState, useEffect } from "react";
import { Spin, Empty } from "@arco-design/web-react";
import toast from "@/components/Toast";
import InterviewQuestionsModal from "./components/InterviewQuestionsModal";
import ResumeInfoModal from "@/pages/CompanyJobsPage/components/ResumeInfoModal";
import { getPageData } from "@/services/resumeInterview";
import jobLevelData from "@/data/joblevel.json";
import TagHigh from "@/assets/images/ResumeInterviewPage/Tag.png";
import TagMiddle from "@/assets/images/ResumeInterviewPage/Tag2.png";
import TagOrdinary from "@/assets/images/ResumeInterviewPage/Tag3.png";
import QuestionIcon from "@/assets/images/ResumeInterviewPage/question_icon2.png";
import "./index.css";
const ResumeInterviewPage = () => {
const [activeIndustry, setActiveIndustry] = useState("frontend");
const [interviewModalVisible, setInterviewModalVisible] = useState(false);
const [resumeModalVisible, setResumeModalVisible] = useState(false);
const [interviewModalData, setInterviewModalData] = useState(undefined);
const [resumeModalData, setResumeModalData] = useState(undefined);
const [pageData, setPageData] = useState(null);
const [loading, setLoading] = useState(true);
const [showLeftBtn, setShowLeftBtn] = useState(false);
const [showRightBtn, setShowRightBtn] = useState(true);
const sectionsRef = useRef({});
const navRef = useRef(null);
// 有真实修改版的岗位列表(来自修改后简历文件夹)
const hasRealModifiedVersion = (positionTitle) => {
const modifiedPositions = [
"会展策划师",
"会展讲解员",
"活动执行",
"活动策划师",
"漫展策划师",
"会展执行助理",
"旅游规划师",
"旅游计调专员",
"景区运营专员",
"文旅运营总监助理"
];
return modifiedPositions.includes(positionTitle);
};
// 获取岗位头像和级别信息
const getPositionInfo = (positionTitle) => {
const jobData = jobLevelData.data;
let positionInfo = null;
let levelName = "";
let levelKey = "";
// 遍历所有级别查找匹配的岗位
for (const [key, levelData] of Object.entries(jobData)) {
const found = levelData.list.find(item => item.position_name === positionTitle);
if (found) {
positionInfo = found;
levelName = levelData.name;
levelKey = key;
break;
}
}
// 根据级别返回对应的标签图片
let tagImage = "";
switch(levelKey) {
case "high":
tagImage = TagHigh;
break;
case "middle":
tagImage = TagMiddle;
break;
case "ordinary":
default:
tagImage = TagOrdinary;
break;
}
return {
avatar: positionInfo?.img || null,
levelName: levelName || "普通岗",
tagImage: tagImage
};
};
// 导航到指定行业段落
const handleNavClick = (industryId) => {
setActiveIndustry(industryId);
sectionsRef.current[industryId]?.scrollIntoView({
behavior: "smooth",
block: "start",
});
};
// 面试题点击处理
const handleQuestionClick = (item) => {
if (item) {
setInterviewModalVisible(true);
setInterviewModalData(item);
} else {
toast.error("加载数据失败");
}
};
// 职位点击处理
const handlePositionClick = (position, industry) => {
// Find resume templates for this industry
const templates = pageData.resumeTemplates[industry.name] || [];
// 首先根据岗位名称精确匹配
const selectedTemplate =
templates.find((t) => t.position === position.title) ||
templates.find((t) => t.level === position.level) ||
templates[0];
// 构造符合ResumeInfoModal期望的数据格式
const resumeData = {
title: position.title,
content: selectedTemplate?.content || selectedTemplate?.oldContent || null,
studentResume: pageData.myResume,
};
setResumeModalData(resumeData);
setResumeModalVisible(true);
};
const handleCloseInterviewModal = () => {
setInterviewModalVisible(false);
setInterviewModalData(undefined);
};
const handleCloseResumeModal = () => {
setResumeModalVisible(false);
setResumeModalData(undefined);
};
const filterPositions = (positions) => {
return positions.filter((position) => position.title?.toLowerCase());
};
// 获取页面数据
useEffect(() => {
const fetchPageData = async () => {
try {
setLoading(true);
const response = await getPageData();
if (response.success) {
console.log('页面数据加载成功:', response.data);
setPageData(response.data);
// 设置默认选中第一个行业
if (response.data.industries && response.data.industries.length > 0) {
setActiveIndustry(response.data.industries[0].id);
}
}
} catch (error) {
console.error("Failed to fetch page data:", error);
} finally {
setLoading(false);
}
};
fetchPageData();
}, []);
// 监听滚动位置更新导航状态
useEffect(() => {
if (!pageData?.industries) return;
const handleScroll = () => {
const scrollPosition = window.scrollY + 200;
pageData.industries.forEach((industry) => {
const section = sectionsRef.current[industry.id];
if (section) {
const sectionTop = section.offsetTop;
const sectionBottom = sectionTop + section.offsetHeight;
if (scrollPosition >= sectionTop && scrollPosition < sectionBottom) {
setActiveIndustry(industry.id);
}
}
});
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, [pageData?.industries]);
// 监听数据变化,更新滚动按钮状态
useEffect(() => {
if (pageData?.industries && navRef.current) {
setTimeout(() => {
checkScrollButtons();
}, 100);
}
}, [pageData?.industries]);
// 检查滚动按钮的显示状态
const checkScrollButtons = () => {
if (!navRef.current) return;
const { scrollLeft, scrollWidth, clientWidth } = navRef.current;
setShowLeftBtn(scrollLeft > 5); // 添加5px的容差
setShowRightBtn(scrollLeft < scrollWidth - clientWidth - 5); // 添加5px的容差确保能滚动到最后
};
// 左右滚动函数
const scrollNav = (direction) => {
if (!navRef.current) return;
const scrollAmount = 200; // 每次滚动的距离
navRef.current.scrollTo({
left: navRef.current.scrollLeft + (direction === 'left' ? -scrollAmount : scrollAmount),
behavior: 'smooth'
});
};
// 添加鼠标滚轮横向滚动功能
useEffect(() => {
// 延迟执行以确保DOM已经渲染
const timer = setTimeout(() => {
const navigation = navRef.current;
if (!navigation) return;
const handleWheel = (e) => {
// 检查是否鼠标在导航栏上
if (navigation.contains(e.target)) {
// 阻止默认垂直滚动
e.preventDefault();
e.stopPropagation();
// 转换为横向滚动
const delta = e.deltaY || e.deltaX;
navigation.scrollLeft += delta;
// 更新按钮状态
checkScrollButtons();
}
};
const handleScroll = () => {
checkScrollButtons();
};
// 使用捕获阶段以确保事件被正确处理
window.addEventListener('wheel', handleWheel, { passive: false, capture: true });
navigation.addEventListener('scroll', handleScroll);
// 初始检查
checkScrollButtons();
return () => {
window.removeEventListener('wheel', handleWheel, { capture: true });
navigation.removeEventListener('scroll', handleScroll);
};
}, 100);
return () => clearTimeout(timer);
}, [pageData]);
return (
<div className="resume-interview-page">
{loading ? (
<Spin size={80} className="resume-interview-spin" />
) : pageData ? (
<>
<div className="resume-interview-navigation-wrapper">
{showLeftBtn && (
<button
className="nav-scroll-btn nav-scroll-btn-left"
onClick={() => scrollNav('left')}
>
</button>
)}
<ul className="resume-interview-navigation" ref={navRef}>
<div className="navigation-tabs">
{pageData.industries.map((industry) => (
<li
key={industry.id}
className={`resume-interview-navigation-item ${
activeIndustry === industry.id ? "active" : ""
}`}
onClick={() => handleNavClick(industry.id)}
>
{industry.name}
</li>
))}
</div>
</ul>
{showRightBtn && (
<button
className="nav-scroll-btn nav-scroll-btn-right"
onClick={() => scrollNav('right')}
>
</button>
)}
</div>
<ul className="resume-interview-content-wrapper">
{pageData.industries.map((item) => (
<li
className="resume-interview-content-item-wrapper"
key={item.id}
ref={(el) => (sectionsRef.current[item.id] = el)}
>
<p className="item-title">{item.name}</p>
<p className="item-subtitle">简历与面试题</p>
<div className="item-content-wrapper">
<ul className="jobs-list">
{filterPositions(item.positions).map((position) => {
const positionInfo = getPositionInfo(position.title);
return (
<li
className={`job-item ${hasRealModifiedVersion(position.title) ? 'job-item-change' : ''}`}
key={position.id}
onClick={() => handlePositionClick(position, item)}
>
<div className="job-avatar-wrapper">
{positionInfo.avatar ? (
<img
src={positionInfo.avatar}
alt={position.title}
className="job-avatar"
/>
) : (
<div className="job-avatar-placeholder" />
)}
</div>
<div className="job-info">
<img
src={positionInfo.tagImage}
alt={positionInfo.levelName}
className="job-level-tag"
/>
<div className="job-name">
<p>{position.title}</p>
<span className="job-arrow"></span>
</div>
</div>
</li>
);
})}
</ul>
<ul className="resumes-list">
{item.questions.map((question) => (
<li
key={question.id}
className="resume-item"
onClick={() =>
handleQuestionClick({ ...item, questions: question.subQuestions || [question] })
}
>
<img
src={QuestionIcon}
alt="question"
className="question-icon"
/>
<p>{question.question}</p>
</li>
))}
</ul>
</div>
</li>
))}
</ul>
</>
) : (
<Empty description="暂无数据" className="empty-data" />
)}
<InterviewQuestionsModal
visible={interviewModalVisible}
onClose={handleCloseInterviewModal}
data={interviewModalData}
/>
<ResumeInfoModal
visible={resumeModalVisible}
onClose={handleCloseResumeModal}
data={resumeModalData}
/>
</div>
);
};
export default ResumeInterviewPage;