SpringBoot+Vue3 员工档案管理系统设计:一人一档、多维信息、一键生成用户——HRM 的数据基石

0 阅读20分钟

SpringBoot+Vue3 员工档案管理系统设计:一人一档、多维信息、一键生成用户——HRM 的数据基石

📦 源码1ruoyi-office-vben |📦 源码2ruoyi-office |📦 源码3ruoyi-office

员工档案是企业人力资源管理的"第一块砖"——考勤要关联人、薪资要绑定人、审批要选人、权限要分配人,所有 HRM 功能的起点都是"人"。但大多数企业的人事档案管理还停留在"Excel 表格 + 人工维护"的阶段:信息散落在多张表格里,入职填一遍、系统开户再录一遍、调部门又改一遍。RuoYi Office 用 1 张主表 + 3 张子表构建一人一档的完整数据模型,30+ 字段覆盖人事信息的所有维度,工号自动生成、一键创建系统用户、档案变更自动同步——让"人"的数据成为整个 HRM 体系的坚实基石。

引言:员工档案管理到底难在哪?

"不就是录个姓名电话吗?"——初次接到 HRM 需求的开发者大多这么想。但真正深入后才发现,员工档案管理的复杂度远超一张表单:

信息维度多,字段量大:姓名、性别、民族、政治面貌、婚姻状况、身份证、学历、血型、籍贯、身高体重——光基本信息就 15+ 个字段。再加上工作信息(部门/职位/职务/入职日期/转正日期)、联系信息(手机/邮箱/地址/紧急联系人)、银行信息(开户行/账号),一个人的完整档案需要 30+ 字段。

经历类信息无法扁平化:工作经历、教育经历、家属信息——这些都是"一对多"的关系,不能简单地加几列到主表里。每段工作经历有起止时间、公司名、职务;每段教育经历有学校、专业、起止时间。必须用子表来承载。

工号与系统用户的关系:每个员工需要唯一的工号,这个工号还要作为系统登录的用户名。手动创建用户容易出现"档案录了但忘了开账号"的问题,最好能一键从档案生成用户。

状态随流程变化:员工不是"录进来就完了"——试用期员工转正了、正式员工调岗了、有人离职了、有人退休了。这些状态变化需要由入职单、转正单、调动单、离职单等流程驱动,而不是人工手改状态。

档案是所有 HRM 模块的基础:考勤打卡要选人、薪资计算要选人、审批流程要选人——每个模块都需要一个"选人弹窗",而这个弹窗必须能过滤离职和退休人员,只展示在职员工。

本文以 RuoYi Office 的员工档案管理模块为例,完整拆解其数据建模、工号生成、用户同步、前端交互的设计方案。 employee-archive-architecture.png


一、业务设计:一人一档的数据模型

1.1 多维信息的组织方式

一个完整的员工档案需要承载四类信息,RuoYi Office 用 1 张主表 + 3 张子表 来组织:

信息类别存储方式核心字段特点
基本信息主表字段姓名/性别/生日/民族/政治面貌/婚姻/身份证/血型/学历/籍贯/身高/体重一人一行,字段固定
工作信息主表字段部门/职位/职务/入职日期/转正日期/工号/人员状态随流程单联动变更
联系与银行主表字段手机/邮箱/地址/紧急联系人/开户行/银行账号敏感信息,按需展示
经历信息3 张子表工作经历/教育经历/家属信息一对多关系,先删后插

1.2 员工状态与流程联动

员工的状态不是一个静态标签,而是由多个流程单据驱动流转的动态值:



入职 → 试用期(2) → 转正单审批通过 → 正式(1)
├── 调动单 → 变更部门/职位(状态不变)
├── 离职单 → 离职(6)
└── 退休 → 退休(7)

其他状态:实习生(3)、临时工(5)

状态枚举值触发来源后续可达状态
试用期2入职时默认正式(转正单)
正式1转正单审批通过离职/退休
实习生3手动设置正式/离职
临时工5手动设置离职
离职6离职单/定时任务无(终态)
退休7手动设置无(终态)

