2 Commits

Author SHA1 Message Date
469b325f0e feat(card): 新增传染病报卡管理系统
- 实现报卡管理服务接口和具体实现类
- 添加报卡统计、分页查询、详情查看功能
- 实现批量审核、批量退回、单条审核功能
- 添加审核记录查询和科室树获取功能
- 实现报卡数据导出Excel功能
- 创建报卡查询参数和统计数据显示对象
- 添加审核记录、传染病卡片等数据传输对象
- 实现报卡和审核记录的数据访问层
- 定义传染病卡片和审核记录领域实体模型
- 提供REST API控制器接口供前端调用
2026-03-06 22:33:36 +08:00
8a3fe5461e fix(common): 统一异常处理并迁移打印功能到hiprint
- 替换所有System.out.println和printStackTrace为slf4j日志记录
- 在BeanUtils、AuditFieldUtil、DateUtils、ServletUtils等工具类中添加Logger实例
- 在Flowable相关控制器和服务中统一错误日志记录格式
- 在代码生成器中添加日志记录功能
- 将前端打印组件从Lodop迁移到hiprint打印方案
- 更新体温单打印功能使用hiprint预览打印
- 移除调试用的console.log语句
- 修复打印模板中线条元素类型定义
2026-03-06 22:32:56 +08:00
26 changed files with 2916 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
/*
* Copyright ©2026 CJB-CNIT Team. All rights reserved
*/
package com.openhis.web.cardmanagement.appservice;
import com.core.common.core.domain.R;
import com.openhis.web.cardmanagement.dto.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 报卡管理 Service接口
*
* @author system
* @date 2026-03-05
*/
public interface ICardManageAppService {
/**
* 获取统计数据
*
* @return 统计数据
*/
CardStatisticsDto getStatistics();
/**
* 分页查询报卡列表
*
* @param queryParams 查询参数
* @return 分页数据
*/
R<?> getCardPage(CardQueryDto queryParams);
/**
* 获取报卡详情
*
* @param cardNo 卡片编号
* @return 报卡详情
*/
InfectiousCardDto getCardDetail(String cardNo);
/**
* 获取审核记录
*
* @param cardNo 卡片编号
* @return 审核记录列表
*/
List<AuditRecordDto> getAuditRecords(String cardNo);
/**
* 批量审核
*
* @param batchAuditDto 批量审核参数
* @return 结果
*/
R<?> batchAudit(BatchAuditDto batchAuditDto);
/**
* 批量退回
*
* @param batchReturnDto 批量退回参数
* @return 结果
*/
R<?> batchReturn(BatchReturnDto batchReturnDto);
/**
* 单条审核通过
*
* @param auditDto 审核参数
* @return 结果
*/
R<?> auditPass(SingleAuditDto auditDto);
/**
* 单条退回
*
* @param returnDto 退回参数
* @return 结果
*/
R<?> auditReturn(SingleReturnDto returnDto);
/**
* 导出报卡列表
*
* @param queryParams 查询参数
* @param response 响应
*/
void exportCards(CardQueryDto queryParams, HttpServletResponse response);
/**
* 获取科室树
*
* @return 科室树数据
*/
R<?> getDeptTree();
}

View File

