600 lines
32 KiB
Vue
600 lines
32 KiB
Vue
<template>
|
||
<!-- 个人资料编辑组件 — 内联模式(直接嵌入页面),通过 modelValue 控制显示/隐藏 -->
|
||
<div v-if="modelValue" class="profile-edit-inline">
|
||
<!-- 表单内容区 — 根据模块名动态渲染不同字段 -->
|
||
<div class="profile-edit-inline__body">
|
||
<!-- ========== 基本信息模块 ========== -->
|
||
<template v-if="module === 'info'">
|
||
<!-- 基本信息字段横排布局 -->
|
||
<div class="profile-edit-inline__fields-row">
|
||
<div
|
||
v-for="field in infoFields"
|
||
:key="field.key"
|
||
class="profile-edit-inline__field-item"
|
||
>
|
||
<label class="profile-edit-inline__label">{{ field.label }}</label>
|
||
<!-- 城市字段:使用地区选择组件 -->
|
||
<RegionSelector
|
||
v-if="field.key === 'location'"
|
||
:regionCodes="locationCodes"
|
||
:level="2"
|
||
:maxSelect="1"
|
||
:triggerStyle="regionTriggerStyle"
|
||
@update:regionCodes="onLocationChange"
|
||
/>
|
||
<!-- 其他字段:普通输入框 -->
|
||
<input
|
||
v-else
|
||
class="profile-edit-inline__input"
|
||
:type="field.type || 'text'"
|
||
:placeholder="field.placeholder"
|
||
v-model="formData[field.key]"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<!-- 作品集链接(占据整行) -->
|
||
<div class="profile-edit-inline__field-full">
|
||
<label class="profile-edit-inline__label">作品集链接</label>
|
||
<input
|
||
class="profile-edit-inline__input"
|
||
type="text"
|
||
placeholder="请输入作品集链接"
|
||
v-model="formData.portfolioUrl"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ========== 教育经历模块 ========== -->
|
||
<template v-else-if="module === 'education'">
|
||
<div v-for="(edu, index) in educationList" :key="index" class="profile-edit-inline__edu-card">
|
||
<div class="profile-edit-inline__edu-header">
|
||
<span class="profile-edit-inline__edu-index">教育经历{{ index + 1 }}</span>
|
||
<button v-if="educationList.length > 1" class="profile-edit-inline__edu-delete" @click="removeEducation(index)">删除</button>
|
||
</div>
|
||
<div class="profile-edit-inline__field"><label class="profile-edit-inline__label">学校</label><input class="profile-edit-inline__input" placeholder="请输入学校名称" v-model="edu.school" /></div>
|
||
<div class="profile-edit-inline__field"><label class="profile-edit-inline__label">专业</label><input class="profile-edit-inline__input" placeholder="请输入专业名称" v-model="edu.major" /></div>
|
||
<div class="profile-edit-inline__row">
|
||
<div class="profile-edit-inline__field profile-edit-inline__field--half"><label class="profile-edit-inline__label">学历类型</label><select class="profile-edit-inline__input" v-model="edu.studyType"><option v-for="opt in studyTypeOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</option></select></div>
|
||
<div class="profile-edit-inline__field profile-edit-inline__field--half"><label class="profile-edit-inline__label">学历</label><select class="profile-edit-inline__input" v-model="edu.degree"><option v-for="opt in degreeOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</option></select></div>
|
||
</div>
|
||
<div class="profile-edit-inline__row">
|
||
<div class="profile-edit-inline__field profile-edit-inline__field--half"><label class="profile-edit-inline__label">入学年份</label><el-date-picker v-model="edu.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" /></div>
|
||
<div class="profile-edit-inline__field profile-edit-inline__field--half"><label class="profile-edit-inline__label">毕业年份</label><el-date-picker v-model="edu.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 class="profile-edit-inline__field">
|
||
<label class="profile-edit-inline__label">经历描述</label>
|
||
<div v-for="(desc, dIdx) in edu.description" :key="desc.id" class="profile-edit-inline__desc-item">
|
||
<textarea class="profile-edit-inline__textarea" placeholder="请输入经历描述" v-model="desc.text" rows="3"></textarea>
|
||
<button v-if="edu.description.length > 1" class="profile-edit-inline__desc-remove" @click="removeEducationDescriptionParagraph(index, dIdx)">×</button>
|
||
</div>
|
||
<button class="profile-edit-inline__add-btn" @click="addEducationDescriptionParagraph(index)">+ 新增一段描述</button>
|
||
</div>
|
||
</div>
|
||
<button class="profile-edit-inline__add-btn profile-edit-inline__add-btn--full" @click="addEducation">+ 新增教育经历</button>
|
||
</template>
|
||
|
||
<!-- ========== 工作经历模块 ========== -->
|
||
<template v-else-if="module === 'work'">
|
||
<div v-for="(work, index) in workList" :key="index" class="profile-edit-inline__edu-card">
|
||
<div class="profile-edit-inline__edu-header">
|
||
<span class="profile-edit-inline__edu-index">工作经历{{ index + 1 }}</span>
|
||
<button v-if="workList.length > 1" class="profile-edit-inline__edu-delete" @click="removeWork(index)">删除</button>
|
||
</div>
|
||
<div class="profile-edit-inline__field"><label class="profile-edit-inline__label">公司名称</label><input class="profile-edit-inline__input" placeholder="请输入公司名称" v-model="work.companyName" /></div>
|
||
<div class="profile-edit-inline__field"><label class="profile-edit-inline__label">职位</label><input class="profile-edit-inline__input" placeholder="请输入职位" v-model="work.position" /></div>
|
||
<div class="profile-edit-inline__row">
|
||
<div class="profile-edit-inline__field profile-edit-inline__field--half"><label class="profile-edit-inline__label">开始时间</label><el-date-picker v-model="work.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" /></div>
|
||
<div class="profile-edit-inline__field profile-edit-inline__field--half"><label class="profile-edit-inline__label">结束时间</label><el-date-picker v-model="work.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 class="profile-edit-inline__field">
|
||
<label class="profile-edit-inline__label">经历描述</label>
|
||
<div v-for="(desc, dIdx) in work.description" :key="desc.id" class="profile-edit-inline__desc-item">
|
||
<textarea class="profile-edit-inline__textarea" placeholder="请输入经历描述" v-model="desc.text" rows="3"></textarea>
|
||
<button v-if="work.description.length > 1" class="profile-edit-inline__desc-remove" @click="removeWorkDescriptionParagraph(index, dIdx)">×</button>
|
||
</div>
|
||
<button class="profile-edit-inline__add-btn" @click="addWorkDescriptionParagraph(index)">+ 新增一段描述</button>
|
||
</div>
|
||
</div>
|
||
<button class="profile-edit-inline__add-btn profile-edit-inline__add-btn--full" @click="addWork">+ 新增工作经历</button>
|
||
</template>
|
||
|
||
<!-- ========== 实习经历模块 ========== -->
|
||
<template v-else-if="module === 'internship'">
|
||
<div v-for="(intern, index) in internshipList" :key="index" class="profile-edit-inline__edu-card">
|
||
<div class="profile-edit-inline__edu-header">
|
||
<span class="profile-edit-inline__edu-index">实习经历{{ index + 1 }}</span>
|
||
<button v-if="internshipList.length > 1" class="profile-edit-inline__edu-delete" @click="removeInternship(index)">删除</button>
|
||
</div>
|
||
<div class="profile-edit-inline__field"><label class="profile-edit-inline__label">公司名称</label><input class="profile-edit-inline__input" placeholder="请输入公司名称" v-model="intern.companyName" /></div>
|
||
<div class="profile-edit-inline__field"><label class="profile-edit-inline__label">职位</label><input class="profile-edit-inline__input" placeholder="请输入职位" v-model="intern.position" /></div>
|
||
<div class="profile-edit-inline__row">
|
||
<div class="profile-edit-inline__field profile-edit-inline__field--half"><label class="profile-edit-inline__label">开始时间</label><el-date-picker v-model="intern.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" /></div>
|
||
<div class="profile-edit-inline__field profile-edit-inline__field--half"><label class="profile-edit-inline__label">结束时间</label><el-date-picker v-model="intern.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 class="profile-edit-inline__field">
|
||
<label class="profile-edit-inline__label">经历描述</label>
|
||
<div v-for="(desc, dIdx) in intern.description" :key="desc.id" class="profile-edit-inline__desc-item">
|
||
<textarea class="profile-edit-inline__textarea" placeholder="请输入经历描述" v-model="desc.text" rows="3"></textarea>
|
||
<button v-if="intern.description.length > 1" class="profile-edit-inline__desc-remove" @click="removeInternDescriptionParagraph(index, dIdx)">×</button>
|
||
</div>
|
||
<button class="profile-edit-inline__add-btn" @click="addInternDescriptionParagraph(index)">+ 新增一段描述</button>
|
||
</div>
|
||
</div>
|
||
<button class="profile-edit-inline__add-btn profile-edit-inline__add-btn--full" @click="addInternship">+ 新增实习经历</button>
|
||
</template>
|
||
|
||
<!-- ========== 项目经历模块 ========== -->
|
||
<template v-else-if="module === 'project'">
|
||
<div v-for="(proj, index) in projectList" :key="index" class="profile-edit-inline__edu-card">
|
||
<div class="profile-edit-inline__edu-header">
|
||
<span class="profile-edit-inline__edu-index">项目经历{{ index + 1 }}</span>
|
||
<button v-if="projectList.length > 1" class="profile-edit-inline__edu-delete" @click="removeProject(index)">删除</button>
|
||
</div>
|
||
<div class="profile-edit-inline__field"><label class="profile-edit-inline__label">项目名称</label><input class="profile-edit-inline__input" placeholder="请输入项目名称" v-model="proj.projectName" /></div>
|
||
<div class="profile-edit-inline__field"><label class="profile-edit-inline__label">项目角色</label><input class="profile-edit-inline__input" placeholder="请输入项目角色" v-model="proj.role" /></div>
|
||
<div class="profile-edit-inline__row">
|
||
<div class="profile-edit-inline__field profile-edit-inline__field--half"><label class="profile-edit-inline__label">开始时间</label><el-date-picker v-model="proj.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" /></div>
|
||
<div class="profile-edit-inline__field profile-edit-inline__field--half"><label class="profile-edit-inline__label">结束时间</label><el-date-picker v-model="proj.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 class="profile-edit-inline__field">
|
||
<label class="profile-edit-inline__label">经历描述</label>
|
||
<div v-for="(desc, dIdx) in proj.description" :key="desc.id" class="profile-edit-inline__desc-item">
|
||
<textarea class="profile-edit-inline__textarea" placeholder="请输入经历描述" v-model="desc.text" rows="3"></textarea>
|
||
<button v-if="proj.description.length > 1" class="profile-edit-inline__desc-remove" @click="removeDescriptionParagraph(index, dIdx)">×</button>
|
||
</div>
|
||
<button class="profile-edit-inline__add-btn" @click="addDescriptionParagraph(index)">+ 新增一段描述</button>
|
||
</div>
|
||
</div>
|
||
<button class="profile-edit-inline__add-btn profile-edit-inline__add-btn--full" @click="addProject">+ 新增项目经历</button>
|
||
</template>
|
||
|
||
<!-- ========== 竞赛模块 ========== -->
|
||
<template v-else-if="module === 'competition'">
|
||
<div v-for="(comp, index) in competitionList" :key="index" class="profile-edit-inline__edu-card">
|
||
<div class="profile-edit-inline__edu-header">
|
||
<span class="profile-edit-inline__edu-index">竞赛经历{{ index + 1 }}</span>
|
||
<button v-if="competitionList.length > 1" class="profile-edit-inline__edu-delete" @click="removeCompetition(index)">删除</button>
|
||
</div>
|
||
<div class="profile-edit-inline__field"><label class="profile-edit-inline__label">竞赛名称</label><input class="profile-edit-inline__input" placeholder="请输入竞赛名称" v-model="comp.competitionName" /></div>
|
||
<div class="profile-edit-inline__field"><label class="profile-edit-inline__label">获奖名次</label><input class="profile-edit-inline__input" placeholder="请输入获奖名次" v-model="comp.award" /></div>
|
||
<div class="profile-edit-inline__field"><label class="profile-edit-inline__label">获奖时间</label><el-date-picker v-model="comp.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 class="profile-edit-inline__field"><label class="profile-edit-inline__label">获奖情况描述</label><textarea class="profile-edit-inline__textarea" placeholder="请输入获奖详情" v-model="comp.description[0].text" rows="4"></textarea></div>
|
||
</div>
|
||
<button class="profile-edit-inline__add-btn profile-edit-inline__add-btn--full" @click="addCompetition">+ 新增竞赛经历</button>
|
||
</template>
|
||
|
||
<!-- ========== 作品集模块 ========== -->
|
||
<template v-else-if="module === 'portfolio'">
|
||
<div class="profile-edit-inline__field">
|
||
<label class="profile-edit-inline__label">作品集链接</label>
|
||
<textarea class="profile-edit-inline__textarea" placeholder="请输入/粘贴作品集链接" v-model="portfolioUrl" rows="4"></textarea>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ========== 技能模块 ========== -->
|
||
<template v-else-if="module === 'skills'">
|
||
<div class="profile-edit-inline__skills-list">
|
||
<div v-for="(skill, index) in skillsList" :key="index" class="profile-edit-inline__skill-tag">
|
||
<span class="profile-edit-inline__skill-text">{{ skill }}</span>
|
||
<button class="profile-edit-inline__skill-remove" @click="removeSkill(index)">×</button>
|
||
</div>
|
||
</div>
|
||
<div class="profile-edit-inline__field">
|
||
<label class="profile-edit-inline__label">添加新技能</label>
|
||
<input class="profile-edit-inline__input" type="text" placeholder="输入技能名称后按回车添加" v-model="newSkillInput" @keydown.enter="handleAddSkill" @blur="handleAddSkill" />
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ========== 证书模块 ========== -->
|
||
<template v-else-if="module === 'certificate'">
|
||
<div class="profile-edit-inline__skills-list">
|
||
<div v-for="(cert, index) in certificatesList" :key="index" class="profile-edit-inline__skill-tag">
|
||
<span class="profile-edit-inline__skill-text">{{ cert }}</span>
|
||
<button class="profile-edit-inline__skill-remove" @click="removeCertificate(index)">×</button>
|
||
</div>
|
||
</div>
|
||
<div class="profile-edit-inline__field">
|
||
<label class="profile-edit-inline__label">添加新证书</label>
|
||
<input class="profile-edit-inline__input" type="text" placeholder="输入证书名称后按回车添加" v-model="newCertificateInput" @keydown.enter="handleAddCertificate" @blur="handleAddCertificate" />
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ========== 个人概述模块 ========== -->
|
||
<template v-else-if="module === 'summary'">
|
||
<!-- 标题 + AI润色按钮 -->
|
||
<div class="profile-edit-inline__summary-header">
|
||
<label class="profile-edit-inline__label">编辑个人概述</label>
|
||
<button class="profile-edit-inline__ai-polish-btn" @click="handleAiPolish" :disabled="aiPolishing">
|
||
<span><span>✦</span> AI润色</span>
|
||
</button>
|
||
</div>
|
||
<div class="profile-edit-inline__field">
|
||
<textarea class="profile-edit-inline__textarea" placeholder="请输入个人概述" v-model="summaryText" rows="6"></textarea>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- 底部操作按钮 -->
|
||
<div class="profile-edit-inline__footer">
|
||
<button class="profile-edit-inline__save-btn" @click="handleSave">保存</button>
|
||
<button class="profile-edit-inline__cancel-btn" @click="handleClose">取消</button>
|
||
</div>
|
||
|
||
<!-- AI润色结果区域(在保存取消按钮下方) -->
|
||
<div v-if="module === 'summary' && aiPolishResult" class="profile-edit-inline__ai-result">
|
||
<p class="profile-edit-inline__ai-result-label">✦ AI润色结果</p>
|
||
<div class="profile-edit-inline__ai-result-card">
|
||
<p class="profile-edit-inline__ai-result-text">{{ aiPolishResult }}</p>
|
||
</div>
|
||
<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>
|
||
<!-- 全屏加载遮罩 -->
|
||
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, watch } from 'vue'
|
||
import { useStore } from 'vuex'
|
||
import { ElMessage } from 'element-plus'
|
||
import RegionSelector from '@/components/tools/RegionSelector.vue'
|
||
import { polishDiagnosisIssue } from '@/api/resume'
|
||
import FullscreenLoading from '@/components/FullscreenLoading.vue'
|
||
|
||
/** 教育经历单条数据结构 */
|
||
interface EducationItem {
|
||
id?: string
|
||
school: string
|
||
major: string
|
||
studyType: number | string
|
||
degree: number | string
|
||
startDate: string
|
||
endDate: string
|
||
description: DescriptionParagraph[]
|
||
}
|
||
|
||
/** 实习经历单条数据结构 */
|
||
interface InternshipItem {
|
||
id?: string
|
||
companyName: string
|
||
position: string
|
||
startDate: string
|
||
endDate: string
|
||
description: DescriptionParagraph[]
|
||
}
|
||
|
||
/** 工作经历单条数据结构 */
|
||
interface WorkItem {
|
||
id?: string
|
||
companyName: string
|
||
position: string
|
||
startDate: string
|
||
endDate: string
|
||
description: DescriptionParagraph[]
|
||
}
|
||
|
||
/** 描述段落数据结构 */
|
||
interface DescriptionParagraph {
|
||
id: string
|
||
text: string
|
||
}
|
||
|
||
/** 项目经历单条数据结构 */
|
||
interface ProjectItem {
|
||
id?: string
|
||
projectName: string
|
||
companyName: string
|
||
role: string
|
||
startDate: string
|
||
endDate: string
|
||
description: DescriptionParagraph[]
|
||
}
|
||
|
||
/** 竞赛经历单条数据结构 */
|
||
interface CompetitionItem {
|
||
id?: string
|
||
competitionName: string
|
||
award: string
|
||
awardDate: string
|
||
description: DescriptionParagraph[]
|
||
}
|
||
|
||
/** 组件 Props */
|
||
const props = defineProps<{
|
||
modelValue: boolean
|
||
module: string
|
||
initialData?: Record<string, any>
|
||
useEducationTextValue?: boolean
|
||
}>()
|
||
|
||
/** 学历类型选项 */
|
||
const studyTypeOptions = computed(() => props.useEducationTextValue
|
||
? [{ value: '全日制', label: '全日制' }, { value: '非全日制', label: '非全日制' }]
|
||
: [{ value: 0, label: '全日制' }, { value: 1, label: '非全日制' }]
|
||
)
|
||
|
||
/** 学历选项 */
|
||
const degreeOptions = computed(() => props.useEducationTextValue
|
||
? [{ value: '大专', label: '大专' }, { value: '本科', label: '本科' }, { value: '硕士', label: '硕士' }, { value: '博士', label: '博士' }]
|
||
: [{ value: 1, label: '大专' }, { value: 2, label: '本科' }, { value: 3, label: '硕士' }, { value: 4, label: '博士' }]
|
||
)
|
||
|
||
/** 组件 Emits */
|
||
const emit = defineEmits<{
|
||
(e: 'update:modelValue', value: boolean): void
|
||
(e: 'save', data: Record<string, any>): void
|
||
(e: 'cancel'): void
|
||
}>()
|
||
|
||
/** Vuex store 实例 */
|
||
const store = useStore()
|
||
|
||
// ==================== 全屏加载状态 ====================
|
||
/** 全屏加载是否显示 */
|
||
const fullLoading = ref(false)
|
||
/** 全屏加载文案 */
|
||
const fullLoadingText = ref('')
|
||
|
||
/** 模块名称映射表 */
|
||
const moduleTitleMap: Record<string, string> = {
|
||
info: '基本信息',
|
||
summary: '个人概述',
|
||
education: '教育经历',
|
||
work: '工作经历',
|
||
internship: '实习经历',
|
||
project: '项目经历',
|
||
portfolio: '作品集',
|
||
skills: '技能',
|
||
competition: '竞赛',
|
||
certificate: '证书',
|
||
}
|
||
|
||
/** 当前模块的中文标题(留给其他场景使用) */
|
||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||
const moduleTitle = computed(() => moduleTitleMap[props.module] || props.module)
|
||
|
||
/** 基本信息模块的字段配置 */
|
||
const infoFields = [
|
||
{ key: 'name', label: '姓名', placeholder: '请输入姓名', required: true },
|
||
{ key: 'phone', label: '电话', placeholder: '请输入联系电话', required: true, type: 'tel' },
|
||
{ key: 'wechat', label: '微信', placeholder: '请输入微信号', required: false },
|
||
{ key: 'email', label: '邮箱', placeholder: '请输入邮箱', required: true, type: 'email' },
|
||
{ key: 'location', label: '所在城市', placeholder: '请输入所在城市', required: true },
|
||
]
|
||
|
||
/** 表单数据 */
|
||
const formData = ref<Record<string, any>>({})
|
||
|
||
/** 城市选择器当前选中的地区编码数组 */
|
||
const locationCodes = ref<string[]>([])
|
||
|
||
/** 地区选择器触发按钮样式 */
|
||
const regionTriggerStyle: Record<string, string> = {
|
||
width: '100%',
|
||
'box-sizing': 'border-box',
|
||
padding: '0.08rem 0.12rem',
|
||
'font-size': '0.13rem',
|
||
color: '#1a1a2e',
|
||
background: '#F3F4F5',
|
||
border: '1px solid #E8E8E8',
|
||
'border-radius': '0.04rem',
|
||
'max-width': 'none',
|
||
'justify-content': 'space-between',
|
||
}
|
||
|
||
/** 城市选择器确认回调 */
|
||
function onLocationChange(codes: string[]) {
|
||
locationCodes.value = codes
|
||
formData.value.location = codes[0] || ''
|
||
formData.value.locationName = resolveRegionName(codes[0] || '')
|
||
}
|
||
|
||
/** 根据城市编码查找城市名称 */
|
||
function resolveRegionName(code: string): string {
|
||
if (!code) return ''
|
||
for (const province of store.state.regions) {
|
||
for (const city of province.children) {
|
||
if (city.code === code) return city.name
|
||
if (city.children) {
|
||
for (const district of city.children) {
|
||
if (district.code === code) return district.name
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return code
|
||
}
|
||
|
||
/** 教育经历列表 */
|
||
const educationList = ref<EducationItem[]>([])
|
||
const createEmptyEducation = (): EducationItem => ({
|
||
school: '', major: '',
|
||
studyType: props.useEducationTextValue ? '全日制' : 0,
|
||
degree: props.useEducationTextValue ? '本科' : 2,
|
||
startDate: '', endDate: '',
|
||
description: [{ id: generateId(), text: '' }],
|
||
})
|
||
const addEducation = () => { educationList.value.push(createEmptyEducation()) }
|
||
const removeEducation = (index: number) => { educationList.value.splice(index, 1) }
|
||
const addEducationDescriptionParagraph = (eduIndex: number) => { educationList.value[eduIndex].description.push({ id: generateId(), text: '' }) }
|
||
const removeEducationDescriptionParagraph = (eduIndex: number, descIndex: number) => { educationList.value[eduIndex].description.splice(descIndex, 1) }
|
||
|
||
/** 工作经历列表 */
|
||
const workList = ref<WorkItem[]>([])
|
||
const createEmptyWork = (): WorkItem => ({ companyName: '', position: '', startDate: '', endDate: '', description: [{ id: generateId(), text: '' }] })
|
||
const addWork = () => { workList.value.push(createEmptyWork()) }
|
||
const removeWork = (index: number) => { workList.value.splice(index, 1) }
|
||
const addWorkDescriptionParagraph = (workIndex: number) => { workList.value[workIndex].description.push({ id: generateId(), text: '' }) }
|
||
const removeWorkDescriptionParagraph = (workIndex: number, descIndex: number) => { workList.value[workIndex].description.splice(descIndex, 1) }
|
||
|
||
/** 实习经历列表 */
|
||
const internshipList = ref<InternshipItem[]>([])
|
||
const createEmptyInternship = (): InternshipItem => ({ companyName: '', position: '', startDate: '', endDate: '', description: [{ id: generateId(), text: '' }] })
|
||
const addInternship = () => { internshipList.value.push(createEmptyInternship()) }
|
||
const removeInternship = (index: number) => { internshipList.value.splice(index, 1) }
|
||
const addInternDescriptionParagraph = (internIndex: number) => { internshipList.value[internIndex].description.push({ id: generateId(), text: '' }) }
|
||
const removeInternDescriptionParagraph = (internIndex: number, descIndex: number) => { internshipList.value[internIndex].description.splice(descIndex, 1) }
|
||
|
||
/** 项目经历列表 */
|
||
const projectList = ref<ProjectItem[]>([])
|
||
const createEmptyProject = (): ProjectItem => ({ projectName: '', companyName: '', role: '', startDate: '', endDate: '', description: [{ id: generateId(), text: '' }] })
|
||
const addProject = () => { projectList.value.push(createEmptyProject()) }
|
||
const removeProject = (index: number) => { projectList.value.splice(index, 1) }
|
||
const addDescriptionParagraph = (projIndex: number) => { projectList.value[projIndex].description.push({ id: generateId(), text: '' }) }
|
||
const removeDescriptionParagraph = (projIndex: number, descIndex: number) => { projectList.value[projIndex].description.splice(descIndex, 1) }
|
||
|
||
/** 竞赛经历列表 */
|
||
const competitionList = ref<CompetitionItem[]>([])
|
||
const createEmptyCompetition = (): CompetitionItem => ({ competitionName: '', award: '', awardDate: '', description: [{ id: generateId(), text: '' }] })
|
||
const addCompetition = () => { competitionList.value.push(createEmptyCompetition()) }
|
||
const removeCompetition = (index: number) => { competitionList.value.splice(index, 1) }
|
||
|
||
/** 生成短标识 ID */
|
||
const generateId = (): string => Math.random().toString(36).substring(2, 8)
|
||
|
||
/** 技能列表 */
|
||
const skillsList = ref<string[]>([])
|
||
const newSkillInput = ref('')
|
||
const handleAddSkill = () => {
|
||
const trimmed = newSkillInput.value.trim()
|
||
if (trimmed.length > 0 && !skillsList.value.includes(trimmed)) {
|
||
skillsList.value.push(trimmed)
|
||
newSkillInput.value = ''
|
||
} else { newSkillInput.value = '' }
|
||
}
|
||
const removeSkill = (index: number) => { skillsList.value.splice(index, 1) }
|
||
|
||
/** 证书列表 */
|
||
const certificatesList = ref<string[]>([])
|
||
const newCertificateInput = ref('')
|
||
const handleAddCertificate = () => {
|
||
const trimmed = newCertificateInput.value.trim()
|
||
if (trimmed.length > 0 && !certificatesList.value.includes(trimmed)) {
|
||
certificatesList.value.push(trimmed)
|
||
newCertificateInput.value = ''
|
||
} else { newCertificateInput.value = '' }
|
||
}
|
||
const removeCertificate = (index: number) => { certificatesList.value.splice(index, 1) }
|
||
|
||
/** 作品集链接 */
|
||
const portfolioUrl = ref('')
|
||
|
||
/** 个人概述文本 */
|
||
const summaryText = ref('')
|
||
|
||
/** AI润色结果 */
|
||
const aiPolishResult = ref('')
|
||
|
||
/** AI润色中状态 */
|
||
const aiPolishing = ref(false)
|
||
|
||
/** 调用AI润色接口 */
|
||
async function handleAiPolish() {
|
||
const text = summaryText.value.trim()
|
||
if (!text) {
|
||
ElMessage.warning('请先输入个人概述内容')
|
||
return
|
||
}
|
||
aiPolishing.value = true
|
||
fullLoadingText.value = 'AI正在润色中……'
|
||
fullLoading.value = true
|
||
try {
|
||
const res = await polishDiagnosisIssue([text])
|
||
if (res.code === 0 && res.data?.content) {
|
||
aiPolishResult.value = res.data.content.join('\n')
|
||
ElMessage.success('AI润色完成')
|
||
} else {
|
||
ElMessage.error(res.msg || '润色失败,请稍后重试')
|
||
}
|
||
} catch {
|
||
ElMessage.error('请求失败,请稍后重试')
|
||
} finally {
|
||
aiPolishing.value = false
|
||
fullLoading.value = false
|
||
}
|
||
}
|
||
|
||
/** 替换为AI润色内容 */
|
||
function handleReplaceWithAi() {
|
||
if (aiPolishResult.value) {
|
||
summaryText.value = aiPolishResult.value
|
||
}
|
||
}
|
||
|
||
/** 监听组件显示 — 初始化表单数据 */
|
||
watch(() => props.modelValue, (visible) => {
|
||
if (visible) {
|
||
if (props.module === 'info') {
|
||
formData.value = props.initialData ? { ...props.initialData } : {}
|
||
locationCodes.value = formData.value.location ? [formData.value.location] : []
|
||
} else if (props.module === 'education') {
|
||
if (props.initialData?.education?.length) {
|
||
educationList.value = props.initialData.education.map((item: any) => ({ ...item, description: item.description.map((d: any) => ({ ...d })) }))
|
||
} else { educationList.value = [createEmptyEducation()] }
|
||
} else if (props.module === 'work') {
|
||
if (props.initialData?.works?.length) {
|
||
workList.value = props.initialData.works.map((item: any) => ({ ...item, description: item.description.map((d: any) => ({ ...d })) }))
|
||
} else { workList.value = [createEmptyWork()] }
|
||
} else if (props.module === 'internship') {
|
||
if (props.initialData?.internships?.length) {
|
||
internshipList.value = props.initialData.internships.map((item: any) => ({ ...item, description: item.description.map((d: any) => ({ ...d })) }))
|
||
} else { internshipList.value = [createEmptyInternship()] }
|
||
} else if (props.module === 'project') {
|
||
if (props.initialData?.projects?.length) {
|
||
projectList.value = props.initialData.projects.map((item: any) => ({ ...item, description: item.description.map((d: any) => ({ ...d })) }))
|
||
} else { projectList.value = [createEmptyProject()] }
|
||
} else if (props.module === 'competition') {
|
||
if (props.initialData?.competitions?.length) {
|
||
competitionList.value = props.initialData.competitions.map((item: any) => ({ ...item, description: item.description.map((d: any) => ({ ...d })) }))
|
||
} else { competitionList.value = [createEmptyCompetition()] }
|
||
} else if (props.module === 'portfolio') {
|
||
portfolioUrl.value = props.initialData?.portfolioUrl || ''
|
||
} else if (props.module === 'summary') {
|
||
summaryText.value = props.initialData?.summary || ''
|
||
aiPolishResult.value = ''
|
||
} else if (props.module === 'skills') {
|
||
skillsList.value = props.initialData?.skills ? [...props.initialData.skills] : []
|
||
newSkillInput.value = ''
|
||
} else if (props.module === 'certificate') {
|
||
certificatesList.value = props.initialData?.certificates ? [...props.initialData.certificates] : []
|
||
newCertificateInput.value = ''
|
||
}
|
||
}
|
||
}, { immediate: true })
|
||
|
||
/** 关闭编辑(取消) */
|
||
const handleClose = () => {
|
||
emit('update:modelValue', false)
|
||
emit('cancel')
|
||
}
|
||
|
||
/** 保存数据 — 将表单数据通过事件传递给父组件 */
|
||
const handleSave = () => {
|
||
if (props.module === 'info') {
|
||
emit('save', { ...formData.value })
|
||
} else if (props.module === 'education') {
|
||
emit('save', { education: educationList.value.map(item => ({ ...item, description: item.description.map(d => ({ ...d })) })) })
|
||
} else if (props.module === 'work') {
|
||
emit('save', { works: workList.value.map(item => ({ ...item, description: item.description.map(d => ({ ...d })) })) })
|
||
} else if (props.module === 'internship') {
|
||
emit('save', { internships: internshipList.value.map(item => ({ ...item, description: item.description.map(d => ({ ...d })) })) })
|
||
} else if (props.module === 'project') {
|
||
emit('save', { projects: projectList.value.map(item => ({ ...item, description: item.description.map(d => ({ ...d })) })) })
|
||
} else if (props.module === 'competition') {
|
||
emit('save', { competitions: competitionList.value.map(item => ({ ...item, description: item.description.map(d => ({ ...d })) })) })
|
||
} else if (props.module === 'portfolio') {
|
||
emit('save', { portfolioUrl: portfolioUrl.value })
|
||
} else if (props.module === 'summary') {
|
||
emit('save', { summary: summaryText.value })
|
||
} else if (props.module === 'skills') {
|
||
emit('save', { skills: [...skillsList.value] })
|
||
} else if (props.module === 'certificate') {
|
||
emit('save', { certificates: [...certificatesList.value] })
|
||
}
|
||
emit('update:modelValue', false)
|
||
}
|
||
</script>
|