关键设计EmployeeServiceImpl 本身不做状态机——状态变更由各流程单的 Service 完成。转正单 EmployeeRegularBillServiceImpl 将员工置为正式(1),离职单 EmployeeResignationBillServiceImpl 将员工置为离职(6),调动单更新部门和职位但不改状态。这种职责分离让档案模块专注于数据管理。

1.3 工号与系统用户的映射关系

RuoYi Office 建立了员工工号 = 系统用户名的映射约定:


员工档案 (hrm_employee)
├── employeeNo = "10000001"   ← 唯一工号
├── userId = 123              ← 关联系统用户ID
└── userGenerated = true      ← 标记已生成

系统用户 (system_users)
├── username = "10000001"     ← 用户名 = 员工工号
├── nickname = "张三"         ← 昵称 = 员工姓名
└── password = "加密(123456)" ← 默认密码(可配置)

这种设计带来三个好处:工号即账号,员工容易记忆;一键生成,减少人工录入;档案更新时自动同步用户信息,数据永远一致。


二、系统设计:模块职责与设计决策

2.1 模块定位

员工档案管理位于 HRM 人力资源 → 员工管理 → 员工档案 目录下,是 HRM 体系的数据基础层:

定位说明
功能员工信息的增删改查、工号生成、系统用户创建与同步
消费方考勤模块、薪资模块、审批流程、员工选人弹窗
数据来源手动录入 + 入职单/转正单/调动单/离职单联动更新
面向角色HR 管理员(全量操作)、部门主管(查看本部门)、员工(查看自己)

2.2 核心设计决策

决策点方案理由
数据模型1 主表 + 3 子表主表放固定字段,子表放一对多的经历类信息
子表更新策略先删后插(全量替换)子表数据量小(通常 3-5 条),全量替换比差异对比更简单可靠
工号生成最大工号 + 1,格式 %08d8 位数字工号,递增有序,支持手动输入覆盖
用户创建一键生成,username = employeeNo减少手动开户的人工成本和遗漏风险
档案与用户同步更新档案时自动 syncEmployeeToUser修改姓名/手机/部门等信息后,系统用户自动跟着变
选人弹窗过滤默认排除离职(6)和退休(7)避免已离职人员出现在审批、考勤等业务选择中
删除级联删档案同时删系统用户和子表数据数据一致性,无孤儿记录

三、PC 端功能实现

3.1 员工档案列表

列表页采用 VxeGrid 表格 + 部门树筛选 的标准布局,支持按工号、姓名、部门、状态、入职日期范围搜索。 employee-archive-list.png

▲ 员工档案列表:工号列可点击跳转详情页,人员状态按字典渲染彩色标签(试用期/正式/离职等),"用户状态"列标识是否已生成系统用户

列表设计要点

  • 部门树选人:搜索区的"所属部门"使用 HelpInput 组件,点击弹出 DeptSelectModal 部门树弹窗。选中部门后自动填充 deptIddeptName 两个参数
  • 工号链接跳转:工号列通过 createRouterLinkColumn 渲染为可点击链接,点击后跳转到 /hrm/employee/employee-archive-info?id=xxx 详情页
  • 批量操作:复选框 + 工具栏按钮,支持批量删除 deleteEmployeeArchiveList 和批量生成用户 batchGenerateUserForEmployee
  • 用户状态标识userGenerated 字段渲染为自定义标签,已生成用户显示绿色标记,未生成显示灰色提示

3.2 员工档案详情

详情页是档案管理的核心交互页面,分为 三块表单 + 三张子表 的布局结构。 employee-archive-detail.png

▲ 档案详情页:左侧基本信息表单(两列布局,15+字段),右侧照片上传组件;下方工作信息表单(部门/职位/入职日期等),最后三张子表分区展示经历信息

三块 useVbenForm 表单

表单布局核心字段
BasicForm(基本信息)两列 grid姓名、性别、生日、民族、政治面貌、婚姻、身份证、学历、血型、籍贯、身高、体重、手机、邮箱、地址、紧急联系人、银行信息
AvatarForm(照片)单列垂直ImageUpload 组件,maxNumber: 1
WorkForm(工作信息)两列 grid部门(DeptSelectModal弹窗选择)、职位、职务、入职日期、转正日期、人员状态、所属公司(自动回填)