@@ -0,0 +1,366 @@
/*
* Copyright ©2026 CJB-CNIT Team. All rights reserved
*/
package com.openhis.web.cardmanagement.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.core.common.utils.SecurityUtils;
import com.openhis.infectious.domain.InfectiousAudit;
import com.openhis.infectious.domain.InfectiousCard;
import com.openhis.web.cardmanagement.appservice.ICardManageAppService;
import com.openhis.web.cardmanagement.dto.*;
import com.openhis.web.cardmanagement.mapper.InfectiousAuditMapper;
import com.openhis.web.cardmanagement.mapper.InfectiousCardMapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 报卡管理 Service实现
*
* @author system
* @date 2026-03-05
*/
@Service
@Slf4j
@AllArgsConstructor
public class CardManageAppServiceImpl implements ICardManageAppService {
private final InfectiousCardMapper infectiousCardMapper;
private final InfectiousAuditMapper infectiousAuditMapper;
@Override
public CardStatisticsDto getStatistics() {
CardStatisticsDto dto = new CardStatisticsDto();
dto.setTodayPending(infectiousCardMapper.countTodayPending());
dto.setMonthFailed(infectiousCardMapper.countMonthFailed());
dto.setMonthSuccess(infectiousCardMapper.countMonthSuccess());
dto.setMonthReported(infectiousCardMapper.countMonthReported());
return dto;
}
@Override
public R<?> getCardPage(CardQueryDto queryParams) {
Page<InfectiousCard> page = new Page<>(queryParams.getPageNo(), queryParams.getPageSize());
LambdaQueryWrapper<InfectiousCard> wrapper = new LambdaQueryWrapper<>();
// 登记来源
if (queryParams.getRegistrationSource() != null) {
wrapper.eq(InfectiousCard::getRegistrationSource, queryParams.getRegistrationSource());
}
// 状态
if (StringUtils.hasText(queryParams.getStatus())) {
wrapper.eq(InfectiousCard::getStatus, queryParams.getStatus());
}
// 患者姓名模糊查询
if (StringUtils.hasText(queryParams.getPatientName())) {
wrapper.like(InfectiousCard::getPatName, queryParams.getPatientName());
}
// 科室
if (queryParams.getDeptId() != null) {
wrapper.eq(InfectiousCard::getDeptId, queryParams.getDeptId());
}
// 时间范围
if (StringUtils.hasText(queryParams.getStartDate())) {
wrapper.ge(InfectiousCard::getCreateTime, queryParams.getStartDate() + " 00:00:00");
}
if (StringUtils.hasText(queryParams.getEndDate())) {
wrapper.le(InfectiousCard::getCreateTime, queryParams.getEndDate() + " 23:59:59");
}
// 按创建时间倒序
wrapper.orderByDesc(InfectiousCard::getCreateTime);
IPage<InfectiousCard> result = infectiousCardMapper.selectPage(page, wrapper);
// 转换为DTO
List<InfectiousCardDto> list = result.getRecords().stream().map(this::convertToDto).collect(Collectors.toList());
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("list", list);
resultMap.put("total", result.getTotal());
return R.ok(resultMap);
}
@Override
public InfectiousCardDto getCardDetail(String cardNo) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
if (card == null) {
return null;
}
return convertToDto(card);
}
@Override
public List<AuditRecordDto> getAuditRecords(String cardNo) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
if (card == null) {
return new ArrayList<>();
}
List<InfectiousAudit> records = infectiousAuditMapper.selectByCardId(card.getId());
return records.stream().map(this::convertAuditToDto).collect(Collectors.toList());
}
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> batchAudit(BatchAuditDto batchAuditDto) {
if (batchAuditDto.getCardNos() == null || batchAuditDto.getCardNos().isEmpty()) {
return R.fail("请选择要审核的报卡");
}
String auditorId = SecurityUtils.getUserId().toString();
String auditorName = SecurityUtils.getUsername();
int successCount = 0;
for (String cardNo : batchAuditDto.getCardNos()) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
if (card == null) continue;
if ("2".equals(card.getStatus()) || "3".equals(card.getStatus())) continue;
// 更新状态为已审核
String oldStatus = card.getStatus();
card.setStatus("2");
card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card);
// 创建审核记录
createAuditRecord(card.getId(), oldStatus, "2", "1", batchAuditDto.getAuditOpinion(),
null, auditorId, auditorName, true, batchAuditDto.getCardNos().size());
successCount++;
}
return R.ok("批量审核成功,共审核" + successCount + "");
}
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> batchReturn(BatchReturnDto batchReturnDto) {
if (batchReturnDto.getCardNos() == null || batchReturnDto.getCardNos().isEmpty()) {
return R.fail("请选择要退回的报卡");
}
String auditorId = SecurityUtils.getUserId().toString();
String auditorName = SecurityUtils.getUsername();
int successCount = 0;
for (String cardNo : batchReturnDto.getCardNos()) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
if (card == null) continue;
if ("2".equals(card.getStatus()) || "3".equals(card.getStatus())) continue;
// 更新状态为退回(审核失败)
String oldStatus = card.getStatus();
card.setStatus("5");
card.setReturnReason(batchReturnDto.getReturnReason());
card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card);
// 创建审核记录
createAuditRecord(card.getId(), oldStatus, "5", "3", null,
batchReturnDto.getReturnReason(), auditorId, auditorName, true, batchReturnDto.getCardNos().size());
successCount++;
}
return R.ok("批量退回成功,共退回" + successCount + "");
}
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> auditPass(SingleAuditDto auditDto) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(auditDto.getCardNo());
if (card == null) {
return R.fail("报卡不存在");
}
String auditorId = SecurityUtils.getUserId().toString();
String auditorName = SecurityUtils.getUsername();
// 更新状态
String oldStatus = card.getStatus();
card.setStatus("2");
card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card);
// 创建审核记录
createAuditRecord(card.getId(), oldStatus, "2", "2", auditDto.getAuditOpinion(),
null, auditorId, auditorName, false, 1);
return R.ok("审核通过");
}
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> auditReturn(SingleReturnDto returnDto) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(returnDto.getCardNo());
if (card == null) {
return R.fail("报卡不存在");
}
String auditorId = SecurityUtils.getUserId().toString();
String auditorName = SecurityUtils.getUsername();
// 更新状态
String oldStatus = card.getStatus();
card.setStatus("5");
card.setReturnReason(returnDto.getReturnReason());
card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card);
// 创建审核记录
createAuditRecord(card.getId(), oldStatus, "5", "4", null,
returnDto.getReturnReason(), auditorId, auditorName, false, 1);
return R.ok("已退回");
}
@Override
public void exportCards(CardQueryDto queryParams, HttpServletResponse response) {
LambdaQueryWrapper<InfectiousCard> wrapper = new LambdaQueryWrapper<>();
// 应用查询条件
if (queryParams.getRegistrationSource() != null) {
wrapper.eq(InfectiousCard::getRegistrationSource, queryParams.getRegistrationSource());
}
if (StringUtils.hasText(queryParams.getStatus())) {
wrapper.eq(InfectiousCard::getStatus, queryParams.getStatus());
}
if (StringUtils.hasText(queryParams.getPatientName())) {
wrapper.like(InfectiousCard::getPatName, queryParams.getPatientName());
}
if (queryParams.getDeptId() != null) {
wrapper.eq(InfectiousCard::getDeptId, queryParams.getDeptId());
}
if (StringUtils.hasText(queryParams.getStartDate())) {
wrapper.ge(InfectiousCard::getCreateTime, queryParams.getStartDate() + " 00:00:00");
}
if (StringUtils.hasText(queryParams.getEndDate())) {
wrapper.le(InfectiousCard::getCreateTime, queryParams.getEndDate() + " 23:59:59");
}
wrapper.orderByDesc(InfectiousCard::getCreateTime);
List<InfectiousCard> cards = infectiousCardMapper.selectList(wrapper);
try (Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet("报卡列表");
// 创建表头
Row headerRow = sheet.createRow(0);
String[] headers = {"报卡编号", "患者姓名", "性别", "年龄", "疾病名称", "科室", "上报时间", "状态"};
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
}
// 填充数据
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (int i = 0; i < cards.size(); i++) {
InfectiousCard card = cards.get(i);
Row row = sheet.createRow(i + 1);
row.createCell(0).setCellValue(card.getCardNo());
row.createCell(1).setCellValue(card.getPatName());
row.createCell(2).setCellValue("1".equals(card.getSex()) ? "" : "2".equals(card.getSex()) ? "" : "未知");
row.createCell(3).setCellValue(card.getAge() != null ? card.getAge() + "" : "");
row.createCell(4).setCellValue(card.getDiseaseName());
row.createCell(5).setCellValue(card.getDeptName());
row.createCell(6).setCellValue(card.getCreateTime() != null ? dateFormat.format(card.getCreateTime()) : "");
row.createCell(7).setCellValue(getStatusText(card.getStatus()));
}
// 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename=" +
URLEncoder.encode("报卡列表.xlsx", StandardCharsets.UTF_8));
workbook.write(response.getOutputStream());
} catch (IOException e) {
log.error("导出报卡列表失败", e);
}
}
@Override
public R<?> getDeptTree() {
// 返回科室树数据,实际应从科室服务获取
return R.ok(new ArrayList<>());
}
/**
* 转换为DTO
*/
private InfectiousCardDto convertToDto(InfectiousCard card) {
InfectiousCardDto dto = new InfectiousCardDto();
BeanUtils.copyProperties(card, dto);
return dto;
}
/**
* 转换审核记录为DTO
*/
private AuditRecordDto convertAuditToDto(InfectiousAudit audit) {
AuditRecordDto dto = new AuditRecordDto();
BeanUtils.copyProperties(audit, dto);
dto.setCardId(audit.getCardId() != null ? audit.getCardId().toString() : null);
return dto;
}
/**
* 创建审核记录
*/
private void createAuditRecord(Long cardId, String statusFrom, String statusTo, String auditType,
String auditOpinion, String returnReason, String auditorId, String auditorName,
Boolean isBatch, Integer batchSize) {
InfectiousAudit audit = new InfectiousAudit();
audit.setCardId(cardId);
audit.setAuditSeq(infectiousAuditMapper.getNextAuditSeq(cardId));
audit.setAuditType(auditType);
audit.setAuditStatusFrom(statusFrom);
audit.setAuditStatusTo(statusTo);
audit.setAuditTime(LocalDateTime.now());
audit.setAuditorId(auditorId);
audit.setAuditorName(auditorName);
audit.setAuditOpinion(auditOpinion);
audit.setReasonForReturn(returnReason);
audit.setIsBatch(isBatch);
audit.setBatchSize(batchSize);
infectiousAuditMapper.insert(audit);
}
/**
* 获取状态文本
*/
private String getStatusText(String status) {
switch (status) {
case "0": return "暂存";
case "1": return "待审核";
case "2": return "审核通过";
case "3": return "已上报";
case "4": return "失败";
case "5": return "审核失败";
default: return "未知";
}
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright ©2026 CJB-CNIT Team. All rights reserved
*/
package com.openhis.web.cardmanagement.controller;
import com.core.common.core.domain.R;
import com.openhis.web.cardmanagement.appservice.ICardManageAppService;
import com.openhis.web.cardmanagement.dto.*;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 报卡管理 Controller
*
* @author system
* @date 2026-03-05
*/
@RestController
@RequestMapping("/card-management")
@Slf4j
@AllArgsConstructor
public class CardManageController {
private final ICardManageAppService cardManageAppService;
/**
* 获取统计数据
*
* @return 统计数据
*/
@GetMapping("/statistics")
public R<CardStatisticsDto> getStatistics() {
return R.ok(cardManageAppService.getStatistics());
}
/**
* 分页查询报卡列表
*
* @param queryParams 查询参数
* @return 分页数据
*/
@GetMapping("/page")
public R<?> getCardPage(CardQueryDto queryParams) {
return cardManageAppService.getCardPage(queryParams);
}
/**
* 获取报卡详情
*
* @param cardNo 卡片编号
* @return 报卡详情
*/
@GetMapping("/detail/{cardNo}")
public R<InfectiousCardDto> getCardDetail(@PathVariable String cardNo) {
return R.ok(cardManageAppService.getCardDetail(cardNo));
}
/**
* 获取审核记录
*
* @param cardNo 卡片编号
* @return 审核记录列表
*/
@GetMapping("/audit-records/{cardNo}")
public R<List<AuditRecordDto>> getAuditRecords(@PathVariable String cardNo) {
return R.ok(cardManageAppService.getAuditRecords(cardNo));
}
/**
* 批量审核
*
* @param batchAuditDto 批量审核参数
* @return 结果
*/
@PostMapping("/batch-audit")
public R<?> batchAudit(@RequestBody BatchAuditDto batchAuditDto) {
return cardManageAppService.batchAudit(batchAuditDto);
}
/**
* 批量退回
*
* @param batchReturnDto 批量退回参数
* @return 结果
*/
@PostMapping("/batch-return")
public R<?> batchReturn(@RequestBody BatchReturnDto batchReturnDto) {
return cardManageAppService.batchReturn(batchReturnDto);
}
/**
* 单条审核通过
*
* @param auditDto 审核参数
* @return 结果
*/
@PostMapping("/audit-pass")
public R<?> auditPass(@RequestBody SingleAuditDto auditDto) {
return cardManageAppService.auditPass(auditDto);
}
/**
* 单条退回
*
* @param returnDto 退回参数
* @return 结果
*/
@PostMapping("/audit-return")
public R<?> auditReturn(@RequestBody SingleReturnDto returnDto) {
return cardManageAppService.auditReturn(returnDto);
}
/**
* 导出报卡列表
*
* @param queryParams 查询参数
* @param response 响应
*/
@GetMapping("/export")
public void exportCards(CardQueryDto queryParams, HttpServletResponse response) {
cardManageAppService.exportCards(queryParams, response);
}
/**
* 获取科室树
*
* @return 科室树数据
*/
@GetMapping("/dept-tree")
public R<?> getDeptTree() {
return cardManageAppService.getDeptTree();
}
}

View File

@@ -0,0 +1,51 @@
package com.openhis.web.cardmanagement.dto;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 审核记录DTO
*
* @author system
* @date 2026-03-05
*/
@Data
public class AuditRecordDto {
/** 审核记录ID */
private Long auditId;
/** 报卡ID */
private String cardId;
/** 审核序号 */
private Integer auditSeq;
/** 审核类型 */
private String auditType;
/** 审核前状态 */
private String auditStatusFrom;
/** 审核后状态 */
private String auditStatusTo;
/** 审核时间 */
private LocalDateTime auditTime;
/** 审核人账号 */
private String auditorId;
/** 审核人姓名 */
private String auditorName;
/** 审核意见 */
private String auditOpinion;
/** 退回原因 */
private String reasonForReturn;
/** 是否批量 */
private Boolean isBatch;
}

View File

@@ -0,0 +1,21 @@
package com.openhis.web.cardmanagement.dto;
import lombok.Data;
import java.util.List;
/**
* 批量审核参数DTO
*
* @author system
* @date 2026-03-05
*/
@Data
public class BatchAuditDto {
/** 卡片编号列表 */
private List<String> cardNos;
/** 审核意见 */
private String auditOpinion;
}

View File

@@ -0,0 +1,21 @@
package com.openhis.web.cardmanagement.dto;
import lombok.Data;
import java.util.List;
/**
* 批量退回参数DTO
*
* @author system
* @date 2026-03-05
*/
@Data
public class BatchReturnDto {
/** 卡片编号列表 */
private List<String> cardNos;
/** 退回原因 */
private String returnReason;
}

View File

@@ -0,0 +1,37 @@
package com.openhis.web.cardmanagement.dto;
import lombok.Data;
/**
* 报卡查询参数DTO
*
* @author system
* @date 2026-03-05
*/
@Data
public class CardQueryDto {
/** 当前页 */
private Integer pageNo = 1;
/** 每页条数 */
private Integer pageSize = 10;
/** 登记来源 */
private Integer registrationSource;
/** 开始日期 */
private String startDate;
/** 结束日期 */
private String endDate;
/** 患者姓名 */
private String patientName;
/** 审核状态 */
private String status;
/** 科室ID */
private Long deptId;
}

View File

@@ -0,0 +1,25 @@
package com.openhis.web.cardmanagement.dto;
import lombok.Data;
/**
* 报卡统计数据DTO
*
* @author system
* @date 2026-03-05
*/
@Data
public class CardStatisticsDto {
/** 今日待审核 */
private Integer todayPending;
/** 本月审核失败 */
private Integer monthFailed;
/** 本月审核成功 */
private Integer monthSuccess;
/** 本月已上报 */
private Integer monthReported;
}

View File

@@ -0,0 +1,127 @@
package com.openhis.web.cardmanagement.dto;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 传染病报卡详情DTO
*
* @author system
* @date 2026-03-05
*/
@Data
public class InfectiousCardDto {
/** 卡片编号 */
private String cardNo;
/** 患者姓名 */
private String patName;
/** 家长姓名 */
private String parentName;
/** 证件号码 */
private String idNo;
/** 性别(1男/2女/0未知) */
private String sex;
/** 出生日期 */
private LocalDate birthday;
/** 实足年龄 */
private Integer age;
/** 年龄单位(1岁/2月/3天) */
private String ageUnit;
/** 工作单位 */
private String workplace;
/** 联系电话 */
private String phone;
/** 紧急联系人电话 */
private String contactPhone;
/** 现住址省 */
private String addressProv;
/** 现住址市 */
private String addressCity;
/** 现住址县 */
private String addressCounty;
/** 现住址街道 */
private String addressTown;
/** 现住址村/居委 */
private String addressVillage;
/** 现住址门牌号 */
private String addressHouse;
/** 病人属于 */
private String patientbelong;
/** 职业 */
private String occupation;
/** 疾病编码 */
private String diseaseCode;
/** 疾病名称 */
private String diseaseName;
/** 分型 */
private String diseaseSubtype;
/** 病例分类 */
private String diseaseType;
/** 发病日期 */
private LocalDate onsetDate;
/** 诊断日期 */
private LocalDateTime diagDate;
/** 死亡日期 */
private LocalDate deathDate;
/** 订正病名 */
private String revisedDiseaseName;
/** 退卡原因 */
private String returnReason;
/** 报告单位 */
private String reportOrg;
/** 联系电话 */
private String reportOrgPhone;
/** 报告医生 */
private String reportDoc;
/** 填卡日期 */
private LocalDate reportDate;
/** 状态 */
private String status;
/** 报卡名称代码 */
private Integer cardNameCode;
/** 登记来源 */
private Integer registrationSource;
/** 科室名称 */
private String deptName;
/** 创建时间 */
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,19 @@
package com.openhis.web.cardmanagement.dto;
import lombok.Data;
/**
* 单条审核参数DTO
*
* @author system
* @date 2026-03-05
*/
@Data
public class SingleAuditDto {
/** 卡片编号 */
private String cardNo;
/** 审核意见 */
private String auditOpinion;
}

View File

@@ -0,0 +1,19 @@
package com.openhis.web.cardmanagement.dto;
import lombok.Data;
/**
* 单条退回参数DTO
*
* @author system
* @date 2026-03-05
*/
@Data
public class SingleReturnDto {
/** 卡片编号 */
private String cardNo;
/** 退回原因 */
private String returnReason;
}

View File

@@ -0,0 +1,31 @@
package com.openhis.web.cardmanagement.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.openhis.infectious.domain.InfectiousAudit;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* 审核记录Mapper
*
* @author system
* @date 2026-03-05
*/
@Mapper
public interface InfectiousAuditMapper extends BaseMapper<InfectiousAudit> {
/**
* 根据报卡ID查询审核记录
*/
@Select("SELECT * FROM infectious_audit WHERE card_id = #{cardId} ORDER BY audit_time DESC")
List<InfectiousAudit> selectByCardId(@Param("cardId") Long cardId);
/**
* 获取下一个审核序号
*/
@Select("SELECT COALESCE(MAX(audit_seq), 0) + 1 FROM infectious_audit WHERE card_id = #{cardId}")
Integer getNextAuditSeq(@Param("cardId") Long cardId);
}

View File

@@ -0,0 +1,50 @@
package com.openhis.web.cardmanagement.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.openhis.infectious.domain.InfectiousCard;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
/**
* 传染病报卡Mapper
*
* @author system
* @date 2026-03-05
*/
@Mapper
public interface InfectiousCardMapper extends BaseMapper<InfectiousCard> {
/**
* 统计今日待审核数量
*/
@Select("SELECT COUNT(*) FROM infectious_card WHERE DATE(create_time) = CURRENT_DATE AND status = '1'")
Integer countTodayPending();
/**
* 统计本月审核失败数量
*/
@Select("SELECT COUNT(*) FROM infectious_card WHERE DATE_TRUNC('month', create_time) = DATE_TRUNC('month', CURRENT_DATE) AND status = '5'")
Integer countMonthFailed();
/**
* 统计本月审核成功数量
*/
@Select("SELECT COUNT(*) FROM infectious_card WHERE DATE_TRUNC('month', create_time) = DATE_TRUNC('month', CURRENT_DATE) AND status = '2'")
Integer countMonthSuccess();
/**
* 统计本月已上报数量
*/
@Select("SELECT COUNT(*) FROM infectious_card WHERE DATE_TRUNC('month', create_time) = DATE_TRUNC('month', CURRENT_DATE) AND status = '3'")
Integer countMonthReported();
/**
* 根据卡片编号查询
*/
@Select("SELECT * FROM infectious_card WHERE card_no = #{cardNo}")
InfectiousCard selectByCardNo(@Param("cardNo") String cardNo);
}

View File

@@ -0,0 +1,75 @@
package com.openhis.infectious.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 审核记录Entity实体
*
* @author system
* @date 2026-03-05
*/
@Data
@TableName("infectious_audit")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
public class InfectiousAudit extends HisBaseEntity {
/** 审核记录ID */
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long auditId;
/** 报卡ID */
@JsonSerialize(using = ToStringSerializer.class)
private Long cardId;
/** 审核序号 */
private Integer auditSeq;
/** 审核类型(1批量审核/2单审核通过/3批量退回/4单退回/5其他) */
private String auditType;
/** 审核前状态 */
private String auditStatusFrom;
/** 审核后状态 */
private String auditStatusTo;
/** 审核时间 */
private LocalDateTime auditTime;
/** 审核人账号 */
private String auditorId;
/** 审核人姓名 */
private String auditorName;
/** 审核意见 */
private String auditOpinion;
/** 退回原因 */
private String reasonForReturn;
/** 失败原因码 */
private String failReasonCode;
/** 失败详情 */
private String failReasonDesc;
/** 是否批量 */
private Boolean isBatch;
/** 批量数量 */
private Integer batchSize;
}

View File

@@ -0,0 +1,174 @@
package com.openhis.infectious.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 传染病报卡Entity实体
*
* @author system
* @date 2026-03-05
*/
@Data
@TableName("infectious_card")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
public class InfectiousCard extends HisBaseEntity {
/** 卡片编号 */
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/** 卡片编号(业务编号) */
private String cardNo;
/** 本次就诊ID */
@JsonSerialize(using = ToStringSerializer.class)
private Long visitId;
/** 诊断记录ID */
@JsonSerialize(using = ToStringSerializer.class)
private Long diagId;
/** 患者主索引 */
@JsonSerialize(using = ToStringSerializer.class)
private Long patId;
/** 证件类型 */
private Integer idType;
/** 证件号码 */
private String idNo;
/** 患者姓名 */
private String patName;
/** 家长姓名 */
private String parentName;
/** 性别(1男/2女/0未知) */
private String sex;
/** 出生日期 */
private LocalDate birthday;
/** 实足年龄 */
private Integer age;
/** 年龄单位(1岁/2月/3天) */
private String ageUnit;
/** 工作单位 */
private String workplace;
/** 联系电话 */
private String phone;
/** 紧急联系人电话 */
private String contactPhone;
/** 现住址省 */
private String addressProv;
/** 现住址市 */
private String addressCity;
/** 现住址县 */
private String addressCounty;
/** 现住址街道 */
private String addressTown;
/** 现住址村/居委 */
private String addressVillage;
/** 现住址门牌号 */
private String addressHouse;
/** 病人属于 */
private String patientbelong;
/** 职业 */
private String occupation;
/** 疾病编码 */
private String diseaseCode;
/** 疾病名称 */
private String diseaseName;
/** 分型 */
private String diseaseSubtype;
/** 其他传染病 */
private String otherDisease;
/** 病例分类 */
private String diseaseType;
/** 发病日期 */
private LocalDate onsetDate;
/** 诊断日期 */
private LocalDateTime diagDate;
/** 死亡日期 */
private LocalDate deathDate;
/** 订正病名 */
private String revisedDiseaseName;
/** 退卡原因 */
private String returnReason;
/** 报告单位 */
private String reportOrg;
/** 联系电话 */
private String reportOrgPhone;
/** 报告医生 */
private String reportDoc;
/** 填卡日期 */
private LocalDate reportDate;
/** 状态(0暂存/1已提交/2已审核/3已上报/4失败/5退回) */
private String status;
/** 失败原因 */
private String failMsg;
/** 上报XML */
private String xmlContent;
/** 报卡名称代码 */
private Integer cardNameCode;
/** 登记来源(1门诊/2住院) */
private Integer registrationSource;
/** 科室ID */
@JsonSerialize(using = ToStringSerializer.class)
private Long deptId;
/** 科室名称 */
private String deptName;
/** 医生ID */
@JsonSerialize(using = ToStringSerializer.class)
private Long doctorId;
}

View File

@@ -0,0 +1,22 @@
{
"panels": [
{
"height": 60,
"index": 1,
"name": "床头卡",
"paperFooter": 170,
"paperHeader": 0,
"paperList": {"height": 60, "type": "自定义", "width": 100},
"paperNumberDisabled": true,
"paperType": "自定义",
"printElements": [
{"options": {"field": "bedName", "fontSize": 10, "fontWeight": "bold", "height": 10, "left": 45, "title": "床号:", "top": 5, "width": 50, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "patientName", "fontSize": 10, "fontWeight": "bold", "height": 10, "left": 45, "title": "姓名:", "top": 17, "width": 50, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "hisId", "fontSize": 10, "fontWeight": "bold", "height": 10, "left": 45, "title": "患者编号:", "top": 29, "width": 50, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "dept", "fontSize": 10, "fontWeight": "bold", "height": 10, "left": 45, "title": "分诊科室:", "top": 41, "width": 50, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "triageLevel", "fontSize": 10, "fontWeight": "bold", "height": 10, "left": 45, "title": "分诊等级:", "top": 53, "width": 50, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}}
],
"width": 100
}
]
}

View File

@@ -0,0 +1,38 @@
{
"panels": [
{
"height": 210,
"index": 1,
"name": "护理交接班",
"paperFooter": 595.5,
"paperHeader": 0,
"paperList": {"height": 210, "type": "A4", "width": 297},
"paperNumberDisabled": true,
"paperType": "A4",
"printElements": [
{"options": {"fontSize": 14, "fontWeight": "bold", "height": 15, "left": 0, "textAlign": "center", "title": "护理交接班", "top": 5, "width": 210}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "date", "fontSize": 10, "height": 10, "left": 5, "title": "日期:", "top": 25, "width": 50, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "initiatorName", "fontSize": 10, "height": 10, "left": 60, "title": "发起人:", "top": 25, "width": 50, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "heirName", "fontSize": 10, "height": 10, "left": 120, "title": "接收人:", "top": 25, "width": 50, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {
"columns": [[
{"align": "center", "field": "typeDisplay", "title": "类别", "width": 20},
{"align": "center", "field": "bedName", "title": "床号", "width": 25},
{"align": "center", "field": "patientName", "title": "姓名", "width": 30},
{"align": "center", "field": "mainSuit", "title": "主诉", "width": 45},
{"align": "center", "field": "previousHistory", "title": "既往史", "width": 45},
{"align": "center", "field": "diagnosis", "title": "诊断", "width": 45},
{"align": "center", "field": "content", "title": "交接信息", "width": 60}
]],
"field": "shiftRecordItems",
"fontSize": 9,
"height": 150,
"left": 5,
"top": 40,
"width": 200
}, "printElementType": {"title": "表格", "type": "table"}}
],
"width": 210
}
]
}

View File

@@ -0,0 +1,27 @@
{
"panels": [
{
"height": 210,
"index": 1,
"name": "会诊申请单",
"paperFooter": 595.5,
"paperHeader": 0,
"paperList": {"height": 210, "type": "A4", "width": 210},
"paperNumberDisabled": true,
"paperType": "A4",
"printElements": [
{"options": {"fontSize": 14, "fontWeight": "bold", "height": 15, "left": 0, "textAlign": "center", "title": "会诊申请单", "top": 5, "width": 180}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "hospitalName", "fontSize": 10, "height": 10, "left": 0, "textAlign": "center", "title": "医院名称", "top": 22, "width": 180}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "patientName", "fontSize": 10, "height": 10, "left": 5, "title": "姓名:", "top": 40, "width": 60, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "gender", "fontSize": 10, "height": 10, "left": 70, "title": "性别:", "top": 40, "width": 30, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "age", "fontSize": 10, "height": 10, "left": 105, "title": "年龄:", "top": 40, "width": 40, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "deptName", "fontSize": 10, "height": 10, "left": 150, "title": "科室:", "top": 40, "width": 30, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "diagnosis", "fontSize": 10, "height": 20, "left": 5, "title": "初步诊断:", "top": 55, "width": 170, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "consultationReason", "fontSize": 10, "height": 40, "left": 5, "title": "会诊目的:", "top": 80, "width": 170, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "applyTime", "fontSize": 10, "height": 10, "left": 5, "title": "申请时间:", "top": 130, "width": 80, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "applyDoctor", "fontSize": 10, "height": 10, "left": 100, "title": "申请医生:", "top": 130, "width": 75, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}}
],
"width": 180
}
]
}

