简历上传,pdf简历下载
This commit is contained in:
+634
-81
@@ -2,6 +2,15 @@
|
||||
<div class="resume-detail dflex">
|
||||
<SideNav />
|
||||
<div class="resume-detail__content">
|
||||
<!-- 个人资料编辑抽屉 -->
|
||||
<ProfileEditDrawer
|
||||
v-model="showEditDrawer"
|
||||
:module="editModule"
|
||||
:initial-data="editInitialData"
|
||||
:use-education-text-value="true"
|
||||
@save="handleSaveEdit"
|
||||
/>
|
||||
|
||||
<!-- 顶部标题 -->
|
||||
<h2 class="resume-detail__page-title">我的简历</h2>
|
||||
|
||||
@@ -13,7 +22,7 @@
|
||||
<path d="M12 4L4 12M4 4l8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<span class="resume-detail__tab-name">{{ resume.name }}</span>
|
||||
<span class="resume-detail__tab-name">{{ resumeMain.resumeName || '未命名简历' }}</span>
|
||||
</div>
|
||||
<div class="resume-detail__toolbar-right">
|
||||
<button class="resume-detail__tool-btn" @click="handleFeedback">问题反馈</button>
|
||||
@@ -41,23 +50,23 @@
|
||||
<!-- 简历评分区域 -->
|
||||
<div class="resume-detail__score-bar">
|
||||
<div class="resume-detail__score-left">
|
||||
<span class="resume-detail__score-avatar" :style="{ background: resume.avatarColor }">
|
||||
{{ resume.avatarLetter }}
|
||||
<span class="resume-detail__score-avatar">
|
||||
{{ getAvatarLetter() }}
|
||||
</span>
|
||||
<span class="resume-detail__score-badge">良好</span>
|
||||
<button class="resume-detail__score-link" @click="handleViewReport">查看评估报告 ></button>
|
||||
</div>
|
||||
<div class="resume-detail__score-right">
|
||||
<div class="resume-detail__score-item">
|
||||
<span class="resume-detail__score-num">{{ resume.urgentCount }}</span>
|
||||
<span class="resume-detail__score-num">0</span>
|
||||
<span class="resume-detail__score-label">紧急修复项</span>
|
||||
</div>
|
||||
<div class="resume-detail__score-item">
|
||||
<span class="resume-detail__score-num">{{ resume.severeCount }}</span>
|
||||
<span class="resume-detail__score-num">0</span>
|
||||
<span class="resume-detail__score-label">严重问题</span>
|
||||
</div>
|
||||
<div class="resume-detail__score-item">
|
||||
<span class="resume-detail__score-num">{{ resume.optionalCount }}</span>
|
||||
<span class="resume-detail__score-num">0</span>
|
||||
<span class="resume-detail__score-label">可选修复项</span>
|
||||
</div>
|
||||
<button class="resume-detail__diagnose-btn" @click="handleDiagnose">重新诊断</button>
|
||||
@@ -70,72 +79,327 @@
|
||||
<div class="resume-detail__card">
|
||||
<div class="resume-detail__card-header">
|
||||
<div>
|
||||
<h3 class="resume-detail__user-name">{{ resume.realName }}</h3>
|
||||
<p class="resume-detail__user-title">{{ resume.jobTitle }}</p>
|
||||
</div>
|
||||
<div class="resume-detail__card-actions">
|
||||
<button class="resume-detail__card-btn resume-detail__card-btn--outline" @click="handleUrgentFix">紧急修复项</button>
|
||||
<button class="resume-detail__card-btn resume-detail__card-btn--dark" @click="handlePolish">修复</button>
|
||||
<h3 class="resume-detail__user-name">{{ resumeMain.name || '未填写姓名' }}</h3>
|
||||
<p class="resume-detail__user-title">{{ resumeMain.targetPosition || '' }}</p>
|
||||
</div>
|
||||
<!-- 编辑按钮 -->
|
||||
<button class="resume-detail__edit-btn" @click="openEditDrawer('info')" aria-label="编辑个人信息">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="resume-detail__contact">
|
||||
<span class="resume-detail__contact-item">
|
||||
<!-- 邮箱 -->
|
||||
<span v-if="resumeMain.email" class="resume-detail__contact-item">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__contact-icon">
|
||||
<rect x="2" y="3" width="12" height="10" rx="1.5" stroke="currentColor" stroke-width="1.2"/>
|
||||
<path d="M2 5.5l6 4 6-4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
{{ resume.email }}
|
||||
{{ resumeMain.email }}
|
||||
</span>
|
||||
<span class="resume-detail__contact-item">
|
||||
<!-- 手机号 -->
|
||||
<span v-if="resumeMain.mobileNumber" class="resume-detail__contact-item">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__contact-icon">
|
||||
<path d="M4 2h2.5l1.5 3-1.5 1.5a8 8 0 003 3L11 8l3 1.5V12a2 2 0 01-2 2C6.5 14 2 9.5 2 4a2 2 0 012-2z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
{{ resume.phone }}
|
||||
{{ resumeMain.mobileNumber }}
|
||||
</span>
|
||||
<span class="resume-detail__contact-item">
|
||||
<!-- 所在城市 -->
|
||||
<span v-if="resumeMain.city" class="resume-detail__contact-item">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__contact-icon">
|
||||
<circle cx="8" cy="6.5" r="2.5" stroke="currentColor" stroke-width="1.2"/>
|
||||
<path d="M8 14s-5-4-5-7.5a5 5 0 0110 0C13 10 8 14 8 14z" stroke="currentColor" stroke-width="1.2"/>
|
||||
</svg>
|
||||
{{ resume.location }}
|
||||
{{ resumeMain.city }}
|
||||
</span>
|
||||
<!-- 微信号 -->
|
||||
<span v-if="resumeMain.wechatNumber" class="resume-detail__contact-item">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__contact-icon">
|
||||
<path d="M2 8a6 6 0 1112 0 6 6 0 01-12 0z" stroke="currentColor" stroke-width="1.2"/>
|
||||
<path d="M5.5 7.5c.5-1 1.5-1.5 2.5-1.5s2 .5 2.5 1.5M6 10s.8 1 2 1 2-1 2-1" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
{{ resumeMain.wechatNumber }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- 问题操作区 -->
|
||||
<div v-if="cardIssueConfig.personalInfo.show" class="resume-detail__card-issue">
|
||||
<div class="resume-detail__issue-type-group">
|
||||
<button
|
||||
v-for="t in getCardIssueTypes('personalInfo')" :key="t.value"
|
||||
class="resume-detail__issue-type-btn"
|
||||
:class="{ 'resume-detail__issue-type-btn--active': cardIssueConfig.personalInfo.activeType === t.value }"
|
||||
@click="cardIssueConfig.personalInfo.activeType = t.value"
|
||||
>{{ t.label }}</button>
|
||||
</div>
|
||||
<button class="resume-detail__card-btn resume-detail__card-btn--dark" @click="handleFixIssue('personalInfo')">修复</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 个人概述 -->
|
||||
<div v-if="resumeMain.summary" class="resume-detail__card">
|
||||
<div class="resume-detail__section-header">
|
||||
<h3 class="resume-detail__section-title">个人概述</h3>
|
||||
<!-- 编辑按钮 -->
|
||||
<button class="resume-detail__edit-btn" @click="openEditDrawer('summary')" aria-label="编辑个人概述">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<p class="resume-detail__summary-text">{{ resumeMain.summary }}</p>
|
||||
<!-- 问题操作区 -->
|
||||
<div v-if="cardIssueConfig.summary.show" class="resume-detail__card-issue">
|
||||
<div class="resume-detail__issue-type-group">
|
||||
<button
|
||||
v-for="t in getCardIssueTypes('summary')" :key="t.value"
|
||||
class="resume-detail__issue-type-btn"
|
||||
:class="{ 'resume-detail__issue-type-btn--active': cardIssueConfig.summary.activeType === t.value }"
|
||||
@click="cardIssueConfig.summary.activeType = t.value"
|
||||
>{{ t.label }}</button>
|
||||
</div>
|
||||
<button class="resume-detail__card-btn resume-detail__card-btn--dark" @click="handleFixIssue('summary')">修复</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 作品集链接 -->
|
||||
<div v-if="resumeMain.portfolioUrl" class="resume-detail__card">
|
||||
<div class="resume-detail__section-header">
|
||||
<h3 class="resume-detail__section-title">作品集</h3>
|
||||
<!-- 编辑按钮 -->
|
||||
<button class="resume-detail__edit-btn" @click="openEditDrawer('portfolio')" aria-label="编辑作品集">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<a :href="resumeMain.portfolioUrl" target="_blank" rel="noopener noreferrer" class="resume-detail__portfolio-link">
|
||||
{{ resumeMain.portfolioUrl }}
|
||||
</a>
|
||||
<!-- 问题操作区 -->
|
||||
<div v-if="cardIssueConfig.portfolio.show" class="resume-detail__card-issue">
|
||||
<div class="resume-detail__issue-type-group">
|
||||
<button
|
||||
v-for="t in getCardIssueTypes('portfolio')" :key="t.value"
|
||||
class="resume-detail__issue-type-btn"
|
||||
:class="{ 'resume-detail__issue-type-btn--active': cardIssueConfig.portfolio.activeType === t.value }"
|
||||
@click="cardIssueConfig.portfolio.activeType = t.value"
|
||||
>{{ t.label }}</button>
|
||||
</div>
|
||||
<button class="resume-detail__card-btn resume-detail__card-btn--dark" @click="handleFixIssue('portfolio')">修复</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 教育背景 -->
|
||||
<div class="resume-detail__card">
|
||||
<div v-if="educationList.length" class="resume-detail__card">
|
||||
<div class="resume-detail__section-header">
|
||||
<h3 class="resume-detail__section-title">教育背景</h3>
|
||||
<div class="resume-detail__card-actions">
|
||||
<button class="resume-detail__card-btn resume-detail__card-btn--outline">紧急修复项</button>
|
||||
<button class="resume-detail__card-btn resume-detail__card-btn--dark">修复</button>
|
||||
<!-- 编辑按钮 -->
|
||||
<button class="resume-detail__edit-btn" @click="openEditDrawer('education')" aria-label="编辑教育背景">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div v-for="edu in educationList" :key="edu.id" class="resume-detail__edu-item">
|
||||
<div class="resume-detail__edu-degree">{{ edu.degree }} · {{ edu.major }}</div>
|
||||
<div class="resume-detail__edu-meta">{{ edu.school }} · {{ edu.studyType }} · {{ edu.startDate }} - {{ edu.endDate }}</div>
|
||||
<div v-if="edu.description?.length" class="resume-detail__desc-list">
|
||||
<p v-for="p in edu.description" :key="p.id" class="resume-detail__desc-item">{{ p.text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(edu, i) in resume.education" :key="i" class="resume-detail__edu-item">
|
||||
<div class="resume-detail__edu-degree">{{ edu.degree }}</div>
|
||||
<div class="resume-detail__edu-meta">{{ edu.school }} · {{ edu.period }}</div>
|
||||
<div v-if="edu.gpa" class="resume-detail__edu-meta">GPA: {{ edu.gpa }}</div>
|
||||
<!-- 问题操作区 -->
|
||||
<div v-if="cardIssueConfig.education.show" class="resume-detail__card-issue">
|
||||
<div class="resume-detail__issue-type-group">
|
||||
<button
|
||||
v-for="t in getCardIssueTypes('education')" :key="t.value"
|
||||
class="resume-detail__issue-type-btn"
|
||||
:class="{ 'resume-detail__issue-type-btn--active': cardIssueConfig.education.activeType === t.value }"
|
||||
@click="cardIssueConfig.education.activeType = t.value"
|
||||
>{{ t.label }}</button>
|
||||
</div>
|
||||
<button class="resume-detail__card-btn resume-detail__card-btn--dark" @click="handleFixIssue('education')">修复</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 工作经验 -->
|
||||
<div class="resume-detail__card">
|
||||
<h3 class="resume-detail__section-title">工作经验</h3>
|
||||
<div v-for="(exp, i) in resume.experience" :key="i" class="resume-detail__exp-item">
|
||||
<div class="resume-detail__exp-title">{{ exp.title }}</div>
|
||||
<div class="resume-detail__exp-meta">{{ exp.company }} · {{ exp.period }}</div>
|
||||
<p class="resume-detail__exp-desc">{{ exp.description }}</p>
|
||||
<!-- 工作经历 -->
|
||||
<div v-if="workList.length" class="resume-detail__card">
|
||||
<div class="resume-detail__section-header">
|
||||
<h3 class="resume-detail__section-title">工作经历</h3>
|
||||
<!-- 编辑按钮 -->
|
||||
<button class="resume-detail__edit-btn" @click="openEditDrawer('work')" aria-label="编辑工作经历">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div v-for="exp in workList" :key="exp.id" class="resume-detail__exp-item">
|
||||
<div class="resume-detail__exp-title dflex">
|
||||
<span>{{ exp.companyName }}</span>
|
||||
<span v-if="exp.startDate||exp.endDate" class="color-5 fw400 fs12 pt5">{{ exp.startDate }} <span v-if="exp.startDate&&exp.endDate"> - </span> {{ exp.endDate }}</span>
|
||||
</div>
|
||||
<div class="resume-detail__exp-meta">{{ exp.position }} </div>
|
||||
<ul v-if="exp.description?.length" class="resume-detail__desc-list pl16">
|
||||
<li v-for="p in exp.description" :key="p.id" class="resume-detail__desc-item fs12 color-5">{{ p.text }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- 问题操作区 -->
|
||||
<div v-if="cardIssueConfig.work.show" class="resume-detail__card-issue">
|
||||
<div class="resume-detail__issue-type-group">
|
||||
<button
|
||||
v-for="t in getCardIssueTypes('work')" :key="t.value"
|
||||
class="resume-detail__issue-type-btn"
|
||||
:class="{ 'resume-detail__issue-type-btn--active': cardIssueConfig.work.activeType === t.value }"
|
||||
@click="cardIssueConfig.work.activeType = t.value"
|
||||
>{{ t.label }}</button>
|
||||
</div>
|
||||
<button class="resume-detail__card-btn resume-detail__card-btn--dark" @click="handleFixIssue('work')">修复</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实习经历 -->
|
||||
<div v-if="internshipList.length" class="resume-detail__card">
|
||||
<div class="resume-detail__section-header">
|
||||
<h3 class="resume-detail__section-title">实习经历</h3>
|
||||
<!-- 编辑按钮 -->
|
||||
<button class="resume-detail__edit-btn" @click="openEditDrawer('internship')" aria-label="编辑实习经历">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div v-for="intern in internshipList" :key="intern.id" class="resume-detail__exp-item">
|
||||
<div class="resume-detail__exp-title dflex">
|
||||
<span>{{ intern.position }}</span>
|
||||
<span v-if="intern.startDate||intern.endDate" class="color-5 fw400 fs12 pt5">{{ intern.startDate }} <span v-if="intern.startDate&&intern.endDate"> - </span> {{ intern.endDate }}</span>
|
||||
</div>
|
||||
<div class="resume-detail__exp-meta">{{ intern.companyName }}</div>
|
||||
<div v-if="intern.description?.length" class="resume-detail__desc-list">
|
||||
<p v-for="p in intern.description" :key="p.id" class="resume-detail__desc-item">{{ p.text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 问题操作区 -->
|
||||
<div v-if="cardIssueConfig.internship.show" class="resume-detail__card-issue">
|
||||
<div class="resume-detail__issue-type-group">
|
||||
<button
|
||||
v-for="t in getCardIssueTypes('internship')" :key="t.value"
|
||||
class="resume-detail__issue-type-btn"
|
||||
:class="{ 'resume-detail__issue-type-btn--active': cardIssueConfig.internship.activeType === t.value }"
|
||||
@click="cardIssueConfig.internship.activeType = t.value"
|
||||
>{{ t.label }}</button>
|
||||
</div>
|
||||
<button class="resume-detail__card-btn resume-detail__card-btn--dark" @click="handleFixIssue('internship')">修复</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 项目经历 -->
|
||||
<div v-if="projectList.length" class="resume-detail__card">
|
||||
<div class="resume-detail__section-header">
|
||||
<h3 class="resume-detail__section-title">项目经历</h3>
|
||||
<!-- 编辑按钮 -->
|
||||
<button class="resume-detail__edit-btn" @click="openEditDrawer('project')" aria-label="编辑项目经历">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div v-for="proj in projectList" :key="proj.id" class="resume-detail__exp-item">
|
||||
<div class="resume-detail__exp-title dflex">
|
||||
<span>{{ proj.projectName }}</span>
|
||||
<span v-if="proj.startDate||proj.endDate" class="color-5 fw400 fs12 pt5">{{ proj.startDate }} <span v-if="proj.startDate&&proj.endDate"> - </span> {{ proj.endDate }}</span>
|
||||
</div>
|
||||
<div class="resume-detail__exp-meta">{{ proj.companyName }} · {{ proj.role }}</div>
|
||||
<div v-if="proj.description?.length" class="resume-detail__desc-list">
|
||||
<p v-for="p in proj.description" :key="p.id" class="resume-detail__desc-item">{{ p.text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 问题操作区 -->
|
||||
<div v-if="cardIssueConfig.project.show" class="resume-detail__card-issue">
|
||||
<div class="resume-detail__issue-type-group">
|
||||
<button
|
||||
v-for="t in getCardIssueTypes('project')" :key="t.value"
|
||||
class="resume-detail__issue-type-btn"
|
||||
:class="{ 'resume-detail__issue-type-btn--active': cardIssueConfig.project.activeType === t.value }"
|
||||
@click="cardIssueConfig.project.activeType = t.value"
|
||||
>{{ t.label }}</button>
|
||||
</div>
|
||||
<button class="resume-detail__card-btn resume-detail__card-btn--dark" @click="handleFixIssue('project')">修复</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 竞赛经历 -->
|
||||
<div v-if="competitionList.length" class="resume-detail__card">
|
||||
<div class="resume-detail__section-header">
|
||||
<h3 class="resume-detail__section-title">竞赛经历</h3>
|
||||
<!-- 编辑按钮 -->
|
||||
<button class="resume-detail__edit-btn" @click="openEditDrawer('competition')" aria-label="编辑竞赛经历">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div v-for="comp in competitionList" :key="comp.id" class="resume-detail__exp-item">
|
||||
<div class="resume-detail__exp-title dflex">
|
||||
<span>{{ comp.competitionName }}</span>
|
||||
<span v-if="comp.awardDate" class="color-5 fw400 fs12 pt5">{{ comp.awardDate }}</span>
|
||||
</div>
|
||||
<div class="resume-detail__exp-meta">{{ comp.award }}</div>
|
||||
<div v-if="comp.description?.length" class="resume-detail__desc-list">
|
||||
<p v-for="p in comp.description" :key="p.id" class="resume-detail__desc-item">{{ p.text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 问题操作区 -->
|
||||
<div v-if="cardIssueConfig.competition.show" class="resume-detail__card-issue">
|
||||
<div class="resume-detail__issue-type-group">
|
||||
<button
|
||||
v-for="t in getCardIssueTypes('competition')" :key="t.value"
|
||||
class="resume-detail__issue-type-btn"
|
||||
:class="{ 'resume-detail__issue-type-btn--active': cardIssueConfig.competition.activeType === t.value }"
|
||||
@click="cardIssueConfig.competition.activeType = t.value"
|
||||
>{{ t.label }}</button>
|
||||
</div>
|
||||
<button class="resume-detail__card-btn resume-detail__card-btn--dark" @click="handleFixIssue('competition')">修复</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 技能 -->
|
||||
<div class="resume-detail__card">
|
||||
<h3 class="resume-detail__section-title">技能</h3>
|
||||
<div v-if="resumeMain.skills?.length" class="resume-detail__card">
|
||||
<div class="resume-detail__section-header">
|
||||
<h3 class="resume-detail__section-title">技能</h3>
|
||||
<!-- 编辑按钮 -->
|
||||
<button class="resume-detail__edit-btn" @click="openEditDrawer('skills')" aria-label="编辑技能">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="resume-detail__skills">
|
||||
<span v-for="(skill, i) in resume.skills" :key="i" class="resume-detail__skill-tag">
|
||||
<span v-for="(skill, i) in resumeMain.skills" :key="i" class="resume-detail__skill-tag">
|
||||
{{ skill }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- 问题操作区 -->
|
||||
<div v-if="cardIssueConfig.skills.show" class="resume-detail__card-issue">
|
||||
<div class="resume-detail__issue-type-group">
|
||||
<button
|
||||
v-for="t in getCardIssueTypes('skills')" :key="t.value"
|
||||
class="resume-detail__issue-type-btn"
|
||||
:class="{ 'resume-detail__issue-type-btn--active': cardIssueConfig.skills.activeType === t.value }"
|
||||
@click="cardIssueConfig.skills.activeType = t.value"
|
||||
>{{ t.label }}</button>
|
||||
</div>
|
||||
<button class="resume-detail__card-btn resume-detail__card-btn--dark" @click="handleFixIssue('skills')">修复</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 证书 -->
|
||||
<div v-if="resumeMain.certificates?.length" class="resume-detail__card">
|
||||
<div class="resume-detail__section-header">
|
||||
<h3 class="resume-detail__section-title">证书</h3>
|
||||
<!-- 编辑按钮 -->
|
||||
<button class="resume-detail__edit-btn" @click="openEditDrawer('certificate')" aria-label="编辑证书">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="resume-detail__skills">
|
||||
<span v-for="(cert, i) in resumeMain.certificates" :key="i" class="resume-detail__skill-tag">
|
||||
{{ cert }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- 问题操作区 -->
|
||||
<div v-if="cardIssueConfig.certificates.show" class="resume-detail__card-issue">
|
||||
<div class="resume-detail__issue-type-group">
|
||||
<button
|
||||
v-for="t in getCardIssueTypes('certificates')" :key="t.value"
|
||||
class="resume-detail__issue-type-btn"
|
||||
:class="{ 'resume-detail__issue-type-btn--active': cardIssueConfig.certificates.activeType === t.value }"
|
||||
@click="cardIssueConfig.certificates.activeType = t.value"
|
||||
>{{ t.label }}</button>
|
||||
</div>
|
||||
<button class="resume-detail__card-btn resume-detail__card-btn--dark" @click="handleFixIssue('certificates')">修复</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,60 +407,149 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import SideNav from '@/components/SideNav.vue'
|
||||
import ProfileEditDrawer from '@/components/ProfileEditDrawer.vue'
|
||||
import {
|
||||
fetchResumeMain,
|
||||
fetchResumeEducation,
|
||||
fetchResumeWork,
|
||||
fetchResumeInternship,
|
||||
fetchResumeProject,
|
||||
fetchResumeCompetition,
|
||||
saveResumeMain,
|
||||
saveResumeEducation,
|
||||
saveResumeWork,
|
||||
saveResumeInternship,
|
||||
saveResumeProject,
|
||||
saveResumeCompetition,
|
||||
type ResumeMainData,
|
||||
type ResumeEducation,
|
||||
type ResumeWork,
|
||||
type ResumeInternship,
|
||||
type ResumeProject,
|
||||
type ResumeCompetition,
|
||||
} from '@/api/resume'
|
||||
|
||||
// ==================== 问题类型相关 ====================
|
||||
|
||||
/** 问题类型枚举值 */
|
||||
type IssueType = 'urgent' | 'optimize' | 'expression'
|
||||
|
||||
/** 问题类型选项列表 */
|
||||
const issueTypes: { label: string; value: IssueType }[] = [
|
||||
{ label: '紧急修复项', value: 'urgent' },
|
||||
{ label: '重点优化项', value: 'optimize' },
|
||||
{ label: '表达提升', value: 'expression' },
|
||||
]
|
||||
|
||||
/** 单个卡片的问题配置 */
|
||||
interface CardIssueItem {
|
||||
/** 是否显示问题操作区 */
|
||||
show: boolean
|
||||
/** 当前选中的问题类型 */
|
||||
activeType: IssueType
|
||||
/** 该模块拥有的问题类型列表(只显示这些) */
|
||||
types: IssueType[]
|
||||
}
|
||||
|
||||
/** 所有卡片模块的 key */
|
||||
type CardKey = 'personalInfo' | 'summary' | 'portfolio' | 'education' | 'work' | 'internship' | 'project' | 'competition' | 'skills' | 'certificates'
|
||||
|
||||
/** 各模块问题操作区的显示与选中状态配置,每个模块只包含自己实际存在的问题类型 */
|
||||
const cardIssueConfig = reactive<Record<CardKey, CardIssueItem>>({
|
||||
personalInfo: { show: true, activeType: 'urgent', types: ['urgent'] },
|
||||
summary: { show: true, activeType: 'expression', types: ['expression'] },
|
||||
portfolio: { show: true, activeType: 'optimize', types: ['optimize'] },
|
||||
education: { show: true, activeType: 'urgent', types: ['urgent'] },
|
||||
work: { show: true, activeType: 'optimize', types: ['optimize'] },
|
||||
internship: { show: true, activeType: 'optimize', types: ['optimize'] },
|
||||
project: { show: true, activeType: 'expression', types: ['expression'] },
|
||||
competition: { show: true, activeType: 'urgent', types: ['urgent'] },
|
||||
skills: { show: true, activeType: 'optimize', types: ['optimize'] },
|
||||
certificates: { show: true, activeType: 'urgent', types: ['urgent'] },
|
||||
})
|
||||
|
||||
/** 根据模块 key 获取该模块可用的问题类型选项 */
|
||||
function getCardIssueTypes(cardKey: CardKey) {
|
||||
return issueTypes.filter(t => cardIssueConfig[cardKey].types.includes(t.value))
|
||||
}
|
||||
|
||||
// ==================== 路由相关 ====================
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
/** 当前简历 ID,从路由参数中获取 */
|
||||
/** 当前简历 ID,从路由参数中获取(保持字符串,避免大整数精度丢失) */
|
||||
const resumeId = route.params.id as string
|
||||
|
||||
// ==================== 简历数据(模拟数据,后续对接接口) ====================
|
||||
// ==================== 数据 ====================
|
||||
|
||||
const resume = ref({
|
||||
id: resumeId,
|
||||
name: '李华_产品经理',
|
||||
avatarLetter: 'B',
|
||||
avatarColor: '#1A1A2E',
|
||||
realName: '李华',
|
||||
jobTitle: '数据产品经理',
|
||||
email: '[email]',
|
||||
phone: '+1 (555) 123-4567',
|
||||
location: '北京',
|
||||
/** 紧急修复项数量 */
|
||||
urgentCount: 8,
|
||||
/** 严重问题数量 */
|
||||
severeCount: 0,
|
||||
/** 可选修复项数量 */
|
||||
optionalCount: 1,
|
||||
/** 教育背景 */
|
||||
education: [
|
||||
{ degree: '计算机科学学士', school: '斯坦福大学', period: '2018 - 2022', gpa: '3.8/4.0' },
|
||||
{ degree: '高中毕业', school: '加州理工学院附属中学', period: '2014 - 2018', gpa: '' },
|
||||
],
|
||||
/** 工作经验 */
|
||||
experience: [
|
||||
{
|
||||
title: '高级软件工程师',
|
||||
company: 'Google',
|
||||
period: '2022 - 至今',
|
||||
description: '负责开发和维护大规模分布式系统,优化系统性能和可靠性。',
|
||||
},
|
||||
{
|
||||
title: '软件工程师实习生',
|
||||
company: 'Microsoft',
|
||||
period: '2021 - 2022',
|
||||
description: '参与云服务平台的开发,协助团队完成多个核心功能模块。',
|
||||
},
|
||||
],
|
||||
/** 技能标签 */
|
||||
skills: ['JavaScript', 'Python', 'React', 'Node.js', 'SQL', 'AWS', 'Docker', 'Kubernetes'],
|
||||
/** 简历主表数据 */
|
||||
const resumeMain = ref<ResumeMainData>({})
|
||||
|
||||
/** 教育经历列表 */
|
||||
const educationList = ref<ResumeEducation[]>([])
|
||||
|
||||
/** 工作经历列表 */
|
||||
const workList = ref<ResumeWork[]>([])
|
||||
|
||||
/** 实习经历列表 */
|
||||
const internshipList = ref<ResumeInternship[]>([])
|
||||
|
||||
/** 项目经历列表 */
|
||||
const projectList = ref<ResumeProject[]>([])
|
||||
|
||||
/** 竞赛经历列表 */
|
||||
const competitionList = ref<ResumeCompetition[]>([])
|
||||
|
||||
// ==================== 数据加载 ====================
|
||||
|
||||
/** 加载所有简历详情数据 */
|
||||
async function loadResumeDetail() {
|
||||
const [mainRes, eduRes, workRes, internRes, projRes, compRes] = await Promise.all([
|
||||
fetchResumeMain(resumeId),
|
||||
fetchResumeEducation(resumeId),
|
||||
fetchResumeWork(resumeId),
|
||||
fetchResumeInternship(resumeId),
|
||||
fetchResumeProject(resumeId),
|
||||
fetchResumeCompetition(resumeId),
|
||||
])
|
||||
|
||||
if (mainRes.code === '0' && mainRes.data) {
|
||||
resumeMain.value = mainRes.data
|
||||
}
|
||||
if (eduRes.code === '0' && eduRes.data) {
|
||||
educationList.value = eduRes.data
|
||||
}
|
||||
if (workRes.code === '0' && workRes.data) {
|
||||
workList.value = workRes.data
|
||||
}
|
||||
if (internRes.code === '0' && internRes.data) {
|
||||
internshipList.value = internRes.data
|
||||
}
|
||||
if (projRes.code === '0' && projRes.data) {
|
||||
projectList.value = projRes.data
|
||||
}
|
||||
if (compRes.code === '0' && compRes.data) {
|
||||
competitionList.value = compRes.data
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 生命周期 ====================
|
||||
|
||||
onMounted(() => {
|
||||
loadResumeDetail()
|
||||
})
|
||||
|
||||
// ==================== 计算属性 ====================
|
||||
|
||||
/** 头像首字母 */
|
||||
function getAvatarLetter(): string {
|
||||
return resumeMain.value.name?.charAt(0) || resumeMain.value.resumeName?.charAt(0) || '?'
|
||||
}
|
||||
|
||||
// ==================== 事件处理 ====================
|
||||
|
||||
/** 返回简历列表页 */
|
||||
@@ -222,9 +575,209 @@ function handleViewReport() { console.log('查看评估报告') }
|
||||
/** 重新诊断简历 */
|
||||
function handleDiagnose() { console.log('重新诊断') }
|
||||
|
||||
/** 紧急修复项 */
|
||||
function handleUrgentFix() { console.log('紧急修复项') }
|
||||
/** 修复指定模块的问题,传入模块 key */
|
||||
function handleFixIssue(cardKey: CardKey) {
|
||||
const config = cardIssueConfig[cardKey]
|
||||
console.log(`修复模块: ${cardKey}, 问题类型: ${config.activeType}`)
|
||||
}
|
||||
|
||||
/** 修复简历问题 */
|
||||
function handlePolish() { console.log('修复') }
|
||||
// ==================== 编辑抽屉相关 ====================
|
||||
|
||||
/** 编辑抽屉的显示状态 */
|
||||
const showEditDrawer = ref(false)
|
||||
|
||||
/** 当前编辑的模块名称 */
|
||||
const editModule = ref('info')
|
||||
|
||||
/** 当前编辑模块的初始数据 */
|
||||
const editInitialData = ref<Record<string, any>>({})
|
||||
|
||||
/** 打开编辑抽屉 — 根据模块名设置初始数据 */
|
||||
function openEditDrawer(section: string) {
|
||||
editModule.value = section
|
||||
|
||||
if (section === 'info') {
|
||||
editInitialData.value = {
|
||||
name: resumeMain.value.name || '',
|
||||
email: resumeMain.value.email || '',
|
||||
phone: resumeMain.value.mobileNumber || '',
|
||||
location: '',
|
||||
wechat: resumeMain.value.wechatNumber || '',
|
||||
}
|
||||
} else if (section === 'summary') {
|
||||
editInitialData.value = {
|
||||
summary: resumeMain.value.summary || '',
|
||||
}
|
||||
} else if (section === 'education') {
|
||||
editInitialData.value = {
|
||||
education: educationList.value.map(edu => ({
|
||||
school: edu.school || '',
|
||||
major: edu.major || '',
|
||||
studyType: edu.studyType || '全日制',
|
||||
degree: edu.degree || '本科',
|
||||
startDate: edu.startDate || '',
|
||||
endDate: edu.endDate || '',
|
||||
description: (edu.description || []).map(d => ({ ...d })),
|
||||
})),
|
||||
}
|
||||
} else if (section === 'work') {
|
||||
editInitialData.value = {
|
||||
works: workList.value.map(w => ({
|
||||
companyName: w.companyName || '',
|
||||
position: w.position || '',
|
||||
startDate: w.startDate || '',
|
||||
endDate: w.endDate || '',
|
||||
description: (w.description || []).map(d => ({ ...d })),
|
||||
})),
|
||||
}
|
||||
} else if (section === 'internship') {
|
||||
editInitialData.value = {
|
||||
internships: internshipList.value.map(i => ({
|
||||
companyName: i.companyName || '',
|
||||
position: i.position || '',
|
||||
startDate: i.startDate || '',
|
||||
endDate: i.endDate || '',
|
||||
description: (i.description || []).map(d => ({ ...d })),
|
||||
})),
|
||||
}
|
||||
} else if (section === 'project') {
|
||||
editInitialData.value = {
|
||||
projects: projectList.value.map(p => ({
|
||||
projectName: p.projectName || '',
|
||||
companyName: p.companyName || '',
|
||||
role: p.role || '',
|
||||
startDate: p.startDate || '',
|
||||
endDate: p.endDate || '',
|
||||
description: (p.description || []).map(d => ({ ...d })),
|
||||
})),
|
||||
}
|
||||
} else if (section === 'competition') {
|
||||
editInitialData.value = {
|
||||
competitions: competitionList.value.map(c => ({
|
||||
competitionName: c.competitionName || '',
|
||||
award: c.award || '',
|
||||
awardDate: c.awardDate || '',
|
||||
description: (c.description || []).map(d => ({ ...d })),
|
||||
})),
|
||||
}
|
||||
} else if (section === 'portfolio') {
|
||||
editInitialData.value = {
|
||||
portfolioUrl: resumeMain.value.portfolioUrl || '',
|
||||
}
|
||||
} else if (section === 'skills') {
|
||||
editInitialData.value = {
|
||||
skills: [...(resumeMain.value.skills || [])],
|
||||
}
|
||||
} else if (section === 'certificate') {
|
||||
editInitialData.value = {
|
||||
certificates: [...(resumeMain.value.certificates || [])],
|
||||
}
|
||||
}
|
||||
|
||||
showEditDrawer.value = true
|
||||
}
|
||||
|
||||
/** 保存编辑数据 — 将抽屉返回的数据调用对应接口持久化,并更新本地数据 */
|
||||
async function handleSaveEdit(data: Record<string, any>) {
|
||||
try {
|
||||
if (editModule.value === 'info') {
|
||||
// 保存基本信息到简历主表
|
||||
await saveResumeMain({
|
||||
resumeId,
|
||||
name: data.name,
|
||||
email: data.email,
|
||||
mobileNumber: data.phone,
|
||||
city: data.locationName || data.location || '',
|
||||
wechatNumber: data.wechat,
|
||||
})
|
||||
resumeMain.value.name = data.name
|
||||
resumeMain.value.email = data.email
|
||||
resumeMain.value.mobileNumber = data.phone
|
||||
resumeMain.value.city = data.locationName || data.location || ''
|
||||
resumeMain.value.wechatNumber = data.wechat
|
||||
} else if (editModule.value === 'summary') {
|
||||
// 保存个人概述到简历主表
|
||||
await saveResumeMain({
|
||||
resumeId,
|
||||
summary: data.summary,
|
||||
})
|
||||
resumeMain.value.summary = data.summary
|
||||
} else if (editModule.value === 'education') {
|
||||
// 保存教育经历
|
||||
await saveResumeEducation(resumeId, data.education.map((edu: any) => ({
|
||||
school: edu.school,
|
||||
major: edu.major,
|
||||
degree: edu.degree,
|
||||
studyType: edu.studyType,
|
||||
startDate: edu.startDate,
|
||||
endDate: edu.endDate,
|
||||
description: edu.description,
|
||||
})))
|
||||
educationList.value = data.education.map((edu: any) => ({ ...edu, description: edu.description.map((d: any) => ({ ...d })) }))
|
||||
} else if (editModule.value === 'work') {
|
||||
// 保存工作经历
|
||||
await saveResumeWork(resumeId, data.works.map((w: any) => ({
|
||||
companyName: w.companyName,
|
||||
position: w.position,
|
||||
startDate: w.startDate,
|
||||
endDate: w.endDate,
|
||||
description: w.description,
|
||||
})))
|
||||
workList.value = data.works.map((w: any) => ({ ...w, description: w.description.map((d: any) => ({ ...d })) }))
|
||||
} else if (editModule.value === 'internship') {
|
||||
// 保存实习经历
|
||||
await saveResumeInternship(resumeId, data.internships.map((i: any) => ({
|
||||
companyName: i.companyName,
|
||||
position: i.position,
|
||||
startDate: i.startDate,
|
||||
endDate: i.endDate,
|
||||
description: i.description,
|
||||
})))
|
||||
internshipList.value = data.internships.map((i: any) => ({ ...i, description: i.description.map((d: any) => ({ ...d })) }))
|
||||
} else if (editModule.value === 'project') {
|
||||
// 保存项目经历
|
||||
await saveResumeProject(resumeId, data.projects.map((p: any) => ({
|
||||
projectName: p.projectName,
|
||||
companyName: p.companyName,
|
||||
role: p.role,
|
||||
startDate: p.startDate,
|
||||
endDate: p.endDate,
|
||||
description: p.description,
|
||||
})))
|
||||
projectList.value = data.projects.map((p: any) => ({ ...p, description: p.description.map((d: any) => ({ ...d })) }))
|
||||
} else if (editModule.value === 'competition') {
|
||||
// 保存竞赛经历
|
||||
await saveResumeCompetition(resumeId, data.competitions.map((c: any) => ({
|
||||
competitionName: c.competitionName,
|
||||
award: c.award,
|
||||
awardDate: c.awardDate,
|
||||
description: c.description,
|
||||
})))
|
||||
competitionList.value = data.competitions.map((c: any) => ({ ...c, description: c.description.map((d: any) => ({ ...d })) }))
|
||||
} else if (editModule.value === 'portfolio') {
|
||||
// 保存作品集链接到简历主表
|
||||
await saveResumeMain({
|
||||
resumeId,
|
||||
portfolioUrl: data.portfolioUrl,
|
||||
})
|
||||
resumeMain.value.portfolioUrl = data.portfolioUrl
|
||||
} else if (editModule.value === 'skills') {
|
||||
// 保存技能到简历主表
|
||||
await saveResumeMain({
|
||||
resumeId,
|
||||
skills: [...data.skills],
|
||||
})
|
||||
resumeMain.value.skills = [...data.skills]
|
||||
} else if (editModule.value === 'certificate') {
|
||||
// 保存证书到简历主表
|
||||
await saveResumeMain({
|
||||
resumeId,
|
||||
certificates: [...data.certificates],
|
||||
})
|
||||
resumeMain.value.certificates = [...data.certificates]
|
||||
}
|
||||
} catch {
|
||||
console.error('[ResumeDetail] 保存失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user