三张 a-table 子表

子表可编辑列操作
工作经历开始时间(DatePicker)、结束时间(DatePicker)、单位名称(Input)、职务(Input)新增行 / 删除行
教育经历开始时间(DatePicker)、结束时间(DatePicker)、学校名称(Input)、专业(Input)新增行 / 删除行
家属信息姓名(Input)、关系(Input)、手机(Input)、工作单位(Input)新增行 / 删除行

子表使用 ant-design-vue 的 Table 组件(非 VxeGrid),列配置中通过 h(DatePicker) / h(Input) 渲染可编辑单元格。只读模式下隐藏操作列和新增按钮,仅展示文本内容。

3.3 关键交互设计

身份证号 zod 校验

身份证号字段使用 zod 的正则校验,确保输入符合 18 位二代身份证格式:

{
  fieldName: 'idCard',
  label: '身份证号码',
  rules: z
    .string()
    .min(1, '身份证号不能为空')
    .regex(
      /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9X]$/i,
      '请输入18位有效身份证号',
    ),
  component: 'Input',
}

正则拆解:[1-9]\d{5} 地址码(6位)→ (18|19|20)\d{2} 出生年份 → (0[1-9]|1[0-2]) 月份 → (0[1-9]|[12]\d|3[01]) 日期 → \d{3}[0-9X] 顺序码+校验位。

公司自动回填

选择部门后,系统自动沿部门树向上查找 orgType === '1'(组织类型为"公司")的节点,将公司名称和 ID 回填到表单中:

export function findCompany(deptId: number): {
  id: number | undefined;
  name: string;
} {
  const dept = deptList.find((d) => d.id === deptId);
  if (!dept) return { name: '', id: undefined };
  if (dept.orgType === '1') {
    return { name: dept.name, id: dept.id };
  }
  function findParentCompany(parentId?: number): {
    id: number | undefined;
    name: string;
  } {
    const parent = deptList.find((d) => d.id === parentId);
    if (!parent) return { name: '', id: undefined };
    if (parent.orgType === '1') {
      return { name: parent.name, id: parent.id };
    }
    return findParentCompany(parent.parentId);
  }
  return findParentCompany(dept.parentId);
}

逻辑清晰:从当前部门出发,如果自身就是公司类型则直接返回;否则递归向上查找 parentId 对应的节点,直到找到 orgType === '1' 的公司节点。

3.4 员工选人弹窗

员工选人弹窗 EmployeeSelectModal 是 HRM 体系中被复用最多的组件——考勤配置选人、薪资关联选人、审批发起选人,都通过它完成。

弹窗采用与 SealSelectModal(印章选择)一致的交互模式:useVbenModal + useVbenVxeGridseparator: false),支持单选 radioConfig,双击行或点击确认按钮提交选中结果。

状态过滤能力

Props类型说明
excludeEmployeeStatusListnumber[]排除指定状态的员工(如 [6, 7] 排除离职和退休)
includeEmployeeStatusListnumber[]只显示指定状态的员工
showEmployeeStatusFilterboolean是否在搜索区展示状态筛选下拉框,默认 true

当两个列表都未指定时,后端 selectPageExcludeFormal 默认排除离职(6)和退休(7)状态的员工。搜索区还支持按部门树、工号、姓名、职位、职务筛选。


四、后端核心实现

4.1 数据模型关系

hrm_employee(员工主表 · 30+字段)
    ├── 1:N ──▶ hrm_employee_work_experience(工作经历)
    │              通过 employee_id 关联
    ├── 1:N ──▶ hrm_employee_education(教育经历)
    │              通过 employee_id 关联
    └── 1:N ──▶ hrm_employee_family(家属信息)
                   通过 employee_id 关联

    ──── 1:1 ──▶ system_users(系统用户)
                   通过 user_id 关联,username = employee_no

4.2 工号自动生成

