个人资料和岗位列表联调,简历优化部分页面
This commit is contained in:
@@ -0,0 +1,602 @@
|
||||
<template>
|
||||
<!-- 岗位专属简历定制弹窗:步骤1居中弹窗,步骤2+右侧抽屉 -->
|
||||
<div v-if="modelValue" class="job-resume-custom-dialog" :class="{ 'job-resume-custom-dialog--drawer': currentStep >= 2 }">
|
||||
<div class="job-resume-custom-dialog__overlay" @click="handleClose"></div>
|
||||
<!-- ===== 步骤一:居中弹窗 ===== -->
|
||||
<div v-if="currentStep === 1" class="job-resume-custom-dialog__panel">
|
||||
<div class="job-resume-custom-dialog__header">
|
||||
<h2 class="job-resume-custom-dialog__title">10s快速定制岗位专属简历</h2>
|
||||
<button class="job-resume-custom-dialog__close-btn" @click="handleClose" aria-label="关闭">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="job-resume-custom-dialog__close-icon"><path d="M12 4L4 12M4 4l8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__tip-bar"><span>你与该岗位的匹配度较低,简历可能无法通过机筛。</span></div>
|
||||
<div class="job-resume-custom-dialog__job-card">
|
||||
<div class="job-resume-custom-dialog__job-left">
|
||||
<div class="job-resume-custom-dialog__company-icon">
|
||||
<img v-if="jobInfo.companyLogoUrl" :src="jobInfo.companyLogoUrl" :alt="jobInfo.company" class="job-resume-custom-dialog__company-logo-img" />
|
||||
<svg v-else viewBox="0 0 24 24" fill="none" class="job-resume-custom-dialog__company-svg"><rect x="3" y="7" width="18" height="14" rx="2" stroke="currentColor" stroke-width="1.5"/><path d="M7 7V5a2 2 0 012-2h6a2 2 0 012 2v2" stroke="currentColor" stroke-width="1.5"/><path d="M3 13h18" stroke="currentColor" stroke-width="1.5"/></svg>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__job-info">
|
||||
<span class="job-resume-custom-dialog__job-title">{{ jobInfo.title }}</span>
|
||||
<span class="job-resume-custom-dialog__job-sub">{{ jobInfo.location }} · {{ jobInfo.company }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__match-area">
|
||||
<div class="job-resume-custom-dialog__match-ring">
|
||||
<svg viewBox="0 0 60 60" class="job-resume-custom-dialog__ring-svg"><circle cx="30" cy="30" r="24" stroke-width="4" stroke="#E8E8E8" fill="none" opacity="0.3"/><circle cx="30" cy="30" r="24" stroke-width="4" fill="none" stroke="#4FC2C9" stroke-linecap="round" :stroke-dasharray="2*Math.PI*24" :stroke-dashoffset="2*Math.PI*24*(1-jobInfo.matchScore/10)" transform="rotate(-90 30 30)"/></svg>
|
||||
<span class="job-resume-custom-dialog__match-score">{{ jobInfo.matchScore }}</span>
|
||||
</div>
|
||||
<span class="job-resume-custom-dialog__match-label">{{ matchLevelText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__skills-section">
|
||||
<p class="job-resume-custom-dialog__skills-title">缺少{{ missingSkills.length }}项技能</p>
|
||||
<div class="job-resume-custom-dialog__skills-list">
|
||||
<span v-for="skill in missingSkills" :key="skill" class="job-resume-custom-dialog__skill-tag">{{ skill }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__footer">
|
||||
<button class="job-resume-custom-dialog__primary-btn" @click="goToStep(2)">立即定制简历</button>
|
||||
<span class="job-resume-custom-dialog__skip-link" @click="handleSkip">不优化,直接投递</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== 步骤2+:右侧抽屉 ===== -->
|
||||
<div v-if="currentStep >= 2" class="job-resume-custom-dialog__drawer">
|
||||
<!-- 抽屉头部 -->
|
||||
<div class="job-resume-custom-dialog__drawer-header">
|
||||
<button class="job-resume-custom-dialog__close-btn" @click="handleClose" aria-label="关闭">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="job-resume-custom-dialog__close-icon"><path d="M12 4L4 12M4 4l8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
||||
</button>
|
||||
<h2 class="job-resume-custom-dialog__drawer-title">生成你的岗位专属简历</h2>
|
||||
</div>
|
||||
<!-- 返回按钮(步骤四显示) -->
|
||||
<button v-if="currentStep === 4" class="job-resume-custom-dialog__back-btn" @click="goToStep(3)">返回</button>
|
||||
<!-- 步骤指示器 -->
|
||||
<div class="job-resume-custom-dialog__steps">
|
||||
<div class="job-resume-custom-dialog__step" :class="{ 'job-resume-custom-dialog__step--active': currentStep === 2 }">
|
||||
<span class="job-resume-custom-dialog__step-num">1</span><span>差距分析</span>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__step" :class="{ 'job-resume-custom-dialog__step--active': currentStep === 3 }">
|
||||
<span class="job-resume-custom-dialog__step-num">2</span><span>定制简历</span>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__step" :class="{ 'job-resume-custom-dialog__step--active': currentStep === 4 }">
|
||||
<span class="job-resume-custom-dialog__step-num">3</span><span>预览</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 抽屉内容区(可滚动) -->
|
||||
<div class="job-resume-custom-dialog__drawer-body">
|
||||
<!-- 步骤二:差距分析 -->
|
||||
<template v-if="currentStep === 2">
|
||||
<div class="job-resume-custom-dialog__gap-header">
|
||||
<div class="job-resume-custom-dialog__gap-left">
|
||||
<h3 class="job-resume-custom-dialog__gap-title">你的简历与该岗位的匹配度较低</h3>
|
||||
<div class="job-resume-custom-dialog__gap-warn">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="job-resume-custom-dialog__warn-icon"><circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.2"/><path d="M8 5v3M8 10.5v.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>
|
||||
<span>匹配度低于 6.0 分的简历,在筛选环节可能会被优先淘汰。我们会帮你快速优化提升。</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__gap-score-area">
|
||||
<div class="job-resume-custom-dialog__match-ring">
|
||||
<svg viewBox="0 0 60 60" class="job-resume-custom-dialog__ring-svg"><circle cx="30" cy="30" r="24" stroke-width="4" stroke="#E8E8E8" fill="none" opacity="0.3"/><circle cx="30" cy="30" r="24" stroke-width="4" fill="none" stroke="#4FC2C9" stroke-linecap="round" :stroke-dasharray="2*Math.PI*24" :stroke-dashoffset="2*Math.PI*24*(1-jobInfo.matchScore/10)" transform="rotate(-90 30 30)"/></svg>
|
||||
<span class="job-resume-custom-dialog__match-score">{{ jobInfo.matchScore }}</span>
|
||||
</div>
|
||||
<span class="job-resume-custom-dialog__match-label">{{ matchLevelText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 对比卡片表格 -->
|
||||
<div class="job-resume-custom-dialog__gap-table">
|
||||
<!-- 第一行:概览 -->
|
||||
<div class="job-resume-custom-dialog__gap-row">
|
||||
<!-- 标签列 -->
|
||||
<div class="job-resume-custom-dialog__gap-cell job-resume-custom-dialog__gap-cell--label">
|
||||
<span class="job-resume-custom-dialog__gap-cell-title">概览</span>
|
||||
</div>
|
||||
<!-- 岗位信息列 -->
|
||||
<div class="job-resume-custom-dialog__gap-cell">
|
||||
<div class="job-resume-custom-dialog__gap-job-info">
|
||||
<div class="job-resume-custom-dialog__gap-company-icon">
|
||||
<img v-if="jobInfo.companyLogoUrl" :src="jobInfo.companyLogoUrl" :alt="jobInfo.company" class="job-resume-custom-dialog__gap-company-logo" />
|
||||
<svg v-else viewBox="0 0 24 24" fill="none" class="job-resume-custom-dialog__gap-company-svg"><rect x="3" y="3" width="7" height="7" rx="1" stroke="currentColor" stroke-width="1.2"/><rect x="14" y="3" width="7" height="7" rx="1" stroke="currentColor" stroke-width="1.2"/><rect x="3" y="14" width="7" height="7" rx="1" stroke="currentColor" stroke-width="1.2"/><rect x="14" y="14" width="7" height="7" rx="1" stroke="currentColor" stroke-width="1.2"/></svg>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__gap-job-text">
|
||||
<span class="job-resume-custom-dialog__gap-job-title">{{ jobInfo.title }}</span>
|
||||
<span class="job-resume-custom-dialog__gap-job-sub">{{ jobInfo.location }} · {{ jobInfo.company }} 独角兽</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 简历选择列 -->
|
||||
<div class="job-resume-custom-dialog__gap-cell">
|
||||
<div class="job-resume-custom-dialog__resume-selector">
|
||||
<div class="job-resume-custom-dialog__resume-info">
|
||||
<svg viewBox="0 0 24 24" fill="none" class="job-resume-custom-dialog__resume-file-icon"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8l-6-6z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="M14 2v6h6" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg>
|
||||
<div class="job-resume-custom-dialog__resume-text">
|
||||
<span class="job-resume-custom-dialog__resume-label">你的简历</span>
|
||||
<span class="job-resume-custom-dialog__resume-name">{{ selectedResume.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="job-resume-custom-dialog__resume-select-btn" @click="toggleResumeDropdown">选择 <svg viewBox="0 0 12 12" fill="none" class="job-resume-custom-dialog__dropdown-arrow"><path d="M3 5l3 3 3-3" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
|
||||
<div v-if="showResumeDropdown" class="job-resume-custom-dialog__resume-dropdown">
|
||||
<div v-for="r in resumeList" :key="r.id" class="job-resume-custom-dialog__resume-option" :class="{'job-resume-custom-dialog__resume-option--active': r.id === selectedResume.id}" @click="selectResume(r)">{{ r.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 第二行:岗位名称 -->
|
||||
<div class="job-resume-custom-dialog__gap-row">
|
||||
<div class="job-resume-custom-dialog__gap-cell job-resume-custom-dialog__gap-cell--label">
|
||||
<span>岗位名称</span>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__gap-cell">
|
||||
<span class="job-resume-custom-dialog__gap-value">{{ jobInfo.title }}</span>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__gap-cell">
|
||||
<span class="job-resume-custom-dialog__gap-value">{{ selectedResume.targetJob || '—' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 第三行:岗位关键词 -->
|
||||
<div class="job-resume-custom-dialog__gap-row">
|
||||
<div class="job-resume-custom-dialog__gap-cell job-resume-custom-dialog__gap-cell--label">
|
||||
<span>岗位关键词</span>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__gap-cell job-resume-custom-dialog__gap-cell--keywords">
|
||||
<div class="job-resume-custom-dialog__gap-keywords">
|
||||
<span v-for="kw in jobInfo.keywords" :key="kw" class="job-resume-custom-dialog__gap-kw-tag">{{ kw }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 步骤三:定制简历 -->
|
||||
<template v-if="currentStep === 3">
|
||||
<div class="job-resume-custom-dialog__custom">
|
||||
<!-- 左侧:选择要优化的部分 -->
|
||||
<div class="job-resume-custom-dialog__custom-panel">
|
||||
<h3 class="job-resume-custom-dialog__custom-panel-title">1.选择你要优化的部分</h3>
|
||||
<div class="job-resume-custom-dialog__custom-options">
|
||||
<label v-for="item in optimizeSections" :key="item.key" class="job-resume-custom-dialog__custom-checkbox">
|
||||
<input type="checkbox" v-model="item.checked" class="job-resume-custom-dialog__custom-input" />
|
||||
<span class="job-resume-custom-dialog__custom-checkmark"></span>
|
||||
<span class="job-resume-custom-dialog__custom-label">{{ item.label }}</span>
|
||||
<!-- 技能和工作经验的问号提示(使用 el-tooltip) -->
|
||||
<el-tooltip
|
||||
v-if="item.tooltip"
|
||||
:content="item.tooltip"
|
||||
placement="right"
|
||||
:show-arrow="true"
|
||||
:popper-options="{ strategy: 'fixed' }"
|
||||
effect="dark"
|
||||
>
|
||||
<span class="job-resume-custom-dialog__custom-tooltip-trigger">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="job-resume-custom-dialog__custom-tooltip-icon">
|
||||
<circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.2"/>
|
||||
<path d="M6.5 6.5a1.5 1.5 0 112.12 1.37c-.42.18-.62.5-.62.88V9.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
|
||||
<circle cx="8" cy="11.5" r="0.6" fill="currentColor"/>
|
||||
</svg>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧:选择要新增的技能关键词 -->
|
||||
<div class="job-resume-custom-dialog__custom-panel">
|
||||
<h3 class="job-resume-custom-dialog__custom-panel-title">2.选择你要新增的技能关键词</h3>
|
||||
<div class="job-resume-custom-dialog__custom-options">
|
||||
<label v-for="skill in newSkillOptions" :key="skill.name" class="job-resume-custom-dialog__custom-checkbox">
|
||||
<input type="checkbox" v-model="skill.checked" class="job-resume-custom-dialog__custom-input" />
|
||||
<span class="job-resume-custom-dialog__custom-checkmark"></span>
|
||||
<span class="job-resume-custom-dialog__custom-label">{{ skill.name }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 步骤四:预览 -->
|
||||
<template v-if="currentStep === 4">
|
||||
<div class="job-resume-custom-dialog__preview">
|
||||
<!-- 左侧:简历模板预览 -->
|
||||
<div class="job-resume-custom-dialog__preview-left">
|
||||
<JobResumeTemplate :resumeData="resumeTemplateData" ref="resumeTemplateRef" />
|
||||
</div>
|
||||
<!-- 右侧:AI帮写 / 编辑 tab -->
|
||||
<div class="job-resume-custom-dialog__preview-right">
|
||||
<!-- Tab 切换 -->
|
||||
<div class="job-resume-custom-dialog__preview-tabs">
|
||||
<button
|
||||
class="job-resume-custom-dialog__preview-tab"
|
||||
:class="{ 'job-resume-custom-dialog__preview-tab--active': previewTab === 'ai' }"
|
||||
@click="previewTab = 'ai'"
|
||||
>AI帮写</button>
|
||||
<button
|
||||
class="job-resume-custom-dialog__preview-tab"
|
||||
:class="{ 'job-resume-custom-dialog__preview-tab--active': previewTab === 'edit' }"
|
||||
@click="previewTab = 'edit'"
|
||||
>编辑</button>
|
||||
</div>
|
||||
<!-- AI帮写内容 -->
|
||||
<div v-if="previewTab === 'ai'" class="job-resume-custom-dialog__preview-ai">
|
||||
<!-- 匹配度提升提示 -->
|
||||
<div class="job-resume-custom-dialog__ai-result">
|
||||
<div class="job-resume-custom-dialog__ai-result-text">
|
||||
<p class="job-resume-custom-dialog__ai-result-title">恭喜!你的简历匹配值从<br/>{{ jobInfo.matchScore }}分提升到了10分!</p>
|
||||
<div class="job-resume-custom-dialog__ai-result-detail">
|
||||
<p class="job-resume-custom-dialog__ai-result-subtitle">做了哪些优化?</p>
|
||||
<ul class="job-resume-custom-dialog__ai-result-list">
|
||||
<li v-for="(item, i) in aiOptimizeResults" :key="i">·{{ item }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__ai-result-score">
|
||||
<div class="job-resume-custom-dialog__match-ring job-resume-custom-dialog__match-ring--large">
|
||||
<svg viewBox="0 0 60 60" class="job-resume-custom-dialog__ring-svg">
|
||||
<circle cx="30" cy="30" r="24" stroke-width="4" stroke="#E8E8E8" fill="none" opacity="0.3"/>
|
||||
<circle cx="30" cy="30" r="24" stroke-width="4" fill="none" stroke="#4FC2C9" stroke-linecap="round" :stroke-dasharray="2*Math.PI*24" :stroke-dashoffset="0" transform="rotate(-90 30 30)"/>
|
||||
</svg>
|
||||
<span class="job-resume-custom-dialog__match-score">10.0</span>
|
||||
</div>
|
||||
<span class="job-resume-custom-dialog__match-label job-resume-custom-dialog__match-label--high">非常匹配</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 快捷操作按钮 -->
|
||||
<div class="job-resume-custom-dialog__ai-quick-actions">
|
||||
<button
|
||||
v-for="(action, i) in aiQuickActions"
|
||||
:key="i"
|
||||
class="job-resume-custom-dialog__ai-quick-btn"
|
||||
@click="sendAiMessage(action)"
|
||||
>{{ action }}</button>
|
||||
</div>
|
||||
<!-- AI聊天消息区域 -->
|
||||
<div class="job-resume-custom-dialog__ai-messages" ref="aiMessagesRef">
|
||||
<div
|
||||
v-for="(msg, i) in aiMessages"
|
||||
:key="i"
|
||||
class="job-resume-custom-dialog__ai-msg"
|
||||
:class="msg.role === 'ai' ? 'job-resume-custom-dialog__ai-msg--ai' : 'job-resume-custom-dialog__ai-msg--user'"
|
||||
>
|
||||
<div class="job-resume-custom-dialog__ai-msg-bubble">{{ msg.content }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- AI输入框 -->
|
||||
<div class="job-resume-custom-dialog__ai-input-area">
|
||||
<input
|
||||
v-model="aiInputText"
|
||||
class="job-resume-custom-dialog__ai-input"
|
||||
placeholder="你要怎么优化"
|
||||
@keyup.enter="sendAiMessage(aiInputText)"
|
||||
/>
|
||||
<button class="job-resume-custom-dialog__ai-send-btn" @click="sendAiMessage(aiInputText)">
|
||||
<svg viewBox="0 0 24 24" fill="none" class="job-resume-custom-dialog__ai-send-icon">
|
||||
<path d="M5 12h14M12 5l7 7-7 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 编辑内容(占位) -->
|
||||
<div v-if="previewTab === 'edit'" class="job-resume-custom-dialog__preview-edit">
|
||||
<div class="job-resume-custom-dialog__placeholder">编辑功能(待开发)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 抽屉底部按钮 -->
|
||||
<div v-if="currentStep < 4" class="job-resume-custom-dialog__drawer-footer">
|
||||
<button class="job-resume-custom-dialog__primary-btn" @click="handleDrawerNext">立即定制简历</button>
|
||||
</div>
|
||||
<!-- 步骤四专属底部:下载简历 + 立即去投递 -->
|
||||
<div v-if="currentStep === 4" class="job-resume-custom-dialog__preview-footer">
|
||||
<!-- 左侧:下载简历按钮(带下拉) -->
|
||||
<div class="job-resume-custom-dialog__download-wrap">
|
||||
<button class="job-resume-custom-dialog__download-btn" @click="toggleDownloadMenu">下载简历</button>
|
||||
<div v-if="showDownloadMenu" class="job-resume-custom-dialog__download-menu">
|
||||
<button class="job-resume-custom-dialog__download-option" @click="handleDownload('pdf')">下载PDF</button>
|
||||
<button class="job-resume-custom-dialog__download-option" @click="handleDownload('word')">下载Word</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧:立即去投递按钮 -->
|
||||
<button class="job-resume-custom-dialog__submit-btn" @click="handleSubmit">立即去投递</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
import JobResumeTemplate from '@/components/JobResumeTemplate.vue'
|
||||
import type { ResumeTemplateData } from '@/components/JobResumeTemplate.vue'
|
||||
import { fetchProfile, fetchEducation, fetchWork, fetchInternship, fetchProject, fetchCompetition } from '@/api/profile'
|
||||
|
||||
// ==================== 类型定义 ====================
|
||||
|
||||
/** 简历选项 */
|
||||
interface ResumeOption {
|
||||
id: string
|
||||
name: string
|
||||
targetJob: string
|
||||
}
|
||||
|
||||
/** 岗位信息 */
|
||||
interface JobInfo {
|
||||
title: string
|
||||
company: string
|
||||
companyLogoUrl: string
|
||||
location: string
|
||||
matchScore: number
|
||||
missingSkills: string[]
|
||||
keywords: string[]
|
||||
sourceUrl: string
|
||||
}
|
||||
|
||||
/** AI聊天消息 */
|
||||
interface AiChatMsg {
|
||||
role: 'ai' | 'user'
|
||||
content: string
|
||||
}
|
||||
|
||||
// ==================== Props & Emits ====================
|
||||
|
||||
const props = defineProps<{
|
||||
/** 控制弹窗显隐 */
|
||||
modelValue: boolean
|
||||
/** 岗位信息 */
|
||||
jobInfo: JobInfo
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', val: boolean): void
|
||||
(e: 'skip'): void
|
||||
(e: 'submit'): void
|
||||
}>()
|
||||
|
||||
// ==================== 步骤控制 ====================
|
||||
|
||||
/** 当前步骤:1-确认入口(居中弹窗) 2-差距分析(右侧抽屉) 3-定制简历 4-预览 */
|
||||
const currentStep = ref(1)
|
||||
|
||||
/** 缺少的技能列表 */
|
||||
const missingSkills = computed(() => props.jobInfo.missingSkills || [])
|
||||
|
||||
/** 匹配度等级文案 */
|
||||
const matchLevelText = computed(() => {
|
||||
const score = props.jobInfo.matchScore
|
||||
if (score >= 8) return '高匹配度'
|
||||
if (score >= 5) return '中匹配度'
|
||||
return '低匹配度'
|
||||
})
|
||||
|
||||
/** 跳转到指定步骤 */
|
||||
function goToStep(step: number) {
|
||||
if (step === 3) initSkillOptions()
|
||||
if (step === 4) loadResumeData()
|
||||
currentStep.value = step
|
||||
}
|
||||
|
||||
/** 抽屉模式下一步 */
|
||||
function handleDrawerNext() {
|
||||
if (currentStep.value < 4) {
|
||||
if (currentStep.value === 2) initSkillOptions()
|
||||
if (currentStep.value === 3) loadResumeData()
|
||||
currentStep.value++
|
||||
}
|
||||
}
|
||||
|
||||
/** 关闭弹窗并重置步骤 */
|
||||
function handleClose() {
|
||||
currentStep.value = 1
|
||||
showResumeDropdown.value = false
|
||||
showDownloadMenu.value = false
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
/** 不优化,直接投递 */
|
||||
function handleSkip() {
|
||||
handleClose()
|
||||
emit('skip')
|
||||
}
|
||||
|
||||
// ==================== 定制简历选项(步骤三) ====================
|
||||
|
||||
/** 优化部分选项 */
|
||||
interface OptimizeSection {
|
||||
key: string
|
||||
label: string
|
||||
checked: boolean
|
||||
tooltip?: string
|
||||
}
|
||||
|
||||
/** 技能关键词选项 */
|
||||
interface SkillOption {
|
||||
name: string
|
||||
checked: boolean
|
||||
}
|
||||
|
||||
/** 左侧:可优化的简历部分 */
|
||||
const optimizeSections = ref<OptimizeSection[]>([
|
||||
{ key: 'summary', label: '个人概述', checked: false },
|
||||
{ key: 'skills', label: '技能', checked: false, tooltip: '我们将把您勾选的技能补充进简历的技能模块。这对于简历能否通过ATS关键词筛选至关重要。' },
|
||||
{ key: 'experience', label: '工作经验', checked: false, tooltip: '我们将把您选择的技能融入工作经历中,并对关键词进行润色,最大程度提升简历与岗位的匹配度。' },
|
||||
])
|
||||
|
||||
/** 右侧:可新增的技能关键词 */
|
||||
const newSkillOptions = ref<SkillOption[]>([])
|
||||
|
||||
/** 根据缺失技能初始化技能选项 */
|
||||
function initSkillOptions() {
|
||||
newSkillOptions.value = missingSkills.value.map(skill => ({
|
||||
name: skill,
|
||||
checked: false,
|
||||
}))
|
||||
}
|
||||
|
||||
// ==================== 简历选择(步骤二) ====================
|
||||
|
||||
/** 简历列表(模拟数据,后续对接接口) */
|
||||
const resumeList = ref<ResumeOption[]>([
|
||||
{ id: '1', name: '李华_产品经理', targetJob: '电商产品经理' },
|
||||
{ id: '2', name: '李华_数据分析', targetJob: '数据分析师' },
|
||||
])
|
||||
|
||||
/** 当前选中的简历 */
|
||||
const selectedResume = ref<ResumeOption>(resumeList.value[0])
|
||||
|
||||
/** 简历下拉是否展开 */
|
||||
const showResumeDropdown = ref(false)
|
||||
|
||||
/** 切换简历下拉 */
|
||||
function toggleResumeDropdown() {
|
||||
showResumeDropdown.value = !showResumeDropdown.value
|
||||
}
|
||||
|
||||
/** 选择简历 */
|
||||
function selectResume(r: ResumeOption) {
|
||||
selectedResume.value = r
|
||||
showResumeDropdown.value = false
|
||||
}
|
||||
|
||||
// ==================== 步骤四:预览相关 ====================
|
||||
|
||||
/** 简历模板组件引用 */
|
||||
const resumeTemplateRef = ref()
|
||||
|
||||
/** 简历模板数据 */
|
||||
const resumeTemplateData = ref<ResumeTemplateData>({
|
||||
name: '',
|
||||
email: '',
|
||||
mobileNumber: '',
|
||||
wechatNumber: '',
|
||||
summary: '',
|
||||
educations: [],
|
||||
workExperiences: [],
|
||||
internships: [],
|
||||
projects: [],
|
||||
competitions: [],
|
||||
skills: [],
|
||||
certificates: [],
|
||||
})
|
||||
|
||||
/** 从接口加载个人资料并组装简历数据 */
|
||||
async function loadResumeData() {
|
||||
try {
|
||||
// 并行请求所有个人资料数据
|
||||
const [profileRes, eduRes, workRes, internRes, projRes, compRes] = await Promise.all([
|
||||
fetchProfile(),
|
||||
fetchEducation(),
|
||||
fetchWork(),
|
||||
fetchInternship(),
|
||||
fetchProject(),
|
||||
fetchCompetition(),
|
||||
])
|
||||
|
||||
// 组装简历模板数据
|
||||
const profile = profileRes.code === '0' ? profileRes.data : null
|
||||
resumeTemplateData.value = {
|
||||
name: profile?.name || '未填写姓名',
|
||||
email: profile?.email || '',
|
||||
mobileNumber: profile?.mobileNumber || '',
|
||||
wechatNumber: profile?.wechatNumber || '',
|
||||
summary: '', // 个人概述字段,后续由AI生成或用户编辑
|
||||
educations: eduRes.code === '0' && eduRes.data ? eduRes.data.map(e => ({
|
||||
school: e.school || '',
|
||||
major: e.major || '',
|
||||
degree: e.degree || 2,
|
||||
startDate: e.startDate || '',
|
||||
endDate: e.endDate || '',
|
||||
description: e.description,
|
||||
})) : [],
|
||||
workExperiences: workRes.code === '0' && workRes.data ? workRes.data.map(w => ({
|
||||
companyName: w.companyName || '',
|
||||
position: w.position || '',
|
||||
startDate: w.startDate || '',
|
||||
endDate: w.endDate || '',
|
||||
description: w.description,
|
||||
})) : [],
|
||||
internships: internRes.code === '0' && internRes.data ? internRes.data.map(i => ({
|
||||
companyName: i.companyName || '',
|
||||
position: i.position || '',
|
||||
startDate: i.startDate || '',
|
||||
endDate: i.endDate || '',
|
||||
description: i.description,
|
||||
})) : [],
|
||||
projects: projRes.code === '0' && projRes.data ? projRes.data.map(p => ({
|
||||
projectName: p.projectName || '',
|
||||
companyName: p.companyName || '',
|
||||
role: p.role || '',
|
||||
startDate: p.startDate || '',
|
||||
endDate: p.endDate || '',
|
||||
description: p.description,
|
||||
})) : [],
|
||||
competitions: compRes.code === '0' && compRes.data ? compRes.data.map(c => ({
|
||||
competitionName: c.competitionName || '',
|
||||
award: c.award || '',
|
||||
awardDate: c.awardDate || '',
|
||||
description: c.description,
|
||||
})) : [],
|
||||
skills: profile?.skills || [],
|
||||
certificates: profile?.certificates || [],
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[JobResumeCustomDialog] 加载简历数据失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
/** 当前预览右侧tab:ai-AI帮写 / edit-编辑 */
|
||||
const previewTab = ref<'ai' | 'edit'>('ai')
|
||||
|
||||
/** AI优化结果列表(模拟数据) */
|
||||
const aiOptimizeResults = ref<string[]>([
|
||||
'增加了个人概述',
|
||||
'优化了5段经历描述',
|
||||
])
|
||||
|
||||
/** AI快捷操作按钮 */
|
||||
const aiQuickActions = ref<string[]>([
|
||||
'精简一下第一段工作经历',
|
||||
'帮我强化一下简历里面的量化成果',
|
||||
'删掉和这个岗位不相关的技能',
|
||||
])
|
||||
|
||||
/** AI聊天消息列表 */
|
||||
const aiMessages = ref<AiChatMsg[]>([])
|
||||
|
||||
/** AI输入框内容 */
|
||||
const aiInputText = ref('')
|
||||
|
||||
/** AI消息区域DOM引用 */
|
||||
const aiMessagesRef = ref<HTMLElement>()
|
||||
|
||||
/** 发送AI消息 */
|
||||
function sendAiMessage(text: string) {
|
||||
if (!text.trim()) return
|
||||
aiMessages.value.push({ role: 'user', content: text.trim() })
|
||||
aiInputText.value = ''
|
||||
// TODO: 接入AI聊天接口,获取AI回复
|
||||
nextTick(() => {
|
||||
if (aiMessagesRef.value) {
|
||||
aiMessagesRef.value.scrollTop = aiMessagesRef.value.scrollHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 下载菜单是否展开 */
|
||||
const showDownloadMenu = ref(false)
|
||||
|
||||
/** 切换下载菜单 */
|
||||
function toggleDownloadMenu() {
|
||||
showDownloadMenu.value = !showDownloadMenu.value
|
||||
}
|
||||
|
||||
/** 处理下载(PDF/Word) */
|
||||
function handleDownload(type: 'pdf' | 'word') {
|
||||
showDownloadMenu.value = false
|
||||
// TODO: 实现简历HTML转PDF/Word下载
|
||||
console.log(`[下载简历] 格式: ${type}`)
|
||||
}
|
||||
|
||||
/** 立即去投递 */
|
||||
function handleSubmit() {
|
||||
handleClose()
|
||||
emit('submit')
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user