diff --git a/src/mocks/resumeInterviewMock.js b/src/mocks/resumeInterviewMock.js index 0647e84..ee538a6 100644 --- a/src/mocks/resumeInterviewMock.js +++ b/src/mocks/resumeInterviewMock.js @@ -3389,35 +3389,35 @@ const resumeTemplates = { ### (五)岗位职责: -1. 协助提供现场咨询服务,包括研学项目、展览、节庆活动信息等,~~处理~~ **在指导下配合处理票务咨询、常见问询及游客投诉,确保游客能及时得到解答并维持顺畅的服务体验**; -2. 收集研学人数、游客满意度、活动参与率等数据,~~通过~~ **并协助整理成热力图或统计报表,为团队提供数据依据,支持高峰区域分析和排程优化**; -3. 支持花展、小自然音乐节、夜游等节庆活动的运营,包括~~流程组织、灯光布置、分区限流与应急预案配合~~ **协助流程组织、灯光布置检查、分区限流执行与应急预案落实,帮助活动顺利推进**; -4. 协助导视与游线设置,~~配合空间动线优化,提升~~ **参与空间动线优化,帮助提升**游客行进节奏与整体文化体验感; -5. 支持文创市集运营,包括~~产品场景布置、IP传播素材拍摄与社区联动推广~~ **协助产品场景布置、配合IP传播素材拍摄及社区联动推广,让市集氛围更完整**; -6. 配合设施维护、安全巡查与环境卫生管理,~~确保~~ **协助检查并确认导视、照明、打卡设施及垃圾分类落实到位,提升现场秩序和安全性**; -7. 协助处理高峰期游客拥堵、设备故障等突发情况,~~配合实施~~ **在预案指引下配合实施疏散与应急引导,并及时反馈情况,减少突发事件影响**; -8. 协助整理活动文档、流程手册与导览模板,~~为未来复用提供标准资料支撑~~ **帮助团队形成可复用的标准资料,为后续活动执行和规范化提供支持**。 +1. 协助提供现场咨询服务,包括研学项目、展览、节庆活动信息等, 在指导下配合处理票务咨询、常见问询及游客投诉,确保游客能及时得到解答并维持顺畅的服务体验; +2. 收集研学人数、游客满意度、活动参与率等数据, 并协助整理成热力图或统计报表,为团队提供数据依据,支持高峰区域分析和排程优化; +3. 支持花展、小自然音乐节、夜游等节庆活动的运营,包括 协助流程组织、灯光布置检查、分区限流执行与应急预案落实,帮助活动顺利推进; +4. 协助导视与游线设置, 参与空间动线优化,帮助提升游客行进节奏与整体文化体验感; +5. 支持文创市集运营,包括 协助产品场景布置、配合IP传播素材拍摄及社区联动推广,让市集氛围更完整; +6. 配合设施维护、安全巡查与环境卫生管理, 协助检查并确认导视、照明、打卡设施及垃圾分类落实到位,提升现场秩序和安全性; +7. 协助处理高峰期游客拥堵、设备故障等突发情况, 在预案指引下配合实施疏散与应急引导,并及时反馈情况,减少突发事件影响; +8. 协助整理活动文档、流程手册与导览模板, 帮助团队形成可复用的标准资料,为后续活动执行和规范化提供支持。 # 二、专业技能 ### (一)核心能力 -1. 熟悉景区现场运营流程,能~~协助导览服务、动线管理与活动节奏控制~~ **在团队指导下协助导览服务,参与动线管理与活动节奏控制,帮助维持整体秩序**; -2. ~~擅长处理游客咨询与投诉,参与提升服务质量与满意度~~ - - **能配合处理游客咨询与一般性投诉,协助提升服务质量与游客满意度**; - -3. 具备基础的数据收集与报告能力,能~~辅助运营数据决策支持~~ **协助统计游客人数、满意度等基础数据,并整理成简要报表,为运营决策提供支持**; -4. 能参与节庆活动~~点位布置、流程排练及时序控制~~ **点位布置、流程排练及现场时序的执行配合**; -5. 拥有现场应急协助经验,能~~配合处理突发状况~~ **在预案指导下配合处理游客拥堵、设备故障等突发状况**; -6. ~~了解文创传播节奏与素材管理,可支持IP推广与现场体验~~ - - **了解文创传播节奏与素材管理,能在现场协助IP推广与体验环节的执行**; - -7. 具备设施巡检与环境维护~~指导经验,确保服务环境优化~~ - - **协助进行设施巡检与环境维护,帮助确保导视、照明和环境卫生到位,优化服务环境**。 - +1. 熟悉景区现场运营流程,能 在团队指导下协助导览服务,参与动线管理与活动节奏控制,帮助维持整体秩序; +2. + + 能配合处理游客咨询与一般性投诉,协助提升服务质量与游客满意度; + +3. 具备基础的数据收集与报告能力,能 协助统计游客人数、满意度等基础数据,并整理成简要报表,为运营决策提供支持; +4. 能参与节庆活动 点位布置、流程排练及现场时序的执行配合; +5. 拥有现场应急协助经验,能 在预案指导下配合处理游客拥堵、设备故障等突发状况; +6. + + 了解文创传播节奏与素材管理,能在现场协助IP推广与体验环节的执行; + +7. 具备设施巡检与环境维护 + + 协助进行设施巡检与环境维护,帮助确保导视、照明和环境卫生到位,优化服务环境。 + ### (二)复合能力 @@ -3433,7 +3433,7 @@ const resumeTemplates = { # 三、个人总结 -我是一名刚完成实习的大专毕业生,所学"智慧旅游技术应用"专业背景~~支撑我对文旅行业的理解~~ **让我逐步建立起对文旅行业运行模式和前沿趋势的理解**。我在"武汉植物园生态科普与文旅运营项目"中,~~深度参与现场导览支持、活动执行、安全维护及数字互动部署~~ **主要参与现场导览支持、活动执行、安全维护及数字互动等环节的协助工作,积累了较为系统的一线运营经验**。同时,我关注环保与文旅融合策略,~~具备协同共赢与可持续运营意识~~ **并在项目中体会到协同共赢与可持续运营的重要性**。未来期望在景区运营与文旅生态融合方向继续成长,成为~~一名具备执行力、数据敏感度和服务意识的专业运营人才~~ **一名具备扎实执行力、数据敏感度和服务意识的专业运营型人才**。` +我是一名刚完成实习的大专毕业生,所学"智慧旅游技术应用"专业背景 让我逐步建立起对文旅行业运行模式和前沿趋势的理解。我在"武汉植物园生态科普与文旅运营项目"中, 主要参与现场导览支持、活动执行、安全维护及数字互动等环节的协助工作,积累了较为系统的一线运营经验。同时,我关注环保与文旅融合策略, 并在项目中体会到协同共赢与可持续运营的重要性。未来期望在景区运营与文旅生态融合方向继续成长,成为 一名具备扎实执行力、数据敏感度和服务意识的专业运营型人才。` }, studentInfo: { project_experience: { diff --git a/src/pages/CompanyJobsPage/components/ResumeInfoModal/index.jsx b/src/pages/CompanyJobsPage/components/ResumeInfoModal/index.jsx index aa7f637..e17f0e0 100644 --- a/src/pages/CompanyJobsPage/components/ResumeInfoModal/index.jsx +++ b/src/pages/CompanyJobsPage/components/ResumeInfoModal/index.jsx @@ -1,25 +1,166 @@ import { useState, useEffect } from "react"; -import { Radio } from "@arco-design/web-react"; +import { Radio, Button, Message } from "@arco-design/web-react"; +import { IconEdit, IconSave, IconClose } from "@arco-design/web-react/icon"; import Modal from "@/components/Modal"; +import * as resumeManager from '@/services/resumeManager'; import "./index.css"; export default ({ visible, onClose, data, initialVersion = "2" }) => { const [version, setVersion] = useState(initialVersion); // 使用传入的初始版本 + const [isEditing, setIsEditing] = useState(false); + const [editableData, setEditableData] = useState(null); + const [customVersions, setCustomVersions] = useState([]); // 响应initialVersion变化 useEffect(() => { setVersion(initialVersion); }, [initialVersion]); + // 加载个人修改版本 + useEffect(() => { + if (visible && data?.title) { + const versions = resumeManager.getVersionsByPosition(data.title); + setCustomVersions(versions); + } + }, [visible, data?.title]); + const onRadioChange = (value, e) => { e?.stopPropagation(); setVersion(value); + setIsEditing(false); // 切换版本时退出编辑模式 }; const handleCloseModal = () => { + setIsEditing(false); + setEditableData(null); onClose(); }; + // 开始编辑 + const handleEditClick = () => { + // 获取当前显示的内容并解析 + let currentContent = ''; + if (version.startsWith('custom_')) { + const customVersion = resumeManager.getVersionById(version.replace('custom_', '')); + currentContent = customVersion?.content || ''; + } else if (data?.content) { + const hasModified = !!data.content.modified; + currentContent = (!hasModified || version === "1") ? data.content.original : data.content.modified; + } + + const parsed = parseResumeMarkdown(currentContent); + setEditableData(parsed); + setIsEditing(true); + }; + + // 保存编辑 + const handleSaveEdit = async () => { + if (!editableData) return; + + // 将编辑后的数据转换回markdown格式 + const contentToSave = convertToMarkdown(editableData); + + if (!contentToSave.trim()) { + Message.error('简历内容不能为空'); + return; + } + + // 创建一个新的个人修改版 + const versionName = `个人版_${new Date().toLocaleString('zh-CN', { + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + }).replace(/\//g, '-').replace(/:/g, '')}`; + + const result = await resumeManager.createCustomVersion({ + name: versionName, + content: contentToSave, + positionTitle: data?.title || '未命名岗位', + originalVersion: version + }); + + if (result.success) { + Message.success('已保存为个人修改版'); + setIsEditing(false); + + // 刷新个人修改版列表 + const versions = resumeManager.getVersionsByPosition(data?.title); + setCustomVersions(versions); + + // 切换到新保存的版本 + setVersion('custom_' + result.data.id); + } else { + Message.error(result.error || '保存失败'); + } + }; + + // 取消编辑 + const handleCancelEdit = () => { + setIsEditing(false); + setEditableData(null); + }; + + // 删除个人版本 + const handleDeleteVersion = (versionId) => { + const result = resumeManager.deleteCustomVersion(versionId); + if (result.success) { + Message.success('已删除'); + const versions = resumeManager.getVersionsByPosition(data?.title); + setCustomVersions(versions); + // 如果删除的是当前版本,切换到原始版 + if (version === `custom_${versionId}`) { + setVersion("1"); + } + } + }; + + // 将结构化数据转换回markdown格式 + const convertToMarkdown = (data) => { + if (!data) return ''; + + let markdown = `# 对应岗位:${data.personalInfo?.name || data?.title || '职位名称'}\n\n`; + + // 项目经历 + if (data.projects && data.projects.length > 0) { + markdown += '# 一、项目经历\n'; + data.projects.forEach(proj => { + markdown += `### (一)项目名称:${proj.name}\n`; + markdown += `### (二)实习岗位:${proj.role || '参与者'}\n`; + if (proj.period) markdown += `### (三)实习时间:${proj.period}\n`; + if (proj.company) markdown += `### (四)实习单位:${proj.company}\n`; + if (proj.responsibilities && proj.responsibilities.length > 0) { + markdown += '### (五)岗位职责:\n'; + proj.responsibilities.forEach((resp, idx) => { + markdown += `${idx + 1}. ${resp}\n`; + }); + } else if (proj.description) { + markdown += `### (五)岗位职责:\n${proj.description}\n`; + } + markdown += '\n'; + }); + } + + // 专业技能 + if (data.skills) { + markdown += '# 二、专业技能\n'; + if (data.skills.core && data.skills.core.length > 0) { + markdown += '## (一)核心技能\n'; + data.skills.core.forEach((skill, idx) => { + markdown += `${idx + 1}. ${skill}\n`; + }); + } + if (data.skills.additional && data.skills.additional.length > 0) { + markdown += '## (二)复合技能\n'; + data.skills.additional.forEach((skill, idx) => { + markdown += `${idx + 1}. ${skill}\n`; + }); + } + } + + return markdown; + }; + // Markdown解析器 - 解析简历内容 const parseResumeMarkdown = (markdownContent) => { @@ -102,7 +243,13 @@ export default ({ visible, onClose, data, initialVersion = "2" }) => { // 获取简历数据 - 支持新的数据结构 let resumeContent = {}; - if (data?.content) { + // 处理自定义版本 + if (version.startsWith('custom_')) { + const customVersion = resumeManager.getVersionById(version.replace('custom_', '')); + if (customVersion?.content) { + resumeContent = parseResumeMarkdown(customVersion.content) || {}; + } + } else if (data?.content) { // 新的数据结构 - 来自resume-interview页面 if (data.content.original) { // 有original字段,可能有或没有modified字段 @@ -226,8 +373,8 @@ export default ({ visible, onClose, data, initialVersion = "2" }) => {
e.stopPropagation()}> - {data?.content?.original && ( -
+ {(data?.content?.original || customVersions.length > 0) && ( +
{ {data?.content?.modified && ( 个人修改版 )} + {customVersions.map((v) => ( + + {v.name} + {isEditing === false && ( + { + e.stopPropagation(); + handleDeleteVersion(v.id); + }} + style={{ marginLeft: '5px', color: '#ff4d4f', cursor: 'pointer' }} + > + × + + )} + + ))}
)} -

- {data?.title || resumeContent.personalInfo?.name || "职位名称"} -

+
+

+ {data?.title || resumeContent.personalInfo?.name || "职位名称"} +

+
+ {!isEditing ? ( + + ) : ( + <> + + + + )} +
+
{/* 统一使用结构化样式展示所有岗位 */}
    @@ -253,12 +459,40 @@ export default ({ visible, onClose, data, initialVersion = "2" }) => {
  • 教育经历

      - {resumeContent.education?.map((edu, index) => ( + {(isEditing && editableData ? editableData.education : resumeContent.education)?.map((edu, index) => (
    • -

      +

      { + if (isEditing && editableData) { + const text = e.target.innerText; + const parts = text.split(' - '); + const newEdu = { ...editableData.education[index] }; + newEdu.school = parts[0] || edu.school; + newEdu.major = parts[1] || edu.major; + const newEducation = [...editableData.education]; + newEducation[index] = newEdu; + setEditableData({ ...editableData, education: newEducation }); + } + }} + style={isEditing ? {border: '1px dashed #d9d9d9', padding: '2px 6px', borderRadius: '4px', cursor: 'text'} : {}} + > {edu.school} - {edu.major}

      -

      {edu.period}

      +

      { + if (isEditing && editableData) { + const newEducation = [...editableData.education]; + newEducation[index] = { ...newEducation[index], period: e.target.innerText }; + setEditableData({ ...editableData, education: newEducation }); + } + }} + style={isEditing ? {border: '1px dashed #d9d9d9', padding: '2px 6px', borderRadius: '4px', cursor: 'text'} : {}}> + {edu.period} +

    • ))}
    @@ -267,18 +501,72 @@ export default ({ visible, onClose, data, initialVersion = "2" }) => {
  • 项目经历

      - {resumeContent.projects?.map((project, index) => ( + {(isEditing && editableData ? editableData.projects : resumeContent.projects)?.map((project, index) => (
    • -

      {project.name}

      -

      +

      { + if (isEditing && editableData) { + const newProjects = [...editableData.projects]; + newProjects[index] = { ...newProjects[index], name: e.target.innerText }; + setEditableData({ ...editableData, projects: newProjects }); + } + }} + style={isEditing ? {border: '1px dashed #d9d9d9', padding: '2px 6px', borderRadius: '4px', cursor: 'text'} : {}}> + {project.name} +

      +

      { + if (isEditing && editableData) { + const text = e.target.innerText; + const newProjects = [...editableData.projects]; + if (text.includes('角色:')) { + const parts = text.split(' - 角色:'); + newProjects[index] = { + ...newProjects[index], + company: parts[0] || '', + role: parts[1] || project.role + }; + } else { + newProjects[index] = { ...newProjects[index], role: text }; + } + setEditableData({ ...editableData, projects: newProjects }); + } + }} + style={isEditing ? {border: '1px dashed #d9d9d9', padding: '2px 6px', borderRadius: '4px', cursor: 'text'} : {}}> {project.company && `${project.company} - `}角色:{project.role}

      -

      {project.period}

      +

      { + if (isEditing && editableData) { + const newProjects = [...editableData.projects]; + newProjects[index] = { ...newProjects[index], period: e.target.innerText }; + setEditableData({ ...editableData, projects: newProjects }); + } + }} + style={isEditing ? {border: '1px dashed #d9d9d9', padding: '2px 6px', borderRadius: '4px', cursor: 'text'} : {}}> + {project.period} +

      -

      +

      { + if (isEditing && editableData) { + const newProjects = [...editableData.projects]; + newProjects[index] = { ...newProjects[index], description: e.target.innerText }; + setEditableData({ ...editableData, projects: newProjects }); + } + }}> {project.description}

      {project.highlights && ( @@ -297,24 +585,48 @@ export default ({ visible, onClose, data, initialVersion = "2" }) => {
    • 专业技能

        - {resumeContent.skills?.core && ( + {(isEditing && editableData ? editableData.skills?.core : resumeContent.skills?.core) && (
      • 核心能力

        - {resumeContent.skills.core.map((skill, index) => ( -

        + {(isEditing && editableData ? editableData.skills.core : resumeContent.skills.core).map((skill, index) => ( +

        { + if (isEditing && editableData) { + const newSkills = { ...editableData.skills }; + const text = e.target.innerText; + // Remove numbering if present + newSkills.core[index] = text.replace(/^[0-9]+\.\s*/, ''); + setEditableData({ ...editableData, skills: newSkills }); + } + }} + style={isEditing ? {border: '1px dashed #d9d9d9', padding: '2px 6px', borderRadius: '4px', cursor: 'text'} : {}}> {index + 1}. {skill}

        ))}
      • )} - {resumeContent.skills?.additional && ( + {(isEditing && editableData ? editableData.skills?.additional : resumeContent.skills?.additional) && (
      • 复合技能

        - {resumeContent.skills.additional.map((skill, index) => ( -

        + {(isEditing && editableData ? editableData.skills.additional : resumeContent.skills.additional).map((skill, index) => ( +

        { + if (isEditing && editableData) { + const newSkills = { ...editableData.skills }; + const text = e.target.innerText; + // Remove numbering if present + newSkills.additional[index] = text.replace(/^[0-9]+\.\s*/, ''); + setEditableData({ ...editableData, skills: newSkills }); + } + }} + style={isEditing ? {border: '1px dashed #d9d9d9', padding: '2px 6px', borderRadius: '4px', cursor: 'text'} : {}}> {index + 1}. {skill}

        ))} diff --git a/src/services/resumeManager.js b/src/services/resumeManager.js new file mode 100644 index 0000000..8be37d3 --- /dev/null +++ b/src/services/resumeManager.js @@ -0,0 +1,165 @@ +/** + * 简历管理服务 + * 处理个人修改版简历的CRUD操作 + */ + +const STORAGE_KEY = 'resume_custom_versions'; + +// 获取所有个人修改版 +export const getCustomVersions = () => { + try { + const saved = localStorage.getItem(STORAGE_KEY); + return saved ? JSON.parse(saved) : []; + } catch (error) { + console.error('获取个人修改版失败:', error); + return []; + } +}; + +// 保存个人修改版列表 +export const saveCustomVersions = (versions) => { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(versions)); + return { success: true }; + } catch (error) { + console.error('保存个人修改版失败:', error); + return { success: false, error: error.message }; + } +}; + +// 创建新的个人修改版 +export const createCustomVersion = (data) => { + try { + const versions = getCustomVersions(); + const newVersion = { + id: Date.now().toString(), + name: data.name, + content: data.content, + positionTitle: data.positionTitle, + originalVersion: data.originalVersion || 'default', + tags: data.tags || [], + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }; + + const updatedVersions = [...versions, newVersion]; + const result = saveCustomVersions(updatedVersions); + + if (result.success) { + return { success: true, data: newVersion }; + } else { + return result; + } + } catch (error) { + console.error('创建个人修改版失败:', error); + return { success: false, error: error.message }; + } +}; + +// 更新个人修改版 +export const updateCustomVersion = (id, data) => { + try { + const versions = getCustomVersions(); + const index = versions.findIndex(v => v.id === id); + + if (index === -1) { + return { success: false, error: '版本不存在' }; + } + + versions[index] = { + ...versions[index], + ...data, + updatedAt: new Date().toISOString() + }; + + const result = saveCustomVersions(versions); + + if (result.success) { + return { success: true, data: versions[index] }; + } else { + return result; + } + } catch (error) { + console.error('更新个人修改版失败:', error); + return { success: false, error: error.message }; + } +}; + +// 删除个人修改版 +export const deleteCustomVersion = (id) => { + try { + const versions = getCustomVersions(); + const updatedVersions = versions.filter(v => v.id !== id); + + const result = saveCustomVersions(updatedVersions); + return result; + } catch (error) { + console.error('删除个人修改版失败:', error); + return { success: false, error: error.message }; + } +}; + +// 根据岗位获取版本 +export const getVersionsByPosition = (positionTitle) => { + const versions = getCustomVersions(); + return versions.filter(v => v.positionTitle === positionTitle); +}; + +// 根据ID获取单个版本 +export const getVersionById = (id) => { + const versions = getCustomVersions(); + return versions.find(v => v.id === id); +}; + +// 检查版本名称是否重复 +export const isVersionNameExists = (name, positionTitle) => { + const versions = getCustomVersions(); + return versions.some(v => v.name === name && v.positionTitle === positionTitle); +}; + +// 导出版本为JSON +export const exportVersion = (id) => { + const version = getVersionById(id); + if (!version) { + return { success: false, error: '版本不存在' }; + } + + try { + const dataStr = JSON.stringify(version, null, 2); + const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); + + const exportFileDefaultName = `resume_${version.positionTitle}_${version.name}.json`; + + const linkElement = document.createElement('a'); + linkElement.setAttribute('href', dataUri); + linkElement.setAttribute('download', exportFileDefaultName); + linkElement.click(); + + return { success: true }; + } catch (error) { + console.error('导出版本失败:', error); + return { success: false, error: error.message }; + } +}; + +// 导入版本 +export const importVersion = (jsonData) => { + try { + const version = JSON.parse(jsonData); + version.id = Date.now().toString(); // 生成新ID + version.importedAt = new Date().toISOString(); + + const versions = getCustomVersions(); + const updatedVersions = [...versions, version]; + const result = saveCustomVersions(updatedVersions); + + if (result.success) { + return { success: true, data: version }; + } else { + return result; + } + } catch (error) { + console.error('导入版本失败:', error); + return { success: false, error: '无效的JSON格式' }; + } +}; \ No newline at end of file