工号生成的核心逻辑是取当前最大工号 + 1,格式化为 8 位数字字符串。当员工手动输入工号时跳过自动生成,但仍需校验唯一性:

private String generateEmployeeNo() {
    Long maxEmployeeNo = employeeArchiveMapper.selectMaxEmployeeNo();
    return String.format("%08d", maxEmployeeNo + 1);
}

selectMaxEmployeeNo() 的实现包含保底逻辑——如果数据库中没有员工记录,或最大工号不是纯数字,或小于 10000000,都返回 9999999L,使首个自动工号从 10000000 开始:

default Long selectMaxEmployeeNo() {
    EmployeeDO maxEmployee = selectOne(new LambdaQueryWrapperX<EmployeeDO>()
            .select(EmployeeDO::getEmployeeNo)
            .orderByDesc(EmployeeDO::getEmployeeNo)
            .last("LIMIT 1"));
    if (maxEmployee == null || maxEmployee.getEmployeeNo() == null) {
        return 9999999L;
    }
    try {
        Long employeeNo = Long.parseLong(maxEmployee.getEmployeeNo());
        return employeeNo >= 10000000 ? employeeNo : 9999999L;
    } catch (NumberFormatException e) {
        return 9999999L;
    }
}

工号唯一性通过双重校验保证——同时检查员工档案表和系统用户表:

private void validateEmployeeNoUnique(String employeeNo, Long employeeId, Long userId) {
    EmployeeDO existsEmployee = employeeArchiveMapper.selectByEmployeeNo(employeeNo);
    if (existsEmployee != null && !Objects.equals(existsEmployee.getId(), employeeId)) {
        throw exception(EMPLOYEE_NO_EXISTS);
    }
    CommonResult<AdminUserRespDTO> userResult = adminUserApi.getUserByUsername(employeeNo);
    AdminUserRespDTO existsUser = userResult != null && userResult.isSuccess()
            ? userResult.getData() : null;
    if (existsUser != null && !Objects.equals(existsUser.getId(), userId)) {
        throw exception(EMPLOYEE_NO_EXISTS);
    }
}

4.3 一键生成系统用户

生成用户是档案管理中最有价值的一个功能——把"录完档案还要手动创建账号"的两步操作合并为一步:

@Transactional(rollbackFor = Exception.class)
public Long generateUserForEmployee(Long employeeId) {
    EmployeeDO employee = employeeArchiveMapper.selectById(employeeId);
    if (employee == null) {
        throw exception(EMPLOYEE_ARCHIVE_NOT_EXISTS);
    }
    if (employee.getUserGenerated() != null && employee.getUserGenerated()
            && employee.getUserId() != null) {
        throw new RuntimeException("该员工已生成用户,无需重复生成");
    }

    AdminUserCreateReqDTO userCreateReqDTO = new AdminUserCreateReqDTO();
    userCreateReqDTO.setUsername(employee.getEmployeeNo());
    userCreateReqDTO.setNickname(employee.getName());
    userCreateReqDTO.setMobile(employee.getMobile());
    userCreateReqDTO.setEmail(employee.getEmail());
    userCreateReqDTO.setSex(employee.getSex());
    userCreateReqDTO.setAvatar(employee.getAvatar());
    userCreateReqDTO.setDeptId(employee.getDeptId());

    String initPassword = "123456";
    CommonResult<String> configResult =
            configApi.getConfigValueByKey("system.user.init-password");
    if (configResult != null && configResult.isSuccess()
            && configResult.getData() != null) {
        initPassword = configResult.getData();
    }
    userCreateReqDTO.setPassword(initPassword);

    Long userId = adminUserApi.createUser(userCreateReqDTO).getCheckedData();

    EmployeeDO updateObj = new EmployeeDO();
    updateObj.setId(employeeId);
    updateObj.setUserId(userId);
    updateObj.setUserGenerated(true);
    employeeArchiveMapper.updateById(updateObj);

    return userId;
}

