feat(hrm): 新增员工紧急联系人管理功能

完成员工紧急联系人模块的全流程开发,包括:
1. 数据库表结构、Mapper、Service、Controller后端代码
2. 前端页面、API接口、导入导出功能
3. 配套SQL脚本、导入模板与使用文档
4. 支持批量导入导出、数据校验与用户关联匹配
This commit is contained in:
2026-05-26 19:19:12 +08:00
parent 81e529a2dd
commit 12076c5d0b
19 changed files with 1509 additions and 0 deletions

View File

@@ -0,0 +1,117 @@
package com.ruoyi.hrm.controller;
import com.alibaba.excel.EasyExcel;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.hrm.domain.bo.HrmEmergencyContactBo;
import com.ruoyi.hrm.domain.vo.HrmEmergencyContactImportVo;
import com.ruoyi.hrm.domain.vo.HrmEmergencyContactVo;
import com.ruoyi.hrm.service.IHrmEmergencyContactService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/hrm/emergencyContact")
public class HrmEmergencyContactController extends BaseController {
private final IHrmEmergencyContactService service;
@GetMapping("/list")
public TableDataInfo<HrmEmergencyContactVo> list(HrmEmergencyContactBo bo, PageQuery pageQuery) {
return service.queryPageList(bo, pageQuery);
}
@GetMapping("/{contactId}")
public R<HrmEmergencyContactVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long contactId) {
return R.ok(service.queryById(contactId));
}
@Log(title = "紧急联系人管理", businessType = BusinessType.INSERT)
@PostMapping
public R<Void> add(@Validated @RequestBody HrmEmergencyContactBo bo) {
return toAjax(service.insertByBo(bo));
}
@Log(title = "紧急联系人管理", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> edit(@Validated @RequestBody HrmEmergencyContactBo bo) {
return toAjax(service.updateByBo(bo));
}
@Log(title = "紧急联系人管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{contactIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] contactIds) {
return toAjax(service.deleteWithValidByIds(Arrays.asList(contactIds), true));
}
@Log(title = "紧急联系人管理", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HrmEmergencyContactBo bo, HttpServletResponse response) {
List<HrmEmergencyContactVo> list = service.queryList(bo);
ExcelUtil.exportExcel(list, "紧急联系人数据", HrmEmergencyContactVo.class, response);
}
@PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<String> importData(@RequestPart("file") MultipartFile file, @RequestParam(defaultValue = "false") boolean updateSupport) throws IOException {
List<HrmEmergencyContactImportVo> list = ExcelUtil.importExcel(file.getInputStream(), HrmEmergencyContactImportVo.class, false).getList();
String msg = service.importByVoList(list, updateSupport);
return R.ok(msg);
}
@PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) throws IOException {
// 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("紧急联系人导入模板", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
// 示例数据(字段名第一行,示例数据第二行)
List<HrmEmergencyContactImportVo> exampleList = new ArrayList<>();
// 添加示例数据
HrmEmergencyContactImportVo example = new HrmEmergencyContactImportVo();
example.setHireDate("2025.06.18");
example.setCompanyName("山东福安德信息科技有限公司");
example.setDeptName("信息化部");
example.setRealName("张三");
example.setPhone("183xxxxxxxx");
example.setIdCard("341xxxxxxxxxxxxxxx");
example.setGender("");
example.setAge(23);
example.setEmergencyContact("李四");
example.setRelationship("母子");
example.setEmergencyPhone1("159xxxxxxxx");
example.setEmergencyPhone2("");
example.setEmergencyAddress("xx省xx市xx县xx镇xx村");
example.setRemark("仅紧急情况联系");
exampleList.add(example);
// 使用EasyExcel写入字段名在第一行示例数据在第二行
ServletOutputStream out = response.getOutputStream();
EasyExcel.write(out, HrmEmergencyContactImportVo.class)
.sheet("导入模板")
.doWrite(exampleList);
out.flush();
}
}

View File

@@ -0,0 +1,39 @@
package com.ruoyi.hrm.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("hrm_emergency_contact")
public class HrmEmergencyContact extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long contactId;
private Long userId;
private Long deptId;
private String realName;
private String phone;
private String idCard;
private String gender;
private Integer age;
private String companyName;
private Date hireDate;
private String emergencyContact;
private String relationship;
private String emergencyPhone1;
private String emergencyPhone2;
private String emergencyAddress;
private String remark;
@TableLogic
private Integer delFlag;
}

View File

@@ -0,0 +1,45 @@
package com.ruoyi.hrm.domain.bo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
public class HrmEmergencyContactBo extends BaseEntity {
private Long contactId;
@NotNull(message = "用户ID不能为空")
private Long userId;
private Long deptId;
private String realName;
private String phone;
private String idCard;
private String gender;
private Integer age;
private String companyName;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date hireDate;
@NotBlank(message = "紧急联系人姓名不能为空")
private String emergencyContact;
private String relationship;
@NotBlank(message = "紧急联系人电话1不能为空")
private String emergencyPhone1;
private String emergencyPhone2;
private String emergencyAddress;
private String remark;
}

View File