View File

@@ -0,0 +1,41 @@
{
"panels": [
{
"height": 210,
"index": 1,
"name": "医嘱执行单",
"paperFooter": 595.5,
"paperHeader": 0,
"paperList": {"height": 210, "type": "A4", "width": 297},
"paperNumberDisabled": true,
"paperType": "A4",
"printElements": [
{"options": {"fontSize": 14, "fontWeight": "bold", "height": 15, "left": 0, "textAlign": "center", "title": "医嘱执行单", "top": 5, "width": 210}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "hospitalName", "fontSize": 12, "fontWeight": "bold", "height": 12, "left": 0, "textAlign": "center", "title": "医院名称", "top": 20, "width": 210}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "bedName", "fontSize": 9, "height": 10, "left": 5, "title": "床号:", "top": 35, "width": 50, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "patientName", "fontSize": 9, "height": 10, "left": 60, "title": "姓名:", "top": 35, "width": 50, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "patientAge", "fontSize": 9, "height": 10, "left": 115, "title": "年龄:", "top": 35, "width": 50, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "diag", "fontSize": 9, "height": 10, "left": 5, "title": "诊断:", "top": 47, "width": 200, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {
"columns": [[
{"align": "center", "field": "moTime", "title": "医嘱日期", "width": 25},
{"align": "center", "field": "orderName", "title": "医嘱", "width": 40},
{"align": "center", "field": "flag", "title": "标记", "width": 15},
{"align": "center", "field": "remark", "title": "嘱托", "width": 25},
{"align": "center", "field": "doseOnceUnit", "title": "用量", "width": 20},
{"align": "center", "field": "usageName", "title": "用法", "width": 20},
{"align": "center", "field": "frequency", "title": "频次", "width": 20},
{"align": "center", "field": "moDocName", "title": "开立医生", "width": 25}
]],
"field": "recordData",
"fontSize": 8,
"height": 140,
"left": 5,
"top": 60,
"width": 200
}, "printElementType": {"title": "表格", "type": "table"}}
],
"width": 210
}
]
}

