接口调整
This commit is contained in:
@@ -15,17 +15,19 @@ const CompanyJobsPage = () => {
|
|||||||
const [isExpand, setIsExpand] = useState(false); // 是否展开
|
const [isExpand, setIsExpand] = useState(false); // 是否展开
|
||||||
const [jobs, setJobs] = useState([]);
|
const [jobs, setJobs] = useState([]);
|
||||||
const [jobsListPage, setJobsListPage] = useState(1);
|
const [jobsListPage, setJobsListPage] = useState(1);
|
||||||
const [joblistHasMore, setJobsListHasMore] = useState(true);
|
const [jobsListHasMore, setJobsListHasMore] = useState(true);
|
||||||
const [interviews, setInterviews] = useState([]);
|
const [interviews, setInterviews] = useState([]);
|
||||||
const [interviewsPage, setInterviewsPage] = useState(1);
|
const [interviewsPage, setInterviewsPage] = useState(1);
|
||||||
const [interviewsHasMore, setInterviewsHasMore] = useState(true);
|
const [interviewsHasMore, setInterviewsHasMore] = useState(true);
|
||||||
const [initialDataLoaded, setInitialDataLoaded] = useState(false);
|
const [initialDataLoaded, setInitialDataLoaded] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// 初始化页面数据 - 使用聚合接口
|
// 初始化页面数据 - 使用聚合接口
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchInitialData = async () => {
|
const fetchInitialData = async () => {
|
||||||
try {
|
try {
|
||||||
|
setLoading(true);
|
||||||
const res = await getCompanyJobsPageData({
|
const res = await getCompanyJobsPageData({
|
||||||
studentId: studentInfo?.id,
|
studentId: studentInfo?.id,
|
||||||
});
|
});
|
||||||
@@ -35,7 +37,7 @@ const CompanyJobsPage = () => {
|
|||||||
if (res.data?.jobs) {
|
if (res.data?.jobs) {
|
||||||
const mappedJobs = mapJobList(res.data.jobs.list || []);
|
const mappedJobs = mapJobList(res.data.jobs.list || []);
|
||||||
setJobs(mappedJobs);
|
setJobs(mappedJobs);
|
||||||
setJoblistHasMore(res.data.jobs.hasMore);
|
setJobsListHasMore(res.data.jobs.hasMore);
|
||||||
if (mappedJobs.length > 0) {
|
if (mappedJobs.length > 0) {
|
||||||
setJobsListPage(2); // 下次从第2页开始
|
setJobsListPage(2); // 下次从第2页开始
|
||||||
}
|
}
|
||||||
@@ -57,6 +59,12 @@ const CompanyJobsPage = () => {
|
|||||||
console.error('Failed to fetch initial page data:', error);
|
console.error('Failed to fetch initial page data:', error);
|
||||||
// 如果聚合接口失败,回退到原来的方式
|
// 如果聚合接口失败,回退到原来的方式
|
||||||
setInitialDataLoaded(true);
|
setInitialDataLoaded(true);
|
||||||
|
// 显示错误信息给用户
|
||||||
|
if (toast && toast.error) {
|
||||||
|
toast.error('加载数据失败,请刷新重试');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -80,8 +88,12 @@ const CompanyJobsPage = () => {
|
|||||||
if (res.success) {
|
if (res.success) {
|
||||||
const mappedInterviews = mapInterviewList(res.data || []);
|
const mappedInterviews = mapInterviewList(res.data || []);
|
||||||
setInterviews((prevList) => {
|
setInterviews((prevList) => {
|
||||||
const newList = [...prevList, ...mappedInterviews];
|
// 去重处理:过滤掉已存在的数据
|
||||||
if (res.total === newList?.length) {
|
const existingIds = new Set(prevList.map(interview => interview.id));
|
||||||
|
const newInterviews = mappedInterviews.filter(interview => !existingIds.has(interview.id));
|
||||||
|
|
||||||
|
const newList = [...prevList, ...newInterviews];
|
||||||
|
if (res.total <= newList?.length) {
|
||||||
setInterviewsHasMore(false);
|
setInterviewsHasMore(false);
|
||||||
} else {
|
} else {
|
||||||
setInterviewsPage((prevPage) => prevPage + 1);
|
setInterviewsPage((prevPage) => prevPage + 1);
|
||||||
@@ -104,6 +116,11 @@ const CompanyJobsPage = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 防止重复请求
|
||||||
|
if (jobsListPage === 1 && jobs.length === 0) {
|
||||||
|
return; // 初始数据应该通过聚合接口加载
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await getJobsList({
|
const res = await getJobsList({
|
||||||
page: jobsListPage,
|
page: jobsListPage,
|
||||||
@@ -114,8 +131,12 @@ const CompanyJobsPage = () => {
|
|||||||
if (res?.success) {
|
if (res?.success) {
|
||||||
const mappedJobs = mapJobList(res.data);
|
const mappedJobs = mapJobList(res.data);
|
||||||
setJobs((prevList) => {
|
setJobs((prevList) => {
|
||||||
const newList = [...prevList, ...mappedJobs];
|
// 去重处理:过滤掉已存在的数据
|
||||||
if (res.total === newList?.length) {
|
const existingIds = new Set(prevList.map(job => job.id));
|
||||||
|
const newJobs = mappedJobs.filter(job => !existingIds.has(job.id));
|
||||||
|
|
||||||
|
const newList = [...prevList, ...newJobs];
|
||||||
|
if (res.total <= newList?.length) {
|
||||||
setJobsListHasMore(false);
|
setJobsListHasMore(false);
|
||||||
} else {
|
} else {
|
||||||
setJobsListPage((prevPage) => prevPage + 1);
|
setJobsListPage((prevPage) => prevPage + 1);
|
||||||
@@ -135,6 +156,19 @@ const CompanyJobsPage = () => {
|
|||||||
navigate("/company-jobs-list");
|
navigate("/company-jobs-list");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (loading && jobs.length === 0 && interviews.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="company-jobs-page-wrapper" style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
minHeight: '400px'
|
||||||
|
}}>
|
||||||
|
<p>正在加载数据...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="company-jobs-page-wrapper">
|
<div className="company-jobs-page-wrapper">
|
||||||
<div className="company-jobs-page">
|
<div className="company-jobs-page">
|
||||||
@@ -142,7 +176,7 @@ const CompanyJobsPage = () => {
|
|||||||
<p className="company-jobs-page-title">企业内推岗位库</p>
|
<p className="company-jobs-page-title">企业内推岗位库</p>
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
loadMore={fetchJobsList}
|
loadMore={fetchJobsList}
|
||||||
hasMore={joblistHasMore}
|
hasMore={jobsListHasMore}
|
||||||
className="company-jobs-page-left-list-wrapper"
|
className="company-jobs-page-left-list-wrapper"
|
||||||
>
|
>
|
||||||
<JobList data={jobs} backgroundColor="#F7F8FA" />
|
<JobList data={jobs} backgroundColor="#F7F8FA" />
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ const EchartsProgress = ({
|
|||||||
},
|
},
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
value: 75,
|
value: percent,
|
||||||
detail: {
|
detail: {
|
||||||
valueAnimation: true,
|
valueAnimation: true,
|
||||||
offsetCenter: ["0%", "0%"],
|
offsetCenter: ["0%", "0%"],
|
||||||
|
|||||||
@@ -7,98 +7,31 @@ export default ({ visible, onClose, data }) => {
|
|||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!data) return null;
|
||||||
|
|
||||||
|
const { question } = data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal visible={visible} onClose={handleCloseModal}>
|
<Modal visible={visible} onClose={handleCloseModal}>
|
||||||
<div className="interview-questions-modal">
|
<div className="interview-questions-modal">
|
||||||
<i className="close-icon" onClick={handleCloseModal} />
|
<i className="close-icon" onClick={handleCloseModal} />
|
||||||
<p className="interview-questions-modal-title">PLC岗位群面试题</p>
|
<p className="interview-questions-modal-title">{data.name}面试题</p>
|
||||||
<p className="interview-questions-modal-subtitle">
|
<p className="interview-questions-modal-subtitle">
|
||||||
在1V1定制求职策略阶段,企业HR会为您讲解面试题
|
在1V1定制求职策略阶段,企业HR会为您讲解面试题
|
||||||
</p>
|
</p>
|
||||||
<ul className="interview-questions-modal-list">
|
<ul className="interview-questions-modal-list">
|
||||||
<li className="interview-questions-modal-item">
|
<li className="interview-questions-modal-item">
|
||||||
<p className="interview-questions-modal-question">
|
<p className="interview-questions-modal-question">
|
||||||
<span>问题1:</span>xxxxxxxxxxxxxxxxxx?
|
<span>问题:</span>{question.question}
|
||||||
</p>
|
</p>
|
||||||
<p className="interview-questions-modal-answer">
|
<p className="interview-questions-modal-answer">
|
||||||
<span>解答:</span>
|
<span>解答:</span>
|
||||||
负责室内平面设计项目的创意和实施2.
|
这是一个{question.difficulty.toLowerCase()}难度的{question.category}相关问题。
|
||||||
与团队合作,确保设计质量和项目进度 3.
|
针对这类问题,建议从以下几个方面来回答:
|
||||||
持续关注设计趋势,提升设计技能 1. 具备出色的沟通能力和团队协作精神
|
1. 理解问题的核心概念和背景
|
||||||
2. 能够高效完成任务,对工作细节有高度关注 3.
|
2. 结合实际项目经验进行说明
|
||||||
具有不断学习和适应新挑战的能力 加分项:有以下行业经验:建筑设计
|
3. 展示对相关技术的深入理解
|
||||||
</p>
|
4. 提及可能的优化或改进方案
|
||||||
</li>
|
|
||||||
<li className="interview-questions-modal-item">
|
|
||||||
<p className="interview-questions-modal-question">
|
|
||||||
<span>问题1:</span>xxxxxxxxxxxxxxxxxx?
|
|
||||||
</p>
|
|
||||||
<p className="interview-questions-modal-answer">
|
|
||||||
<span>解答:</span>
|
|
||||||
负责室内平面设计项目的创意和实施2.
|
|
||||||
与团队合作,确保设计质量和项目进度 3.
|
|
||||||
持续关注设计趋势,提升设计技能 1. 具备出色的沟通能力和团队协作精神
|
|
||||||
2. 能够高效完成任务,对工作细节有高度关注 3.
|
|
||||||
具有不断学习和适应新挑战的能力 加分项:有以下行业经验:建筑设计
|
|
||||||
负责室内平面设计项目的创意和实施2.
|
|
||||||
与团队合作,确保设计质量和项目进度 3.
|
|
||||||
持续关注设计趋势,提升设计技能 1. 具备出色的沟通能力和团队协作精神
|
|
||||||
2. 能够高效完成任务,对工作细节有高度关注 3.
|
|
||||||
具有不断学习和适应新挑战的能力 加分项:有以下行业经验:建筑设计
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
<li className="interview-questions-modal-item">
|
|
||||||
<p className="interview-questions-modal-question">
|
|
||||||
<span>问题1:</span>xxxxxxxxxxxxxxxxxx?
|
|
||||||
</p>
|
|
||||||
<p className="interview-questions-modal-answer">
|
|
||||||
<span>解答:</span>
|
|
||||||
负责室内平面设计项目的创意和实施2.
|
|
||||||
与团队合作,确保设计质量和项目进度 3.
|
|
||||||
持续关注设计趋势,提升设计技能 1. 具备出色的沟通能力和团队协作精神
|
|
||||||
2. 能够高效完成任务,对工作细节有高度关注 3.
|
|
||||||
具有不断学习和适应新挑战的能力 加分项:有以下行业经验:建筑设计
|
|
||||||
负责室内平面设计项目的创意和实施2.
|
|
||||||
与团队合作,确保设计质量和项目进度 3.
|
|
||||||
持续关注设计趋势,提升设计技能 1. 具备出色的沟通能力和团队协作精神
|
|
||||||
2. 能够高效完成任务,对工作细节有高度关注 3.
|
|
||||||
具有不断学习和适应新挑战的能力 加分项:有以下行业经验:建筑设计
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
<li className="interview-questions-modal-item">
|
|
||||||
<p className="interview-questions-modal-question">
|
|
||||||
<span>问题1:</span>xxxxxxxxxxxxxxxxxx?
|
|
||||||
</p>
|
|
||||||
<p className="interview-questions-modal-answer">
|
|
||||||
<span>解答:</span>
|
|
||||||
负责室内平面设计项目的创意和实施2.
|
|
||||||
与团队合作,确保设计质量和项目进度 3.
|
|
||||||
持续关注设计趋势,提升设计技能 1. 具备出色的沟通能力和团队协作精神
|
|
||||||
2. 能够高效完成任务,对工作细节有高度关注 3.
|
|
||||||
具有不断学习和适应新挑战的能力 加分项:有以下行业经验:建筑设计
|
|
||||||
负责室内平面设计项目的创意和实施2.
|
|
||||||
与团队合作,确保设计质量和项目进度 3.
|
|
||||||
持续关注设计趋势,提升设计技能 1. 具备出色的沟通能力和团队协作精神
|
|
||||||
2. 能够高效完成任务,对工作细节有高度关注 3.
|
|
||||||
具有不断学习和适应新挑战的能力 加分项:有以下行业经验:建筑设计
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
<li className="interview-questions-modal-item">
|
|
||||||
<p className="interview-questions-modal-question">
|
|
||||||
<span>问题1:</span>xxxxxxxxxxxxxxxxxx?
|
|
||||||
</p>
|
|
||||||
<p className="interview-questions-modal-answer">
|
|
||||||
<span>解答:</span>
|
|
||||||
负责室内平面设计项目的创意和实施2.
|
|
||||||
与团队合作,确保设计质量和项目进度 3.
|
|
||||||
持续关注设计趋势,提升设计技能 1. 具备出色的沟通能力和团队协作精神
|
|
||||||
2. 能够高效完成任务,对工作细节有高度关注 3.
|
|
||||||
具有不断学习和适应新挑战的能力 加分项:有以下行业经验:建筑设计
|
|
||||||
负责室内平面设计项目的创意和实施2.
|
|
||||||
与团队合作,确保设计质量和项目进度 3.
|
|
||||||
持续关注设计趋势,提升设计技能 1. 具备出色的沟通能力和团队协作精神
|
|
||||||
2. 能够高效完成任务,对工作细节有高度关注 3.
|
|
||||||
具有不断学习和适应新挑战的能力 加分项:有以下行业经验:建筑设计
|
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
421
src/pages/ResumeInterviewPage/components/ResumeModal/index.css
Normal file
421
src/pages/ResumeInterviewPage/components/ResumeModal/index.css
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
.resume-modal {
|
||||||
|
width: 900px;
|
||||||
|
max-width: 95vw;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resume-modal .close-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2'%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3C/svg%3E") no-repeat center;
|
||||||
|
background-size: contain;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resume-modal .close-icon:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resume-header {
|
||||||
|
padding: 30px;
|
||||||
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resume-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resume-subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-container {
|
||||||
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
padding: 16px 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 3px solid transparent;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #666;
|
||||||
|
transition: all 0.3s;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab:hover {
|
||||||
|
color: #1890ff;
|
||||||
|
background: rgba(24, 144, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active {
|
||||||
|
color: #1890ff;
|
||||||
|
border-bottom-color: #1890ff;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-header {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-header h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-header p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-toggle {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #1890ff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-toggle:hover {
|
||||||
|
background: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-toggle.editing {
|
||||||
|
background: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resume-content {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personal-info {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personal-info h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding-left: 12px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personal-info h3::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 3px;
|
||||||
|
height: 16px;
|
||||||
|
background: #1890ff;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
width: 60px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item span {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding-left: 12px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section h3::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 3px;
|
||||||
|
height: 16px;
|
||||||
|
background: #1890ff;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section p {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.education-item,
|
||||||
|
.experience-item,
|
||||||
|
.project-item {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.education-header,
|
||||||
|
.experience-header,
|
||||||
|
.project-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.education-header strong,
|
||||||
|
.experience-header strong,
|
||||||
|
.project-header strong {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date,
|
||||||
|
.role {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
background: #fff;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.education-details {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skills-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 12px;
|
||||||
|
background: #e6f7ff;
|
||||||
|
color: #1890ff;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-link {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
background: #1890ff;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-link:hover {
|
||||||
|
background: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.awards-list {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.awards-list li {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-content {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px;
|
||||||
|
color: #999;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-personal-resume {
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px 40px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-personal-resume p {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-btn {
|
||||||
|
padding: 12px 24px;
|
||||||
|
background: #1890ff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-btn:hover {
|
||||||
|
background: #40a9ff;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resume-edit {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-notice {
|
||||||
|
background: #fff7e6;
|
||||||
|
border: 1px solid #ffd591;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-notice p {
|
||||||
|
color: #d48806;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-editor {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: 'Consolas', 'Monaco', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
resize: none;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-editor:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #1890ff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 15px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-btn,
|
||||||
|
.cancel-btn {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-btn {
|
||||||
|
background: #52c41a;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-btn:hover {
|
||||||
|
background: #73d13d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background: #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.development-notice {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-content {
|
||||||
|
text-align: center;
|
||||||
|
color: #86909c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-content h4 {
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
259
src/pages/ResumeInterviewPage/components/ResumeModal/index.jsx
Normal file
259
src/pages/ResumeInterviewPage/components/ResumeModal/index.jsx
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import Modal from "@/components/Modal";
|
||||||
|
import "./index.css";
|
||||||
|
|
||||||
|
const ResumeModal = ({ visible, onClose, data }) => {
|
||||||
|
const [activeTab, setActiveTab] = useState("original");
|
||||||
|
const [editMode, setEditMode] = useState(false);
|
||||||
|
const [editContent, setEditContent] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data && data.selectedTemplate) {
|
||||||
|
setEditContent(data.selectedTemplate.content);
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const handleCloseModal = () => {
|
||||||
|
setActiveTab("original");
|
||||||
|
setEditMode(false);
|
||||||
|
setEditContent(null);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!data) return null;
|
||||||
|
|
||||||
|
const { selectedTemplate, studentResume } = data;
|
||||||
|
|
||||||
|
const handleSaveResume = async () => {
|
||||||
|
// TODO: 调用API保存简历
|
||||||
|
console.log("保存简历:", editContent);
|
||||||
|
setEditMode(false);
|
||||||
|
// 这里应该调用后端API保存简历
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditToggle = () => {
|
||||||
|
if (editMode) {
|
||||||
|
// 退出编辑模式,询问是否保存
|
||||||
|
if (JSON.stringify(editContent) !== JSON.stringify(selectedTemplate.content)) {
|
||||||
|
if (confirm("是否保存修改?")) {
|
||||||
|
handleSaveResume();
|
||||||
|
} else {
|
||||||
|
setEditContent(selectedTemplate.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setEditMode(!editMode);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderResumeContent = (content) => {
|
||||||
|
if (!content) return <div className="no-content">暂无简历内容</div>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="resume-content">
|
||||||
|
<div className="personal-info">
|
||||||
|
<h3>个人信息</h3>
|
||||||
|
<div className="info-grid">
|
||||||
|
<div className="info-item">
|
||||||
|
<label>姓名:</label>
|
||||||
|
<span>{content.personalInfo?.name || '未填写'}</span>
|
||||||
|
</div>
|
||||||
|
<div className="info-item">
|
||||||
|
<label>电话:</label>
|
||||||
|
<span>{content.personalInfo?.phone || '未填写'}</span>
|
||||||
|
</div>
|
||||||
|
<div className="info-item">
|
||||||
|
<label>邮箱:</label>
|
||||||
|
<span>{content.personalInfo?.email || '未填写'}</span>
|
||||||
|
</div>
|
||||||
|
<div className="info-item">
|
||||||
|
<label>学校:</label>
|
||||||
|
<span>{content.personalInfo?.school || '未填写'}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="section">
|
||||||
|
<h3>求职意向</h3>
|
||||||
|
<p>{content.objective || '未填写'}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="section">
|
||||||
|
<h3>教育背景</h3>
|
||||||
|
{content.education && content.education.length > 0 ? (
|
||||||
|
content.education.map((edu, index) => (
|
||||||
|
<div key={index} className="education-item">
|
||||||
|
<div className="education-header">
|
||||||
|
<strong>{edu.school}</strong>
|
||||||
|
<span className="date">{edu.startDate} - {edu.endDate}</span>
|
||||||
|
</div>
|
||||||
|
<div className="education-details">
|
||||||
|
<span>{edu.major} | {edu.degree}</span>
|
||||||
|
{edu.description && <p>{edu.description}</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p>暂无教育背景</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="section">
|
||||||
|
<h3>工作经历</h3>
|
||||||
|
{content.experience && content.experience.length > 0 ? (
|
||||||
|
content.experience.map((exp, index) => (
|
||||||
|
<div key={index} className="experience-item">
|
||||||
|
<div className="experience-header">
|
||||||
|
<strong>{exp.company} - {exp.position}</strong>
|
||||||
|
<span className="date">{exp.startDate} - {exp.endDate}</span>
|
||||||
|
</div>
|
||||||
|
<p>{exp.description}</p>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p>暂无工作经历</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="section">
|
||||||
|
<h3>专业技能</h3>
|
||||||
|
{content.skills && content.skills.length > 0 ? (
|
||||||
|
<div className="skills-list">
|
||||||
|
{content.skills.map((skill, index) => (
|
||||||
|
<span key={index} className="skill-tag">{skill}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p>暂无专业技能</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="section">
|
||||||
|
<h3>项目经历</h3>
|
||||||
|
{content.projects && content.projects.length > 0 ? (
|
||||||
|
content.projects.map((project, index) => (
|
||||||
|
<div key={index} className="project-item">
|
||||||
|
<div className="project-header">
|
||||||
|
<strong>{project.name}</strong>
|
||||||
|
<span className="role">担任:{project.role}</span>
|
||||||
|
</div>
|
||||||
|
<p>{project.description}</p>
|
||||||
|
{project.link && (
|
||||||
|
<a href={project.link} target="_blank" rel="noopener noreferrer" className="project-link">
|
||||||
|
项目链接
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p>暂无项目经历</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{content.awards && content.awards.length > 0 && (
|
||||||
|
<div className="section">
|
||||||
|
<h3>获奖情况</h3>
|
||||||
|
<ul className="awards-list">
|
||||||
|
{content.awards.map((award, index) => (
|
||||||
|
<li key={index}>{award}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderEditableContent = (content) => {
|
||||||
|
// 简化的编辑界面,实际项目中可能需要更复杂的表单
|
||||||
|
return (
|
||||||
|
<div className="resume-edit">
|
||||||
|
<div className="edit-notice">
|
||||||
|
<p>📝 编辑模式:您可以基于系统模板修改个人信息</p>
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
className="content-editor"
|
||||||
|
value={JSON.stringify(editContent, null, 2)}
|
||||||
|
onChange={(e) => {
|
||||||
|
try {
|
||||||
|
const newContent = JSON.parse(e.target.value);
|
||||||
|
setEditContent(newContent);
|
||||||
|
} catch (error) {
|
||||||
|
// 忽略JSON解析错误,保持当前内容
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder="请输入JSON格式的简历内容"
|
||||||
|
/>
|
||||||
|
<div className="edit-buttons">
|
||||||
|
<button className="save-btn" onClick={handleSaveResume}>
|
||||||
|
保存修改
|
||||||
|
</button>
|
||||||
|
<button className="cancel-btn" onClick={() => setEditMode(false)}>
|
||||||
|
取消编辑
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal visible={visible} onClose={handleCloseModal}>
|
||||||
|
<div className="resume-modal">
|
||||||
|
<i className="close-icon" onClick={handleCloseModal} />
|
||||||
|
|
||||||
|
<div className="resume-header">
|
||||||
|
<h2 className="resume-title">
|
||||||
|
{selectedTemplate?.name || '简历模板'}
|
||||||
|
</h2>
|
||||||
|
<p className="resume-subtitle">
|
||||||
|
{selectedTemplate?.description || '基于系统模板的简历展示'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="tabs-container">
|
||||||
|
<div className="tabs">
|
||||||
|
<div
|
||||||
|
className={`tab ${activeTab === 'original' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('original')}
|
||||||
|
>
|
||||||
|
原始版
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`tab ${activeTab === 'personal' ? 'active' : ''}`}
|
||||||
|
onClick={() => {
|
||||||
|
alert('个人修改版功能开发中,敬请期待!');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
个人修改版
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="tab-content">
|
||||||
|
{activeTab === 'original' && (
|
||||||
|
<div className="original-tab">
|
||||||
|
<div className="tab-header">
|
||||||
|
<h3>原始版</h3>
|
||||||
|
</div>
|
||||||
|
{renderResumeContent(selectedTemplate?.content)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'personal' && (
|
||||||
|
<div className="personal-tab">
|
||||||
|
<div className="tab-header">
|
||||||
|
<h3>个人修改版</h3>
|
||||||
|
</div>
|
||||||
|
<div className="development-notice">
|
||||||
|
<div className="notice-content">
|
||||||
|
<h4>开发中</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResumeModal;
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
height: 56px;
|
height: 56px;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -23,6 +23,11 @@
|
|||||||
background-color: #f2f3f5;
|
background-color: #f2f3f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navigation-tabs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.resume-interview-navigation-item {
|
.resume-interview-navigation-item {
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
width: 101px;
|
width: 101px;
|
||||||
@@ -36,6 +41,63 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
width: 200px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 35px 0 12px;
|
||||||
|
border: 1px solid #e5e8ed;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: #86909c;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: #2c7aff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(44, 122, 255, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: #86909c;
|
||||||
|
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.35-4.35'/%3E%3C/svg%3E") center/contain no-repeat;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #86909c;
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.resume-interview-content-wrapper {
|
.resume-interview-content-wrapper {
|
||||||
@@ -201,6 +263,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-results {
|
||||||
|
width: 100%;
|
||||||
|
height: 80px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #86909c;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.resumes-list {
|
.resumes-list {
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
import { useRef, useState, useEffect } from "react";
|
import { useRef, useState, useEffect } from "react";
|
||||||
import InterviewQuestionsModal from "./components/InterviewQuestionsModal";
|
import InterviewQuestionsModal from "./components/InterviewQuestionsModal";
|
||||||
|
import ResumeModal from "./components/ResumeModal";
|
||||||
import { getPageData } from "@/services/resumeInterview";
|
import { getPageData } from "@/services/resumeInterview";
|
||||||
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
const ResumeInterviewPage = () => {
|
const ResumeInterviewPage = () => {
|
||||||
const [activeIndustry, setActiveIndustry] = useState("frontend");
|
const [activeIndustry, setActiveIndustry] = useState("frontend");
|
||||||
|
const [interviewModalVisible, setInterviewModalVisible] = useState(false);
|
||||||
const [resumeModalVisible, setResumeModalVisible] = useState(false);
|
const [resumeModalVisible, setResumeModalVisible] = useState(false);
|
||||||
const [modalData, setModalData] = useState(undefined);
|
const [interviewModalData, setInterviewModalData] = useState(undefined);
|
||||||
|
const [resumeModalData, setResumeModalData] = useState(undefined);
|
||||||
const [pageData, setPageData] = useState(null);
|
const [pageData, setPageData] = useState(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const sectionsRef = useRef({});
|
const sectionsRef = useRef({});
|
||||||
|
|
||||||
// 导航到指定行业段落
|
// 导航到指定行业段落
|
||||||
@@ -21,15 +25,50 @@ const ResumeInterviewPage = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 钩子题目点击处理
|
// 面试题点击处理
|
||||||
const handleHookQuestionClick = (item) => {
|
const handleQuestionClick = (item) => {
|
||||||
setModalData(item);
|
setInterviewModalData(item);
|
||||||
|
setInterviewModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 职位点击处理
|
||||||
|
const handlePositionClick = (position, industry) => {
|
||||||
|
// Find resume templates for this industry
|
||||||
|
const templates = pageData.resumeTemplates[industry.name] || [];
|
||||||
|
const selectedTemplate = templates.find(t => t.level === position.level) || templates[0];
|
||||||
|
|
||||||
|
setResumeModalData({
|
||||||
|
selectedTemplate,
|
||||||
|
studentResume: pageData.myResume
|
||||||
|
});
|
||||||
setResumeModalVisible(true);
|
setResumeModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCloseModal = () => {
|
const handleCloseInterviewModal = () => {
|
||||||
|
setInterviewModalVisible(false);
|
||||||
|
setInterviewModalData(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseResumeModal = () => {
|
||||||
setResumeModalVisible(false);
|
setResumeModalVisible(false);
|
||||||
setModalData(undefined);
|
setResumeModalData(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Search functionality
|
||||||
|
const handleSearchChange = (e) => {
|
||||||
|
setSearchTerm(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClearSearch = () => {
|
||||||
|
setSearchTerm("");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filter positions based on search term
|
||||||
|
const filterPositions = (positions) => {
|
||||||
|
if (!searchTerm.trim()) return positions;
|
||||||
|
return positions.filter(position =>
|
||||||
|
position.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取页面数据
|
// 获取页面数据
|
||||||
@@ -98,17 +137,34 @@ const ResumeInterviewPage = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="resume-interview-page">
|
<div className="resume-interview-page">
|
||||||
<ul className="resume-interview-navigation">
|
<ul className="resume-interview-navigation">
|
||||||
{pageData.industries.map((industry) => (
|
<div className="navigation-tabs">
|
||||||
<li
|
{pageData.industries.map((industry) => (
|
||||||
key={industry.id}
|
<li
|
||||||
className={`resume-interview-navigation-item ${
|
key={industry.id}
|
||||||
activeIndustry === industry.id ? "active" : ""
|
className={`resume-interview-navigation-item ${
|
||||||
}`}
|
activeIndustry === industry.id ? "active" : ""
|
||||||
onClick={() => handleNavClick(industry.id)}
|
}`}
|
||||||
>
|
onClick={() => handleNavClick(industry.id)}
|
||||||
{industry.name}
|
>
|
||||||
</li>
|
{industry.name}
|
||||||
))}
|
</li>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="search-container">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="search-input"
|
||||||
|
placeholder="搜索职位名称..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
/>
|
||||||
|
{searchTerm && (
|
||||||
|
<button className="clear-button" onClick={handleClearSearch}>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{!searchTerm && <div className="search-icon" />}
|
||||||
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="resume-interview-content-wrapper">
|
<ul className="resume-interview-content-wrapper">
|
||||||
{pageData.industries.map((item) => (
|
{pageData.industries.map((item) => (
|
||||||
@@ -121,8 +177,12 @@ const ResumeInterviewPage = () => {
|
|||||||
<p className="item-subtitle">简历与面试题</p>
|
<p className="item-subtitle">简历与面试题</p>
|
||||||
<div className="item-content-wrapper">
|
<div className="item-content-wrapper">
|
||||||
<ul className="jobs-list">
|
<ul className="jobs-list">
|
||||||
{item.positions.map((position) => (
|
{filterPositions(item.positions).map((position) => (
|
||||||
<li className="job-item job-item-change" key={position.id}>
|
<li
|
||||||
|
className="job-item job-item-change"
|
||||||
|
key={position.id}
|
||||||
|
onClick={() => handlePositionClick(position, item)}
|
||||||
|
>
|
||||||
<span>{position.level}</span>
|
<span>{position.level}</span>
|
||||||
<div className="job-name">
|
<div className="job-name">
|
||||||
<p>{position.name}</p>
|
<p>{position.name}</p>
|
||||||
@@ -130,13 +190,18 @@ const ResumeInterviewPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
{searchTerm && filterPositions(item.positions).length === 0 && (
|
||||||
|
<li className="no-results">
|
||||||
|
<p>未找到匹配的职位</p>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="resumes-list">
|
<ul className="resumes-list">
|
||||||
{item.questions.map((question) => (
|
{item.questions.map((question) => (
|
||||||
<li
|
<li
|
||||||
key={question.id}
|
key={question.id}
|
||||||
className="resume-item"
|
className="resume-item"
|
||||||
onClick={() => handleHookQuestionClick({ ...item, question })}
|
onClick={() => handleQuestionClick({ ...item, question })}
|
||||||
>
|
>
|
||||||
<p>{question.question}</p>
|
<p>{question.question}</p>
|
||||||
</li>
|
</li>
|
||||||
@@ -147,9 +212,14 @@ const ResumeInterviewPage = () => {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
<InterviewQuestionsModal
|
<InterviewQuestionsModal
|
||||||
|
visible={interviewModalVisible}
|
||||||
|
onClose={handleCloseInterviewModal}
|
||||||
|
data={interviewModalData}
|
||||||
|
/>
|
||||||
|
<ResumeModal
|
||||||
visible={resumeModalVisible}
|
visible={resumeModalVisible}
|
||||||
onClose={handleCloseModal}
|
onClose={handleCloseResumeModal}
|
||||||
data={modalData}
|
data={resumeModalData}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -199,9 +199,14 @@ export const mapInterview = (backendData) => {
|
|||||||
feedback: backendData.feedback,
|
feedback: backendData.feedback,
|
||||||
result: backendData.result,
|
result: backendData.result,
|
||||||
student: backendData.student ? mapStudent(backendData.student) : null,
|
student: backendData.student ? mapStudent(backendData.student) : null,
|
||||||
job: backendData.job ? mapJob(backendData.job) : null,
|
job: backendData.job ? mapJob(backendData.job) : {
|
||||||
company: backendData.job?.company?.name || "",
|
// Provide default job object when no job relation exists
|
||||||
position: backendData.job?.title || "",
|
salary: "面议",
|
||||||
|
tags: [],
|
||||||
|
company: { name: backendData.company || "" }
|
||||||
|
},
|
||||||
|
company: backendData.company || backendData.job?.company?.name || "",
|
||||||
|
position: backendData.position || backendData.job?.title || "",
|
||||||
// Map status for frontend
|
// Map status for frontend
|
||||||
statusText: mapInterviewStatus(backendData.status, backendData.result),
|
statusText: mapInterviewStatus(backendData.status, backendData.result),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user