设计细节

  • 防止重复生成:通过 userGenerated + userId 双重判断,已生成过的员工直接拒绝
  • 密码可配置:优先从系统配置 system.user.init-password 读取初始密码,配置不存在时降级为 "123456"
  • 信息映射:username = 工号、nickname = 姓名、mobile/email/sex/avatar/deptId 全量同步
  • 批量生成batchGenerateUserForEmployee 循环调用单条生成,单条失败吞异常继续处理下一个,保证批量操作的容错性

4.4 档案与用户同步

当 HR 更新员工档案(如修改手机号、调整部门)时,如果该员工已生成系统用户,则自动同步变更:

@Transactional(rollbackFor = Exception.class)
public void updateEmployeeArchive(EmployeeSaveReqVO updateReqVO) {
    EmployeeDO oldEmployee = employeeArchiveMapper.selectById(updateReqVO.getId());
    if (oldEmployee == null) {
        throw exception(EMPLOYEE_ARCHIVE_NOT_EXISTS);
    }
    prepareAndValidateEmployeeNo(updateReqVO, oldEmployee.getId(), oldEmployee.getUserId());

    EmployeeDO updateObj = BeanUtils.toBean(updateReqVO, EmployeeDO.class);
    employeeArchiveMapper.updateById(updateObj);

    // 先删后插:全量替换子表数据
    employeeWorkExperienceMapper.deleteByEmployeeId(updateReqVO.getId());
    employeeEducationMapper.deleteByEmployeeId(updateReqVO.getId());
    employeeFamilyMapper.deleteByEmployeeId(updateReqVO.getId());
    saveWorkExperiences(updateReqVO.getId(), updateReqVO.getWorkExperienceList());
    saveEducations(updateReqVO.getId(), updateReqVO.getEducationList());
    saveFamilies(updateReqVO.getId(), updateReqVO.getFamilyList());

    // 档案→用户自动同步
    if (oldEmployee.getUserGenerated() != null && oldEmployee.getUserGenerated()
            && oldEmployee.getUserId() != null) {
        syncEmployeeToUser(updateReqVO, oldEmployee.getUserId());
    }
}

syncEmployeeToUser 将工号、姓名、手机、邮箱、性别、头像、部门、备注同步到系统用户,不改密码——这是一个重要的安全决策,避免 HR 修改档案时意外重置员工密码。

4.5 删除级联清理

删除档案时需要同时清理系统用户和三张子表,保持数据一致性:

@Transactional(rollbackFor = Exception.class)
public void deleteEmployeeArchive(Long id) {
    EmployeeDO employee = employeeArchiveMapper.selectById(id);
    if (employee == null) {
        throw exception(EMPLOYEE_ARCHIVE_NOT_EXISTS);
    }
    // 如果已生成用户,同步删除
    if (employee.getUserGenerated() != null && employee.getUserGenerated()
            && employee.getUserId() != null) {
        adminUserApi.deleteUser(employee.getUserId());
    }
    employeeArchiveMapper.deleteById(id);
    employeeWorkExperienceMapper.deleteByEmployeeId(id);
    employeeEducationMapper.deleteByEmployeeId(id);
    employeeFamilyMapper.deleteByEmployeeId(id);
}

批量删除 deleteEmployeeArchiveList 先收集所有需要删除的 userId,调用 adminUserApi.deleteUserList 批量删除用户,再逐个清理子表。


五、RuoYi Office 的创新设计

5.1 工号 = 用户名:一个字段串联两个系统

传统做法是员工档案和系统用户各自维护用户名,容易出现"工号 A001 的员工,登录账号却是 zhangsan"的混乱。RuoYi Office 用 employeeNo 同时作为档案工号和系统用户名,一个字段打通 HRM 和权限两个体系

创建时双重唯一性校验(档案表 + 用户表),更新时自动同步——整个生命周期内工号和用户名始终保持一致。

5.2 先删后插:子表更新的务实选择

经历类子表(工作/教育/家属)的更新策略有两种选择:差异对比(比较新旧数据,逐行增删改)vs 先删后插(清空旧数据,全量写入新数据)。