View File

@@ -0,0 +1,39 @@
{
"panels": [
{
"height": 80,
"index": 1,
"name": "输液标签",
"paperFooter": 226.8,
"paperHeader": 0,
"paperList": {"height": 80, "type": "自定义", "width": 100},
"paperNumberDisabled": true,
"paperType": "自定义",
"printElements": [
{"options": {"fontSize": 12, "fontWeight": "bold", "height": 12, "left": 0, "textAlign": "center", "title": "急诊输液贴", "top": 5, "width": 60}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "qrCode", "height": 30, "left": 65, "qrCodeLevel": 0, "title": "二维码", "top": 5, "width": 30}, "printElementType": {"title": "二维码", "type": "text"}},
{"options": {"field": "hisNo", "fontSize": 9, "height": 10, "left": 5, "title": "病历号:", "top": 20, "width": 25, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "name", "fontSize": 9, "height": 10, "left": 30, "title": "姓名:", "top": 20, "width": 20, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "sexName", "fontSize": 9, "height": 10, "left": 50, "title": "性别:", "top": 20, "width": 15, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "patientAge", "fontSize": 9, "height": 10, "left": 65, "title": "年龄:", "top": 20, "width": 30, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {
"columns": [[
{"align": "center", "field": "orderName", "title": "药品名称", "width": 35},
{"align": "center", "field": "doseOnceUnit", "title": "用量", "width": 15},
{"align": "center", "field": "flag", "title": "标记", "width": 10},
{"align": "center", "field": "frequency", "title": "频次", "width": 15},
{"align": "center", "field": "usageName", "title": "用法", "width": 15}
]],
"field": "orderDetail",
"fontSize": 8,
"height": 35,
"left": 5,
"top": 35,
"width": 90
}, "printElementType": {"title": "表格", "type": "table"}},
{"options": {"field": "printDate", "fontSize": 8, "height": 8, "left": 5, "title": "日期:", "top": 72, "width": 90, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}}
],
"width": 100
}
]
}