@@ -0,0 +1,138 @@
package com.ruoyi.hrm.domain.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentStyle;
import com.alibaba.excel.annotation.write.style.HeadStyle;
import com.alibaba.excel.enums.poi.HorizontalAlignmentEnum;
import com.ruoyi.common.annotation.Excel;
import lombok.Data;
import java.io.Serializable;
/**
* 紧急联系人导入 VO
* 与用户提供的Excel格式保持一致
*
* Excel表头顺序
* 入职时间 | 公司名称 | 部门 | 姓名 | 联系电话 | 身份证号 | 性别 | 年龄 | 紧急联系人 | 与本人关系 | 联系电话1 | 联系电话2 | 紧急联系人地址 | 备注
*/
@Data
@HeadStyle(fillForegroundColor = 44, horizontalAlignment = HorizontalAlignmentEnum.CENTER)
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.LEFT)
public class HrmEmergencyContactImportVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 入职时间
* 格式yyyy.MM.dd 或 yyyy-MM-dd
*/
@Excel(name = "入职时间", width = 12)
@ExcelProperty("入职时间")
@ColumnWidth(12)
private String hireDate;
/**
* 公司名称
*/
@Excel(name = "公司名称", width = 40)
@ExcelProperty("公司名称")
@ColumnWidth(40)
private String companyName;
/**
* 部门
*/
@Excel(name = "部门", width = 12)
@ExcelProperty("部门")
@ColumnWidth(12)
private String deptName;
/**
* 姓名
*/
@Excel(name = "姓名", width = 10)
@ExcelProperty("姓名")
@ColumnWidth(10)
private String realName;
/**
* 联系电话
*/
@Excel(name = "联系电话", width = 15)
@ExcelProperty("联系电话")
@ColumnWidth(15)
private String phone;
/**
* 身份证号
*/
@Excel(name = "身份证号", width = 22)
@ExcelProperty("身份证号")
@ColumnWidth(22)
private String idCard;
/**
* 性别
*/
@Excel(name = "性别", width = 8, readConverterExp = "男=0,女=1,未知=2")
@ExcelProperty("性别")
@ColumnWidth(8)
private String gender;
/**
* 年龄
*/
@Excel(name = "年龄", width = 8)
@ExcelProperty("年龄")
@ColumnWidth(8)
private Integer age;
/**
* 紧急联系人
*/
@Excel(name = "紧急联系人", width = 12)
@ExcelProperty("紧急联系人")
@ColumnWidth(12)
private String emergencyContact;
/**
* 与本人关系
*/
@Excel(name = "与本人关系", width = 12)
@ExcelProperty("与本人关系")
@ColumnWidth(12)
private String relationship;
/**
* 联系电话1
*/
@Excel(name = "联系电话1", width = 15)
@ExcelProperty("联系电话1")
@ColumnWidth(15)
private String emergencyPhone1;
/**
* 联系电话2
*/
@Excel(name = "联系电话2", width = 15)
@ExcelProperty("联系电话2")
@ColumnWidth(15)
private String emergencyPhone2;
/**
* 紧急联系人地址
*/
@Excel(name = "紧急联系人地址", width = 40)
@ExcelProperty("紧急联系人地址")
@ColumnWidth(40)
private String emergencyAddress;
/**
* 备注
*/
@Excel(name = "备注", width = 20)
@ExcelProperty("备注")
@ColumnWidth(20)
private String remark;
}

View File