RuoYi Office 选择了先删后插。原因很简单——每个员工的工作经历通常 3-5 条,教育经历 2-3 条,家属信息 2-4 条。在这个数据量级下,差异对比的代码复杂度远高于其带来的性能收益。全量替换只需 deleteByEmployeeId + 循环 insert,逻辑清晰、不易出错。

5.3 公司自动回填:沿部门树向上查找

组织架构通常是"集团 → 公司 → 部门 → 子部门"的树形结构。员工选择所属部门后,系统自动沿 parentId 向上遍历,找到 orgType === '1'(公司节点)后回填公司名称和 ID。

这个设计消除了"员工在 A 公司的市场部,结果手动选了 B 公司"的人工失误。

5.4 选人弹窗的状态过滤

EmployeeSelectModal 的 Props 设计非常灵活:excludeEmployeeStatusListincludeEmployeeStatusList 两个参数支持黑名单和白名单两种过滤模式。

考勤模块调用时传 excludeEmployeeStatusList: [6, 7] 排除离职和退休;薪资模块调用时可能传 includeEmployeeStatusList: [1] 只选正式员工。同一个组件,通过 Props 适配不同的业务场景。

5.5 批量操作的容错设计

batchGenerateUserForEmployee 对每个员工单独 try-catch,单条失败不影响后续处理。这在实际业务中非常实用——假设选了 20 个员工批量生成用户,其中 2 个因工号冲突失败,剩下 18 个仍然正常完成。


六、数据结构

6.1 表结构:hrm_employee(员工主表)

字段类型说明
idbigint主键
employee_novarchar(50)员工工号(唯一,= 系统用户名)
namevarchar(100)姓名
sextinyint性别(1男 2女)
birthdaydate出生日期
blood_typetinyint血型(1A 2B 3AB 4O)
educationvarchar(50)文化程度
nationvarchar(50)民族
political_statusvarchar(50)政治面貌
marital_statusvarchar(50)婚姻状况
job_titlevarchar(100)职称
native_placevarchar(200)籍贯
heightdecimal(5,2)身高(cm)
weightdecimal(5,2)体重(kg)
id_cardvarchar(18)身份证号
mobilevarchar(20)手机号
emailvarchar(100)邮箱
household_addressvarchar(500)户籍地址
current_addressvarchar(500)现住址
emergency_contactvarchar(100)紧急联系人
emergency_phonevarchar(20)紧急联系电话
avatarvarchar(500)员工照片
bank_namevarchar(200)工资开户行
bank_accountvarchar(50)工资卡号
job_postvarchar(100)职位
job_positionvarchar(100)职务
employee_statustinyint人员状态(1正式 2试用 3实习 5临时 6离职 7退休)
dept_idbigint部门ID
dept_namevarchar(200)部门名称(冗余)
company_idbigint公司ID
company_namevarchar(200)单位名称(冗余)
entry_datedate入职日期
formal_datedate转正日期
remarkvarchar(500)备注
user_idbigint关联系统用户ID
user_generatedtinyint(1)是否已生成用户

6.2 表结构:hrm_employee_work_experience(工作经历)

字段类型说明
idbigint主键
employee_idbigint员工ID(外键)
start_timedate开始时间
end_timedate结束时间
company_namevarchar(200)单位名称
job_positionvarchar(100)职务

6.3 表结构:hrm_employee_education(教育经历)

字段类型说明
idbigint主键
employee_idbigint员工ID(外键)
start_timedate开始时间
end_timedate结束时间
school_namevarchar(200)学校名称
majorvarchar(200)专业

6.4 表结构:hrm_employee_family(家属信息)

字段类型说明
idbigint主键
employee_idbigint员工ID(外键)
namevarchar(100)姓名
relationshipvarchar(50)与本人关系
mobilevarchar(20)联系电话
work_unitvarchar(200)工作单位

6.5 设计要点

  • 冗余存储:主表冗余了 deptNamecompanyName,避免列表页大量 JOIN 查询。部门名称变更时由业务操作更新,而非依赖实时查询
  • 双向关联user_id 指向系统用户,user_generated 标记是否已生成。两个字段配合使用,避免仅靠 user_id 是否为空来判断(可能被手动清空)
  • 状态留白:枚举值 4 空缺,预留给"兼职"等未来可能新增的状态类型
  • 身份证唯一但非数据库约束:身份证号通过前端 zod 校验格式,但数据库层面未加唯一索引——因为实习生和临时工可能暂未提供身份证