View File

@@ -0,0 +1,39 @@
{
"panels": [
{
"height": 180,
"index": 1,
"name": "体温单",
"paperFooter": 510,
"paperHeader": 0,
"paperList": {"height": 180, "type": "A4", "width": 210},
"paperNumberDisabled": true,
"paperType": "A4",
"printElements": [
{"options": {"fontSize": 12, "fontWeight": "bold", "height": 15, "left": 0, "textAlign": "center", "title": "体温单", "top": 5, "width": 180}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "patientName", "fontSize": 9, "height": 10, "left": 5, "title": "姓名:", "top": 25, "width": 50, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "bedName", "fontSize": 9, "height": 10, "left": 60, "title": "床号:", "top": 25, "width": 50, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "hospitalName", "fontSize": 9, "height": 10, "left": 115, "title": "科室:", "top": 25, "width": 60, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {
"columns": [[
{"align": "center", "field": "date", "title": "日期", "width": 25},
{"align": "center", "field": "time", "title": "时间", "width": 20},
{"align": "center", "field": "temperature", "title": "体温", "width": 20},
{"align": "center", "field": "pulse", "title": "脉搏", "width": 20},
{"align": "center", "field": "breath", "title": "呼吸", "width": 20},
{"align": "center", "field": "bloodPressure", "title": "血压", "width": 25},
{"align": "center", "field": "bloodOxygen", "title": "血氧", "width": 20},
{"align": "center", "field": "nurseName", "title": "签名", "width": 30}
]],
"field": "tprData",
"fontSize": 8,
"height": 130,
"left": 5,
"top": 40,
"width": 180
}, "printElementType": {"title": "表格", "type": "table"}}
],
"width": 180
}
]
}

View File

@@ -0,0 +1,35 @@
{
"panels": [
{
"height": 100,
"index": 1,
"name": "分诊条",
"paperFooter": 283.5,
"paperHeader": 0,
"paperList": {"height": 100, "type": "自定义", "width": 80},
"paperNumberDisabled": true,
"paperType": "自定义",
"printElements": [
{"options": {"fontSize": 14, "fontWeight": "bold", "height": 15, "left": 0, "textAlign": "center", "title": "分诊单", "top": 5, "width": 80}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "hisId", "height": 40, "left": 15, "qrCodeLevel": 0, "textAlign": "center", "title": "病历号二维码", "top": 25, "width": 50}, "printElementType": {"title": "二维码", "type": "text"}},
{"options": {"field": "hisId", "fontSize": 10, "height": 10, "left": 0, "textAlign": "center", "title": "病历号:", "top": 70, "width": 80, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "triageLevel", "fontSize": 10, "height": 10, "left": 0, "textAlign": "center", "title": "分级(科室)", "top": 82, "width": 80, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "patientName", "fontSize": 10, "height": 10, "left": 5, "title": "姓名:", "top": 100, "width": 35, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "sex", "fontSize": 10, "height": 10, "left": 40, "title": "性别:", "top": 100, "width": 15, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "age", "fontSize": 10, "height": 10, "left": 55, "title": "年龄:", "top": 100, "width": 20, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "temperature", "fontSize": 9, "height": 10, "left": 5, "title": "体温:", "top": 115, "width": 35, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "sphygmus", "fontSize": 9, "height": 10, "left": 40, "title": "脉搏:", "top": 115, "width": 35, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "breath", "fontSize": 9, "height": 10, "left": 5, "title": "呼吸:", "top": 127, "width": 35, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "bloodPressure", "fontSize": 9, "height": 10, "left": 40, "title": "血压:", "top": 127, "width": 35, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "bloodOxygen", "fontSize": 9, "height": 10, "left": 5, "title": "血氧:", "top": 139, "width": 70, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "triageTime", "fontSize": 9, "height": 10, "left": 5, "title": "分诊时间:", "top": 155, "width": 70, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "tel", "fontSize": 9, "height": 10, "left": 5, "title": "联系电话:", "top": 167, "width": 70, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"fontSize": 9, "fontWeight": "bold", "height": 10, "left": 5, "title": "请仔细核对个人信息后进行挂号", "top": 185, "width": 70}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"fontSize": 8, "height": 8, "left": 5, "title": "为了您家人和其他患者的健康", "top": 197, "width": 70}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"fontSize": 8, "height": 8, "left": 5, "title": "请您保持就诊秩序保持诊区安静", "top": 207, "width": 70}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"fontSize": 8, "height": 8, "left": 5, "title": "祝您早日康复", "top": 217, "width": 70}, "printElementType": {"title": "文本", "type": "text"}}
],
"width": 80
}
]
}

View File

@@ -0,0 +1,25 @@
{
"panels": [
{
"height": 25,
"index": 1,
"name": "腕带",
"paperFooter": 94.5,
"paperHeader": 0,
"paperList": {"height": 25, "type": "自定义", "width": 100},
"paperNumberContinue": true,
"paperNumberDisabled": true,
"paperType": "自定义",
"printElements": [
{"options": {"field": "patientName", "fontSize": 8, "fontWeight": "bold", "height": 9, "left": 5, "title": "姓名:", "top": 3, "width": 40, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "hisId", "fontSize": 8, "fontWeight": "bold", "height": 9, "left": 50, "title": "病历号:", "top": 3, "width": 45, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "gender", "fontSize": 8, "fontWeight": "bold", "height": 9, "left": 5, "title": "性别:", "top": 13, "width": 40, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "dept", "fontSize": 8, "fontWeight": "bold", "height": 9, "left": 50, "title": "科室:", "top": 13, "width": 45, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "bedName", "fontSize": 8, "fontWeight": "bold", "height": 9, "left": 5, "title": "床号:", "top": 23, "width": 40, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "triageLevel", "fontSize": 8, "fontWeight": "bold", "height": 9, "left": 50, "title": "分级:", "top": 23, "width": 45, "hideTitle": false}, "printElementType": {"title": "文本", "type": "text"}},
{"options": {"field": "hisId", "height": 18, "left": 80, "qrCodeLevel": 0, "textAlign": "center", "title": "腕带二维码", "top": 5, "width": 18}, "printElementType": {"title": "二维码", "type": "text"}}
],
"width": 100
}
]
}

View File

@@ -0,0 +1,108 @@
import request from '@/utils/request';
/**
* 报卡管理 - 统计数据
*/
export function getCardStatistics() {
return request({
url: '/card-management/statistics',
method: 'get',
});
}
/**
* 报卡管理 - 分页列表
*/
export function getCardList(params) {
return request({
url: '/card-management/page',
method: 'get',
params: params,
});
}
/**
* 报卡管理 - 获取报卡详情
*/
export function getCardDetail(cardNo) {
return request({
url: `/card-management/detail/${cardNo}`,
method: 'get',
});
}
/**
* 报卡管理 - 获取审核记录
*/
export function getAuditRecords(cardNo) {
return request({
url: `/card-management/audit-records/${cardNo}`,
method: 'get',
});
}
/**
* 报卡管理 - 批量审核
*/
export function batchAudit(data) {
return request({
url: '/card-management/batch-audit',
method: 'post',
data: data,
});
}
/**
* 报卡管理 - 批量退回
*/
export function batchReturn(data) {
return request({
url: '/card-management/batch-return',
method: 'post',
data: data,
});
}
/**
* 报卡管理 - 单条审核通过
*/
export function auditPass(data) {
return request({
url: '/card-management/audit-pass',
method: 'post',
data: data,
});
}
/**
* 报卡管理 - 单条退回
*/
export function auditReturn(data) {
return request({
url: '/card-management/audit-return',
method: 'post',
data: data,
});
}
/**
* 报卡管理 - 导出
*/
export function exportCards(params) {
return request({
url: '/card-management/export',
method: 'get',
params: params,
responseType: 'blob',
});
}
/**
* 报卡管理 - 获取科室列表(树形)
*/
export function getDeptTree() {
return request({
url: '/card-management/dept-tree',
method: 'get',
});
}

View File

