260604-简历信息模块开发记录.md 16 KB

简历信息模块开发记录

日期: 2026-06-04

模块概述

简历信息模块(resume)是招聘管理系统中个人基本信息模块的子模块,负责管理求职者的简历信息及其关联的子表数据。该模块前端基于 Vue3 + Ant Design Vue,采用 JeecgBoot 的前后端分离架构。

业务关系

  • ResumeInfo(简历信息):关联表,与个人信息关联,记录简历概要(简历名称、是否公开、有效期、是否默认简历、个人优势、其他证书)
  • 简历子表(8个):求职意向、工作经历、教育经历、培训经历、职业技能、职称情况、获奖情况、留学经历,与简历形成一对多关系

模块文件结构

前端(jeecgboot-vue3/src/views/recruitment/personal/)

文件 用途
components/ResumeInfoForm.vue 简历信息表单组件,包含主表字段和 8 个子表 JVxeTable
components/ResumeInfoModal.vue 简历信息弹窗组件,包裹 ResumeInfoForm
components/ResumeListModal.vue 简历列表弹窗组件,查看个人信息关联的简历列表

后端(jeecg-boot-module/jeecg-module-zjrs/.../org/jeecg/modules/zjrs/personal/)

层次 文件
Service Impl service/impl/ResumeInfoServiceImpl.java

开发记录

功能开发

日期 内容 涉及文件 说明
2026-06-04 默认简历互斥逻辑 ResumeInfoServiceImpl.java 新增/编辑简历时,若设置当前简历为默认,则同人员其他简历自动置为非默认

2026-06-04:默认简历互斥逻辑

需求描述: 一个求职者只能有一份默认简历。在新增或编辑简历时,如果用户将当前简历的"是否默认简历"设置为"是"( isDefault = "1"),保存成功后系统应自动将该人员的其他所有简历的默认状态清除(置为 "0"),确保同一人员下最多只有一份默认简历。

实现方案:ResumeInfoServiceImpl 中新增私有方法 clearOtherDefaultResumes(),使用 MyBatis-Plus 的 lambdaUpdate() 按人员ID匹配、排除当前操作的简历ID,将匹配到的其他简历的 isDefault 字段批量更新为 "0"。在 saveMainAndSub()updateMainAndSub() 方法中,保存或更新主表成功后,判断 dto.getIsDefault() == "1" 时调用该方法。

涉及文件:

  • jeecg-boot-module/jeecg-module-zjrs/.../personal/service/impl/ResumeInfoServiceImpl.java

主要逻辑代码:

// saveMainAndSub() 中:保存主表后调用
if ("1".equals(dto.getIsDefault())) {
    clearOtherDefaultResumes(dto.getPersonalId(), main.getId());
}

// updateMainAndSub() 中:更新主表后调用
if ("1".equals(dto.getIsDefault())) {
    clearOtherDefaultResumes(dto.getPersonalId(), main.getId());
}

/**
 * 清除指定人员下除当前简历外的其他简历的默认状态
 */
private void clearOtherDefaultResumes(String personalId, String excludeResumeId) {
    this.lambdaUpdate()
            .eq(ResumeInfo::getPersonalId, personalId)
            .ne(ResumeInfo::getId, excludeResumeId)
            .set(ResumeInfo::getIsDefault, "0")
            .update();
}

Bug 修复

2026-06-04:详情查看时表单未禁用修复(先编辑后详情场景)

问题描述: 在简历列表弹窗(ResumeListModal)中,先点击"修改"后关闭弹窗,再点击"查看"时,表单字段和子表编辑功能未处于禁用/只读状态。

复现条件: 页面刷新后先点修改再点查看才会出现;页面刷新后直接点查看则正常禁用。

