1307 lines
52 KiB
Vue
1307 lines
52 KiB
Vue
<template>
|
||
<!-- 个人资料编辑抽屉 — 从右侧滑入,占满屏幕高度 -->
|
||
<Teleport to="body">
|
||
<!-- 遮罩层 -->
|
||
<Transition name="profile-drawer-overlay">
|
||
<div
|
||
v-if="modelValue"
|
||
class="profile-drawer-overlay"
|
||
@click="handleClose"
|
||
/>
|
||
</Transition>
|
||
|
||
<!-- 抽屉主体 -->
|
||
<Transition name="profile-drawer-slide">
|
||
<div v-if="modelValue" class="profile-drawer" @click.stop>
|
||
<!-- 顶部栏:关闭按钮 + 模块标题 -->
|
||
<div class="profile-drawer__header">
|
||
<button class="profile-drawer__close-btn" @click="handleClose" aria-label="关闭">
|
||
<svg viewBox="0 0 16 16" fill="none" class="profile-drawer__close-icon">
|
||
<path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||
</svg>
|
||
</button>
|
||
<h3 class="profile-drawer__title">{{ moduleTitle }}</h3>
|
||
</div>
|
||
|
||
<!-- 表单内容区 — 根据模块名动态渲染不同字段 -->
|
||
<div class="profile-drawer__body">
|
||
<!-- ========== 基本信息模块 ========== -->
|
||
<template v-if="module === 'info'">
|
||
<div
|
||
v-for="field in infoFields"
|
||
:key="field.key"
|
||
class="profile-drawer__field"
|
||
>
|
||
<label class="profile-drawer__label">
|
||
<span v-if="field.required" class="profile-drawer__required">*</span>{{ 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-drawer__input"
|
||
:type="field.type || 'text'"
|
||
:placeholder="field.placeholder"
|
||
v-model="formData[field.key]"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ========== 教育经历模块 ========== -->
|
||
<template v-else-if="module === 'education'">
|
||
<div
|
||
v-for="(edu, index) in educationList"
|
||
:key="index"
|
||
class="profile-drawer__edu-card"
|
||
>
|
||
<!-- 教育经历标题栏:序号 + 删除按钮 -->
|
||
<div class="profile-drawer__edu-header">
|
||
<span class="profile-drawer__edu-index">
|
||
<svg viewBox="0 0 16 16" fill="none" class="profile-drawer__edu-index-icon">
|
||
<circle cx="8" cy="8" r="3" fill="currentColor"/>
|
||
</svg>
|
||
教育经历{{ index + 1 }}
|
||
</span>
|
||
<button
|
||
v-if="educationList.length > 1"
|
||
class="profile-drawer__edu-delete"
|
||
@click="removeEducation(index)"
|
||
aria-label="删除"
|
||
>
|
||
<svg viewBox="0 0 16 16" fill="none" class="profile-drawer__edu-delete-icon">
|
||
<path d="M3 4h10M6 4V3a1 1 0 011-1h2a1 1 0 011 1v1M5 4v8a1 1 0 001 1h4a1 1 0 001-1V4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 学校 -->
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>学校
|
||
</label>
|
||
<div class="profile-drawer__select-wrap">
|
||
<input class="profile-drawer__input" placeholder="请输入学校名称" v-model="edu.school" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 专业 -->
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>专业
|
||
</label>
|
||
<div class="profile-drawer__select-wrap">
|
||
<input class="profile-drawer__input" placeholder="请输入专业名称" v-model="edu.major" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 学历类型 + 学历(横排) -->
|
||
<div class="profile-drawer__row">
|
||
<div class="profile-drawer__field profile-drawer__field--half">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>学历类型
|
||
</label>
|
||
<div class="profile-drawer__select-wrap">
|
||
<select class="profile-drawer__input" v-model="edu.studyType">
|
||
<option v-for="opt in studyTypeOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="profile-drawer__field profile-drawer__field--half">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>学历
|
||
</label>
|
||
<div class="profile-drawer__select-wrap">
|
||
<select class="profile-drawer__input" v-model="edu.degree">
|
||
<option v-for="opt in degreeOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 入学年份 + 毕业年份(横排) -->
|
||
<div class="profile-drawer__row">
|
||
<div class="profile-drawer__field profile-drawer__field--half">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>入学年份
|
||
</label>
|
||
<el-date-picker
|
||
v-model="edu.startDate"
|
||
type="month"
|
||
placeholder="请选择入学年份"
|
||
format="YYYY-MM"
|
||
value-format="YYYY-MM"
|
||
class="profile-drawer__date-picker"
|
||
:teleported="true"
|
||
popper-class="profile-drawer-date-popper"
|
||
/>
|
||
</div>
|
||
<div class="profile-drawer__field profile-drawer__field--half">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>毕业年份
|
||
</label>
|
||
<el-date-picker
|
||
v-model="edu.endDate"
|
||
type="month"
|
||
placeholder="请选择毕业年份"
|
||
format="YYYY-MM"
|
||
value-format="YYYY-MM"
|
||
class="profile-drawer__date-picker"
|
||
:teleported="true"
|
||
popper-class="profile-drawer-date-popper"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 经历描述 — 分段编辑,每段带删除按钮 -->
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">经历描述</label>
|
||
<!-- 描述段落列表 -->
|
||
<div
|
||
v-for="(desc, dIdx) in edu.description"
|
||
:key="desc.id"
|
||
class="profile-drawer__desc-item"
|
||
>
|
||
<textarea
|
||
class="profile-drawer__textarea profile-drawer__textarea--short"
|
||
placeholder="请输入经历描述"
|
||
v-model="desc.text"
|
||
rows="4"
|
||
></textarea>
|
||
<!-- 段落内右侧删除按钮 -->
|
||
<button
|
||
v-if="edu.description.length > 1"
|
||
class="profile-drawer__desc-remove"
|
||
@click="removeEducationDescriptionParagraph(index, dIdx)"
|
||
aria-label="删除该段描述"
|
||
>
|
||
<svg viewBox="0 0 16 16" fill="none" class="profile-drawer__desc-remove-icon">
|
||
<path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
<!-- 新增一段描述按钮 -->
|
||
<button class="profile-drawer__add-btn profile-drawer__add-btn--small" @click="addEducationDescriptionParagraph(index)">
|
||
<span class="profile-drawer__add-icon">+</span> 新增一段描述
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 新增教育经历按钮 -->
|
||
<button class="profile-drawer__add-btn wp100 mb20" @click="addEducation">
|
||
<span class="profile-drawer__add-icon">+</span> 新增教育经历
|
||
</button>
|
||
</template>
|
||
|
||
<!-- ========== 工作经历模块 ========== -->
|
||
<template v-else-if="module === 'work'">
|
||
<div
|
||
v-for="(work, index) in workList"
|
||
:key="index"
|
||
class="profile-drawer__edu-card"
|
||
>
|
||
<!-- 工作经历标题栏:序号 + 删除按钮 -->
|
||
<div class="profile-drawer__edu-header">
|
||
<span class="profile-drawer__edu-index">
|
||
<svg viewBox="0 0 16 16" fill="none" class="profile-drawer__edu-index-icon">
|
||
<circle cx="8" cy="8" r="3" fill="currentColor"/>
|
||
</svg>
|
||
工作经历{{ index + 1 }}
|
||
</span>
|
||
<button
|
||
v-if="workList.length > 1"
|
||
class="profile-drawer__edu-delete"
|
||
@click="removeWork(index)"
|
||
aria-label="删除"
|
||
>
|
||
<svg viewBox="0 0 16 16" fill="none" class="profile-drawer__edu-delete-icon">
|
||
<path d="M3 4h10M6 4V3a1 1 0 011-1h2a1 1 0 011 1v1M5 4v8a1 1 0 001 1h4a1 1 0 001-1V4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 公司名称 -->
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>公司名称
|
||
</label>
|
||
<div class="profile-drawer__select-wrap">
|
||
<input class="profile-drawer__input" placeholder="请输入公司名称" v-model="work.companyName" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 职位 -->
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>职位
|
||
</label>
|
||
<div class="profile-drawer__select-wrap">
|
||
<input class="profile-drawer__input" placeholder="请输入职位" v-model="work.position" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 开始时间 + 结束时间(横排) -->
|
||
<div class="profile-drawer__row">
|
||
<div class="profile-drawer__field profile-drawer__field--half">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>开始时间
|
||
</label>
|
||
<el-date-picker
|
||
v-model="work.startDate"
|
||
type="month"
|
||
placeholder="请输入开始时间"
|
||
format="YYYY-MM"
|
||
value-format="YYYY-MM"
|
||
class="profile-drawer__date-picker"
|
||
:teleported="true"
|
||
popper-class="profile-drawer-date-popper"
|
||
/>
|
||
</div>
|
||
<div class="profile-drawer__field profile-drawer__field--half">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>结束时间
|
||
</label>
|
||
<el-date-picker
|
||
v-model="work.endDate"
|
||
type="month"
|
||
placeholder="请输入结束时间"
|
||
format="YYYY-MM"
|
||
value-format="YYYY-MM"
|
||
class="profile-drawer__date-picker"
|
||
:teleported="true"
|
||
popper-class="profile-drawer-date-popper"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 经历描述 — 分段编辑,每段带删除按钮 -->
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>经历描述
|
||
</label>
|
||
<!-- 描述段落列表 -->
|
||
<div
|
||
v-for="(desc, dIdx) in work.description"
|
||
:key="desc.id"
|
||
class="profile-drawer__desc-item"
|
||
>
|
||
<textarea
|
||
class="profile-drawer__textarea profile-drawer__textarea--short"
|
||
placeholder="请输入经历描述"
|
||
v-model="desc.text"
|
||
rows="4"
|
||
></textarea>
|
||
<!-- 段落内右侧删除按钮 -->
|
||
<button
|
||
v-if="work.description.length > 1"
|
||
class="profile-drawer__desc-remove"
|
||
@click="removeWorkDescriptionParagraph(index, dIdx)"
|
||
aria-label="删除该段描述"
|
||
>
|
||
<svg viewBox="0 0 16 16" fill="none" class="profile-drawer__desc-remove-icon">
|
||
<path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
<!-- 新增一段描述按钮 -->
|
||
<button class="profile-drawer__add-btn profile-drawer__add-btn--small" @click="addWorkDescriptionParagraph(index)">
|
||
<span class="profile-drawer__add-icon">+</span> 新增一段描述
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 新增工作经历按钮 -->
|
||
<button class="profile-drawer__add-btn wp100 mb20" @click="addWork">
|
||
<span class="profile-drawer__add-icon">+</span> 新增工作经历
|
||
</button>
|
||
</template>
|
||
|
||
<!-- ========== 实习经历模块 ========== -->
|
||
<template v-else-if="module === 'internship'">
|
||
<div
|
||
v-for="(intern, index) in internshipList"
|
||
:key="index"
|
||
class="profile-drawer__edu-card"
|
||
>
|
||
<!-- 实习经历标题栏:序号 + 删除按钮 -->
|
||
<div class="profile-drawer__edu-header">
|
||
<span class="profile-drawer__edu-index">
|
||
<svg viewBox="0 0 16 16" fill="none" class="profile-drawer__edu-index-icon">
|
||
<circle cx="8" cy="8" r="3" fill="currentColor"/>
|
||
</svg>
|
||
实习经历{{ index + 1 }}
|
||
</span>
|
||
<button
|
||
v-if="internshipList.length > 1"
|
||
class="profile-drawer__edu-delete"
|
||
@click="removeInternship(index)"
|
||
aria-label="删除"
|
||
>
|
||
<svg viewBox="0 0 16 16" fill="none" class="profile-drawer__edu-delete-icon">
|
||
<path d="M3 4h10M6 4V3a1 1 0 011-1h2a1 1 0 011 1v1M5 4v8a1 1 0 001 1h4a1 1 0 001-1V4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 公司名称 -->
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>公司名称
|
||
</label>
|
||
<div class="profile-drawer__select-wrap">
|
||
<input class="profile-drawer__input" placeholder="请输入公司名称" v-model="intern.companyName" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 职位 -->
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>职位
|
||
</label>
|
||
<div class="profile-drawer__select-wrap">
|
||
<input class="profile-drawer__input" placeholder="请输入职位" v-model="intern.position" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 开始时间 + 结束时间(横排) -->
|
||
<div class="profile-drawer__row">
|
||
<div class="profile-drawer__field profile-drawer__field--half">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>开始时间
|
||
</label>
|
||
<el-date-picker
|
||
v-model="intern.startDate"
|
||
type="month"
|
||
placeholder="请输入开始时间"
|
||
format="YYYY-MM"
|
||
value-format="YYYY-MM"
|
||
class="profile-drawer__date-picker"
|
||
:teleported="true"
|
||
popper-class="profile-drawer-date-popper"
|
||
/>
|
||
</div>
|
||
<div class="profile-drawer__field profile-drawer__field--half">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>结束时间
|
||
</label>
|
||
<el-date-picker
|
||
v-model="intern.endDate"
|
||
type="month"
|
||
placeholder="请输入结束时间"
|
||
format="YYYY-MM"
|
||
value-format="YYYY-MM"
|
||
class="profile-drawer__date-picker"
|
||
:teleported="true"
|
||
popper-class="profile-drawer-date-popper"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 经历描述 — 分段编辑,每段带删除按钮 -->
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>经历描述
|
||
</label>
|
||
<!-- 描述段落列表 -->
|
||
<div
|
||
v-for="(desc, dIdx) in intern.description"
|
||
:key="desc.id"
|
||
class="profile-drawer__desc-item"
|
||
>
|
||
<textarea
|
||
class="profile-drawer__textarea profile-drawer__textarea--short"
|
||
placeholder="请输入经历描述"
|
||
v-model="desc.text"
|
||
rows="4"
|
||
></textarea>
|
||
<!-- 段落内右侧删除按钮 -->
|
||
<button
|
||
v-if="intern.description.length > 1"
|
||
class="profile-drawer__desc-remove"
|
||
@click="removeInternDescriptionParagraph(index, dIdx)"
|
||
aria-label="删除该段描述"
|
||
>
|
||
<svg viewBox="0 0 16 16" fill="none" class="profile-drawer__desc-remove-icon">
|
||
<path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
<!-- 新增一段描述按钮 -->
|
||
<button class="profile-drawer__add-btn profile-drawer__add-btn--small" @click="addInternDescriptionParagraph(index)">
|
||
<span class="profile-drawer__add-icon">+</span> 新增一段描述
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 新增实习经历按钮 -->
|
||
<button class="profile-drawer__add-btn wp100 mb20" @click="addInternship">
|
||
<span class="profile-drawer__add-icon">+</span> 新增实习经历
|
||
</button>
|
||
</template>
|
||
|
||
<!-- ========== 项目经历模块 ========== -->
|
||
<template v-else-if="module === 'project'">
|
||
<div
|
||
v-for="(proj, index) in projectList"
|
||
:key="index"
|
||
class="profile-drawer__edu-card"
|
||
>
|
||
<!-- 项目经历标题栏:序号 + 删除按钮 -->
|
||
<div class="profile-drawer__edu-header">
|
||
<span class="profile-drawer__edu-index">
|
||
<svg viewBox="0 0 16 16" fill="none" class="profile-drawer__edu-index-icon">
|
||
<circle cx="8" cy="8" r="3" fill="currentColor"/>
|
||
</svg>
|
||
项目经历{{ index + 1 }}
|
||
</span>
|
||
<button
|
||
v-if="projectList.length > 1"
|
||
class="profile-drawer__edu-delete"
|
||
@click="removeProject(index)"
|
||
aria-label="删除"
|
||
>
|
||
<svg viewBox="0 0 16 16" fill="none" class="profile-drawer__edu-delete-icon">
|
||
<path d="M3 4h10M6 4V3a1 1 0 011-1h2a1 1 0 011 1v1M5 4v8a1 1 0 001 1h4a1 1 0 001-1V4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 项目名称 -->
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>项目名称
|
||
</label>
|
||
<div class="profile-drawer__select-wrap">
|
||
<input class="profile-drawer__input" placeholder="请输入项目名称" v-model="proj.projectName" />
|
||
<!-- <svg viewBox="0 0 16 16" fill="none" class="profile-drawer__select-arrow"><path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg> -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 项目角色 -->
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>项目角色
|
||
</label>
|
||
<div class="profile-drawer__select-wrap">
|
||
<input class="profile-drawer__input" placeholder="请输入项目角色" v-model="proj.role" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 开始时间 + 结束时间(横排) -->
|
||
<div class="profile-drawer__row">
|
||
<div class="profile-drawer__field profile-drawer__field--half">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>开始时间
|
||
</label>
|
||
<el-date-picker
|
||
v-model="proj.startDate"
|
||
type="month"
|
||
placeholder="请输入开始时间"
|
||
format="YYYY-MM"
|
||
value-format="YYYY-MM"
|
||
class="profile-drawer__date-picker"
|
||
:teleported="true"
|
||
popper-class="profile-drawer-date-popper"
|
||
/>
|
||
</div>
|
||
<div class="profile-drawer__field profile-drawer__field--half">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>结束时间
|
||
</label>
|
||
<el-date-picker
|
||
v-model="proj.endDate"
|
||
type="month"
|
||
placeholder="请输入结束时间"
|
||
format="YYYY-MM"
|
||
value-format="YYYY-MM"
|
||
class="profile-drawer__date-picker"
|
||
:teleported="true"
|
||
popper-class="profile-drawer-date-popper"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 经历描述 — 分段编辑,每段带删除按钮 -->
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>经历描述
|
||
</label>
|
||
<!-- 描述段落列表 -->
|
||
<div
|
||
v-for="(desc, dIdx) in proj.description"
|
||
:key="desc.id"
|
||
class="profile-drawer__desc-item"
|
||
>
|
||
<textarea
|
||
class="profile-drawer__textarea profile-drawer__textarea--short"
|
||
placeholder="请输入经历描述"
|
||
v-model="desc.text"
|
||
rows="4"
|
||
></textarea>
|
||
<!-- 段落内右侧删除按钮 -->
|
||
<button
|
||
v-if="proj.description.length > 1"
|
||
class="profile-drawer__desc-remove"
|
||
@click="removeDescriptionParagraph(index, dIdx)"
|
||
aria-label="删除该段描述"
|
||
>
|
||
<svg viewBox="0 0 16 16" fill="none" class="profile-drawer__desc-remove-icon">
|
||
<path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
<!-- 新增一段描述按钮 -->
|
||
<button class="profile-drawer__add-btn profile-drawer__add-btn--small" @click="addDescriptionParagraph(index)">
|
||
<span class="profile-drawer__add-icon">+</span> 新增一段描述
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 新增项目经历按钮 -->
|
||
<button class="profile-drawer__add-btn wp100 mb20" @click="addProject">
|
||
<span class="profile-drawer__add-icon">+</span> 新增项目经历
|
||
</button>
|
||
</template>
|
||
|
||
<!-- ========== 竞赛模块 ========== -->
|
||
<template v-else-if="module === 'competition'">
|
||
<div
|
||
v-for="(comp, index) in competitionList"
|
||
:key="index"
|
||
class="profile-drawer__edu-card"
|
||
>
|
||
<!-- 竞赛经历标题栏:序号 + 删除按钮 -->
|
||
<div class="profile-drawer__edu-header">
|
||
<span class="profile-drawer__edu-index">
|
||
<svg viewBox="0 0 16 16" fill="none" class="profile-drawer__edu-index-icon">
|
||
<circle cx="8" cy="8" r="3" fill="currentColor"/>
|
||
</svg>
|
||
竞赛经历{{ index + 1 }}
|
||
</span>
|
||
<button
|
||
v-if="competitionList.length > 1"
|
||
class="profile-drawer__edu-delete"
|
||
@click="removeCompetition(index)"
|
||
aria-label="删除"
|
||
>
|
||
<svg viewBox="0 0 16 16" fill="none" class="profile-drawer__edu-delete-icon">
|
||
<path d="M3 4h10M6 4V3a1 1 0 011-1h2a1 1 0 011 1v1M5 4v8a1 1 0 001 1h4a1 1 0 001-1V4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 竞赛名称 -->
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>竞赛名称
|
||
</label>
|
||
<div class="profile-drawer__select-wrap">
|
||
<input class="profile-drawer__input" placeholder="请输入竞赛名称" v-model="comp.competitionName" />
|
||
<!-- <svg viewBox="0 0 16 16" fill="none" class="profile-drawer__select-arrow"><path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg> -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 获奖名次 -->
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>获奖名次
|
||
</label>
|
||
<input class="profile-drawer__input" placeholder="请输入获奖名次" v-model="comp.award" />
|
||
</div>
|
||
|
||
<!-- 获奖时间 -->
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">
|
||
<span class="profile-drawer__required">*</span>获奖时间
|
||
</label>
|
||
<el-date-picker
|
||
v-model="comp.awardDate"
|
||
type="month"
|
||
placeholder="请选择获奖时间"
|
||
format="YYYY-MM"
|
||
value-format="YYYY-MM"
|
||
class="profile-drawer__date-picker"
|
||
:teleported="true"
|
||
popper-class="profile-drawer-date-popper"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 获奖情况描述 — 单段 textarea -->
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">获奖情况描述</label>
|
||
<textarea
|
||
class="profile-drawer__textarea"
|
||
placeholder="请输入获奖详情"
|
||
v-model="comp.description[0].text"
|
||
rows="5"
|
||
></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 新增获奖经历按钮 -->
|
||
<button class="profile-drawer__add-btn mb20" @click="addCompetition">
|
||
<span class="profile-drawer__add-icon">+</span> 新增获奖经历
|
||
</button>
|
||
</template>
|
||
|
||
<!-- ========== 作品集模块 ========== -->
|
||
<template v-else-if="module === 'portfolio'">
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">作品集链接</label>
|
||
<textarea
|
||
class="profile-drawer__textarea"
|
||
placeholder="请输入/粘贴作品集链接"
|
||
v-model="portfolioUrl"
|
||
rows="6"
|
||
></textarea>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ========== 技能模块 ========== -->
|
||
<template v-else-if="module === 'skills'">
|
||
<!-- 技能标签列表 -->
|
||
<div class="profile-drawer__skills-list">
|
||
<div
|
||
v-for="(skill, index) in skillsList"
|
||
:key="index"
|
||
class="profile-drawer__skill-tag"
|
||
>
|
||
<span class="profile-drawer__skill-text">{{ skill }}</span>
|
||
<button
|
||
class="profile-drawer__skill-remove"
|
||
@click="removeSkill(index)"
|
||
aria-label="删除技能"
|
||
>
|
||
<svg viewBox="0 0 16 16" fill="none" class="profile-drawer__skill-remove-icon">
|
||
<path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 添加技能输入框 -->
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">添加新技能</label>
|
||
<input
|
||
class="profile-drawer__input"
|
||
type="text"
|
||
placeholder="输入技能名称后按回车或失焦自动添加"
|
||
v-model="newSkillInput"
|
||
@keydown.enter="handleAddSkill"
|
||
@blur="handleAddSkill"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ========== 证书模块 ========== -->
|
||
<template v-else-if="module === 'certificate'">
|
||
<!-- 证书标签列表 -->
|
||
<div class="profile-drawer__skills-list">
|
||
<div
|
||
v-for="(cert, index) in certificatesList"
|
||
:key="index"
|
||
class="profile-drawer__skill-tag"
|
||
>
|
||
<span class="profile-drawer__skill-text">{{ cert }}</span>
|
||
<button
|
||
class="profile-drawer__skill-remove"
|
||
@click="removeCertificate(index)"
|
||
aria-label="删除证书"
|
||
>
|
||
<svg viewBox="0 0 16 16" fill="none" class="profile-drawer__skill-remove-icon">
|
||
<path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 添加证书输入框 -->
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">添加新证书</label>
|
||
<input
|
||
class="profile-drawer__input"
|
||
type="text"
|
||
placeholder="输入证书名称后按回车或失焦自动添加"
|
||
v-model="newCertificateInput"
|
||
@keydown.enter="handleAddCertificate"
|
||
@blur="handleAddCertificate"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ========== 个人概述模块(仅简历使用) ========== -->
|
||
<template v-else-if="module === 'summary'">
|
||
<div class="profile-drawer__field">
|
||
<label class="profile-drawer__label">个人概述</label>
|
||
<textarea
|
||
class="profile-drawer__textarea"
|
||
placeholder="请输入个人概述,简要介绍自己的优势和职业目标"
|
||
v-model="summaryText"
|
||
rows="8"
|
||
></textarea>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ========== 其他模块占位 ========== -->
|
||
<template v-else>
|
||
<div class="profile-drawer__empty">
|
||
<p class="profile-drawer__empty-text">{{ moduleTitle }}编辑功能开发中…</p>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- 底部保存按钮 -->
|
||
<div class="profile-drawer__footer">
|
||
<button class="profile-drawer__save-btn" @click="handleSave">保存</button>
|
||
</div>
|
||
</div>
|
||
</Transition>
|
||
</Teleport>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, watch } from 'vue'
|
||
import { useStore } from 'vuex'
|
||
import RegionSelector from '@/components/tools/RegionSelector.vue'
|
||
|
||
/** 教育经历单条数据结构 — 对应数据库 bg_user_profile_education */
|
||
interface EducationItem {
|
||
/** 记录ID(已有经历从后端获取,新增经历无此字段) */
|
||
id?: string
|
||
/** 学校名称 */
|
||
school: string
|
||
/** 专业 */
|
||
major: string
|
||
/** 学历类型(数字模式:0=全日制 1=非全日制;文本模式:中文字符串) */
|
||
studyType: number | string
|
||
/** 学历(数字模式:1=大专 2=本科 3=硕士 4=博士;文本模式:中文字符串) */
|
||
degree: number | string
|
||
/** 入学时间(日期选择器使用字符串,格式:YYYY-MM) */
|
||
startDate: string
|
||
/** 毕业时间(日期选择器使用字符串,格式:YYYY-MM) */
|
||
endDate: string
|
||
/** 描述段落数组 */
|
||
description: DescriptionParagraph[]
|
||
}
|
||
|
||
/** 实习经历单条数据结构 — 对应数据库 bg_user_profile_internship */
|
||
interface InternshipItem {
|
||
/** 记录ID(已有经历从后端获取,新增经历无此字段) */
|
||
id?: string
|
||
/** 公司名称 */
|
||
companyName: string
|
||
/** 职位 */
|
||
position: string
|
||
/** 开始时间 */
|
||
startDate: string
|
||
/** 结束时间 */
|
||
endDate: string
|
||
/** 描述段落数组 */
|
||
description: DescriptionParagraph[]
|
||
}
|
||
|
||
/** 工作经历单条数据结构 — 对应数据库 bg_user_profile_work */
|
||
interface WorkItem {
|
||
/** 记录ID(已有经历从后端获取,新增经历无此字段) */
|
||
id?: string
|
||
/** 公司名称 */
|
||
companyName: string
|
||
/** 职位 */
|
||
position: string
|
||
/** 开始时间 */
|
||
startDate: string
|
||
/** 结束时间 */
|
||
endDate: string
|
||
/** 描述段落数组 */
|
||
description: DescriptionParagraph[]
|
||
}
|
||
|
||
/** 描述段落单条数据结构 — 对应数据库 description JSON 数组中的一项 */
|
||
interface DescriptionParagraph {
|
||
/** 前端生成的短标识,用于简历优化时精确定位段落 */
|
||
id: string
|
||
/** 段落文本内容 */
|
||
text: string
|
||
}
|
||
|
||
/** 项目经历单条数据结构 — 对应数据库 bg_user_profile_project */
|
||
interface ProjectItem {
|
||
/** 记录ID(已有经历从后端获取,新增经历无此字段) */
|
||
id?: string
|
||
/** 项目名称 */
|
||
projectName: string
|
||
/** 所属公司 */
|
||
companyName: string
|
||
/** 担任角色 */
|
||
role: string
|
||
/** 开始时间 */
|
||
startDate: string
|
||
/** 结束时间 */
|
||
endDate: string
|
||
/** 描述段落数组 */
|
||
description: DescriptionParagraph[]
|
||
}
|
||
|
||
/** 竞赛经历单条数据结构 — 对应数据库 bg_user_profile_competition */
|
||
interface CompetitionItem {
|
||
/** 记录ID(已有经历从后端获取,新增经历无此字段) */
|
||
id?: string
|
||
/** 竞赛名称 */
|
||
competitionName: string
|
||
/** 获奖情况 */
|
||
award: string
|
||
/** 获奖时间 */
|
||
awardDate: string
|
||
/** 描述段落(仅一段),格式:[{id, text}] */
|
||
description: DescriptionParagraph[]
|
||
}
|
||
|
||
/** 组件 Props */
|
||
const props = defineProps<{
|
||
/** 控制抽屉显示/隐藏 */
|
||
modelValue: boolean
|
||
/** 模块名称标识,如 'info' | 'education' | 'internship' 等 */
|
||
module: string
|
||
/** 初始数据 — 不同模块传入不同 JSON 结构 */
|
||
initialData?: Record<string, any>
|
||
/** 教育经历是否使用中文文本作为选项值(默认 false 使用数字编码,true 则输出中文字符串) */
|
||
useEducationTextValue?: boolean
|
||
}>()
|
||
|
||
/** 学历类型选项映射 — 根据 useEducationTextValue 决定 value 类型 */
|
||
const studyTypeOptions = computed(() => props.useEducationTextValue
|
||
? [{ value: '全日制', label: '全日制' }, { value: '非全日制', label: '非全日制' }]
|
||
: [{ value: 0, label: '全日制' }, { value: 1, label: '非全日制' }]
|
||
)
|
||
|
||
/** 学历选项映射 — 根据 useEducationTextValue 决定 value 类型 */
|
||
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
|
||
}>()
|
||
|
||
/** Vuex store 实例 — 用于从地区树中查找城市名称 */
|
||
const store = useStore()
|
||
|
||
/** 模块名称映射表 — 模块标识 → 中文标题 */
|
||
const moduleTitleMap: Record<string, string> = {
|
||
info: '基本信息',
|
||
summary: '个人概述',
|
||
education: '教育经历',
|
||
work: '工作经历',
|
||
internship: '实习经历',
|
||
project: '项目经历',
|
||
portfolio: '作品集',
|
||
skills: '技能',
|
||
competition: '竞赛',
|
||
certificate: '证书',
|
||
}
|
||
|
||
/** 当前模块的中文标题 */
|
||
const moduleTitle = computed(() => moduleTitleMap[props.module] || props.module)
|
||
|
||
/** 基本信息模块的字段配置 */
|
||
const infoFields = [
|
||
{ key: 'name', label: '姓名', placeholder: '请输入姓名', required: true },
|
||
{ key: 'email', label: '邮箱', placeholder: '请输入邮箱', required: true, type: 'email' },
|
||
{ key: 'phone', label: '电话', placeholder: '请输入联系电话', required: true, type: 'tel' },
|
||
{ key: 'location', label: '所在城市', placeholder: '请输入所在城市', required: true },
|
||
{ key: 'wechat', label: '微信', placeholder: '请输入微信号', required: false },
|
||
]
|
||
|
||
/** 表单数据 — 基本信息模块使用 */
|
||
const formData = ref<Record<string, any>>({})
|
||
|
||
/** 城市选择器当前选中的地区编码数组 */
|
||
const locationCodes = ref<string[]>([])
|
||
|
||
/** 地区选择器触发按钮的自定义样式 — 与抽屉内输入框保持一致 */
|
||
const regionTriggerStyle: Record<string, string> = {
|
||
width: '100%',
|
||
'box-sizing': 'border-box',
|
||
padding: '0.1rem 0.14rem',
|
||
'font-size': '0.13rem',
|
||
color: '#1a1a2e',
|
||
background: '#f6f6f9',
|
||
border: '1px solid transparent',
|
||
'border-radius': '0.06rem',
|
||
'max-width': 'none',
|
||
'justify-content': 'space-between',
|
||
|
||
}
|
||
|
||
/** 城市选择器确认回调 — 更新 locationCodes,同时将编码和城市名同步到 formData */
|
||
function onLocationChange(codes: string[]) {
|
||
locationCodes.value = codes
|
||
// 取第一个编码作为 regionCode 存入 formData
|
||
formData.value.location = codes[0] || ''
|
||
// 根据编码从地区树中查找城市名称,用于页面展示
|
||
formData.value.locationName = resolveRegionName(codes[0] || '')
|
||
}
|
||
|
||
/** 根据城市编码从 store 地区树中查找对应的城市名称 */
|
||
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: '' }],
|
||
})
|
||
|
||
/** 生成短标识 ID — 用于描述段落的唯一标识 */
|
||
const generateId = (): string => {
|
||
return Math.random().toString(36).substring(2, 8)
|
||
}
|
||
|
||
/** 为指定项目新增一条描述段落 */
|
||
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 addProject = () => {
|
||
projectList.value.push(createEmptyProject())
|
||
}
|
||
|
||
/** 删除指定索引的项目经历 */
|
||
const removeProject = (index: number) => {
|
||
projectList.value.splice(index, 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)
|
||
}
|
||
|
||
/** 技能列表 — 技能模块使用 */
|
||
const skillsList = ref<string[]>([])
|
||
|
||
/** 作品集链接 — 作品集模块使用 */
|
||
const portfolioUrl = ref('')
|
||
|
||
/** 个人概述文本 — 个人概述模块使用 */
|
||
const summaryText = ref('')
|
||
|
||
/** 新技能输入框的值 */
|
||
const newSkillInput = ref('')
|
||
|
||
/** 添加新技能 — 当输入长度大于0的非空字符串时添加 */
|
||
const handleAddSkill = () => {
|
||
const trimmedSkill = newSkillInput.value.trim()
|
||
// 只有当输入的内容去除空格后长度大于0,且不重复时才添加
|
||
if (trimmedSkill.length > 0 && !skillsList.value.includes(trimmedSkill)) {
|
||
skillsList.value.push(trimmedSkill)
|
||
newSkillInput.value = ''
|
||
} else if (trimmedSkill.length === 0) {
|
||
// 如果是空字符串,直接清空输入框
|
||
newSkillInput.value = ''
|
||
}
|
||
}
|
||
|
||
/** 删除指定索引的技能 */
|
||
const removeSkill = (index: number) => {
|
||
skillsList.value.splice(index, 1)
|
||
}
|
||
|
||
/** 证书列表 — 证书模块使用 */
|
||
const certificatesList = ref<string[]>([])
|
||
|
||
/** 新证书输入框的值 */
|
||
const newCertificateInput = ref('')
|
||
|
||
/** 添加新证书 — 当输入长度大于0的非空字符串时添加 */
|
||
const handleAddCertificate = () => {
|
||
const trimmedCertificate = newCertificateInput.value.trim()
|
||
// 只有当输入的内容去除空格后长度大于0,且不重复时才添加
|
||
if (trimmedCertificate.length > 0 && !certificatesList.value.includes(trimmedCertificate)) {
|
||
certificatesList.value.push(trimmedCertificate)
|
||
newCertificateInput.value = ''
|
||
} else if (trimmedCertificate.length === 0) {
|
||
// 如果是空字符串,直接清空输入框
|
||
newCertificateInput.value = ''
|
||
}
|
||
}
|
||
|
||
/** 删除指定索引的证书 */
|
||
const removeCertificate = (index: number) => {
|
||
certificatesList.value.splice(index, 1)
|
||
}
|
||
|
||
/** 监听抽屉打开 — 初始化表单数据并锁定页面滚动 */
|
||
watch(() => props.modelValue, (visible) => {
|
||
if (visible) {
|
||
if (props.module === 'info') {
|
||
// 基本信息:用初始数据填充表单
|
||
formData.value = props.initialData ? { ...props.initialData } : {}
|
||
// 将城市编码同步到 locationCodes,确保 RegionSelector 能正确回显
|
||
locationCodes.value = formData.value.location ? [formData.value.location] : []
|
||
} else if (props.module === 'education') {
|
||
// 教育经历:用初始数据填充列表,若无则创建一条空记录(深拷贝 description 数组)
|
||
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') {
|
||
// 工作经历:用初始数据填充列表,若无则创建一条空记录(深拷贝 description 数组)
|
||
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') {
|
||
// 实习经历:用初始数据填充列表,若无则创建一条空记录(深拷贝 description 数组)
|
||
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 || ''
|
||
} 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 = ''
|
||
}
|
||
document.body.style.overflow = 'hidden'
|
||
} else {
|
||
document.body.style.overflow = ''
|
||
}
|
||
})
|
||
|
||
/** 关闭抽屉 */
|
||
const handleClose = () => {
|
||
emit('update:modelValue', false)
|
||
}
|
||
|
||
/** 保存数据 — 将表单数据通过事件传递给父组件 */
|
||
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>
|