@@ -0,0 +1,483 @@
<template>
<el-drawer
:model-value="visible"
:title="mode === 'audit' ? '审核报卡' : '查看报卡'"
direction="rtl"
size="700px"
:before-close="handleClose"
>
<div class="drawer-content">
<!-- 报卡表单 -->
<div class="card-form-section">
<h3 class="section-title">中华人民共和国传染病报告卡</h3>
<el-form :model="cardData" label-width="100px" class="card-form">
<!-- 卡片编号 -->
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="卡片编号">
<el-input v-model="cardData.cardNo" disabled />
</el-form-item>
</el-col>
</el-row>
<!-- 患者基本信息 -->
<div class="form-divider">患者基本信息</div>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="患者姓名">
<el-input v-model="cardData.patName" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="家长姓名">
<el-input v-model="cardData.parentName" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="身份证号">
<el-input v-model="cardData.idNo" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="性别">
<el-input :value="getSexName(cardData.sex)" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="出生日期">
<el-input v-model="cardData.birthday" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="实足年龄">
<el-input :value="cardData.age + getAgeUnit(cardData.ageUnit)" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="联系电话">
<el-input v-model="cardData.phone" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="紧急电话">
<el-input v-model="cardData.contactPhone" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="24">
<el-form-item label="现住地址">
<el-input :value="getFullAddress(cardData)" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="病人属于">
<el-input v-model="cardData.patientbelong" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="职业">
<el-input v-model="cardData.occupation" disabled />
</el-form-item>
</el-col>
</el-row>
<!-- 临床信息 -->
<div class="form-divider">临床信息</div>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="病例分类">
<el-input :value="getCaseTypeName(cardData.diseaseType)" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="发病日期">
<el-input :value="formatDate(cardData.onsetDate)" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="诊断日期">
<el-input :value="formatDate(cardData.diagDate)" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="疾病名称">
<el-input :value="cardData.diseaseName || cardData.diseaseCode" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="分型">
<el-input v-model="cardData.diseaseSubtype" disabled />
</el-form-item>
</el-col>
</el-row>
<!-- 上报信息 -->
<div class="form-divider">上报信息</div>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="报告单位">
<el-input v-model="cardData.reportOrg" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="报告医生">
<el-input v-model="cardData.reportDoc" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="填卡日期">
<el-input :value="formatDate(cardData.reportDate)" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="登记来源">
<el-input :value="getSourceName(cardData.registrationSource)" disabled />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<!-- 审核记录 -->
<div class="audit-records-section" v-if="auditRecords.length > 0">
<h3 class="section-title">审核记录</h3>
<el-timeline>
<el-timeline-item
v-for="record in auditRecords"
:key="record.auditId"
:timestamp="record.auditTime"
placement="top"
:type="getAuditType(record.auditStatusTo)"
>
<el-card>
<div class="record-content">
<div class="record-header">
<span class="auditor">{{ record.auditorName }}</span>
<el-tag size="small" :type="getAuditType(record.auditStatusTo)">
{{ getAuditTypeName(record.auditType) }}
</el-tag>
</div>
<div class="record-detail">
<span v-if="record.auditOpinion">审核意见{{ record.auditOpinion }}</span>
<span v-if="record.reasonForReturn">退回原因{{ record.reasonForReturn }}</span>
</div>
<div class="record-status">
{{ getStatusName(record.auditStatusFrom) }} {{ getStatusName(record.auditStatusTo) }}
</div>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</div>
<!-- 审核操作区域 -->
<div class="audit-action-section" v-if="mode === 'audit'">
<h3 class="section-title">审核操作</h3>
<el-form :model="auditForm" label-width="100px">
<el-form-item label="审核意见" required>
<el-input
v-model="auditForm.auditOpinion"
type="textarea"
:rows="3"
placeholder="请填写审核意见"
/>
</el-form-item>
<el-form-item label="退回原因">
<el-input
v-model="auditForm.returnReason"
type="textarea"
:rows="3"
placeholder="如需退回,请填写退回原因"
/>
</el-form-item>
</el-form>
</div>
</div>
<template #footer>
<div class="drawer-footer">
<el-button @click="handleClose">关闭</el-button>
<template v-if="mode === 'audit'">
<el-button type="warning" @click="handleReturn" :disabled="!auditForm.returnReason">
退回修改
</el-button>
<el-button type="success" @click="handlePass" :disabled="!auditForm.auditOpinion">
审核通过
</el-button>
</template>
</div>
</template>
</el-drawer>
</template>
<script setup>
import { ref, watch } from 'vue';
import { ElMessage } from 'element-plus';
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
mode: {
type: String,
default: 'view', // view | audit
},
cardData: {
type: Object,
default: () => ({}),
},
auditRecords: {
type: Array,
default: () => [],
},
});
const emit = defineEmits(['close', 'submit']);
const auditForm = ref({
auditOpinion: '',
returnReason: '',
});
// 状态映射
const statusMap = {
'0': '暂存',
'1': '待审核',
'2': '审核通过',
'3': '已上报',
'4': '失败',
'5': '审核失败',
};
// 审核类型映射
const auditTypeMap = {
'1': '批量审核',
'2': '审核通过',
'3': '批量退回',
'4': '退回修改',
'5': '其他',
};
// 病例分类映射
const caseTypeMap = {
'1': '疑似病例',
'2': '临床诊断病例',
'3': '确诊病例',
'4': '病原携带者',
'5': '阳性检测结果',
};
// 年龄单位映射
const ageUnitMap = {
'1': '岁',
'2': '月',
'3': '天',
};
// 来源映射
const sourceMap = {
1: '门诊',
2: '住院',
3: '急诊',
4: '体检',
};
// 获取状态名称
function getStatusName(status) {
return statusMap[status] || '未知';
}
// 获取审核类型名称
function getAuditTypeName(type) {
return auditTypeMap[type] || '未知';
}
// 获取审核类型样式
function getAuditType(status) {
if (status === '2') return 'success';
if (status === '5') return 'warning';
return 'primary';
}
// 获取性别名称
function getSexName(sex) {
if (sex === '1') return '男';
if (sex === '2') return '女';
return '未知';
}
// 获取年龄单位
function getAgeUnit(unit) {
return ageUnitMap[unit] || '岁';
}
// 获取病例分类名称
function getCaseTypeName(type) {
return caseTypeMap[type] || '-';
}
// 获取来源名称
function getSourceName(source) {
return sourceMap[source] || '-';
}
// 获取完整地址
function getFullAddress(data) {
const parts = [
data.addressProv,
data.addressCity,
data.addressCounty,
data.addressTown,
data.addressVillage,
data.addressHouse,
].filter(Boolean);
return parts.join('') || '-';
}
// 格式化日期
function formatDate(date) {
if (!date) return '-';
if (typeof date === 'string') return date.split('T')[0];
return new Date(date).toISOString().split('T')[0];
}
// 关闭抽屉
function handleClose() {
emit('close');
}
// 审核通过
function handlePass() {
if (!auditForm.value.auditOpinion) {
ElMessage.warning('请填写审核意见');
return;
}
emit('submit', {
type: 'pass',
cardNo: props.cardData.cardNo,
auditOpinion: auditForm.value.auditOpinion,
});
}
// 退回修改
function handleReturn() {
if (!auditForm.value.returnReason) {
ElMessage.warning('请填写退回原因');
return;
}
emit('submit', {
type: 'return',
cardNo: props.cardData.cardNo,
returnReason: auditForm.value.returnReason,
});
}
// 监听visible变化重置表单
watch(() => props.visible, (val) => {
if (val) {
auditForm.value = {
auditOpinion: '',
returnReason: '',
};
}
});
</script>
<style scoped>
.drawer-content {
padding: 0 20px;
}
.section-title {
font-size: 16px;
font-weight: bold;
color: #303133;
margin: 0 0 16px 0;
padding-bottom: 8px;
border-bottom: 2px solid #409eff;
}
.card-form-section {
margin-bottom: 24px;
}
.card-form {
padding: 16px;
background: #fafafa;
border-radius: 8px;
}
.form-divider {
font-size: 14px;
font-weight: bold;
color: #606266;
margin: 16px 0 12px 0;
padding-left: 8px;
border-left: 3px solid #409eff;
}
.audit-records-section {
margin-bottom: 24px;
}
.record-content {
padding: 8px 0;
}
.record-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.auditor {
font-weight: bold;
color: #303133;
}
.record-detail {
font-size: 13px;
color: #606266;
margin-bottom: 8px;
}
.record-status {
font-size: 12px;
color: #909399;
}
.audit-action-section {
margin-bottom: 24px;
}
.audit-action-section .el-form {
padding: 16px;
background: #fafafa;
border-radius: 8px;
}
.drawer-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
:deep(.el-drawer__footer) {
padding: 16px 20px;
border-top: 1px solid #ebeef5;
}
</style>

View File