原因分析: Ant Design Vue 的 <a-modal> 默认 destroyOnClose=false,弹窗关闭时内部 ResumeInfoForm 组件仍然挂载在 DOM 中。

  • 先点击"修改"时,ResumeInfoForm 以 formDisabled=false 的状态挂载
  • 关闭弹窗后组件保持挂载
  • 再点击"查看"时,虽然 disableSubmit 被设为 true,但由于组件实例未重新创建,props 的响应式更新未能正确触发 JFormContainer 的 <fieldset disabled> 状态变更

修复方案: 使用 :key 方式强制组件重建(与 PersonalInfoModal 相同方案)。

  • 在 ResumeInfoModal 中引入 formKey ref 和 nextFormKey() 方法
  • <ResumeInfoForm> 上绑定 :key="formKey"
  • 每次打开弹窗(add/edit)时先调用 nextFormKey() 递增 key,触发 Vue 重建组件实例
  • 由于组件始终挂载在 DOM 中(非 v-if 控制),ref 在重建完成后自然可用

涉及文件:

  • jeecgboot-vue3/src/views/recruitment/personal/components/ResumeInfoModal.vue
    • 新增 formKey ref(初始值 0)
    • 新增 nextFormKey() 方法,递增 formKey
    • <ResumeInfoForm> 上添加 :key="formKey" 绑定
    • add()edit() 方法中在设置 visible=true 之前调用 nextFormKey()

2026-06-04:日期转换检查

问题描述: 检查 ResumeInfoForm 中是否存在与 PersonalInfoForm 相同的日期字符串未转为 dayjs 对象的问题。

检查结果: ResumeInfoForm.vue 中日期字段已正确处理,无需修复。

  • validDate 字段在 edit() 方法中已将字符串转为 dayjs() 对象
  • 子表日期字段(startDateendDateissueDate)已通过 convertDateFields() 工具方法统一转换
  • ResumeListModal.vue 仅作为列表容器,不直接管理表单状态,无此问题

2026-06-04:求职意向从子表迁移至主表

需求描述: 求职意向(expectedIndustry、expectedPosition、positionName、expectedSalary、workLocation、workNature)之前被错误地设计为子表(RESUME_INTENTION),实际需求中这些字段应属于简历主表(RESUME_INFO)的一对一字段,不存在一对多关系。

修复方案:

  1. SQL — 将求职意向6个字段移入 RESUME_INFO 主表,删除 RESUME_INTENTION 子表 DDL
  2. 实体类 — 在 ResumeInfo.java 中新增求职意向相关字段(含 BigDecimal 类型的 expectedSalary)
  3. DTO/VO — ResumeInfoDTO 新增求职意向字段并移除 resumeIntentionList;ResumeInfoPage 同步调整
  4. Service — ResumeInfoServiceImpl 移除 resumeIntentionMapper 的注入及 save/update/delete 中的子表 CRUD 代码
  5. Controller — 移除 resumeIntentionMapper 注入、queryIntentionListByMainId 接口、queryById 中的意向列表查询
  6. 删除文件 — 删除 ResumeIntention.java、ResumeIntentionMapper.java、ResumeIntentionMapper.xml、IResumeIntentionService.java、ResumeIntentionServiceImpl.java 共5个文件
  7. 前端表单 — ResumeInfoForm.vue 移除求职意向 JVxeTable 子表 tab,在原有主表行区域末尾添加6个求职意向表单字段;移除 intentionColumns 导入、intentionTableRef、intentionData、computedIntentionColumns 等
  8. 前端列定义 — ResumeInfo.data.ts 移除 intentionColumns 导出

