diff --git a/src/pages/CompanyJobsPage/index.jsx b/src/pages/CompanyJobsPage/index.jsx
index 6745420..9b04e73 100644
--- a/src/pages/CompanyJobsPage/index.jsx
+++ b/src/pages/CompanyJobsPage/index.jsx
@@ -15,17 +15,19 @@ const CompanyJobsPage = () => {
const [isExpand, setIsExpand] = useState(false); // 是否展开
const [jobs, setJobs] = useState([]);
const [jobsListPage, setJobsListPage] = useState(1);
- const [joblistHasMore, setJobsListHasMore] = useState(true);
+ 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 navigate = useNavigate();
// 初始化页面数据 - 使用聚合接口
useEffect(() => {
const fetchInitialData = async () => {
try {
+ setLoading(true);
const res = await getCompanyJobsPageData({
studentId: studentInfo?.id,
});
@@ -35,7 +37,7 @@ const CompanyJobsPage = () => {
if (res.data?.jobs) {
const mappedJobs = mapJobList(res.data.jobs.list || []);
setJobs(mappedJobs);
- setJoblistHasMore(res.data.jobs.hasMore);
+ setJobsListHasMore(res.data.jobs.hasMore);
if (mappedJobs.length > 0) {
setJobsListPage(2); // 下次从第2页开始
}
@@ -57,6 +59,12 @@ const CompanyJobsPage = () => {
console.error('Failed to fetch initial page data:', error);
// 如果聚合接口失败,回退到原来的方式
setInitialDataLoaded(true);
+ // 显示错误信息给用户
+ if (toast && toast.error) {
+ toast.error('加载数据失败,请刷新重试');
+ }
+ } finally {
+ setLoading(false);
}
};
@@ -80,8 +88,12 @@ const CompanyJobsPage = () => {
if (res.success) {
const mappedInterviews = mapInterviewList(res.data || []);
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);
} else {
setInterviewsPage((prevPage) => prevPage + 1);
@@ -104,6 +116,11 @@ const CompanyJobsPage = () => {
return;
}
+ // 防止重复请求
+ if (jobsListPage === 1 && jobs.length === 0) {
+ return; // 初始数据应该通过聚合接口加载
+ }
+
try {
const res = await getJobsList({
page: jobsListPage,
@@ -114,8 +131,12 @@ const CompanyJobsPage = () => {
if (res?.success) {
const mappedJobs = mapJobList(res.data);
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);
} else {
setJobsListPage((prevPage) => prevPage + 1);
@@ -135,6 +156,19 @@ const CompanyJobsPage = () => {
navigate("/company-jobs-list");
};
+ if (loading && jobs.length === 0 && interviews.length === 0) {
+ return (
+
+ );
+ }
+
return (
@@ -142,7 +176,7 @@ const CompanyJobsPage = () => {
企业内推岗位库
diff --git a/src/pages/Dashboard/components/EchartsProgress/index.jsx b/src/pages/Dashboard/components/EchartsProgress/index.jsx
index dc7773c..2b7c8fe 100644
--- a/src/pages/Dashboard/components/EchartsProgress/index.jsx
+++ b/src/pages/Dashboard/components/EchartsProgress/index.jsx
@@ -62,7 +62,7 @@ const EchartsProgress = ({
},
data: [
{
- value: 75,
+ value: percent,
detail: {
valueAnimation: true,
offsetCenter: ["0%", "0%"],
diff --git a/src/pages/ResumeInterviewPage/components/InterviewQuestionsModal/index.css b/src/pages/ResumeInterviewPage/components/InterviewQuestionsModal/index.css
index 5daf0d8..becfb64 100644
--- a/src/pages/ResumeInterviewPage/components/InterviewQuestionsModal/index.css
+++ b/src/pages/ResumeInterviewPage/components/InterviewQuestionsModal/index.css
@@ -94,4 +94,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/pages/ResumeInterviewPage/components/InterviewQuestionsModal/index.jsx b/src/pages/ResumeInterviewPage/components/InterviewQuestionsModal/index.jsx
index 900cd40..b8e4069 100644
--- a/src/pages/ResumeInterviewPage/components/InterviewQuestionsModal/index.jsx
+++ b/src/pages/ResumeInterviewPage/components/InterviewQuestionsModal/index.jsx
@@ -7,102 +7,35 @@ export default ({ visible, onClose, data }) => {
onClose();
};
+ if (!data) return null;
+
+ const { question } = data;
+
return (
-
PLC岗位群面试题
+
{data.name}面试题
在1V1定制求职策略阶段,企业HR会为您讲解面试题
-
- 问题1:xxxxxxxxxxxxxxxxxx?
+ 问题:{question.question}
解答:
- 负责室内平面设计项目的创意和实施2.
- 与团队合作,确保设计质量和项目进度 3.
- 持续关注设计趋势,提升设计技能 1. 具备出色的沟通能力和团队协作精神
- 2. 能够高效完成任务,对工作细节有高度关注 3.
- 具有不断学习和适应新挑战的能力 加分项:有以下行业经验:建筑设计
-
-
- -
-
- 问题1:xxxxxxxxxxxxxxxxxx?
-
-
- 解答:
- 负责室内平面设计项目的创意和实施2.
- 与团队合作,确保设计质量和项目进度 3.
- 持续关注设计趋势,提升设计技能 1. 具备出色的沟通能力和团队协作精神
- 2. 能够高效完成任务,对工作细节有高度关注 3.
- 具有不断学习和适应新挑战的能力 加分项:有以下行业经验:建筑设计
- 负责室内平面设计项目的创意和实施2.
- 与团队合作,确保设计质量和项目进度 3.
- 持续关注设计趋势,提升设计技能 1. 具备出色的沟通能力和团队协作精神
- 2. 能够高效完成任务,对工作细节有高度关注 3.
- 具有不断学习和适应新挑战的能力 加分项:有以下行业经验:建筑设计
-
-
- -
-
- 问题1:xxxxxxxxxxxxxxxxxx?
-
-
- 解答:
- 负责室内平面设计项目的创意和实施2.
- 与团队合作,确保设计质量和项目进度 3.
- 持续关注设计趋势,提升设计技能 1. 具备出色的沟通能力和团队协作精神
- 2. 能够高效完成任务,对工作细节有高度关注 3.
- 具有不断学习和适应新挑战的能力 加分项:有以下行业经验:建筑设计
- 负责室内平面设计项目的创意和实施2.
- 与团队合作,确保设计质量和项目进度 3.
- 持续关注设计趋势,提升设计技能 1. 具备出色的沟通能力和团队协作精神
- 2. 能够高效完成任务,对工作细节有高度关注 3.
- 具有不断学习和适应新挑战的能力 加分项:有以下行业经验:建筑设计
-
-
- -
-
- 问题1:xxxxxxxxxxxxxxxxxx?
-
-
- 解答:
- 负责室内平面设计项目的创意和实施2.
- 与团队合作,确保设计质量和项目进度 3.
- 持续关注设计趋势,提升设计技能 1. 具备出色的沟通能力和团队协作精神
- 2. 能够高效完成任务,对工作细节有高度关注 3.
- 具有不断学习和适应新挑战的能力 加分项:有以下行业经验:建筑设计
- 负责室内平面设计项目的创意和实施2.
- 与团队合作,确保设计质量和项目进度 3.
- 持续关注设计趋势,提升设计技能 1. 具备出色的沟通能力和团队协作精神
- 2. 能够高效完成任务,对工作细节有高度关注 3.
- 具有不断学习和适应新挑战的能力 加分项:有以下行业经验:建筑设计
-
-
- -
-
- 问题1:xxxxxxxxxxxxxxxxxx?
-
-
- 解答:
- 负责室内平面设计项目的创意和实施2.
- 与团队合作,确保设计质量和项目进度 3.
- 持续关注设计趋势,提升设计技能 1. 具备出色的沟通能力和团队协作精神
- 2. 能够高效完成任务,对工作细节有高度关注 3.
- 具有不断学习和适应新挑战的能力 加分项:有以下行业经验:建筑设计
- 负责室内平面设计项目的创意和实施2.
- 与团队合作,确保设计质量和项目进度 3.
- 持续关注设计趋势,提升设计技能 1. 具备出色的沟通能力和团队协作精神
- 2. 能够高效完成任务,对工作细节有高度关注 3.
- 具有不断学习和适应新挑战的能力 加分项:有以下行业经验:建筑设计
+ 这是一个{question.difficulty.toLowerCase()}难度的{question.category}相关问题。
+ 针对这类问题,建议从以下几个方面来回答:
+ 1. 理解问题的核心概念和背景
+ 2. 结合实际项目经验进行说明
+ 3. 展示对相关技术的深入理解
+ 4. 提及可能的优化或改进方案
);
-};
+};
\ No newline at end of file
diff --git a/src/pages/ResumeInterviewPage/components/ResumeModal/index.css b/src/pages/ResumeInterviewPage/components/ResumeModal/index.css
new file mode 100644
index 0000000..4fdbd0d
--- /dev/null
+++ b/src/pages/ResumeInterviewPage/components/ResumeModal/index.css
@@ -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;
+}
diff --git a/src/pages/ResumeInterviewPage/components/ResumeModal/index.jsx b/src/pages/ResumeInterviewPage/components/ResumeModal/index.jsx
new file mode 100644
index 0000000..bbbcfe3
--- /dev/null
+++ b/src/pages/ResumeInterviewPage/components/ResumeModal/index.jsx
@@ -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 暂无简历内容
;
+
+ return (
+
+
+
个人信息
+
+
+
+ {content.personalInfo?.name || '未填写'}
+
+
+
+ {content.personalInfo?.phone || '未填写'}
+
+
+
+ {content.personalInfo?.email || '未填写'}
+
+
+
+ {content.personalInfo?.school || '未填写'}
+
+
+
+
+
+
求职意向
+
{content.objective || '未填写'}
+
+
+
+
教育背景
+ {content.education && content.education.length > 0 ? (
+ content.education.map((edu, index) => (
+
+
+ {edu.school}
+ {edu.startDate} - {edu.endDate}
+
+
+
{edu.major} | {edu.degree}
+ {edu.description &&
{edu.description}
}
+
+
+ ))
+ ) : (
+
暂无教育背景
+ )}
+
+
+
+
工作经历
+ {content.experience && content.experience.length > 0 ? (
+ content.experience.map((exp, index) => (
+
+
+ {exp.company} - {exp.position}
+ {exp.startDate} - {exp.endDate}
+
+
{exp.description}
+
+ ))
+ ) : (
+
暂无工作经历
+ )}
+
+
+
+
专业技能
+ {content.skills && content.skills.length > 0 ? (
+
+ {content.skills.map((skill, index) => (
+ {skill}
+ ))}
+
+ ) : (
+
暂无专业技能
+ )}
+
+
+
+
项目经历
+ {content.projects && content.projects.length > 0 ? (
+ content.projects.map((project, index) => (
+
+
+ {project.name}
+ 担任:{project.role}
+
+
{project.description}
+ {project.link && (
+
+ 项目链接
+
+ )}
+
+ ))
+ ) : (
+
暂无项目经历
+ )}
+
+
+ {content.awards && content.awards.length > 0 && (
+
+
获奖情况
+
+ {content.awards.map((award, index) => (
+ - {award}
+ ))}
+
+
+ )}
+
+ );
+ };
+
+ const renderEditableContent = (content) => {
+ // 简化的编辑界面,实际项目中可能需要更复杂的表单
+ return (
+
+
+
📝 编辑模式:您可以基于系统模板修改个人信息
+
+
+ );
+ };
+
+ return (
+
+
+
+
+
+
+ {selectedTemplate?.name || '简历模板'}
+
+
+ {selectedTemplate?.description || '基于系统模板的简历展示'}
+
+
+
+
+
+
setActiveTab('original')}
+ >
+ 原始版
+
+
{
+ alert('个人修改版功能开发中,敬请期待!');
+ }}
+ >
+ 个人修改版
+
+
+
+
+
+ {activeTab === 'original' && (
+
+
+
原始版
+
+ {renderResumeContent(selectedTemplate?.content)}
+
+ )}
+
+ {activeTab === 'personal' && (
+
+ )}
+
+
+
+ );
+};
+
+export default ResumeModal;
diff --git a/src/pages/ResumeInterviewPage/index.css b/src/pages/ResumeInterviewPage/index.css
index 601aaa8..43b51c1 100644
--- a/src/pages/ResumeInterviewPage/index.css
+++ b/src/pages/ResumeInterviewPage/index.css
@@ -11,7 +11,7 @@
height: 56px;
background-color: #ffffff;
display: flex;
- justify-content: flex-start;
+ justify-content: space-between;
align-items: center;
overflow-x: auto;
box-sizing: border-box;
@@ -23,6 +23,11 @@
background-color: #f2f3f5;
}
+ .navigation-tabs {
+ display: flex;
+ align-items: center;
+ }
+
.resume-interview-navigation-item {
margin-right: 20px;
width: 101px;
@@ -36,6 +41,63 @@
font-weight: 600;
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 {
@@ -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 {
@@ -240,4 +312,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/pages/ResumeInterviewPage/index.jsx b/src/pages/ResumeInterviewPage/index.jsx
index a62906b..55e6449 100644
--- a/src/pages/ResumeInterviewPage/index.jsx
+++ b/src/pages/ResumeInterviewPage/index.jsx
@@ -1,15 +1,19 @@
import { useRef, useState, useEffect } from "react";
import InterviewQuestionsModal from "./components/InterviewQuestionsModal";
+import ResumeModal from "./components/ResumeModal";
import { getPageData } from "@/services/resumeInterview";
import "./index.css";
const ResumeInterviewPage = () => {
const [activeIndustry, setActiveIndustry] = useState("frontend");
+ const [interviewModalVisible, setInterviewModalVisible] = 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 [loading, setLoading] = useState(true);
+ const [searchTerm, setSearchTerm] = useState("");
const sectionsRef = useRef({});
// 导航到指定行业段落
@@ -21,15 +25,50 @@ const ResumeInterviewPage = () => {
});
};
- // 钩子题目点击处理
- const handleHookQuestionClick = (item) => {
- setModalData(item);
+ // 面试题点击处理
+ const handleQuestionClick = (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);
};
- const handleCloseModal = () => {
+ const handleCloseInterviewModal = () => {
+ setInterviewModalVisible(false);
+ setInterviewModalData(undefined);
+ };
+
+ const handleCloseResumeModal = () => {
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 (
- {pageData.industries.map((industry) => (
- - handleNavClick(industry.id)}
- >
- {industry.name}
-
- ))}
+
+ {pageData.industries.map((industry) => (
+
- handleNavClick(industry.id)}
+ >
+ {industry.name}
+
+ ))}
+
+
+
+ {searchTerm && (
+
+ )}
+ {!searchTerm &&
}
+
{pageData.industries.map((item) => (
@@ -121,8 +177,12 @@ const ResumeInterviewPage = () => {
简历与面试题
- {item.positions.map((position) => (
- -
+ {filterPositions(item.positions).map((position) => (
+
- handlePositionClick(position, item)}
+ >
{position.level}
{position.name}
@@ -130,13 +190,18 @@ const ResumeInterviewPage = () => {
))}
+ {searchTerm && filterPositions(item.positions).length === 0 && (
+ -
+
未找到匹配的职位
+
+ )}
{item.questions.map((question) => (
- handleHookQuestionClick({ ...item, question })}
+ onClick={() => handleQuestionClick({ ...item, question })}
>
{question.question}
@@ -147,12 +212,17 @@ const ResumeInterviewPage = () => {
))}
+
);
};
-export default ResumeInterviewPage;
+export default ResumeInterviewPage;
\ No newline at end of file
diff --git a/src/utils/dataMapper.js b/src/utils/dataMapper.js
index 2d2881b..b39b011 100644
--- a/src/utils/dataMapper.js
+++ b/src/utils/dataMapper.js
@@ -199,9 +199,14 @@ export const mapInterview = (backendData) => {
feedback: backendData.feedback,
result: backendData.result,
student: backendData.student ? mapStudent(backendData.student) : null,
- job: backendData.job ? mapJob(backendData.job) : null,
- company: backendData.job?.company?.name || "",
- position: backendData.job?.title || "",
+ job: backendData.job ? mapJob(backendData.job) : {
+ // Provide default job object when no job relation exists
+ salary: "面议",
+ tags: [],
+ company: { name: backendData.company || "" }
+ },
+ company: backendData.company || backendData.job?.company?.name || "",
+ position: backendData.position || backendData.job?.title || "",
// Map status for frontend
statusText: mapInterviewStatus(backendData.status, backendData.result),
};