@@ -0,0 +1,72 @@
package com.ruoyi.hrm.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.annotation.Excel;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class HrmEmergencyContactVo implements Serializable {
private static final long serialVersionUID = 1L;
@Excel(name = "联系人ID")
private Long contactId;
@Excel(name = "用户ID")
private Long userId;
@Excel(name = "部门ID")
private Long deptId;
@Excel(name = "部门名称")
private String deptName;
@Excel(name = "姓名")
private String realName;
@Excel(name = "联系电话")
private String phone;
@Excel(name = "身份证号")
private String idCard;
@Excel(name = "性别", readConverterExp = "0=男,1=女,2=未知")
private String gender;
@Excel(name = "年龄")
private Integer age;
@Excel(name = "公司名称")
private String companyName;
@Excel(name = "入职时间", width = 20, dateFormat = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date hireDate;
@Excel(name = "紧急联系人")
private String emergencyContact;
@Excel(name = "与本人关系")
private String relationship;
@Excel(name = "紧急电话1")
private String emergencyPhone1;
@Excel(name = "紧急电话2")
private String emergencyPhone2;
@Excel(name = "紧急联系人地址")
private String emergencyAddress;
@Excel(name = "备注")
private String remark;
private String createBy;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date createTime;
private String updateBy;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date updateTime;
}

View File

@@ -0,0 +1,8 @@
package com.ruoyi.hrm.mapper;
import com.ruoyi.common.core.mapper.BaseMapperPlus;
import com.ruoyi.hrm.domain.HrmEmergencyContact;
import com.ruoyi.hrm.domain.vo.HrmEmergencyContactVo;
public interface HrmEmergencyContactMapper extends BaseMapperPlus<HrmEmergencyContactMapper, HrmEmergencyContact, HrmEmergencyContactVo> {
}

View File

@@ -0,0 +1,27 @@
package com.ruoyi.hrm.service;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.hrm.domain.bo.HrmEmergencyContactBo;
import com.ruoyi.hrm.domain.vo.HrmEmergencyContactVo;
import com.ruoyi.hrm.domain.vo.HrmEmergencyContactImportVo;
import java.util.Collection;
import java.util.List;
public interface IHrmEmergencyContactService {
HrmEmergencyContactVo queryById(Long contactId);
TableDataInfo<HrmEmergencyContactVo> queryPageList(HrmEmergencyContactBo bo, PageQuery pageQuery);
List<HrmEmergencyContactVo> queryList(HrmEmergencyContactBo bo);
Boolean insertByBo(HrmEmergencyContactBo bo);
Boolean updateByBo(HrmEmergencyContactBo bo);
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
String importByVoList(List<HrmEmergencyContactImportVo> voList, boolean updateSupport);
}

View File

@@ -0,0 +1,254 @@
package com.ruoyi.hrm.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.hrm.domain.HrmEmergencyContact;
import com.ruoyi.hrm.domain.bo.HrmEmergencyContactBo;
import com.ruoyi.hrm.domain.vo.HrmEmergencyContactVo;
import com.ruoyi.hrm.domain.vo.HrmEmergencyContactImportVo;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.hrm.mapper.HrmEmergencyContactMapper;
import com.ruoyi.hrm.service.IHrmEmergencyContactService;
import com.ruoyi.system.mapper.SysUserMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;
@RequiredArgsConstructor
@Service
public class HrmEmergencyContactServiceImpl implements IHrmEmergencyContactService {
private final HrmEmergencyContactMapper baseMapper;
private final SysUserMapper sysUserMapper;
// 手机号正则
private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
// 身份证号正则15位或18位
private static final Pattern ID_CARD_PATTERN = Pattern.compile("(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$)");
@Override
public HrmEmergencyContactVo queryById(Long contactId) {
return baseMapper.selectVoById(contactId);
}
@Override
public TableDataInfo<HrmEmergencyContactVo> queryPageList(HrmEmergencyContactBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<HrmEmergencyContact> lqw = buildQueryWrapper(bo);
Page<HrmEmergencyContactVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
@Override
public List<HrmEmergencyContactVo> queryList(HrmEmergencyContactBo bo) {
LambdaQueryWrapper<HrmEmergencyContact> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(HrmEmergencyContactBo bo) {
HrmEmergencyContact add = BeanUtil.toBean(bo, HrmEmergencyContact.class);
return baseMapper.insert(add) > 0;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(HrmEmergencyContactBo bo) {
HrmEmergencyContact update = BeanUtil.toBean(bo, HrmEmergencyContact.class);
return baseMapper.updateById(update) > 0;
}
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
return baseMapper.deleteBatchIds(ids) > 0;
}
@Override
@Transactional(rollbackFor = Exception.class)
public String importByVoList(List<HrmEmergencyContactImportVo> voList, boolean updateSupport) {
if (CollUtil.isEmpty(voList)) {
throw new ServiceException("导入数据不能为空");
}
int successNum = 0;
int failNum = 0;
StringBuilder failMsg = new StringBuilder();
for (int i = 0; i < voList.size(); i++) {
HrmEmergencyContactImportVo vo = voList.get(i);
int rowNum = i + 2;
try {
// 转换日期格式:将 yyyy.MM.dd 转换为 yyyy-MM-dd
Date hireDate = null;
if (StrUtil.isNotBlank(vo.getHireDate())) {
String dateStr = vo.getHireDate().replace(".", "-");
vo.setHireDate(dateStr);
hireDate = DateUtil.parseDate(dateStr);
}
// 性别转换Excel 中为"男"/"女"/"未知",统一转代码值
String gender = vo.getGender();
if (StrUtil.isNotBlank(gender)) {
if ("".equals(gender)) { gender = "0"; }
else if ("".equals(gender)) { gender = "1"; }
else if ("未知".equals(gender)) { gender = "2"; }
vo.setGender(gender);
}
// 数据校验
String validateMsg = validateImportData(vo);
if (StrUtil.isNotBlank(validateMsg)) {
failNum++;
failMsg.append("<br/>").append(failNum).append("、第").append(rowNum).append("")
.append(StrUtil.blankToDefault(vo.getRealName(), "无名")).append("").append(validateMsg);
continue;
}
HrmEmergencyContact entity = BeanUtil.toBean(vo, HrmEmergencyContact.class);
// 根据姓名+身份证号匹配 sys_user自动填充 userId/deptId
Long matchedUserId = matchUserId(vo.getRealName(), vo.getIdCard());
entity.setUserId(matchedUserId != null ? matchedUserId : 0L);
// 显式设置入职日期BeanUtil 可能无法自动转换 String -> Date
if (hireDate != null) {
entity.setHireDate(hireDate);
}
// 按「姓名 + 身份证号」去重
LambdaQueryWrapper<HrmEmergencyContact> lqw = Wrappers.lambdaQuery();
lqw.eq(HrmEmergencyContact::getRealName, entity.getRealName());
lqw.eq(HrmEmergencyContact::getIdCard, entity.getIdCard());
HrmEmergencyContact existing = baseMapper.selectOne(lqw);
if (existing != null) {
if (updateSupport) {
entity.setContactId(existing.getContactId());
baseMapper.updateById(entity);
successNum++;
} else {
failNum++;
failMsg.append("<br/>").append(failNum).append("、第").append(rowNum).append("")
.append(vo.getRealName()).append(":数据已存在(姓名+身份证号重复)");
}
} else {
baseMapper.insert(entity);
successNum++;
}
} catch (Exception e) {
failNum++;
failMsg.append("<br/>").append(failNum).append("、第").append(rowNum).append("")
.append(StrUtil.blankToDefault(vo.getRealName(), "无名")).append(":导入失败,").append(e.getMessage());
}
}
if (failNum > 0) {
return "导入完成!成功 " + successNum + " 条,失败 " + failNum + " 条。错误信息:" + failMsg;
}
return "恭喜您,数据已全部导入成功!共 " + successNum + "";
}
/**
* 根据姓名+身份证号匹配 sys_user返回 userId匹配不到返回 null
*/
private Long matchUserId(String realName, String idCard) {
// 1. 优先按 realName + idCard 精确匹配
if (StrUtil.isNotBlank(realName) && StrUtil.isNotBlank(idCard)) {
SysUser user = sysUserMapper.selectOne(Wrappers.<SysUser>lambdaQuery()
.eq(SysUser::getNickName, realName)
.eq(SysUser::getIdCard, idCard)
.last("limit 1"));
if (user != null) {
return user.getUserId();
}
}
// 2. 仅按 idCard 匹配(唯一性高)
if (StrUtil.isNotBlank(idCard)) {
SysUser user = sysUserMapper.selectOne(Wrappers.<SysUser>lambdaQuery()
.eq(SysUser::getIdCard, idCard)
.last("limit 1"));
if (user != null) {
return user.getUserId();
}
}
// 3. 仅按 realName 匹配(有重名风险,作为最后的兜底)
if (StrUtil.isNotBlank(realName)) {
SysUser user = sysUserMapper.selectOne(Wrappers.<SysUser>lambdaQuery()
.eq(SysUser::getNickName, realName)
.last("limit 1"));
if (user != null) {
return user.getUserId();
}
}
return null;
}
/**
* 校验导入数据
*/
private String validateImportData(HrmEmergencyContactImportVo vo) {
StringBuilder msg = new StringBuilder();
if (StrUtil.isBlank(vo.getRealName())) {
msg.append("姓名为空;");
}
if (StrUtil.isBlank(vo.getPhone())) {
msg.append("联系电话为空;");
} else if (!PHONE_PATTERN.matcher(vo.getPhone()).matches()) {
msg.append("联系电话格式错误需11位手机号");
}
if (StrUtil.isBlank(vo.getIdCard())) {
msg.append("身份证号为空;");
} else if (!ID_CARD_PATTERN.matcher(vo.getIdCard()).matches()) {
msg.append("身份证号格式错误;");
}
if (StrUtil.isBlank(vo.getGender())) {
msg.append("性别为空;");
}
if (vo.getAge() == null) {
msg.append("年龄为空;");
}
if (StrUtil.isBlank(vo.getEmergencyContact())) {
msg.append("紧急联系人为空;");
}
if (StrUtil.isBlank(vo.getRelationship())) {
msg.append("与本人关系为空;");
}
if (StrUtil.isBlank(vo.getEmergencyPhone1())) {
msg.append("联系电话1为空");
} else if (!PHONE_PATTERN.matcher(vo.getEmergencyPhone1()).matches()) {
msg.append("联系电话1格式错误需11位手机号");
}
if (StrUtil.isBlank(vo.getEmergencyAddress())) {
msg.append("紧急联系人地址为空;");
}
if (StrUtil.isNotBlank(vo.getEmergencyPhone2()) && !PHONE_PATTERN.matcher(vo.getEmergencyPhone2()).matches()) {
msg.append("联系电话2格式错误需11位手机号");
}
return msg.toString();
}
private LambdaQueryWrapper<HrmEmergencyContact> buildQueryWrapper(HrmEmergencyContactBo bo) {
LambdaQueryWrapper<HrmEmergencyContact> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getContactId() != null, HrmEmergencyContact::getContactId, bo.getContactId());
lqw.eq(bo.getUserId() != null, HrmEmergencyContact::getUserId, bo.getUserId());
lqw.like(bo.getRealName() != null, HrmEmergencyContact::getRealName, bo.getRealName());
lqw.like(bo.getPhone() != null, HrmEmergencyContact::getPhone, bo.getPhone());
lqw.like(bo.getEmergencyContact() != null, HrmEmergencyContact::getEmergencyContact, bo.getEmergencyContact());
lqw.eq(bo.getDeptId() != null, HrmEmergencyContact::getDeptId, bo.getDeptId());
lqw.eq(bo.getGender() != null, HrmEmergencyContact::getGender, bo.getGender());
return lqw;
}
}

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.hrm.mapper.HrmEmergencyContactMapper">
<resultMap id="BaseResultMap" type="com.ruoyi.hrm.domain.HrmEmergencyContact">
<id column="contact_id" property="contactId"/>
<result column="user_id" property="userId"/>
<result column="dept_id" property="deptId"/>
<result column="real_name" property="realName"/>
<result column="phone" property="phone"/>
<result column="id_card" property="idCard"/>
<result column="gender" property="gender"/>
<result column="age" property="age"/>
<result column="company_name" property="companyName"/>
<result column="hire_date" property="hireDate"/>
<result column="emergency_contact" property="emergencyContact"/>
<result column="relationship" property="relationship"/>
<result column="emergency_phone1" property="emergencyPhone1"/>
<result column="emergency_phone2" property="emergencyPhone2"/>
<result column="emergency_address" property="emergencyAddress"/>
<result column="remark" property="remark"/>
<result column="create_by" property="createBy"/>
<result column="create_time" property="createTime"/>
<result column="update_by" property="updateBy"/>
<result column="update_time" property="updateTime"/>
<result column="del_flag" property="delFlag"/>
</resultMap>
</mapper>

View File

@@ -0,0 +1,55 @@
import request from '@/utils/request'
import { download } from '@/utils/request'
// 查询紧急联系人列表
export function listEmergencyContact(query) {
return request({
url: '/hrm/emergencyContact/list',
method: 'get',
params: query
})
}
// 查询紧急联系人详细
export function getEmergencyContact(contactId) {
return request({
url: `/hrm/emergencyContact/${contactId}`,
method: 'get'
})
}
// 新增紧急联系人
export function addEmergencyContact(data) {
return request({
url: '/hrm/emergencyContact',
method: 'post',
data: data
})
}
// 修改紧急联系人
export function updateEmergencyContact(data) {
return request({
url: '/hrm/emergencyContact',
method: 'put',
data: data
})
}
// 删除紧急联系人
export function delEmergencyContact(contactIds) {
return request({
url: `/hrm/emergencyContact/${contactIds}`,
method: 'delete'
})
}
// 导出紧急联系人
export function exportEmergencyContact(query) {
return download('/hrm/emergencyContact/export', query, '紧急联系人数据.xlsx')
}
// 下载导入模板
export function importTemplate() {
return download('/hrm/emergencyContact/importTemplate', {}, '紧急联系人导入模板.xlsx')
}

View File

@@ -12,4 +12,5 @@ export * from './org'
export * from './reimburse' export * from './reimburse'
export * from './seal' export * from './seal'
export * from './travel' export * from './travel'
export * from './emergencyContact'

View File

@@ -0,0 +1,449 @@
<template>
<div class="emergency-contact-page">
<el-card shadow="never" class="main-card">
<div slot="header" class="card-header">
<span class="header-title">员工紧急联系人</span>
<div class="header-actions">
<el-input v-model="query.realName" placeholder="姓名" size="small" clearable style="width: 160px;"
@keyup.enter.native="loadList" />
<el-input v-model="query.emergencyContact" placeholder="紧急联系人" size="small" clearable style="width: 160px;"
@keyup.enter.native="loadList" />
<el-select v-model="query.deptId" size="small" placeholder="部门" clearable filterable style="width: 160px;"
@change="loadList">
<el-option v-for="dept in deptOptions" :key="dept.deptId" :label="dept.deptName" :value="dept.deptId" />
</el-select>
<el-button size="small" type="primary" icon="el-icon-search" @click="loadList">查询</el-button>
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAdd">新增</el-button>
<el-button size="small" icon="el-icon-download" @click="handleExport">导出</el-button>
<el-button size="small" icon="el-icon-upload2" @click="importVisible = true">导入</el-button>
</div>
</div>
<el-table :data="list" v-loading="loading" stripe height="calc(100vh - 280px)">
<el-table-column prop="hireDate" label="入职时间" min-width="110" />
<el-table-column prop="realName" label="姓名" min-width="100" />
<el-table-column prop="phone" label="联系电话" min-width="130" />
<el-table-column prop="gender" label="性别" min-width="60">
<template slot-scope="scope">
<span>{{ { '0': '男', '1': '女', '2': '未知' }[scope.row.gender] || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="age" label="年龄" min-width="60" />
<el-table-column prop="companyName" label="公司名称" min-width="200" show-overflow-tooltip />
<el-table-column prop="emergencyContact" label="紧急联系人" min-width="100" />
<el-table-column prop="relationship" label="关系" min-width="80" />
<el-table-column prop="emergencyPhone1" label="紧急电话1" min-width="130" />
<el-table-column prop="emergencyPhone2" label="紧急电话2" min-width="130" />
<el-table-column prop="remark" label="备注" min-width="140" show-overflow-tooltip />
<el-table-column label="操作" width="180" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="text" size="mini" icon="el-icon-delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-wrapper">
<el-pagination :current-page="query.pageNum" :page-sizes="[10, 20, 50, 100]" :page-size="query.pageSize"
:total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange"
@current-change="handlePageChange" />
</div>
</el-card>
<!-- 新增/编辑弹窗 -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="800px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" size="small">
<el-divider content-position="left">员工信息</el-divider>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="选择人员" prop="userId">
<el-select v-model="form.userId" placeholder="请选择人员" filterable remote
:remote-method="searchUsers" :loading="userLoading" style="width: 100%" clearable
@change="handleUserChange">
<el-option v-for="user in userOptions" :key="user.userId"
:label="`${user.nickName} (${user.userName})`" :value="user.userId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="姓名" prop="realName">
<el-input v-model="form.realName" placeholder="自动带出可修改" :disabled="!form.userId" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="联系电话" prop="phone">
<el-input v-model="form.phone" placeholder="自动带出可修改" :disabled="!form.userId" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="性别" prop="gender">
<el-select v-model="form.gender" placeholder="自动带出可修改" style="width: 100%" :disabled="!form.userId">
<el-option label="男" value="0" />
<el-option label="女" value="1" />
<el-option label="未知" value="2" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="身份证号" prop="idCard">
<el-input v-model="form.idCard" placeholder="自动带出可修改" :disabled="!form.userId" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="年龄" prop="age">
<el-input-number v-model="form.age" :min="0" :max="150" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left">公司信息</el-divider>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="公司名称" prop="companyName">
<el-input v-model="form.companyName" placeholder="请输入公司名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="入职时间" prop="hireDate">
<el-date-picker v-model="form.hireDate" type="date" placeholder="选择日期" style="width: 100%"
value-format="yyyy-MM-dd" />
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left">紧急联系人</el-divider>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="联系人姓名" prop="emergencyContact">
<el-input v-model="form.emergencyContact" placeholder="请输入紧急联系人姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="与本人关系" prop="relationship">
<el-select v-model="form.relationship" placeholder="请选择关系" style="width: 100%" clearable filterable>
<el-option label="配偶" value="配偶" />
<el-option label="父母" value="父母" />
<el-option label="子女" value="子女" />
<el-option label="兄弟姐妹" value="兄弟姐妹" />
<el-option label="朋友" value="朋友" />
<el-option label="同事" value="同事" />
<el-option label="其他" value="其他" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="联系电话1" prop="emergencyPhone1">
<el-input v-model="form.emergencyPhone1" placeholder="请输入紧急联系人电话" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话2" prop="emergencyPhone2">
<el-input v-model="form.emergencyPhone2" placeholder="请输入备用电话" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="24">
<el-form-item label="联系地址" prop="emergencyAddress">
<el-input v-model="form.emergencyAddress" placeholder="请输入紧急联系人地址" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button>
</div>
</el-dialog>
<!-- 导入弹窗 -->
<el-dialog title="导入紧急联系人数据" :visible.sync="importVisible" width="500px" append-to-body>
<!-- 导入规则说明 -->
<el-alert
title="导入规则说明"
type="info"
:closable="false"
style="margin-bottom: 15px;"
>
<div slot="description" style="font-size: 12px; line-height: 1.8;">
<p>1. <strong>文件格式</strong>必须是 .xlsx 格式表头顺序不能乱</p>
<p>2. <strong>必填字段</strong> * 号的为必填项姓名联系电话身份证号性别年龄等</p>
<p>3. <strong>重复数据</strong>系统会按姓名 + 身份证号去重已存在的记录会自动更新</p>
<p>4. <strong>数据校验</strong>手机号必须为 11 位数字身份证号会做基础格式校验</p>
<p>5. <strong>导入后核对</strong>导入完成后请在列表页核对数据如有错误可直接修改</p>
</div>
</el-alert>
<el-upload ref="upload" :limit="1" accept=".xlsx,.xls" :headers="upload.headers"
:action="upload.url" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess" :on-error="handleFileError" :auto-upload="false"
drag>
<i class="el-icon-upload" />
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<div slot="tip" class="el-upload__tip" style="color:red;padding:0 12px">
仅允许导入 xlsx / xls 格式文件
<el-link type="primary" :underline="false" style="font-size:12px" @click="handleDownloadTemplate">下载导入模板含字段说明和示例数据</el-link>
</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button @click="importVisible = false">取消</el-button>
<el-button type="primary" :loading="upload.isUploading" @click="submitImport">确定导入</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listDept } from '@/api/system/dept'
import { selectUser } from '@/api/system/user'
import { listEmergencyContact, addEmergencyContact, updateEmergencyContact, delEmergencyContact, exportEmergencyContact, importTemplate } from '@/api/hrm/emergencyContact'
import { getToken } from '@/utils/auth'
export default {
name: 'HrmEmergencyContact',
data() {
return {
loading: false,
submitting: false,
list: [],
total: 0,
deptOptions: [],
userOptions: [],
userLoading: false,
query: {
pageNum: 1,
pageSize: 20,
realName: '',
emergencyContact: '',
deptId: null
},
dialogVisible: false,
dialogTitle: '新增紧急联系人',
form: {},
rules: {
userId: [{ required: true, message: '请选择人员', trigger: 'change' }],
emergencyContact: [{ required: true, message: '请输入紧急联系人姓名', trigger: 'blur' }],
emergencyPhone1: [{ required: true, message: '请输入紧急联系人电话', trigger: 'blur' }]
},
// 导入相关
importVisible: false,
upload: {
headers: { Authorization: 'Bearer ' + getToken() },
url: process.env.VUE_APP_BASE_API + '/hrm/emergencyContact/importData',
isUploading: false
},
// 导入字段说明
importFieldDesc: [
{ field: '入职时间', required: false, desc: '格式yyyy-MM-dd', example: '2025-06-18' },
{ field: '公司名称', required: true, desc: '全称与OA内一致', example: '山东福安德信息科技有限公司' },
{ field: '部门', required: true, desc: '与OA内部门名称一致', example: '信息化部' },
{ field: '姓名', required: true, desc: '员工真实姓名', example: '张三' },
{ field: '联系电话', required: true, desc: '员工本人手机号11位数字', example: '183xxxxxxxx' },
{ field: '身份证号', required: true, desc: '18位或15位含X大写', example: '341xxxxxxxxxxxxxxx' },
{ field: '性别', required: true, desc: '男 / 女', example: '男' },
{ field: '年龄', required: true, desc: '整数,按周岁填写', example: '23' },
{ field: '紧急联系人', required: true, desc: '联系人真实姓名', example: '李四' },
{ field: '与本人关系', required: true, desc: '如父子/母子/夫妻等', example: '母子' },
{ field: '联系电话1', required: true, desc: '紧急联系人手机号11位数字', example: '159xxxxxxxx' },
{ field: '联系电话2', required: false, desc: '备用手机号11位数字', example: '182xxxxxxxx' },
{ field: '紧急联系人地址', required: true, desc: '详细地址', example: 'xx省xx市xx县xx镇' },
{ field: '备注', required: false, desc: '补充说明', example: '仅紧急情况联系' }
]
}
},
created() {
this.loadDeptOptions()
this.loadList()
},
methods: {
loadDeptOptions() {
listDept({}).then(res => {
this.deptOptions = res.data || []
})
},
loadList() {
this.loading = true
listEmergencyContact(this.query).then(res => {
this.list = res.rows || []
this.total = res.total || 0
}).finally(() => {
this.loading = false
})
},
handleSizeChange(val) {
this.query.pageSize = val
this.loadList()
},
handlePageChange(val) {
this.query.pageNum = val
this.loadList()
},
handleAdd() {
this.dialogTitle = '新增紧急联系人'
this.form = {
userId: undefined,
realName: '',
phone: '',
idCard: '',
gender: '0',
age: undefined,
companyName: '',
hireDate: undefined,
emergencyContact: '',
relationship: '',
emergencyPhone1: '',
emergencyPhone2: '',
emergencyAddress: '',
remark: ''
}
this.userOptions = []
this.dialogVisible = true
this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
},
handleEdit(row) {
this.dialogTitle = '编辑紧急联系人'
this.form = { ...row }
// 加载已选用户信息用于展示
if (this.form.userId) {
this.searchUsers('')
}
this.dialogVisible = true
this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
},
handleDelete(row) {
this.$confirm(`确认删除 "${row.realName}" 的紧急联系人信息吗?`, '提示', { type: 'warning' }).then(() => {
delEmergencyContact(row.contactId).then(() => {
this.$message.success('删除成功')
this.loadList()
})
})
},
searchUsers(query) {
this.userLoading = true
selectUser({ userName: query, nickName: query, pageNum: 1, pageSize: 20 }).then(res => {
this.userOptions = res.rows || []
// 编辑时确保已选用户在选项中
if (this.form.userId && !this.userOptions.find(u => u.userId === this.form.userId)) {
selectUser({ userId: this.form.userId }).then(res2 => {
if (res2.rows && res2.rows.length > 0) {
this.userOptions.unshift(res2.rows[0])
}
})
}
}).finally(() => {
this.userLoading = false
})
},
handleUserChange(userId) {
if (!userId) return
const user = this.userOptions.find(u => u.userId === userId)
if (user) {
this.form.realName = user.nickName || ''
this.form.phone = user.phonenumber || ''
this.form.gender = user.sex || '0'
this.form.idCard = user.idCard || ''
// 根据身份证号计算年龄
if (user.idCard && user.idCard.length >= 18) {
const birthYear = parseInt(user.idCard.substring(6, 10))
const currentYear = new Date().getFullYear()
this.form.age = currentYear - birthYear
}
}
},
handleSubmit() {
this.$refs.formRef.validate(valid => {
if (!valid) return
this.submitting = true
const api = this.form.contactId ? updateEmergencyContact : addEmergencyContact
api(this.form).then(() => {
this.$message.success(this.form.contactId ? '更新成功' : '新增成功')
this.dialogVisible = false
if (!this.form.contactId) {
this.query.pageNum = 1
}
this.loadList()
}).finally(() => {
this.submitting = false
})
})
},
// 导出
handleExport() {
this.$confirm('确认导出紧急联系人数据吗?', '提示', { type: 'warning' }).then(() => {
exportEmergencyContact(this.query)
})
},
// 下载导入模板
handleDownloadTemplate() {
importTemplate()
},
handleFileUploadProgress() {
this.upload.isUploading = true
},
handleFileSuccess(res) {
this.upload.isUploading = false
this.$refs.upload.clearFiles()
this.importVisible = false
this.$modal.msgSuccess(res.msg || '导入成功')
this.loadList()
},
handleFileError() {
this.upload.isUploading = false
this.$message.error('导入失败,请检查文件格式')
},
submitImport() {
this.$refs.upload.submit()
}
}
}
</script>
<style lang="scss" scoped>
.emergency-contact-page {
padding: 20px;
background-color: #f0f2f5;
}
.main-card {
border: none;
border-radius: 4px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
.header-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.header-actions {
display: flex;
gap: 8px;
}
}
.pagination-wrapper {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
.dialog-footer {
text-align: right;
}
</style>

View File

@@ -0,0 +1,41 @@
-- =====================================================================
-- 员工紧急联系人信息表
-- 通过 user_id 关联 sys_user自动带出姓名/性别/身份证号/联系电话
-- =====================================================================
CREATE TABLE IF NOT EXISTS `hrm_emergency_contact` (
`contact_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '紧急联系人ID',
-- 关联信息(从 sys_user 自动带出 / 关联查询)
`user_id` BIGINT NOT NULL COMMENT '用户ID关联 sys_user',
`dept_id` BIGINT DEFAULT NULL COMMENT '部门ID关联 sys_dept从 sys_user 自动带出)',
-- 员工基本信息(新增/导入时从 sys_user 自动填充,允许手动修改)
`real_name` VARCHAR(30) DEFAULT NULL COMMENT '姓名(从 sys_user.nick_name 自动带出)',
`phone` VARCHAR(20) DEFAULT NULL COMMENT '联系电话(从 sys_user.phonenumber 自动带出)',
`id_card` VARCHAR(200) DEFAULT NULL COMMENT '身份证号(从 sys_user.id_card 自动带出)',
`gender` CHAR(1) DEFAULT '0' COMMENT '性别0男 1女 2未知从 sys_user.sex 自动带出)',
`age` INT DEFAULT NULL COMMENT '年龄',
-- 公司信息sys_user 无此字段,手动录入)
`company_name` VARCHAR(100) DEFAULT NULL COMMENT '公司名称',
`hire_date` DATE DEFAULT NULL COMMENT '入职时间',
-- 紧急联系人信息
`emergency_contact` VARCHAR(30) DEFAULT NULL COMMENT '紧急联系人姓名',
`relationship` VARCHAR(30) DEFAULT NULL COMMENT '与本人关系',
`emergency_phone1` VARCHAR(20) DEFAULT NULL COMMENT '紧急联系人电话1',
`emergency_phone2` VARCHAR(20) DEFAULT NULL COMMENT '紧急联系人电话2',
`emergency_address` VARCHAR(200) DEFAULT NULL COMMENT '紧急联系人地址',
-- 基础字段
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
`del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志0代表存在 2代表删除',
`create_by` VARCHAR(64) DEFAULT '' COMMENT '创建者',
`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
`update_by` VARCHAR(64) DEFAULT '' COMMENT '更新者',
`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`contact_id`) USING BTREE,
KEY `idx_user_id` (`user_id`) USING BTREE,
KEY `idx_dept_id` (`dept_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='员工紧急联系人信息表';

View File

@@ -0,0 +1,30 @@
-- =====================================================================
-- 清理紧急联系人菜单脏数据(执行前请确认)
-- =====================================================================
-- 方式1按 menu_id 范围删除(如果之前用的是 2000-2100 或 2070-2076
-- DELETE FROM sys_menu WHERE menu_id BETWEEN 2000 AND 2100;
-- DELETE FROM sys_menu WHERE menu_id BETWEEN 2070 AND 2076;
-- 方式2按菜单名称删除推荐更精确
-- DELETE FROM sys_menu WHERE menu_name = '紧急联系人';
-- 方式3按权限标识删除删除所有相关按钮权限
-- DELETE FROM sys_menu WHERE perms LIKE 'hrm:emergencyContact:%';
-- 方式4组合删除先查后删安全
-- 先查询确认
SELECT menu_id, menu_name, parent_id, perms, create_time
FROM sys_menu
WHERE menu_name = '紧急联系人'
OR perms LIKE 'hrm:emergencyContact:%';
-- 确认无误后再执行删除
-- DELETE FROM sys_menu WHERE menu_name = '紧急联系人' OR perms LIKE 'hrm:emergencyContact:%';
-- =====================================================================
-- 执行步骤:
-- 1. 先执行 SELECT 查询,确认要删除的数据
-- 2. 确认无误后,再执行 DELETE 语句
-- 3. 删除完成后,在若依系统菜单管理界面手动添加菜单
-- =====================================================================

View File

@@ -0,0 +1,115 @@
# 紧急联系人菜单手动添加指南
## 第一步清理脏数据在Navicat/DBeaver中执行
```sql
-- 先查询确认
SELECT menu_id, menu_name, parent_id, perms, create_time
FROM sys_menu
WHERE menu_name = '紧急联系人'
OR perms LIKE 'hrm:emergencyContact:%';
-- 确认无误后再执行删除
DELETE FROM sys_menu
WHERE menu_name = '紧急联系人'
OR perms LIKE 'hrm:emergencyContact:%';
```
---
## 第二步:在若依系统菜单管理中手动添加
### 1. 添加主菜单
进入 **系统管理 → 菜单管理**,点击【新增】按钮,填写以下信息:
| 字段 | 值 |
|------|-----|
| 上级菜单 | 人事中心(或你想要的父菜单) |
| 菜单类型 | 菜单 |
| 菜单图标 | user |
| 菜单名称 | 紧急联系人 |
| 显示排序 | 99 |
| 是否外链 | 否 |
| 路由地址 | emergencyContact |
| 组件路径 | hrm/emergencyContact/index |
| 权限字符 | hrm:emergencyContact:list |
| 路由参数 | (留空) |
| 是否缓存 | 缓存 |
| 显示状态 | 显示 |
| 菜单状态 | 正常 |
点击【确定】保存。
---
### 2. 添加按钮权限
主菜单添加成功后,在菜单列表中找到【紧急联系人】,点击右侧的【新增】按钮(添加子菜单/按钮)。
需要添加以下6个按钮权限
#### ① 查询按钮
- 菜单类型:按钮
- 菜单名称:查询
- 显示排序1
- 权限字符hrm:emergencyContact:query
- 显示状态:显示
- 菜单状态:正常
#### ② 新增按钮
- 菜单类型:按钮
- 菜单名称:新增
- 显示排序2
- 权限字符hrm:emergencyContact:add
- 显示状态:显示
- 菜单状态:正常
#### ③ 修改按钮
- 菜单类型:按钮
- 菜单名称:修改
- 显示排序3
- 权限字符hrm:emergencyContact:edit
- 显示状态:显示
- 菜单状态:正常
#### ④ 删除按钮
- 菜单类型:按钮
- 菜单名称:删除
- 显示排序4
- 权限字符hrm:emergencyContact:remove
- 显示状态:显示
- 菜单状态:正常
#### ⑤ 导出按钮
- 菜单类型:按钮
- 菜单名称:导出
- 显示排序5
- 权限字符hrm:emergencyContact:export
- 显示状态:显示
- 菜单状态:正常
#### ⑥ 导入按钮
- 菜单类型:按钮
- 菜单名称:导入
- 显示排序6
- 权限字符hrm:emergencyContact:import
- 显示状态:显示
- 菜单状态:正常
---
## 第三步:刷新系统
1. 添加完成后,**退出登录**或 **刷新页面**
2. 重新登录后,在对应父菜单下应该能看到【紧急联系人】菜单
3. 进入菜单测试功能是否正常
---
## 注意事项
1. **上级菜单选择**:如果想放在"人事中心"下,就选择"人事中心";如果想放在顶级,就选择"主类目"
2. **权限字符**:必须严格按照 `hrm:emergencyContact:list``hrm:emergencyContact:query` 等格式填写
3. **路由地址**:必须与前端路由配置一致,这里是 `emergencyContact`
4. **组件路径**:必须与前端组件路径一致,这里是 `hrm/emergencyContact/index`

View File

@@ -0,0 +1,42 @@
-- =====================================================================
-- 菜单 SQL员工紧急联系人管理若依框架兼容版
-- 必须分步执行,不能批量执行!
-- =====================================================================
-- ============================================================
-- 第一步:清理可能存在的脏数据(如果之前执行失败过)
-- ============================================================
-- DELETE FROM sys_menu WHERE menu_id BETWEEN 2070 AND 2076;
-- ============================================================
-- 第二步:插入主菜单(先执行这行,成功后再执行按钮)
-- parent_id = 0 表示顶级菜单可根据需要改为实际的HRM菜单ID
-- ============================================================
INSERT INTO sys_menu (
menu_id, menu_name, parent_id, order_num, path, component, query_param,
is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time
) VALUES (
2070, '紧急联系人', 0, 99, 'emergencyContact', 'hrm/emergencyContact/index', '',
1, 0, 'C', '0', '0', 'hrm:emergencyContact:list', 'user', 'admin', NOW()
);
-- ============================================================
-- 第三步:主菜单插入成功后,再执行按钮权限
-- ============================================================
INSERT INTO sys_menu (
menu_id, menu_name, parent_id, order_num, path, component,
is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time
) VALUES
(2071, '查询', 2070, 1, '#', NULL, 1, 0, 'F', '0', '0', 'hrm:emergencyContact:query', '#', 'admin', NOW()),
(2072, '新增', 2070, 2, '#', NULL, 1, 0, 'F', '0', '0', 'hrm:emergencyContact:add', '#', 'admin', NOW()),
(2073, '修改', 2070, 3, '#', NULL, 1, 0, 'F', '0', '0', 'hrm:emergencyContact:edit', '#', 'admin', NOW()),
(2074, '删除', 2070, 4, '#', NULL, 1, 0, 'F', '0', '0', 'hrm:emergencyContact:remove', '#', 'admin', NOW()),
(2075, '导出', 2070, 5, '#', NULL, 1, 0, 'F', '0', '0', 'hrm:emergencyContact:export', '#', 'admin', NOW()),
(2076, '导入', 2070, 6, '#', NULL, 1, 0, 'F', '0', '0', 'hrm:emergencyContact:import', '#', 'admin', NOW());
-- ============================================================
-- 使用说明:
-- 1. 如果之前执行失败过,先执行第一步的 DELETE 清理脏数据
-- 2. 先执行第二步(主菜单),确认成功后再执行第三步(按钮权限)
-- 3. 如需将菜单放在HRM模块下将 parent_id 从 0 改为 HRM菜单的实际ID
-- ============================================================

View File

@@ -0,0 +1,24 @@
-- =====================================================================
-- 示例数据:信息部员工紧急联系人
-- 使用前请将 user_id 替换为实际 sys_user 表中的 ID
-- =====================================================================
-- 示例数据1张伟
INSERT INTO `hrm_emergency_contact` (`user_id`, `dept_id`, `real_name`, `phone`, `id_card`, `gender`, `age`, `company_name`, `hire_date`, `emergency_contact`, `relationship`, `emergency_phone1`, `emergency_phone2`, `emergency_address`, `remark`, `del_flag`, `create_by`, `create_time`)
VALUES (1, 100, '张伟', '13800138001', '110101199001011234', '0', 36, '上海泛微网络科技股份有限公司', '2022-03-15', '李芳', '配偶', '13900139001', '13800138002', '上海市浦东新区张江高科技园区200号', '信息部经理', '0', 'admin', NOW());
-- 示例数据2李娜
INSERT INTO `hrm_emergency_contact` (`user_id`, `dept_id`, `real_name`, `phone`, `id_card`, `gender`, `age`, `company_name`, `hire_date`, `emergency_contact`, `relationship`, `emergency_phone1`, `emergency_phone2`, `emergency_address`, `remark`, `del_flag`, `create_by`, `create_time`)
VALUES (2, 100, '李娜', '13800138003', '110101199205022345', '1', 34, '上海泛微网络科技股份有限公司', '2021-07-01', '王强', '配偶', '13900139003', '', '上海市徐汇区漕河泾开发区100号', '需求分析师', '0', 'admin', NOW());
-- 示例数据3王强
INSERT INTO `hrm_emergency_contact` (`user_id`, `dept_id`, `real_name`, `phone`, `id_card`, `gender`, `age`, `company_name`, `hire_date`, `emergency_contact`, `relationship`, `emergency_phone1`, `emergency_phone2`, `emergency_address`, `remark`, `del_flag`, `create_by`, `create_time`)
VALUES (3, 100, '王强', '13800138005', '110101198803033456', '0', 38, '上海泛微网络科技股份有限公司', '2020-01-10', '刘静', '配偶', '13900139005', '13800138006', '上海市闵行区七宝镇50号', '开发主管', '0', 'admin', NOW());
-- 示例数据4赵敏
INSERT INTO `hrm_emergency_contact` (`user_id`, `dept_id`, `real_name`, `phone`, `id_card`, `gender`, `age`, `company_name`, `hire_date`, `emergency_contact`, `relationship`, `emergency_phone1`, `emergency_phone2`, `emergency_address`, `remark`, `del_flag`, `create_by`, `create_time`)
VALUES (4, 100, '赵敏', '13800138007', '110101199507044567', '1', 31, '上海泛微网络科技股份有限公司', '2023-05-20', '赵建国', '父母', '13900139007', '', '上海市静安区南京西路300号', '前端开发', '0', 'admin', NOW());
-- 示例数据5陈浩
INSERT INTO `hrm_emergency_contact` (`user_id`, `dept_id`, `real_name`, `phone`, `id_card`, `gender`, `age`, `company_name`, `hire_date`, `emergency_contact`, `relationship`, `emergency_phone1`, `emergency_phone2`, `emergency_address`, `remark`, `del_flag`, `create_by`, `create_time`)
VALUES (5, 100, '陈浩', '13800138009', '110101199608055678', '0', 30, '上海泛微网络科技股份有限公司', '2022-11-01', '陈芳', '父母', '13900139009', '13800138010', '上海市浦东新区陆家嘴环路1000号', '后端开发', '0', 'admin', NOW());

Binary file not shown.

View File

@@ -0,0 +1,25 @@
这是一个示例说明文件实际Excel模板需要通过系统下载
Excel模板字段顺序
1. 入职时间 (yyyy.MM.dd格式2025.06.18)
2. 公司名称 (如:山东福安德信息科技有限公司)
3. 部门 (如:信息化部)
4. 姓名 (如:高鑫磊)
5. 联系电话 (如18324818443)
6. 身份证号 (如341222200309052816)
7. 性别 (男/女/未知)
8. 年龄 (如23)
9. 紧急联系人 (如:李继敏)
10. 与本人关系 (如:母子)
11. 联系电话1 (如15955868031)
12. 联系电话2 (可选)
13. 紧急联系人地址 (如:安徽省阜阳市太和县皮条孙镇)
14. 备注 (可选)
示例数据:
2025.06.18,山东福安德信息科技有限公司,信息化部,高鑫磊,18324818443,341222200309052816,男,23,李继敏,母子,15955868031,,安徽省阜阳市太和县皮条孙镇,
2025.06.18,山东福安德信息科技有限公司,信息化部,刘敬超,19932047759,13043520021117093X,男,24,刘建领,父子,18932719783,,河北省邯郸市曲周县槐桥乡,
,山东福安德信息科技有限公司,信息化部,王文昊,19711921991,130923200312073014,男,22,王海军,父子,18233665966,18233691998,河北省沧州市东光县,
,山东福安德信息科技有限公司,信息化部,井昊东,17803227053,13063420041124001X,男,22,井小苗,母子,13803279482,18603228769,河北省保定市曲阳县七里庄村,
,山东福安德信息科技有限公司,信息化部,祖启佳,13308587452,520221200406220769,女,22,黄倩,母女,18188276838,,贵州省六盘水市水城县,
,山东福安德信息科技有限公司,信息化部,朱昊天,19930382154,131121200310093614,男,22,朱庆忠,父子,15832865136,,河北省衡水市枣强县,