涉及文件:

  • .docs/sql/个人信息与简历信息.sql — 求职意向字段合并入主表,删除子表 DDL
  • jeecg-boot-module/jeecg-module-zjrs/.../personal/entity/ResumeInfo.java — 新增6个求职意向字段
  • jeecg-boot-module/jeecg-module-zjrs/.../personal/vo/ResumeInfoDTO.java — 新增求职意向字段,移除 resumeIntentionList
  • jeecg-boot-module/jeecg-module-zjrs/.../personal/vo/ResumeInfoPage.java — 新增求职意向字段,移除 resumeIntentionList
  • jeecg-boot-module/jeecg-module-zjrs/.../personal/service/impl/ResumeInfoServiceImpl.java — 移除意向子表注入及 CRUD
  • jeecg-boot-module/jeecg-module-zjrs/.../personal/controller/ResumeInfoController.java — 移除意向子表注入及接口
  • jeecgboot-vue3/src/views/recruitment/personal/components/ResumeInfoForm.vue — 移除意向子表 tab,新增6个主表表单字段
  • jeecgboot-vue3/src/views/recruitment/personal/ResumeInfo.data.ts — 移除 intentionColumns 导出 - 删除文件:ResumeIntention.java、ResumeIntentionMapper.java、ResumeIntentionMapper.xml、IResumeIntentionService.java、ResumeIntentionServiceImpl.java

2026-06-04:RESUME_INFO 主表 NOT NULL 字段前后端校验补全

需求描述: 按照框架开发规范(后端规范 第七节 + 前端规范第四节),对 RESUME_INFO 表中所有 NOT NULL 字段,前后端均应有对应的数据校验。

检查结果: 以下6个 NOT NULL 字段(排除自增主键 ID 和自动填充的 CREATE_TIME/UPDATE_TIME)均缺少校验:

字段 SQL 后端JSR303 前端required
personalId NOT NULL 缺失 缺失(系统内部填充,非用户输入)
expectedPosition NOT NULL 缺失 缺失
positionName NOT NULL 缺失 缺失
expectedSalary NOT NULL 缺失 缺失
workLocation NOT NULL 缺失 缺失
workNature NOT NULL 缺失 缺失

修复方案:

  1. ResumeInfoDTO.java — 新增 import jakarta.validation.constraints.NotBlank/NotNull,对 NOT NULL 字段添加注解:
    • String 字段 → @NotBlank(message = "...")
    • BigDecimal 字段 → @NotNull(message = "...")
  2. ResumeInfoController.javaadd()edit() 方法的 DTO 参数添加 @Validated 注解,触发 JSR303 校验
  3. ResumeInfoForm.vuevalidatorRules 新增 expectedPosition、positionName、expectedSalary、workLocation、workNature 的 required: true 规则

涉及文件:

  • jeecg-boot-module/jeecg-module-zjrs/.../personal/vo/ResumeInfoDTO.java — 新增 JSR303 注解及 import
  • jeecg-boot-module/jeecg-module-zjrs/.../personal/controller/ResumeInfoController.java — add/edit 加 @Validated
  • jeecgboot-vue3/src/views/recruitment/personal/components/ResumeInfoForm.vue — validatorRules 新增5个必填规则

2026-06-04:子表区域从Tabs改为纵向依次排列

需求描述: 简历表单中子表区域原来使用 <a-tabs> 组件以标签页方式展示7个子表(工作经历、教育经历等),新需求改为不使用 tabs,所有子表纵向依次排列,各子表之间用 <a-divider> 分隔。

修复方案: 在 ResumeInfoForm.vue 中:

  • 模板:移除 <a-tabs><a-tab-pane> 包裹层,每个子表的标题改用 <a-divider orientation="left"> 显示,JVxeTable 直接放在下方
  • 脚本:移除不再需要的 activeKey ref 和 handleChangeTabs() 方法(该方法用于 tab 切换时重置滚动位置,改为纵向排列后不再需要)

涉及文件:

  • jeecgboot-vue3/src/views/recruitment/personal/components/ResumeInfoForm.vue — 移除 tabs,子表纵向排布,移除 activeKey/handleChangeTabs

2026-06-04:简历表单元数据字典化(主表 workNature + 子表字典字段)

需求描述: 将简历信息表单中部分字段从普通输入框(input/radio)改为下拉选择框,选项数据来源于数据字典。涉及以下字段:

主表字段:

字段 字典编码 变更前 变更后
workNature(工作性质) WorkNature 硬编码 radio 组(全职/实习) <a-select> 下拉选择

