Files
offerpai_web/src/components/ProfileEditDrawer.vue
T

600 lines
32 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>