308 lines
17 KiB
Vue
308 lines
17 KiB
Vue
<template>
|
||
<!-- 单条经历内联编辑组件 -->
|
||
<div class="profile-edit-inline">
|
||
<div class="profile-edit-inline__body pb0">
|
||
<!-- 教育经历 -->
|
||
<template v-if="moduleType === 'education'">
|
||
<div class="profile-edit-inline__fields-row">
|
||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">学校</label><input class="profile-edit-inline__input" placeholder="请输入学校名称" v-model="form.school" /></div>
|
||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">专业</label><input class="profile-edit-inline__input" placeholder="请输入专业名称" v-model="form.major" /></div>
|
||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">学历</label><select class="profile-edit-inline__input" v-model="form.degree"><option :value="1">大专</option><option :value="2">本科</option><option :value="3">硕士</option><option :value="4">博士</option></select></div>
|
||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">学历类型</label><select class="profile-edit-inline__input" v-model="form.studyType"><option :value="0">全日制</option><option :value="1">非全日制</option></select></div>
|
||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">就读时间</label>
|
||
<div class="profile-edit-inline__date-range">
|
||
<el-date-picker v-model="form.startDate" type="month" placeholder="开始" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" />
|
||
<span class="profile-edit-inline__date-sep">-</span>
|
||
<el-date-picker v-model="form.endDate" type="month" placeholder="结束" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 工作经历 -->
|
||
<template v-else-if="moduleType === 'work'">
|
||
<div class="profile-edit-inline__fields-row">
|
||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">公司名称</label><input class="profile-edit-inline__input" placeholder="请输入公司名称" v-model="form.companyName" /></div>
|
||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">职位</label><input class="profile-edit-inline__input" placeholder="请输入职位" v-model="form.position" /></div>
|
||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">时间</label>
|
||
<div class="profile-edit-inline__date-range">
|
||
<el-date-picker v-model="form.startDate" type="month" placeholder="开始" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" />
|
||
<span class="profile-edit-inline__date-sep">-</span>
|
||
<el-date-picker v-model="form.endDate" type="month" placeholder="结束" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 实习经历 -->
|
||
<template v-else-if="moduleType === 'internship'">
|
||
<div class="profile-edit-inline__fields-row">
|
||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">公司名称</label><input class="profile-edit-inline__input" placeholder="请输入公司名称" v-model="form.companyName" /></div>
|
||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">职位</label><input class="profile-edit-inline__input" placeholder="请输入职位" v-model="form.position" /></div>
|
||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">时间</label>
|
||
<div class="profile-edit-inline__date-range">
|
||
<el-date-picker v-model="form.startDate" type="month" placeholder="开始" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" />
|
||
<span class="profile-edit-inline__date-sep">-</span>
|
||
<el-date-picker v-model="form.endDate" type="month" placeholder="结束" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 项目经历 -->
|
||
<template v-else-if="moduleType === 'project'">
|
||
<div class="profile-edit-inline__fields-row">
|
||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">项目名称</label><input class="profile-edit-inline__input" placeholder="请输入项目名称" v-model="form.projectName" /></div>
|
||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">项目角色</label><input class="profile-edit-inline__input" placeholder="请输入项目角色" v-model="form.role" /></div>
|
||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">时间</label>
|
||
<div class="profile-edit-inline__date-range">
|
||
<el-date-picker v-model="form.startDate" type="month" placeholder="开始" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" />
|
||
<span class="profile-edit-inline__date-sep">-</span>
|
||
<el-date-picker v-model="form.endDate" type="month" placeholder="结束" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 竞赛经历 -->
|
||
<template v-else-if="moduleType === 'competition'">
|
||
<div class="profile-edit-inline__fields-row">
|
||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">竞赛名称</label><input class="profile-edit-inline__input" placeholder="请输入竞赛名称" v-model="form.competitionName" /></div>
|
||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">获奖名次</label><input class="profile-edit-inline__input" placeholder="请输入获奖名次" v-model="form.award" /></div>
|
||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">获奖时间</label><el-date-picker v-model="form.awardDate" type="month" placeholder="请选择" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" /></div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 经历描述 + AI润色 -->
|
||
<div class="profile-edit-inline__summary-header">
|
||
<label class="profile-edit-inline__label">经历描述</label>
|
||
<div class="dib dflex-center w66 h24 lh24 border-ra4 color-f aliite-c bg-linear-1 cursor-po mb4" @click="handleAiPolish" :disabled="aiPolishing">
|
||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<g clip-path="url(#clip0_589_1500)">
|
||
<path d="M4.55306 2.724C4.85206 1.849 6.06106 1.8225 6.41556 2.6445L6.44556 2.7245L6.84906 3.9045C6.94154 4.17512 7.09097 4.42276 7.28728 4.63071C7.48359 4.83867 7.72222 5.00211 7.98706 5.11L8.09556 5.1505L9.27556 5.5535C10.1506 5.8525 10.1771 7.0615 9.35556 7.416L9.27556 7.446L8.09556 7.8495C7.82485 7.94191 7.57712 8.09131 7.36907 8.28763C7.16103 8.48394 6.99751 8.7226 6.88956 8.9875L6.84906 9.0955L6.44606 10.276C6.14706 11.151 4.93806 11.1775 4.58406 10.356L4.55306 10.276L4.15006 9.096C4.05765 8.82529 3.90825 8.57755 3.71193 8.36951C3.51562 8.16146 3.27696 7.99795 3.01206 7.89L2.90406 7.8495L1.72406 7.4465C0.848564 7.1475 0.822064 5.9385 1.64406 5.5845L1.72406 5.5535L2.90406 5.1505C3.17468 5.05803 3.42232 4.9086 3.63028 4.71229C3.83823 4.51597 4.00167 4.27735 4.10956 4.0125L4.15006 3.9045L4.55306 2.724ZM5.49956 3.047L5.09656 4.227C4.95576 4.63965 4.7267 5.01665 4.42533 5.33175C4.12397 5.64684 3.75754 5.89246 3.35156 6.0515L3.22656 6.097L2.04656 6.5L3.22656 6.903C3.63922 7.0438 4.01622 7.27287 4.33131 7.57423C4.64641 7.8756 4.89203 8.24203 5.05106 8.648L5.09656 8.773L5.49956 9.953L5.90256 8.773C6.04337 8.36035 6.27243 7.98335 6.5738 7.66825C6.87516 7.35316 7.24159 7.10754 7.64756 6.9485L7.77256 6.9035L8.95256 6.5L7.77256 6.097C7.35991 5.9562 6.98291 5.72713 6.66782 5.42577C6.35272 5.1244 6.1071 4.75797 5.94806 4.352L5.90306 4.227L5.49956 3.047ZM9.49956 1C9.5931 1 9.68477 1.02624 9.76414 1.07574C9.84351 1.12523 9.90741 1.196 9.94856 1.28L9.97256 1.3385L10.1476 1.8515L10.6611 2.0265C10.7548 2.05835 10.837 2.11731 10.8972 2.19591C10.9574 2.27451 10.9929 2.36921 10.9992 2.46801C11.0055 2.56681 10.9824 2.66527 10.9327 2.75089C10.883 2.83652 10.809 2.90547 10.7201 2.949L10.6611 2.973L10.1481 3.148L9.97306 3.6615C9.94116 3.75521 9.88216 3.83735 9.80353 3.8975C9.7249 3.95764 9.63018 3.9931 9.53138 3.99936C9.43259 4.00563 9.33415 3.98242 9.24856 3.93269C9.16296 3.88296 9.09405 3.80893 9.05056 3.72L9.02656 3.6615L8.85156 3.1485L8.33806 2.9735C8.24432 2.94165 8.16215 2.88269 8.10195 2.80409C8.04175 2.72549 8.00624 2.63079 7.99993 2.53199C7.99361 2.43319 8.01676 2.33473 8.06645 2.24911C8.11615 2.16348 8.19014 2.09453 8.27906 2.051L8.33806 2.027L8.85106 1.852L9.02606 1.3385C9.05978 1.23971 9.12357 1.15395 9.20848 1.09325C9.2934 1.03254 9.39518 0.999937 9.49956 1Z" fill="currentColor"/>
|
||
</g>
|
||
<defs>
|
||
<clipPath id="clip0_589_1500">
|
||
<rect width="12" height="12" fill="white"/>
|
||
</clipPath>
|
||
</defs>
|
||
</svg>
|
||
<span class="ml2 fs12 fw600">AI润色</span>
|
||
</div>
|
||
</div>
|
||
<div class="profile-edit-inline__field">
|
||
<textarea class="profile-edit-inline__textarea" placeholder="请输入经历描述" v-model="descriptionText" rows="4"></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- AI润色结果(保存取消下方) -->
|
||
<div v-if="aiPolishResult" class="profile-edit-inline__ai-result">
|
||
|
||
<div class="profile-edit-inline__ai-result-card">
|
||
<p class="profile-edit-inline__ai-result-label">✦ AI润色结果</p>
|
||
<p class="profile-edit-inline__ai-result-text">{{ aiPolishResult }}</p>
|
||
<div class="profile-edit-inline__ai-result-actions">
|
||
<button class="profile-edit-inline__ai-replace-btn" @click="handleReplaceWithAi">+ 替换为此内容</button>
|
||
<button class="profile-edit-inline__ai-regenerate-btn" @click="handleAiPolish" :disabled="aiPolishing">↻ 重新生成</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 底部操作按钮 -->
|
||
<div class="profile-edit-inline__footer mt16">
|
||
<button class="profile-edit-inline__save-btn" @click.stop="handleSave">保存</button>
|
||
<button class="profile-edit-inline__cancel-btn" @click.stop="handleCancel">取消</button>
|
||
</div>
|
||
</div>
|
||
<!-- 全屏加载遮罩 -->
|
||
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
|
||
<!-- AI润色步骤进度遮罩 -->
|
||
<StepProgressOverlay
|
||
v-if="showPolishProgress"
|
||
:title="polishProgressTitle"
|
||
:subtitle="polishProgressSubtitle"
|
||
:steps="polishProgressSteps"
|
||
:durations="polishProgressDurations"
|
||
:done="polishProgressDone"
|
||
@finished="handlePolishProgressFinished"
|
||
@close="handlePolishProgressClose"
|
||
/>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, watch } from 'vue'
|
||
import { ElMessage } from 'element-plus'
|
||
import { polishDiagnosisIssue } from '@/api/resume'
|
||
import FullscreenLoading from '@/components/FullscreenLoading.vue'
|
||
import StepProgressOverlay from '@/components/StepProgressOverlay.vue'
|
||
|
||
/** 描述段落结构 */
|
||
interface DescriptionParagraph {
|
||
id: string
|
||
text: string
|
||
}
|
||
|
||
/** Props */
|
||
const props = defineProps<{
|
||
/** 经历模块类型 */
|
||
moduleType: string
|
||
/** 初始数据(编辑已有经历时传入,新增时为空) */
|
||
initialData?: Record<string, any>
|
||
}>()
|
||
|
||
/** Emits */
|
||
const emit = defineEmits<{
|
||
(e: 'save', data: Record<string, any>): void
|
||
(e: 'cancel'): void
|
||
}>()
|
||
|
||
/** 表单数据 */
|
||
const form = ref<Record<string, any>>({})
|
||
|
||
/** 经历描述文本(多段用换行拼接) */
|
||
const descriptionText = ref('')
|
||
|
||
/** AI润色结果 */
|
||
const aiPolishResult = ref('')
|
||
|
||
/** AI润色中 */
|
||
const aiPolishing = ref(false)
|
||
|
||
/** 全屏加载是否显示 */
|
||
const fullLoading = ref(false)
|
||
/** 全屏加载文案 */
|
||
const fullLoadingText = ref('')
|
||
|
||
/** 生成短ID */
|
||
const generateId = (): string => Math.random().toString(36).substring(2, 8)
|
||
|
||
/** 初始化表单 */
|
||
function initForm() {
|
||
aiPolishResult.value = ''
|
||
if (props.initialData) {
|
||
form.value = { ...props.initialData }
|
||
// 教育经历:确保 degree 和 studyType 为整数(兼容旧数据可能是中文字符串)
|
||
if (props.moduleType === 'education') {
|
||
const degreeMap: Record<string, number> = { '大专': 1, '本科': 2, '硕士': 3, '博士': 4 }
|
||
const studyTypeMap: Record<string, number> = { '全日制': 0, '非全日制': 1 }
|
||
if (typeof form.value.degree === 'string') {
|
||
form.value.degree = degreeMap[form.value.degree] ?? (Number(form.value.degree) || null)
|
||
}
|
||
if (typeof form.value.studyType === 'string') {
|
||
form.value.studyType = studyTypeMap[form.value.studyType] ?? (Number(form.value.studyType) || null)
|
||
}
|
||
}
|
||
// 将 description 数组拼接为文本
|
||
if (props.initialData.description?.length) {
|
||
descriptionText.value = props.initialData.description.map((d: DescriptionParagraph) => d.text).join('\n')
|
||
} else {
|
||
descriptionText.value = ''
|
||
}
|
||
} else {
|
||
// 新增模式 — 空表单
|
||
form.value = {}
|
||
descriptionText.value = ''
|
||
}
|
||
}
|
||
|
||
// 立即初始化
|
||
initForm()
|
||
|
||
// 监听 initialData 变化
|
||
watch(() => props.initialData, () => { initForm() }, { deep: true })
|
||
|
||
/** AI润色 */
|
||
async function handleAiPolish() {
|
||
const text = descriptionText.value.trim()
|
||
if (!text) {
|
||
ElMessage.warning('请先输入经历描述')
|
||
return
|
||
}
|
||
aiPolishing.value = true
|
||
// 显示步骤进度遮罩
|
||
showPolishProgress.value = true
|
||
polishProgressDone.value = false
|
||
try {
|
||
const content = text.split('\n').filter(s => s.trim())
|
||
const res = await polishDiagnosisIssue(content)
|
||
if (res.code === 0 && res.data?.content) {
|
||
pendingPolishResult.value = res.data.content.join('\n')
|
||
polishProgressDone.value = true
|
||
} else {
|
||
showPolishProgress.value = false
|
||
aiPolishing.value = false
|
||
ElMessage.error(res.msg || '润色失败')
|
||
}
|
||
} catch {
|
||
showPolishProgress.value = false
|
||
aiPolishing.value = false
|
||
ElMessage.error('请求失败')
|
||
}
|
||
}
|
||
|
||
// ==================== AI润色步骤进度遮罩 ====================
|
||
|
||
/** 润色步骤进度遮罩是否显示 */
|
||
const showPolishProgress = ref(false)
|
||
/** 润色接口是否已完成 */
|
||
const polishProgressDone = ref(false)
|
||
/** 大标题 */
|
||
const polishProgressTitle = 'Nova正在润色中...'
|
||
/** 副标题 */
|
||
const polishProgressSubtitle = '正在根据岗位要求润色简历,突出你的核心优势'
|
||
/** 4个步骤名称 */
|
||
const polishProgressSteps = ['提取核心关键词...', '寻找符合的语义...', '排版文字格式...', '最终生成中...']
|
||
/** 每步时长1400毫秒 */
|
||
const polishProgressDurations = [1400, 1400, 1400, 1400]
|
||
/** 缓存润色结果 */
|
||
const pendingPolishResult = ref('')
|
||
|
||
/** 润色进度走完后填充结果 */
|
||
function handlePolishProgressFinished() {
|
||
showPolishProgress.value = false
|
||
aiPolishing.value = false
|
||
if (pendingPolishResult.value) {
|
||
aiPolishResult.value = pendingPolishResult.value
|
||
ElMessage.success('AI润色完成')
|
||
}
|
||
pendingPolishResult.value = ''
|
||
}
|
||
|
||
/** 关闭润色进度遮罩 */
|
||
function handlePolishProgressClose() {
|
||
showPolishProgress.value = false
|
||
aiPolishing.value = false
|
||
pendingPolishResult.value = ''
|
||
}
|
||
|
||
/** 替换为AI润色内容 */
|
||
function handleReplaceWithAi() {
|
||
if (aiPolishResult.value) {
|
||
descriptionText.value = aiPolishResult.value
|
||
}
|
||
}
|
||
|
||
/** 保存 */
|
||
function handleSave() {
|
||
// 将描述文本按换行拆分为 description 数组
|
||
const lines = descriptionText.value.split('\n').filter(s => s.trim())
|
||
const description: DescriptionParagraph[] = lines.map(text => ({
|
||
id: generateId(),
|
||
text,
|
||
}))
|
||
// 如果有原始 description,保留原有的 id
|
||
if (props.initialData?.description?.length) {
|
||
props.initialData.description.forEach((d: DescriptionParagraph, i: number) => {
|
||
if (i < description.length) {
|
||
description[i].id = d.id
|
||
}
|
||
})
|
||
}
|
||
emit('save', { ...form.value, description })
|
||
}
|
||
|
||
/** 取消 */
|
||
function handleCancel() {
|
||
emit('cancel')
|
||
}
|
||
</script>
|