子表 JVxeTable 列:

子表 字典编码 变更前 变更后
工作经历 companyType(单位类型) CompanyType <a-input> JVxeTypes.slot 下拉选择
工作经历 companySize(人员规模) CompanySize <a-input> JVxeTypes.slot 下拉选择
教育经历 education(学历) Education <a-input> JVxeTypes.slot 下拉选择
教育经历 degree(学位) Degree <a-input> JVxeTypes.slot 下拉选择
教育经历 eduSystem(学制) EduSystem <a-input> JVxeTypes.slot 下拉选择
职业技能 skillLevel(技能等级) SkillLevel <a-input> JVxeTypes.slot 下拉选择
职称情况 level(级别) TitleLevel <a-input> JVxeTypes.slot 下拉选择

实现方案:

  1. 列定义文件(ResumeInfo.data.ts) — 将上述7个 JVxeTable 列的 typeJVxeTypes.input 改为 JVxeTypes.slot ,并指定对应的 slotName(如 companyTypeSloteducationSlot 等),placeholder 同步调整为"请选择"。
  2. 表单组件(ResumeInfoForm.vue)
    • 引入 useDict composable,预加载 WorkNatureCompanyTypeCompanySizeEducationDegreeEduSystemSkillLevelTitleLevel 共8个数据字典
    • 通过 computed 为每个字典创建对应的选项列表(如 workNatureOptionscompanyTypeOptions 等)
    • 将主表 workNature 字段由硬编码的 <a-radio-group> 改为 <a-select :options="workNatureOptions" />
    • 为每个 JVxeTable 添加插槽模板,使用 <a-select> 绑定对应的字典选项,通过 triggerChange 将选中值同步回表格数据源

涉及文件:

  • jeecgboot-vue3/src/views/recruitment/personal/ResumeInfo.data.ts — 7个字典列由 JVxeTypes.input 改为 JVxeTypes.slot,添加 slotName 配置
  • jeecgboot-vue3/src/views/recruitment/personal/components/ResumeInfoForm.vue — 引入 useDict 加载8个字典,主表 workNature 改为 <a-select>,为5个 JVxeTable 添加字典下拉插槽模板

2026-06-05:详情查看改用独立的 a-descriptions + a-table 详情弹窗

问题描述: 原简历详情查看功能复用 ResumeInfoModal + ResumeInfoForm,打开的是禁用状态的编辑表单,布局与编辑界面相同但字段不可编辑,体验与人员信息详情查看一致。

解决方案: 新建独立的 ResumeInfoDetailModal 组件,主表字段使用 a-descriptions 描述列表展示,子表数据使用 a-table 只读展示。

实现要点:

  • 基本信息区 — 使用 a-descriptions :column="3" bordered 展示简历名称、有效期、是否公开、是否默认简历 4 个字段
  • 求职意向区 — 带 <a-divider> 分隔标题,展示期望行业、期望职位、职位名称、期望薪酬、工作地点、工作性质(字典转换)、个人优势(全宽)、证书名称(全宽)
  • 7个子表 — 工作经历、教育经历、培训经历、职业技能、职称情况、获奖情况、留学经历,均使用 a-table bordered size="small" 只读展示,字典字段(公司类型、人员规模、学历、学位、学制、技能等级、职称级别)通过 getDictText 转换
  • 弹窗默认为全屏模式(:fullscreen="true"),底部仅保留「关闭」按钮
  • 内容区域添加 padding: 14px 20px 内边距,解决内容紧贴弹窗边框问题
  • 修复具名插槽 <template #footer> 未作为 j-modal 直接子节点导致的 Vue 编译报错

涉及文件:

  • jeecgboot-vue3/src/views/recruitment/personal/components/ResumeInfoDetailModal.vue — 新增简历详情弹窗组件
  • jeecgboot-vue3/src/views/recruitment/personal/components/ResumeListModal.vue — 导入新组件,修改 handleView 方法调用新弹窗