七、技术亮点总结

设计要点实现方式价值
1主表+3子表hrm_employee + 工作/教育/家属子表固定信息与经历信息分离,数据结构清晰
工号自动生成selectMaxEmployeeNo() + 1%08d 格式化8位递增工号,支持手动覆盖,保底从10000000起
工号 = 用户名username = employeeNo 全生命周期绑定一个字段打通 HRM 与权限系统
一键生成用户generateUserForEmployee 单条/批量减少手动开户的人工成本和遗漏风险
档案→用户同步syncEmployeeToUser 更新时自动触发修改档案后系统用户信息自动跟着变
先删后插deleteByEmployeeId + 循环 insert子表更新逻辑简单可靠,适合小数据量场景
状态流程联动转正单/离职单各自修改 employeeStatus职责分离,档案模块不做状态机
选人弹窗过滤exclude/includeEmployeeStatusList Props一个组件适配所有业务场景的选人需求
公司自动回填findCompany() 沿部门树向上查找消除人工选择公司的失误
身份证 zod 校验正则匹配 18 位二代身份证格式前端拦截无效输入,减少后端压力
批量操作容错单条 try-catch,失败不中断批量20个员工生成用户,2个失败不影响其余18个
删除级联档案+用户+3张子表一起清理数据一致性,无孤儿记录

八、快速体验

操作路径:HRM 人力资源 → 员工管理 → 员工档案

推荐体验流程

  1. 新建档案:进入员工档案列表,点击「新建」,填写姓名、性别、手机号等基本信息,上传员工照片
  2. 选择部门:在工作信息区域点击「所属部门」弹出部门树,选择后观察公司名称自动回填
  3. 录入经历:展开工作经历、教育经历、家属信息三个区域,逐行录入历史记录
  4. 观察工号:不填写工号直接保存,观察系统自动生成的 8 位工号(如 10000001
  5. 生成用户:回到列表页,勾选刚创建的员工,点击「生成用户」,观察用户状态变为绿色标记
  6. 验证登录:用生成的工号作为用户名、123456 作为密码,尝试登录系统
  7. 修改同步:编辑档案修改手机号,保存后到系统管理 → 用户管理中验证手机号已自动同步
  8. 选人弹窗:到考勤或其他模块的选人弹窗中,确认离职/退休员工不会出现在列表中

源码仓库

平台地址
GitCode(后端)gitcode.com/zhouzhongya…
GitCode(前端)gitcode.com/zhouzhongya…
GitHub(后端)github.com/yuqing2026/…

结语

员工档案看起来是 HRM 系统中最"基础"的模块,但它是所有人力资源功能的数据原点。1 主表 + 3 子表的分层模型让固定信息与经历信息各得其所,工号自动生成 + 一键创建用户让"录档案"和"开账号"合二为一,先删后插的子表策略在小数据量场景下做到了逻辑简洁与功能完备的平衡,状态流程联动让档案模块不必关心"为什么变"只需响应"变成什么"。

这套数据建模思路不仅适用于员工档案,还可以推广到供应商管理、客户档案、设备台账等"主表 + 多张子表"的实体管理场景。核心思想是:用主表承载确定性的固定信息,用子表承载不确定数量的经历/明细信息,用唯一标识串联多个系统,用流程驱动状态而非手动维护。

如果你正在设计企业 HRM 系统的员工管理模块,或者对"一对多子表管理"和"系统间数据同步"感兴趣,欢迎参考源码实现。


💡 想要体验 RuoYi Office 的强大功能?

🌐 在线演示ruoyioffice.com/web/(账号 admin / admin123)

📦 GitCode 开源gitcode.com/zhouzhongya…

💬 技术咨询:添加💬 17156169080,备注「RuoYi Office」

如果觉得不错,请给个 Star 支持一下!