Files
jiaowu-test/src/pages/ResumeInterviewPage/index.jsx

385 lines
12 KiB
React
Raw Normal View History

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;