@@ -0,0 +1,810 @@
<template>
<div class="card-management-container">
<!-- 统计卡片区域 -->
<div class="statistics-section">
<div class="stat-card" @click="handleStatClick('todayPending')">
<div class="stat-icon pending">
<el-icon><Clock /></el-icon>
</div>
<div class="stat-content">
<div class="stat-value">{{ statistics.todayPending || 0 }}</div>
<div class="stat-label">今日待审核</div>
</div>
</div>
<div class="stat-card" @click="handleStatClick('monthFailed')">
<div class="stat-icon failed">
<el-icon><CircleClose /></el-icon>
</div>
<div class="stat-content">
<div class="stat-value">{{ statistics.monthFailed || 0 }}</div>
<div class="stat-label">本月审核失败</div>
</div>
</div>
<div class="stat-card" @click="handleStatClick('monthSuccess')">
<div class="stat-icon success">
<el-icon><CircleCheck /></el-icon>
</div>
<div class="stat-content">
<div class="stat-value">{{ statistics.monthSuccess || 0 }}</div>
<div class="stat-label">本月审核成功</div>
</div>
</div>
<div class="stat-card" @click="handleStatClick('monthReported')">
<div class="stat-icon reported">
<el-icon><Upload /></el-icon>
</div>
<div class="stat-content">
<div class="stat-value">{{ statistics.monthReported || 0 }}</div>
<div class="stat-label">本月已上报</div>
</div>
</div>
</div>
<!-- 筛选控制区 -->
<div class="filter-section">
<el-form :model="queryParams" :inline="true" class="filter-form">
<el-form-item label="登记来源">
<el-select v-model="queryParams.registrationSource" placeholder="全部" clearable style="width: 120px">
<el-option label="全部" value="" />
<el-option label="门诊" value="1" />
<el-option label="住院" value="2" />
<el-option label="急诊" value="3" />
<el-option label="体检" value="4" />
</el-select>
</el-form-item>
<el-form-item label="上报时间">
<el-date-picker
v-model="queryParams.reportTimeRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
style="width: 240px"
/>
</el-form-item>
<el-form-item label="患者姓名">
<el-input v-model="queryParams.patientName" placeholder="请输入患者姓名" clearable style="width: 150px" />
</el-form-item>
<el-form-item label="审核状态">
<el-select v-model="queryParams.status" placeholder="全部" clearable style="width: 120px">
<el-option label="全部" value="" />
<el-option label="待审核" value="1" />
<el-option label="审核通过" value="2" />
<el-option label="审核失败" value="5" />
<el-option label="已上报" value="3" />
</el-select>
</el-form-item>
<el-form-item label="上报科室">
<el-tree-select
v-model="queryParams.deptId"
:data="deptTreeData"
:props="{ value: 'id', label: 'name', children: 'children' }"
placeholder="全部科室"
clearable
check-strictly
style="width: 180px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>
查询
</el-button>
<el-button @click="handleReset">
<el-icon><Refresh /></el-icon>
重置
</el-button>
</el-form-item>
</el-form>
</div>
<!-- 操作按钮区 -->
<div class="action-section">
<el-button type="primary" :disabled="selectedRows.length === 0" @click="handleBatchAudit">
<el-icon><Check /></el-icon>
批量审核
</el-button>
<el-button type="warning" :disabled="selectedRows.length === 0" @click="handleBatchReturn">
<el-icon><Close /></el-icon>
批量退回
</el-button>
<el-button @click="handleExport">
<el-icon><Download /></el-icon>
导出当前
</el-button>
</div>
<!-- 报卡列表区 -->
<div class="table-section">
<el-table
v-loading="loading"
:data="cardList"
@selection-change="handleSelectionChange"
:row-class-name="getRowClassName"
border
stripe
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="报卡名称" prop="cardName" min-width="120">
<template #default="{ row }">
{{ getCardName(row.cardNameCode) }}
</template>
</el-table-column>
<el-table-column label="病种名称" prop="diseaseName" min-width="120">
<template #default="{ row }">
{{ row.diseaseName || getDiseaseName(row.diseaseCode) }}
</template>
</el-table-column>
<el-table-column label="报卡编号" prop="cardNo" min-width="150" />
<el-table-column label="患者姓名" prop="patName" width="100">
<template #default="{ row }">
{{ maskName(row.patName) }}
</template>
</el-table-column>
<el-table-column label="性别" prop="sex" width="60" align="center">
<template #default="{ row }">
{{ row.sex === '1' ? '男' : row.sex === '2' ? '女' : '未知' }}
</template>
</el-table-column>
<el-table-column label="年龄" prop="age" width="70" align="center">
<template #default="{ row }">
{{ row.age ? row.age + getAgeUnit(row.ageUnit) : '-' }}
</template>
</el-table-column>
<el-table-column label="上报科室" prop="deptName" min-width="100" />
<el-table-column label="登记来源" prop="registrationSource" width="90" align="center">
<template #default="{ row }">
{{ getSourceName(row.registrationSource) }}
</template>
</el-table-column>
<el-table-column label="上报时间" prop="createTime" width="160" align="center" />
<el-table-column label="状态" prop="status" width="100" align="center">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status)" size="small">
{{ getStatusName(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="{ row }">
<el-button
v-if="row.status !== '2' && row.status !== '3'"
type="primary"
link
size="small"
@click="handleAudit(row)"
>
审核
</el-button>
<el-button type="default" link size="small" @click="handleView(row)">
查看
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-section">
<el-pagination
v-model:current-page="queryParams.pageNo"
v-model:page-size="queryParams.pageSize"
:page-sizes="[5, 10, 20, 50]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="getList"
@current-change="getList"
/>
</div>
</div>
<!-- 审核抽屉 -->
<AuditDrawer
ref="auditDrawerRef"
:visible="drawerVisible"
:mode="drawerMode"
:card-data="currentCard"
:audit-records="currentAuditRecords"
@close="handleDrawerClose"
@submit="handleAuditSubmit"
/>
<!-- 批量审核弹窗 -->
<el-dialog v-model="batchAuditVisible" title="批量审核" width="500px">
<el-form :model="batchForm" label-width="100px">
<el-form-item label="审核意见" required>
<el-input
v-model="batchForm.auditOpinion"
type="textarea"
:rows="4"
placeholder="请填写审核意见(必填)"
/>
</el-form-item>
<el-form-item label="选中数量">
<span>{{ selectedRows.length }} </span>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="batchAuditVisible = false">取消</el-button>
<el-button type="primary" @click="confirmBatchAudit" :disabled="!batchForm.auditOpinion">
确认审核
</el-button>
</template>
</el-dialog>
<!-- 批量退回弹窗 -->
<el-dialog v-model="batchReturnVisible" title="批量退回" width="500px">
<el-form :model="batchForm" label-width="100px">
<el-form-item label="退回原因" required>
<el-input
v-model="batchForm.returnReason"
type="textarea"
:rows="4"
placeholder="请填写退回原因(必填)"
/>
</el-form-item>
<el-form-item label="选中数量">
<span>{{ selectedRows.length }} </span>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="batchReturnVisible = false">取消</el-button>
<el-button type="warning" @click="confirmBatchReturn" :disabled="!batchForm.returnReason">
确认退回
</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import {
Clock, CircleClose, CircleCheck, Upload, Search, Refresh,
Check, Close, Download
} from '@element-plus/icons-vue';
import {
getCardStatistics, getCardList, getCardDetail, getAuditRecords,
batchAudit, batchReturn, auditPass, auditReturn, exportCards, getDeptTree
} from './api';
import AuditDrawer from './components/AuditDrawer.vue';
// 状态定义
const loading = ref(false);
const cardList = ref([]);
const total = ref(0);
const selectedRows = ref([]);
const statistics = ref({
todayPending: 0,
monthFailed: 0,
monthSuccess: 0,
monthReported: 0,
});
const deptTreeData = ref([]);
// 查询参数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
registrationSource: '',
reportTimeRange: [],
patientName: '',
status: '',
deptId: '',
});
// 抽屉相关
const drawerVisible = ref(false);
const drawerMode = ref('view'); // view | audit
const currentCard = ref({});
const currentAuditRecords = ref([]);
const auditDrawerRef = ref(null);
// 批量操作弹窗
const batchAuditVisible = ref(false);
const batchReturnVisible = ref(false);
const batchForm = reactive({
auditOpinion: '',
returnReason: '',
});
// 状态映射
const statusMap = {
'0': { name: '暂存', type: 'info' },
'1': { name: '待审核', type: 'warning' },
'2': { name: '审核通过', type: 'success' },
'3': { name: '已上报', type: 'primary' },
'4': { name: '失败', type: 'danger' },
'5': { name: '审核失败', type: 'danger' },
};
// 报卡名称映射
const cardNameMap = {
1: '传染病报告卡',
2: '肺结核报告卡',
3: '艾滋病报告卡',
};
// 登记来源映射
const sourceMap = {
1: '门诊',
2: '住院',
3: '急诊',
4: '体检',
};
// 年龄单位映射
const ageUnitMap = {
'1': '岁',
'2': '月',
'3': '天',
};
// 获取状态名称
function getStatusName(status) {
return statusMap[status]?.name || '未知';
}
// 获取状态类型
function getStatusType(status) {
return statusMap[status]?.type || 'info';
}
// 获取报卡名称
function getCardName(code) {
return cardNameMap[code] || '传染病报告卡';
}
// 获取登记来源名称
function getSourceName(source) {
return sourceMap[source] || '-';
}
// 获取年龄单位
function getAgeUnit(unit) {
return ageUnitMap[unit] || '岁';
}
// 脱敏姓名
function maskName(name) {
if (!name || name.length <= 2) return name;
return name[0] + '*'.repeat(name.length - 2) + name[name.length - 1];
}
// 获取疾病名称(从代码映射)
function getDiseaseName(code) {
// 实际应从字典获取
const diseaseMap = {
'0203': '病毒性肝炎',
'0206': '麻疹',
'0213': '肺结核',
'0222': '梅毒',
'0311': '手足口病',
};
return diseaseMap[code] || code || '-';
}
// 获取行样式类名
function getRowClassName({ row }) {
if (row.status === '2') return 'row-success';
if (row.status === '5') return 'row-warning';
return '';
}
// 获取统计数据
async function getStatistics() {
try {
const res = await getCardStatistics();
if (res.code === 200) {
statistics.value = res.data || {};
}
} catch (error) {
console.error('获取统计数据失败:', error);
}
}
// 获取列表数据
async function getList() {
loading.value = true;
try {
const params = { ...queryParams };
if (params.reportTimeRange && params.reportTimeRange.length === 2) {
params.startDate = params.reportTimeRange[0];
params.endDate = params.reportTimeRange[1];
}
delete params.reportTimeRange;
const res = await getCardList(params);
if (res.code === 200) {
cardList.value = res.data?.list || [];
total.value = res.data?.total || 0;
}
} catch (error) {
console.error('获取列表失败:', error);
ElMessage.error('获取数据失败,请检查网络');
} finally {
loading.value = false;
}
}
// 获取科室树
async function getDeptTreeData() {
try {
const res = await getDeptTree();
if (res.code === 200) {
deptTreeData.value = res.data || [];
}
} catch (error) {
console.error('获取科室树失败:', error);
}
}
// 查询
function handleQuery() {
queryParams.pageNo = 1;
getList();
}
// 重置
function handleReset() {
queryParams.pageNo = 1;
queryParams.pageSize = 10;
queryParams.registrationSource = '';
queryParams.reportTimeRange = [];
queryParams.patientName = '';
queryParams.status = '';
queryParams.deptId = '';
getList();
}
// 统计卡片点击
function handleStatClick(type) {
const today = new Date();
const todayStr = today.toISOString().split('T')[0];
const monthStart = new Date(today.getFullYear(), today.getMonth(), 1).toISOString().split('T')[0];
switch (type) {
case 'todayPending':
queryParams.reportTimeRange = [todayStr, todayStr];
queryParams.status = '1';
break;
case 'monthFailed':
queryParams.reportTimeRange = [monthStart, todayStr];
queryParams.status = '5';
break;
case 'monthSuccess':
queryParams.reportTimeRange = [monthStart, todayStr];
queryParams.status = '2';
break;
case 'monthReported':
queryParams.reportTimeRange = [monthStart, todayStr];
queryParams.status = '3';
break;
}
handleQuery();
}
// 选择变化
function handleSelectionChange(selection) {
selectedRows.value = selection;
}
// 批量审核
function handleBatchAudit() {
const invalidRows = selectedRows.value.filter(row => row.status === '2' || row.status === '3');
if (invalidRows.length > 0) {
ElMessage.warning('只能选择待审核或审核失败的报卡');
return;
}
batchForm.auditOpinion = '';
batchAuditVisible.value = true;
}
// 确认批量审核
async function confirmBatchAudit() {
if (!batchForm.auditOpinion) {
ElMessage.warning('请填写审核意见');
return;
}
try {
const cardNos = selectedRows.value.map(row => row.cardNo);
const res = await batchAudit({
cardNos,
auditOpinion: batchForm.auditOpinion,
});
if (res.code === 200) {
ElMessage.success('批量审核成功');
batchAuditVisible.value = false;
getStatistics();
getList();
} else {
ElMessage.error(res.msg || '审核失败');
}
} catch (error) {
ElMessage.error('审核失败,请检查网络');
}
}
// 批量退回
function handleBatchReturn() {
const invalidRows = selectedRows.value.filter(row => row.status === '2' || row.status === '3');
if (invalidRows.length > 0) {
ElMessage.warning('只能选择待审核或审核失败的报卡');
return;
}
batchForm.returnReason = '';
batchReturnVisible.value = true;
}
// 确认批量退回
async function confirmBatchReturn() {
if (!batchForm.returnReason) {
ElMessage.warning('请填写退回原因');
return;
}
try {
const cardNos = selectedRows.value.map(row => row.cardNo);
const res = await batchReturn({
cardNos,
returnReason: batchForm.returnReason,
});
if (res.code === 200) {
ElMessage.success('批量退回成功');
batchReturnVisible.value = false;
getStatistics();
getList();
} else {
ElMessage.error(res.msg || '退回失败');
}
} catch (error) {
ElMessage.error('退回失败,请检查网络');
}
}
// 导出
async function handleExport() {
try {
const res = await exportCards(queryParams);
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `报卡列表_${new Date().toISOString().split('T')[0]}.xlsx`;
link.click();
window.URL.revokeObjectURL(url);
} catch (error) {
ElMessage.error('导出失败');
}
}
// 审核(单条)
async function handleAudit(row) {
try {
const [cardRes, auditRes] = await Promise.all([
getCardDetail(row.cardNo),
getAuditRecords(row.cardNo),
]);
if (cardRes.code === 200) {
currentCard.value = cardRes.data || {};
currentAuditRecords.value = auditRes.code === 200 ? (auditRes.data || []) : [];
drawerMode.value = 'audit';
drawerVisible.value = true;
} else {
ElMessage.error('数据加载失败,请重试');
}
} catch (error) {
ElMessage.error('数据加载失败,请重试');
}
}
// 查看
async function handleView(row) {
try {
const [cardRes, auditRes] = await Promise.all([
getCardDetail(row.cardNo),
getAuditRecords(row.cardNo),
]);
if (cardRes.code === 200) {
currentCard.value = cardRes.data || {};
currentAuditRecords.value = auditRes.code === 200 ? (auditRes.data || []) : [];
drawerMode.value = 'view';
drawerVisible.value = true;
} else {
ElMessage.error('数据加载失败,请重试');
}
} catch (error) {
ElMessage.error('数据加载失败,请重试');
}
}
// 关闭抽屉
function handleDrawerClose() {
drawerVisible.value = false;
}
// 审核提交
async function handleAuditSubmit(data) {
try {
let res;
if (data.type === 'pass') {
res = await auditPass({
cardNo: data.cardNo,
auditOpinion: data.auditOpinion,
});
} else {
res = await auditReturn({
cardNo: data.cardNo,
returnReason: data.returnReason,
});
}
if (res.code === 200) {
ElMessage.success(data.type === 'pass' ? '审核通过' : '已退回');
drawerVisible.value = false;
getStatistics();
getList();
} else {
ElMessage.error(res.msg || '操作失败');
}
} catch (error) {
ElMessage.error('操作失败,请检查网络');
}
}
// 初始化
onMounted(() => {
// 设置默认时间范围为最近一个月
const today = new Date();
const monthAgo = new Date(today.getFullYear(), today.getMonth() - 1, today.getDate());
queryParams.reportTimeRange = [
monthAgo.toISOString().split('T')[0],
today.toISOString().split('T')[0],
];
getStatistics();
getList();
getDeptTreeData();
});
</script>
<style scoped>
.card-management-container {
padding: 20px;
background-color: #f5f7fa;
min-height: calc(100vh - 84px);
}
/* 统计卡片区域 */
.statistics-section {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
margin-bottom: 20px;
}
.stat-card {
background: #fff;
border-radius: 8px;
padding: 20px;
display: flex;
align-items: center;
gap: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
cursor: pointer;
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-3px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
}
.stat-icon {
width: 56px;
height: 56px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
}
.stat-icon.pending {
background: rgba(64, 158, 255, 0.1);
color: #409eff;
}
.stat-icon.failed {
background: rgba(245, 108, 108, 0.1);
color: #f56c6c;
}
.stat-icon.success {
background: rgba(103, 194, 58, 0.1);
color: #67c23a;
}
.stat-icon.reported {
background: rgba(144, 147, 153, 0.1);
color: #909399;
}
.stat-content {
flex: 1;
}
.stat-value {
font-size: 28px;
font-weight: bold;
color: #303133;
line-height: 1.2;
}
.stat-label {
font-size: 14px;
color: #909399;
margin-top: 4px;
}
/* 筛选区域 */
.filter-section {
background: #fff;
border-radius: 8px;
padding: 16px 20px;
margin-bottom: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
}
.filter-form {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
/* 操作区域 */
.action-section {
background: #fff;
border-radius: 8px;
padding: 12px 20px;
margin-bottom: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
display: flex;
gap: 12px;
}
/* 表格区域 */
.table-section {
background: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
}
.pagination-section {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
/* 行样式 */
:deep(.row-success) {
background-color: rgba(103, 194, 58, 0.05) !important;
}
:deep(.row-warning) {
background-color: rgba(230, 162, 60, 0.05) !important;
}
/* 响应式 */
@media (max-width: 1200px) {
.statistics-section {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.statistics-section {
grid-template-columns: 1fr;
}
}
</style>