From ba4098e1d8f18777ecd021f9d033ffa58d79aec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E7=8B=B82023?= Date: Mon, 25 Aug 2025 23:50:22 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/CompanyJobsPage/index.jsx | 48 +- .../components/EchartsProgress/index.jsx | 2 +- .../InterviewQuestionsModal/index.css | 2 +- .../InterviewQuestionsModal/index.jsx | 93 +--- .../components/ResumeModal/index.css | 421 ++++++++++++++++++ .../components/ResumeModal/index.jsx | 259 +++++++++++ src/pages/ResumeInterviewPage/index.css | 76 +++- src/pages/ResumeInterviewPage/index.jsx | 116 ++++- src/utils/dataMapper.js | 11 +- 9 files changed, 911 insertions(+), 117 deletions(-) create mode 100644 src/pages/ResumeInterviewPage/components/ResumeModal/index.css create mode 100644 src/pages/ResumeInterviewPage/components/ResumeModal/index.jsx 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 ( +
+
+

📝 编辑模式:您可以基于系统模板修改个人信息

+
+