103 增加医生个人报卡管理界面(需求)

This commit is contained in:
2026-04-14 17:23:44 +08:00
parent fe7778e6e0
commit 26e0665eeb
15 changed files with 833 additions and 507 deletions

View File

@@ -12,6 +12,8 @@ import com.core.common.utils.SecurityUtils;
import com.core.common.core.domain.model.LoginUser;
import com.openhis.infectious.domain.InfectiousAudit;
import com.openhis.infectious.domain.InfectiousCard;
import com.openhis.administration.domain.Practitioner;
import com.openhis.administration.service.IPractitionerService;
import com.openhis.web.cardmanagement.appservice.ICardManageAppService;
import com.openhis.web.cardmanagement.dto.*;
import com.openhis.web.cardmanagement.mapper.InfectiousAuditMapper;
@@ -52,6 +54,7 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
private final InfectiousCardMapper infectiousCardMapper;
private final InfectiousAuditMapper infectiousAuditMapper;
private final IPractitionerService iPractitionerService;
@Override
public CardStatisticsDto getStatistics() {
@@ -74,7 +77,7 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
}
// 状态
if (StringUtils.hasText(queryParams.getStatus())) {
if (queryParams.getStatus() != null) {
wrapper.eq(InfectiousCard::getStatus, queryParams.getStatus());
}
@@ -127,7 +130,7 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
if (card == null) {
return new ArrayList<>();
}
List<InfectiousAudit> records = infectiousAuditMapper.selectByCardId(card.getId());
List<InfectiousAudit> records = infectiousAuditMapper.selectByCardId(card.getCardNo());
return records.stream().map(this::convertAuditToDto).collect(Collectors.toList());
}
@@ -145,16 +148,16 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
for (String cardNo : batchAuditDto.getCardNos()) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
if (card == null) continue;
if ("2".equals(card.getStatus()) || "3".equals(card.getStatus())) continue;
if (Integer.valueOf(2).equals(card.getStatus()) || Integer.valueOf(3).equals(card.getStatus())) continue;
// 更新状态为已审核
String oldStatus = card.getStatus();
card.setStatus("2");
Integer oldStatus = card.getStatus();
card.setStatus(2);
card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card);
// 创建审核记录
createAuditRecord(card.getId(), oldStatus, "2", "1", batchAuditDto.getAuditOpinion(),
createAuditRecord(card.getCardNo(), oldStatus, 2, 1, batchAuditDto.getAuditOpinion(),
null, auditorId, auditorName, true, batchAuditDto.getCardNos().size());
successCount++;
}
@@ -176,17 +179,17 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
for (String cardNo : batchReturnDto.getCardNos()) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
if (card == null) continue;
if ("2".equals(card.getStatus()) || "3".equals(card.getStatus())) continue;
if (Integer.valueOf(2).equals(card.getStatus()) || Integer.valueOf(3).equals(card.getStatus())) continue;
// 更新状态为退回 (审核失败)
String oldStatus = card.getStatus();
card.setStatus("5");
Integer oldStatus = card.getStatus();
card.setStatus(5);
card.setReturnReason(batchReturnDto.getReturnReason());
card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card);
// 创建审核记录
createAuditRecord(card.getId(), oldStatus, "5", "3", null,
createAuditRecord(card.getCardNo(), oldStatus, 5, 3, null,
batchReturnDto.getReturnReason(), auditorId, auditorName, true, batchReturnDto.getCardNos().size());
successCount++;
}
@@ -206,13 +209,13 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
String auditorName = SecurityUtils.getUsername();
// 更新状态
String oldStatus = card.getStatus();
card.setStatus("2");
Integer oldStatus = card.getStatus();
card.setStatus(2);
card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card);
// 创建审核记录
createAuditRecord(card.getId(), oldStatus, "2", "2", auditDto.getAuditOpinion(),
createAuditRecord(card.getCardNo(), oldStatus, 2, 2, auditDto.getAuditOpinion(),
null, auditorId, auditorName, false, 1);
return R.ok("审核通过");
@@ -230,14 +233,14 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
String auditorName = SecurityUtils.getUsername();
// 更新状态
String oldStatus = card.getStatus();
card.setStatus("5");
Integer oldStatus = card.getStatus();
card.setStatus(5);
card.setReturnReason(returnDto.getReturnReason());
card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card);
// 创建审核记录
createAuditRecord(card.getId(), oldStatus, "5", "4", null,
createAuditRecord(card.getCardNo(), oldStatus, 5, 4, null,
returnDto.getReturnReason(), auditorId, auditorName, false, 1);
return R.ok("已退回");
@@ -251,7 +254,7 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
if (queryParams.getRegistrationSource() != null) {
wrapper.eq(InfectiousCard::getRegistrationSource, queryParams.getRegistrationSource());
}
if (StringUtils.hasText(queryParams.getStatus())) {
if (queryParams.getStatus() != null) {
wrapper.eq(InfectiousCard::getStatus, queryParams.getStatus());
}
if (StringUtils.hasText(queryParams.getPatientName())) {
@@ -292,7 +295,7 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
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(4).setCellValue(card.getDiseaseCode());
row.createCell(5).setCellValue(card.getDeptName());
row.createCell(6).setCellValue(card.getCreateTime() != null ? dateFormat.format(card.getCreateTime()) : "");
row.createCell(7).setCellValue(getStatusText(card.getStatus()));
@@ -316,7 +319,19 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
@Override
public DoctorCardStatisticsDto getDoctorCardStatistics() {
Long doctorId = SecurityUtils.getUserId();
Long userId = SecurityUtils.getUserId();
// 通过 sys_user 表的 user_id 查询医生表 (adm_practitioner) 获取医生 ID
Practitioner practitioner = iPractitionerService.getPractitionerByUserId(userId);
if (practitioner == null) {
DoctorCardStatisticsDto dto = new DoctorCardStatisticsDto();
dto.setTotalCount(0);
dto.setPendingFailedCount(0);
dto.setReportedCount(0);
return dto;
}
Long doctorId = practitioner.getId();
DoctorCardStatisticsDto dto = new DoctorCardStatisticsDto();
Integer totalCount = infectiousCardMapper.countByDoctorId(doctorId);
@@ -331,7 +346,18 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
@Override
public R<?> getDoctorCardPage(DoctorCardQueryDto queryParams) {
Long doctorId = SecurityUtils.getUserId();
Long userId = SecurityUtils.getUserId();
// 通过 sys_user 表的 user_id 查询医生表 (adm_practitioner) 获取医生 ID
Practitioner practitioner = iPractitionerService.getPractitionerByUserId(userId);
if (practitioner == null) {
Map<String, Object> emptyResult = new HashMap<>();
emptyResult.put("list", new ArrayList<>());
emptyResult.put("total", 0L);
return R.ok(emptyResult);
}
Long doctorId = practitioner.getId();
Page<InfectiousCard> page = new Page<>(queryParams.getPageNo(), queryParams.getPageSize());
LambdaQueryWrapper<InfectiousCard> wrapper = new LambdaQueryWrapper<>();
@@ -340,7 +366,7 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
wrapper.eq(InfectiousCard::getDoctorId, doctorId);
// 状态筛选
if (StringUtils.hasText(queryParams.getStatus())) {
if (queryParams.getStatus() != null) {
wrapper.eq(InfectiousCard::getStatus, queryParams.getStatus());
}
@@ -354,13 +380,24 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
wrapper.le(InfectiousCard::getCreateTime, endDateTime);
}
// 关键词搜索(患者姓名报卡名称)
// 关键词搜索(患者姓名、疾病编码、报卡名称)
if (StringUtils.hasText(queryParams.getKeyword())) {
wrapper.and(w -> w
.like(InfectiousCard::getPatName, queryParams.getKeyword())
.or()
.like(InfectiousCard::getDiseaseName, queryParams.getKeyword())
);
String kw = queryParams.getKeyword();
// 将关键词匹配报卡名称,找出对应的 cardNameCode 列表
List<Integer> matchedCodes = getMatchedCardNameCodes(kw);
// cardNameCode为null的记录默认也属于"中华人民共和国传染病报告卡"匹配到code=1时需包含Null记录
boolean includeNull = matchedCodes.contains(1);
wrapper.and(w -> {
w.like(InfectiousCard::getPatName, kw)
.or()
.like(InfectiousCard::getDiseaseCode, kw);
if (!matchedCodes.isEmpty()) {
w.or().in(InfectiousCard::getCardNameCode, matchedCodes);
}
if (includeNull) {
w.or().isNull(InfectiousCard::getCardNameCode);
}
});
}
// 按创建时间倒序
@@ -388,17 +425,19 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
}
// 验证权限:只能提交自己的报卡
if (!card.getDoctorId().equals(SecurityUtils.getUserId())) {
Long userId = SecurityUtils.getUserId();
Practitioner practitioner = iPractitionerService.getPractitionerByUserId(userId);
if (practitioner == null || !practitioner.getId().equals(card.getDoctorId())) {
return R.fail("无权操作此报卡");
}
// 证状态:只有暂存状态可以提交
if (!"0".equals(card.getStatus())) {
// 证状态:只有暂存状态可以提交
if (!Integer.valueOf(0).equals(card.getStatus())) {
return R.fail("只能提交暂存状态的报卡");
}
// 更新状态为已提交
card.setStatus("1");
card.setStatus(1);
card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card);
@@ -414,17 +453,19 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
}
// 验证权限:只能撤回自己的报卡
if (!card.getDoctorId().equals(SecurityUtils.getUserId())) {
Long userId = SecurityUtils.getUserId();
Practitioner practitioner = iPractitionerService.getPractitionerByUserId(userId);
if (practitioner == null || !practitioner.getId().equals(card.getDoctorId())) {
return R.fail("无权操作此报卡");
}
// 证状态:只有已提交状态可以撤回
if (!"1".equals(card.getStatus())) {
// 证状态:只有已提交状态可以撤回
if (!Integer.valueOf(1).equals(card.getStatus())) {
return R.fail("只能撤回已提交状态的报卡");
}
// 更新状态为暂存
card.setStatus("0");
card.setStatus(0);
card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card);
@@ -440,17 +481,19 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
}
// 验证权限:只能删除自己的报卡
if (!card.getDoctorId().equals(SecurityUtils.getUserId())) {
Long userId = SecurityUtils.getUserId();
Practitioner practitioner = iPractitionerService.getPractitionerByUserId(userId);
if (practitioner == null || !practitioner.getId().equals(card.getDoctorId())) {
return R.fail("无权操作此报卡");
}
// 证状态:只有暂存状态可以删除
if (!"0".equals(card.getStatus())) {
// 证状态:只有暂存状态可以删除
if (!Integer.valueOf(0).equals(card.getStatus())) {
return R.fail("只能删除暂存状态的报卡");
}
// 更新状态为作废
card.setStatus("6");
card.setStatus(6);
card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card);
@@ -464,7 +507,12 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
return R.fail("请选择要提交的报卡");
}
Long doctorId = SecurityUtils.getUserId();
Long userId = SecurityUtils.getUserId();
Practitioner practitioner = iPractitionerService.getPractitionerByUserId(userId);
if (practitioner == null) {
return R.fail("当前用户未关联医生信息");
}
Long doctorId = practitioner.getId();
int successCount = 0;
for (String cardNo : cardNos) {
@@ -472,13 +520,13 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
if (card == null) continue;
// 验证权限:只能提交自己的报卡
if (!card.getDoctorId().equals(doctorId)) continue;
if (!doctorId.equals(card.getDoctorId())) continue;
// 证状态:只有暂存状态可以提交
if (!"0".equals(card.getStatus())) continue;
// 证状态:只有暂存状态可以提交
if (!Integer.valueOf(0).equals(card.getStatus())) continue;
// 更新状态为已提交
card.setStatus("1");
card.setStatus(1);
card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card);
successCount++;
@@ -498,7 +546,12 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
return R.fail("请选择要删除的报卡");
}
Long doctorId = SecurityUtils.getUserId();
Long userId = SecurityUtils.getUserId();
Practitioner practitioner = iPractitionerService.getPractitionerByUserId(userId);
if (practitioner == null) {
return R.fail("当前用户未关联医生信息");
}
Long doctorId = practitioner.getId();
int successCount = 0;
for (String cardNo : cardNos) {
@@ -506,13 +559,13 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
if (card == null) continue;
// 验证权限:只能删除自己的报卡
if (!card.getDoctorId().equals(doctorId)) continue;
if (!doctorId.equals(card.getDoctorId())) continue;
// 证状态:只有暂存状态可以删除
if (!"0".equals(card.getStatus())) continue;
// 证状态:只有暂存状态可以删除
if (!Integer.valueOf(0).equals(card.getStatus())) continue;
// 更新状态为作废
card.setStatus("6");
card.setStatus(6);
card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card);
successCount++;
@@ -531,6 +584,13 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
LoginUser loginUser = SecurityUtils.getLoginUser();
Long currentUserId = loginUser.getUserId();
// 通过 sys_user 表的 user_id 查询医生表 (adm_practitioner) 获取医生 ID
Practitioner practitioner = iPractitionerService.getPractitionerByUserId(currentUserId);
if (practitioner == null) {
return R.fail("当前用户未关联医生信息");
}
Long doctorId = practitioner.getId();
// 查询报卡
InfectiousCard card = infectiousCardMapper.selectByCardNo(updateDto.getCardNo());
if (card == null) {
@@ -538,12 +598,12 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
}
// 验证是否当前医生的报卡 - 根据 doctorId 字段验证
if (!currentUserId.equals(card.getDoctorId())) {
if (!doctorId.equals(card.getDoctorId())) {
return R.fail("只能修改自己的报卡");
}
// 证状态是否允许修改(只能修改暂存状态的报卡)
if (!"0".equals(card.getStatus())) {
// 证状态是否允许修改(只能修改暂存状态的报卡)
if (!Integer.valueOf(0).equals(card.getStatus())) {
return R.fail("只能修改暂存状态的报卡");
}
@@ -559,15 +619,6 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
card.setUpdateTime(new Date());
card.setUpdateBy(loginUser.getUsername()); // 使用username作为更新者
card.setUpdateTime(new Date());
card.setUpdateBy(loginUser.getUsername()); // 使用 username 作为更新者
card.setUpdateTime(new Date());
card.setUpdateBy(loginUser.getUsername()); // 使用 username 作为更新者
card.setUpdateTime(new Date());
card.setUpdateBy(loginUser.getUsername()); // 使用 username 作为更新者
int rows = infectiousCardMapper.updateById(card);
if (rows > 0) {
return R.ok("更新成功");
@@ -583,12 +634,14 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
}
// 验证权限:只能导出自己的报卡
if (!card.getDoctorId().equals(SecurityUtils.getUserId())) {
Long userId = SecurityUtils.getUserId();
Practitioner practitioner = iPractitionerService.getPractitionerByUserId(userId);
if (practitioner == null || !practitioner.getId().equals(card.getDoctorId())) {
return;
}
// 证状态:只有已上报状态可以导出
if (!"3".equals(card.getStatus())) {
// 证状态:只有已上报状态可以导出
if (!Integer.valueOf(3).equals(card.getStatus())) {
return;
}
@@ -612,6 +665,8 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
private DoctorCardListDto convertToDoctorCardListDto(InfectiousCard card) {
DoctorCardListDto dto = new DoctorCardListDto();
BeanUtils.copyProperties(card, dto);
// 由于数据库中没有 disease_name 字段,使用 disease_code 作为疾病名称展示
dto.setDiseaseName(card.getDiseaseCode());
dto.setCardName(getCardName(card.getCardNameCode()));
dto.setSubmitTime(card.getCreateTime() != null ?
new SimpleDateFormat("yyyy-MM-dd HH:mm").format(card.getCreateTime()) : null);
@@ -632,13 +687,35 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
}
}
/**
* 根据关键词匹配报卡名称,返回匹配的 cardNameCode 列表
*/
private List<Integer> getMatchedCardNameCodes(String keyword) {
// 报卡名称映射表 code -> name
java.util.Map<Integer, String> cardNameMap = new java.util.LinkedHashMap<>();
cardNameMap.put(1, "中华人民共和国传染病报告卡");
cardNameMap.put(2, "甲类传染病报告卡");
cardNameMap.put(3, "乙类传染病报告卡");
cardNameMap.put(4, "丙类传染病报告卡");
List<Integer> matchedCodes = new ArrayList<>();
for (java.util.Map.Entry<Integer, String> entry : cardNameMap.entrySet()) {
if (entry.getValue().contains(keyword)) {
matchedCodes.add(entry.getKey());
}
}
// cardNameCode 为 null 的数据默认也是「中华人民共和国传染病报告卡」
// 如果关键词匹配 code=1则同时要包含 null 的记录
return matchedCodes;
}
/**
* 转换审核记录为 DTO
*/
private AuditRecordDto convertAuditToDto(InfectiousAudit audit) {
AuditRecordDto dto = new AuditRecordDto();
BeanUtils.copyProperties(audit, dto);
dto.setCardId(audit.getCardId() != null ? audit.getCardId().toString() : null);
dto.setCardId(audit.getCardId());
return dto;
}
@@ -648,6 +725,8 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
private InfectiousCardDto convertToDto(InfectiousCard card) {
InfectiousCardDto dto = new InfectiousCardDto();
BeanUtils.copyProperties(card, dto);
// 由于数据库中没有 disease_name 字段,使用 disease_code 作为疾病名称展示
dto.setDiseaseName(card.getDiseaseCode());
dto.setStatusText(getStatusText(card.getStatus()));
return dto;
}
@@ -655,15 +734,15 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
/**
* 创建审核记录
*/
private void createAuditRecord(Long cardId, String statusFrom, String statusTo, String auditType,
private void createAuditRecord(String cardId, Integer statusFrom, Integer statusTo, Integer 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.setAuditType(String.valueOf(auditType));
audit.setAuditStatusFrom(statusFrom != null ? String.valueOf(statusFrom) : null);
audit.setAuditStatusTo(statusTo != null ? String.valueOf(statusTo) : null);
audit.setAuditTime(LocalDateTime.now());
audit.setAuditorId(auditorId);
audit.setAuditorName(auditorName);
@@ -677,15 +756,16 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
/**
* 获取状态文本
*/
private String getStatusText(String status) {
private String getStatusText(Integer status) {
if (status == null) return "未知";
switch (status) {
case "0": return "暂存";
case "1": return "已提交";
case "2": return "审核通过";
case "3": return "已上报";
case "4": return "失败";
case "5": return "审核失败";
case "6": return "作废";
case 0: return "暂存";
case 1: return "已提交";
case 2: return "审核通过";
case 3: return "已上报";
case 4: return "失败";
case 5: return "审核失败";
case 6: return "作废";
default: return "未知";
}
}

View File

@@ -29,8 +29,8 @@ public class CardQueryDto {
/** 患者姓名 */
private String patientName;
/** 审核状态 */
private String status;
/** 审核状态(0暂存/1已提交/2已审核/3已上报/4失败/5退回/6作废) */
private Integer status;
/** 科室ID */
private Long deptId;

View File

@@ -7,6 +7,9 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 医生个人报卡列表DTO
*
@@ -41,6 +44,51 @@ public class DoctorCardListDto {
/** 提交时间 */
private String submitTime;
/** 状态 */
private String status;
/** 状态(0暂存/1已提交/2已审核/3已上报/4失败/5退回/6作废) */
private Integer status;
/** 疾病名称 */
private String diseaseName;
/** 发病日期 */
private LocalDate onsetDate;
/** 诊断日期 */
private LocalDateTime diagDate;
/** 报告单位 */
private String reportOrg;
/** 报告医生 */
private String reportDoc;
/** 传染病类别 */
private String diseaseType;
/** 性别 (1男/2女/0未知) */
private String sex;
/** 年龄 */
private Integer age;
/** 年龄单位 (1岁/2月/3天) */
private String ageUnit;
/** 现住址省 */
private String addressProv;
/** 现住址市 */
private String addressCity;
/** 现住址县 */
private String addressCounty;
/** 现住址街道 */
private String addressTown;
/** 现住址村/居委 */
private String addressVillage;
/** 现住址门牌号 */
private String addressHouse;
}

View File

@@ -26,8 +26,8 @@ public class DoctorCardQueryDto {
/** 结束日期 */
private String endDate;
/** 状态(0暂存/1已提交/2已审核/3已上报/4失败/5退回) */
private String status;
/** 状态(0暂存/1已提交/2已审核/3已上报/4失败/5退回/6作废) */
private Integer status;
/** 患者姓名或报卡名称 */
private String keyword;

View File

@@ -1,18 +1,44 @@
package com.openhis.web.cardmanagement.dto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
public class DoctorCardUpdateDto {
@NotBlank(message = "卡片编号不能为空")
private String cardNo;
private String phone;
private String contactPhone; // 紧急联系人电话
private LocalDate onsetDate;
private LocalDateTime diagDate;
private String diseaseType; // 修改为diseaseType对应InfectiousCard中的diseaseType字段
private String addressProv;
private String addressCity;
private String addressCounty;
private String addressHouse;
private String diseaseType; // 病例分类对应InfectiousCard中的diseaseType字段
private String diseaseCode; // 疾病编码
@NotNull(message = "病例类别不能为空")
private Integer caseClass; // 病例类别(1疑似病例/2临床诊断病例/3实验室确诊病例/4病原携带者/5阳性检测结果)
private String occupation; // 职业
@NotNull(message = "病人属于不能为空")
private Integer patientBelong; // 病人属于(1本县区/2本市其他县区/3本省其他地市/4外省/5港澳台/6外籍)
private String addressProv; // 现住址省
private String addressCity; // 现住址市
private String addressCounty; // 现住址县
private String addressTown; // 现住址街道
private String addressVillage; // 现住址村/居委
private String addressHouse; // 现住址门牌号
private String parentName; // 家长姓名
private String workplace; // 工作单位
private String correctName; // 订正病名
private LocalDate deathDate; // 死亡日期
private String withdrawReason; // 退卡原因
private String otherDisease; // 其他传染病名称
}

View File

@@ -65,8 +65,8 @@ public class InfectiousCardDto {
/** 现住址门牌号 */
private String addressHouse;
/** 病人属于 */
private String patientbelong;
/** 病人属于(1本县区/2本市其他县区/3本省其他地市/4外省/5港澳台/6外籍) */
private Integer patientBelong;
/** 职业 */
private String occupation;
@@ -110,8 +110,8 @@ public class InfectiousCardDto {
/** 填卡日期 */
private LocalDate reportDate;
/** 状态 */
private String status;
/** 状态(0暂存/1已提交/2已审核/3已上报/4失败/5退回/6作废) */
private Integer status;
/** 状态文本 */
private String statusText;

View File

@@ -18,14 +18,14 @@ import java.util.List;
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);
List<InfectiousAudit> selectByCardId(@Param("cardId") String cardId);
/**
* 获取下一个审核序号
*/
@Select("SELECT COALESCE(MAX(audit_seq), 0) + 1 FROM infectious_audit WHERE card_id = #{cardId}")
Integer getNextAuditSeq(@Param("cardId") Long cardId);
Integer getNextAuditSeq(@Param("cardId") String cardId);
}

View File

@@ -21,25 +21,25 @@ public interface InfectiousCardMapper extends BaseMapper<InfectiousCard> {
/**
* 统计今日待审核数量
*/
@Select("SELECT COUNT(*) FROM infectious_card WHERE DATE(create_time) = CURRENT_DATE AND status = '1'")
@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'")
@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'")
@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'")
@Select("SELECT COUNT(*) FROM infectious_card WHERE DATE_TRUNC('month', create_time) = DATE_TRUNC('month', CURRENT_DATE) AND status = 3")
Integer countMonthReported();
/**
@@ -55,14 +55,14 @@ public interface InfectiousCardMapper extends BaseMapper<InfectiousCard> {
Integer countByDoctorId(@Param("doctorId") Long doctorId);
/**
* 统计医生待处理失败状态为0暂存或4失败
* 统计医生待提交状态为0暂存待提交
*/
@Select("SELECT COUNT(*) FROM infectious_card WHERE doctor_id = #{doctorId} AND status IN ('0', '4')")
@Select("SELECT COUNT(*) FROM infectious_card WHERE doctor_id = #{doctorId} AND status = 0")
Integer countPendingFailedByDoctorId(@Param("doctorId") Long doctorId);
/**
* 统计医生已成功上报数状态为3已上报
*/
@Select("SELECT COUNT(*) FROM infectious_card WHERE doctor_id = #{doctorId} AND status = '3'")
@Select("SELECT COUNT(*) FROM infectious_card WHERE doctor_id = #{doctorId} AND status = 3")
Integer countReportedByDoctorId(@Param("doctorId") Long doctorId);
}

View File

@@ -127,14 +127,11 @@ public class InfectiousCardDto {
/** 审核意见 */
private String auditOpinion;
/** 退原因 */
/** 退原因 */
private String returnReason;
/** 订正病名 */
private String correctName;
/** 退卡原因 */
private String withdrawReason;
private String revisedDiseaseName;
/** 其他传染病 */
private String otherDisease;

View File

@@ -29,9 +29,8 @@ public class InfectiousAudit extends HisBaseEntity {
@JsonSerialize(using = ToStringSerializer.class)
private Long auditId;
/** 报卡ID */
@JsonSerialize(using = ToStringSerializer.class)
private Long cardId;
/** 报卡编号(关联 infectious_card.card_no */
private String cardId;
/** 审核序号 */
private Integer auditSeq;

View File

@@ -1,6 +1,7 @@
package com.openhis.infectious.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
@@ -26,12 +27,8 @@ import java.time.LocalDateTime;
@EqualsAndHashCode(callSuper = false)
public class InfectiousCard extends HisBaseEntity {
/** 卡片编号 */
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/** 卡片编号(业务编号) */
/** 卡片编号(业务编号,主键) */
@TableId(type = IdType.INPUT)
private String cardNo;
/** 本次就诊ID */
@@ -97,8 +94,9 @@ public class InfectiousCard extends HisBaseEntity {
/** 现住址门牌号 */
private String addressHouse;
/** 病人属于 */
private String patientbelong;
/** 病人属于(1本县区/2本市其他县区/3本省其他地市/4外省/5港澳台/6外籍) */
@TableField("patient_belong")
private Integer patientBelong;
/** 职业 */
private String occupation;
@@ -106,18 +104,15 @@ public class InfectiousCard extends HisBaseEntity {
/** 疾病编码 */
private String diseaseCode;
/** 疾病名称 */
private String diseaseName;
/** 分型 */
private String diseaseSubtype;
/** 其他传染病 */
private String otherDisease;
/** 病例分类 */
private String diseaseType;
/** 其他传染病名称 */
private String otherDisease;
/** 病例类别(1疑似病例/2临床诊断病例/3实验室确诊病例/4病原携带者/5阳性检测结果) */
private Integer caseClass;
/** 发病日期 */
private LocalDate onsetDate;
@@ -146,7 +141,7 @@ public class InfectiousCard extends HisBaseEntity {
private LocalDate reportDate;
/** 状态(0暂存/1已提交/2已审核/3已上报/4失败/5退回) */
private String status;
private Integer status;
/** 失败原因 */
private String failMsg;
@@ -165,6 +160,7 @@ public class InfectiousCard extends HisBaseEntity {
private Long deptId;
/** 科室名称 */
@TableField(exist = false)
private String deptName;
/** 医生ID */

View File

@@ -334,12 +334,12 @@
<el-col :span="12">
<el-form-item label="病人属于">
<el-select v-model="currentCard.patientBelong" :disabled="drawerMode !== 'edit'" style="width: 100%">
<el-option label="本县区" value="1" />
<el-option label="本市其他县区" value="2" />
<el-option label="本省其他地市" value="3" />
<el-option label="外省" value="4" />
<el-option label="港澳台" value="5" />
<el-option label="外籍" value="6" />
<el-option label="本县区" :value="1" />
<el-option label="本市其他县区" :value="2" />
<el-option label="本省其他地市" :value="3" />
<el-option label="外省" :value="4" />
<el-option label="港澳台" :value="5" />
<el-option label="外籍" :value="6" />
</el-select>
</el-form-item>
</el-col>

View File

@@ -130,12 +130,12 @@
<el-col :span="24" class="form-item full-width">
<span class="form-label required">病人属于</span>
<el-radio-group v-model="form.patientBelong">
<el-radio label="1">本县区</el-radio>
<el-radio label="2">本市其他县区</el-radio>
<el-radio label="3">本省其他地市</el-radio>
<el-radio label="4">外省</el-radio>
<el-radio label="5">港澳台</el-radio>
<el-radio label="6">外籍</el-radio>
<el-radio :label="1">本县区</el-radio>
<el-radio :label="2">本市其他县区</el-radio>
<el-radio :label="3">本省其他地市</el-radio>
<el-radio :label="4">外省</el-radio>
<el-radio :label="5">港澳台</el-radio>
<el-radio :label="6">外籍</el-radio>
</el-radio-group>
</el-col>
</el-row>
@@ -732,7 +732,7 @@ const form = ref({
addressTown: '',
addressVillage: '',
addressHouse: '',
patientBelong: '1',
patientBelong: 1,
occupation: '',
caseClass: '',
onsetDate: '',
@@ -1186,7 +1186,7 @@ function show(diagnosisData) {
addressHouse: diagnosisData?.addressHouse || '', // 门牌号
// 患者类型和职业
patientBelong: '1', // 患者属于(本县区/本市其他县区/本省其他地市/外省/港澳台/外籍)
patientBelong: 1, // 患者属于(本县区/本市其他县区/本省其他地市/外省/港澳台/外籍)
occupation: '', // 职业
caseClass: '', // 病例分类(疑似病例/临床诊断病例/确诊病例/病原携带者/阳性检测)
@@ -1297,7 +1297,7 @@ async function buildSubmitData() {
addressTown: formData.addressTown || '',
addressVillage: formData.addressVillage || '',
addressHouse: formData.addressHouse || '',
patientBelong: patientBelongMap[formData.patientBelong] || 1,
patientBelong: formData.patientBelong || 1,
occupation: formData.occupation || null,
diseaseCode: diseaseCode || null,
diseaseType: diseaseType || null,

View File

@@ -31,12 +31,12 @@
</el-col>
<el-col :span="8">
<el-form-item :label="'家长姓名' + (isChildPatient ? '' : '')" :prop="isChildPatient ? 'parentName' : ''" :required="isChildPatient">
<el-input v-model="form.parentName" placeholder="≤14岁必填" />
<el-input v-model="form.parentName" placeholder="≤14岁必填" :disabled="isReadOnly" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="身份证号" prop="idNo" required>
<el-input v-model="form.idNo" placeholder="请输入身份证号" maxlength="18" @change="handleIdCardChange" />
<el-input v-model="form.idNo" placeholder="请输入身份证号" maxlength="18" :disabled="isReadOnly" @change="isReadOnly ? null : handleIdCardChange($event)" />
</el-form-item>
</el-col>
</el-row>
@@ -45,7 +45,7 @@
<el-row :gutter="16" class="form-row">
<el-col :span="6">
<el-form-item label="性别" prop="sex" required>
<el-radio-group v-model="form.sex">
<el-radio-group v-model="form.sex" :disabled="isReadOnly">
<el-radio label="男"></el-radio>
<el-radio label="女"></el-radio>
<el-radio label="未知">未知</el-radio>
@@ -55,11 +55,11 @@
<el-col :span="10">
<el-form-item label="出生日期" required>
<div class="date-inputs">
<el-input v-model="form.birthYear" placeholder="年" maxlength="4" @change="calculateAge" style="width: 80px" />
<el-input v-model="form.birthYear" placeholder="年" maxlength="4" :disabled="isReadOnly" @change="isReadOnly ? null : calculateAge()" style="width: 80px" />
<span></span>
<el-input v-model="form.birthMonth" placeholder="月" maxlength="2" @change="calculateAge" style="width: 60px" />
<el-input v-model="form.birthMonth" placeholder="月" maxlength="2" :disabled="isReadOnly" @change="isReadOnly ? null : calculateAge()" style="width: 60px" />
<span></span>
<el-input v-model="form.birthDay" placeholder="日" maxlength="2" @change="calculateAge" style="width: 60px" />
<el-input v-model="form.birthDay" placeholder="日" maxlength="2" :disabled="isReadOnly" @change="isReadOnly ? null : calculateAge()" style="width: 60px" />
<span></span>
</div>
</el-form-item>
@@ -67,8 +67,8 @@
<el-col :span="8">
<el-form-item label="或 实足年龄">
<div class="age-inputs">
<el-input v-model="form.age" placeholder="年龄" style="width: 100px" />
<el-select v-model="form.ageUnit" style="width: 80px">
<el-input v-model="form.age" placeholder="年龄" style="width: 100px" :disabled="isReadOnly" />
<el-select v-model="form.ageUnit" style="width: 80px" :disabled="isReadOnly">
<el-option label="岁" value="岁" />
<el-option label="月" value="月" />
<el-option label="天" value="天" />
@@ -82,7 +82,7 @@
<el-row :gutter="16" class="form-row">
<el-col :span="24">
<el-form-item label="工作单位(学校)">
<el-input v-model="form.workplace" placeholder="请输入工作单位" />
<el-input v-model="form.workplace" placeholder="请输入工作单位" :disabled="isReadOnly" />
</el-form-item>
</el-col>
</el-row>
@@ -91,12 +91,12 @@
<el-row :gutter="16" class="form-row">
<el-col :span="12">
<el-form-item label="联系电话" prop="phone" required>
<el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" />
<el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" :disabled="isReadOnly" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="紧急联系人电话" prop="contactPhone" required>
<el-input v-model="form.contactPhone" placeholder="请输入紧急联系人电话" maxlength="11" />
<el-input v-model="form.contactPhone" placeholder="请输入紧急联系人电话" maxlength="11" :disabled="isReadOnly" />
</el-form-item>
</el-col>
</el-row>
@@ -112,6 +112,7 @@
placeholder="请选择省/市/区县/街道"
clearable
style="width: 100%"
:disabled="isReadOnly"
@change="handleAddressChange"
/>
</el-form-item>
@@ -119,10 +120,10 @@
</el-row>
<el-row :gutter="16" class="form-row">
<el-col :span="12">
<el-input v-model="form.addressVillage" placeholder="村(居)" />
<el-input v-model="form.addressVillage" placeholder="村(居)" :disabled="isReadOnly" />
</el-col>
<el-col :span="12">
<el-input v-model="form.addressHouse" placeholder="门牌号" />
<el-input v-model="form.addressHouse" placeholder="门牌号" :disabled="isReadOnly" />
</el-col>
</el-row>
@@ -130,13 +131,13 @@
<el-row :gutter="16" class="form-row">
<el-col :span="24">
<el-form-item label="病人属于" prop="patientBelong" required>
<el-radio-group v-model="form.patientBelong">
<el-radio label="1">本县区</el-radio>
<el-radio label="2">本市其他县区</el-radio>
<el-radio label="3">本省其他地市</el-radio>
<el-radio label="4">外省</el-radio>
<el-radio label="5">港澳台</el-radio>
<el-radio label="6">外籍</el-radio>
<el-radio-group v-model="form.patientBelong" :disabled="isReadOnly">
<el-radio :label="1">本县区</el-radio>
<el-radio :label="2">本市其他县区</el-radio>
<el-radio :label="3">本省其他地市</el-radio>
<el-radio :label="4">外省</el-radio>
<el-radio :label="5">港澳台</el-radio>
<el-radio :label="6">外籍</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
@@ -146,14 +147,14 @@
<el-row :gutter="16" class="form-row">
<el-col :span="12">
<el-form-item label="职业" prop="occupation" required>
<el-select v-model="form.occupation" placeholder="请选择职业" style="width: 100%">
<el-select v-model="form.occupation" placeholder="请选择职业" style="width: 100%" :disabled="isReadOnly">
<el-option v-for="item in occupationList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="病例分类" prop="caseClass" required>
<el-radio-group v-model="form.caseClass">
<el-radio-group v-model="form.caseClass" :disabled="isReadOnly">
<el-radio label="1">疑似病例</el-radio>
<el-radio label="2">临床诊断病例</el-radio>
<el-radio label="3">确诊病例</el-radio>
@@ -175,6 +176,7 @@
style="width: 100%"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
:disabled="isReadOnly"
/>
</el-form-item>
</el-col>
@@ -187,6 +189,7 @@
style="width: 100%"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
:disabled="isReadOnly"
/>
</el-form-item>
</el-col>
@@ -199,6 +202,7 @@
style="width: 100%"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
:disabled="isReadOnly"
/>
</el-form-item>
</el-col>
@@ -215,11 +219,13 @@
<div class="disease-list">
<el-checkbox
:model-value="form.selectedClassA === '0101'"
@update:model-value="(checked) => handleClassACheckbox('0101', checked)"
:disabled="isReadOnly"
@update:model-value="(checked) => isReadOnly ? null : handleClassACheckbox('0101', checked)"
>鼠疫</el-checkbox>
<el-checkbox
:model-value="form.selectedClassA === '0102'"
@update:model-value="(checked) => handleClassACheckbox('0102', checked)"
:disabled="isReadOnly"
@update:model-value="(checked) => isReadOnly ? null : handleClassACheckbox('0102', checked)"
>霍乱</el-checkbox>
</div>
</div>
@@ -232,7 +238,8 @@
v-for="disease in classBDiseases"
:key="disease.code"
:model-value="form.selectedClassB === disease.code"
@update:model-value="(checked) => handleClassBCheckbox(disease.code, checked)"
:disabled="isReadOnly"
@update:model-value="(checked) => isReadOnly ? null : handleClassBCheckbox(disease.code, checked)"
>{{ disease.name }}</el-checkbox>
</div>
</div>
@@ -245,7 +252,8 @@
v-for="disease in classCDiseases"
:key="disease.code"
:model-value="form.selectedClassC === disease.code"
@update:model-value="(checked) => handleClassCCheckbox(disease.code, checked)"
:disabled="isReadOnly"
@update:model-value="(checked) => isReadOnly ? null : handleClassCCheckbox(disease.code, checked)"
>{{ disease.name }}</el-checkbox>
</div>
</div>
@@ -254,12 +262,12 @@
<el-row :gutter="16" class="form-row">
<el-col :span="showSubtypeSelect ? 16 : 24">
<el-form-item label="其他法定管理以及重点监测传染病">
<el-input v-model="form.otherDisease" placeholder="手动输入非列表疾病" />
<el-input v-model="form.otherDisease" placeholder="手动输入非列表疾病" :disabled="isReadOnly" />
</el-form-item>
</el-col>
<el-col v-if="showSubtypeSelect" :span="8">
<el-form-item label="分型" prop="diseaseType" :required="showSubtypeSelect">
<el-select v-model="form.diseaseType" placeholder="请选择分型" style="width: 100%">
<el-select v-model="form.diseaseType" placeholder="请选择分型" style="width: 100%" :disabled="isReadOnly">
<el-option
v-for="option in currentSubtypeOptions"
:key="option.value"
@@ -284,7 +292,7 @@
</el-col>
<el-col :span="8">
<el-form-item label="联系电话">
<el-input v-model="form.reportOrgPhone" placeholder="请输入联系电话" />
<el-input v-model="form.reportOrgPhone" placeholder="请输入联系电话" :disabled="isReadOnly" />
</el-form-item>
</el-col>
<el-col :span="8">
@@ -304,17 +312,18 @@
style="width: 100%"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
:disabled="isReadOnly"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="订正病名">
<el-input v-model="form.correctName" placeholder="请输入订正病名" />
<el-input v-model="form.correctName" placeholder="请输入订正病名" :disabled="isReadOnly" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="退卡原因">
<el-input v-model="form.withdrawReason" placeholder="请输入退卡原因" />
<el-input v-model="form.withdrawReason" placeholder="请输入退卡原因" :disabled="isReadOnly" />
</el-form-item>
</el-col>
</el-row>
@@ -322,15 +331,15 @@
<el-row :gutter="16" class="form-row">
<el-col :span="24">
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" :disabled="isReadOnly" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<!-- 操作按钮区 -->
<div class="action-buttons">
<!-- 操作按钮区create/edit 模式下显示) -->
<div v-if="mode !== 'view'" class="action-buttons">
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">保 存</el-button>
<el-button @click="handleReset"> </el-button>
</div>
@@ -347,6 +356,9 @@ import { useDict } from '@/utils/dict';
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
// 是否只读模式
const isReadOnly = computed(() => props.mode === 'view');
// 获取职业字典数据
const { prfs: occupationList } = useDict('prfs');
@@ -418,9 +430,19 @@ const props = defineProps({
type: String,
default: '',
},
// 'create' | 'view' | 'edit'
mode: {
type: String,
default: 'create',
},
// 已有报卡数据view/edit 模式下使用)
cardData: {
type: Object,
default: null,
},
});
const emit = defineEmits(['saved']);
const emit = defineEmits(['saved', 'submit-edit']);
// 地址级联选择器配置
const addressOptions = ref(pcas);
@@ -453,7 +475,7 @@ const form = ref({
addressTown: '',
addressVillage: '',
addressHouse: '',
patientBelong: '1',
patientBelong: 1,
occupation: '',
caseClass: '',
onsetDate: '',
@@ -567,19 +589,27 @@ const isChildPatient = computed(() => {
// 监听疾病选择变化,自动清空分型选择
watch(() => [form.value.selectedClassA, form.value.selectedClassB, form.value.selectedClassC], (newVal, oldVal) => {
if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
// 在view/edit模式下初始化时不清空分型避免数据加载时被错误清空
if (JSON.stringify(newVal) !== JSON.stringify(oldVal) && props.mode !== 'view' && props.mode !== 'edit') {
form.value.diseaseType = '';
}
updateSelectedDiseases();
}, { deep: true });
// 监听患者信息变化,自动填充表单
// 监听患者信息变化,自动填充表单(仅 create 模式)
watch(() => props.patientInfo, (newVal) => {
if (newVal && Object.keys(newVal).length > 0) {
if (props.mode === 'create' && newVal && Object.keys(newVal).length > 0) {
initForm();
}
}, { deep: true, immediate: true });
// 监听 cardData 变化,在 view/edit 模式下初始化
watch(() => props.cardData, (newVal) => {
if ((props.mode === 'view' || props.mode === 'edit') && newVal) {
initFormFromCard(newVal);
}
}, { immediate: true });
// 监听Tab切换重新初始化
watch(() => props.activeTab, (newVal) => {
if (newVal === 'infectiousReport' && props.patientInfo && Object.keys(props.patientInfo).length > 0) {
@@ -808,7 +838,7 @@ async function initForm() {
addressTown: patientInfo.addressStreet || '',
addressVillage: '',
addressHouse: '',
patientBelong: '1',
patientBelong: 1,
occupation: '',
caseClass: '',
onsetDate: getCurrentDate(),
@@ -893,7 +923,7 @@ function buildSubmitData() {
addressTown: formData.addressTown || '',
addressVillage: formData.addressVillage || '',
addressHouse: formData.addressHouse || '',
patientBelong: parseInt(formData.patientBelong) || 1,
patientBelong: formData.patientBelong || 1,
occupation: formData.occupation || null,
diseaseCode: diseaseCode || null,
diseaseType: diseaseType || null,
@@ -1015,15 +1045,20 @@ async function handleSubmit() {
const submitData = buildSubmitData();
const res = await saveInfectiousDiseaseReport(submitData);
if (res.code === 200) {
proxy.$modal.msgSuccess('传染病报告卡保存成功');
emit('saved');
// 重新初始化表单,获取新的卡片编号
initForm();
if (props.mode === 'edit') {
// 编辑模式:通过事件通知父组件处理保存
emit('submit-edit', submitData);
} else {
proxy.$modal.msgError(res.msg || '保存失败');
// 新建模式
const res = await saveInfectiousDiseaseReport(submitData);
if (res.code === 200) {
proxy.$modal.msgSuccess('传染病报告卡保存成功');
emit('saved');
// 重新初始化表单,获取新的卡片编号
initForm();
} else {
proxy.$modal.msgError(res.msg || '保存失败');
}
}
} catch (err) {
console.error('传染病报告卡保存失败:', err);
@@ -1065,7 +1100,7 @@ function handleReset() {
form.value.contactPhone = '';
form.value.occupation = '';
form.value.caseClass = '';
form.value.patientBelong = '1';
form.value.patientBelong = 1;
form.value.otherDisease = '';
form.value.correctName = '';
form.value.withdrawReason = '';
@@ -1075,22 +1110,108 @@ function handleReset() {
updateSelectedDiseases();
}
// 从已有报卡数据初始化表单view/edit 模式)
function initFormFromCard(card) {
if (!card) return;
const ageUnitReverseMap = { '1': '岁', '2': '月', '3': '天' };
const sexMap = { '1': '男', '2': '女', '0': '未知' };
// 拆解出生日期
let birthYear = '', birthMonth = '', birthDay = '';
if (card.birthday) {
const parts = card.birthday.split('-');
birthYear = parts[0] || '';
birthMonth = parts[1] || '';
birthDay = parts[2] || '';
}
// 还原疾病分类选中状态
let selectedClassA = '', selectedClassB = '', selectedClassC = '';
if (card.diseaseCode) {
if (card.diseaseCode.startsWith('01')) selectedClassA = card.diseaseCode;
else if (card.diseaseCode.startsWith('02')) selectedClassB = card.diseaseCode;
else if (card.diseaseCode.startsWith('03')) selectedClassC = card.diseaseCode;
}
form.value = {
cardNo: card.cardNo || '',
patName: card.patName || '',
parentName: card.parentName || '',
idNo: card.idNo || '',
sex: sexMap[card.sex] || card.sex || '男',
birthYear,
birthMonth,
birthDay,
age: card.age != null ? String(card.age) : '',
ageUnit: ageUnitReverseMap[card.ageUnit] || '岁',
workplace: card.workplace || '',
phone: card.phone || '',
contactPhone: card.contactPhone || '',
addressProv: card.addressProv || '',
addressCity: card.addressCity || '',
addressCounty: card.addressCounty || '',
addressTown: card.addressTown || '',
addressVillage: card.addressVillage || '',
addressHouse: card.addressHouse || '',
patientBelong: card.patientBelong || 1,
occupation: card.occupation || '',
caseClass: card.caseClass != null ? String(card.caseClass) : '',
onsetDate: card.onsetDate || '',
diagDate: card.diagDate ? card.diagDate.substring(0, 10) : '',
deathDate: card.deathDate || '',
selectedDiseases: card.diseaseCode && card.diseaseCode !== 'OTHER' ? [card.diseaseCode] : [],
selectedClassA,
selectedClassB,
selectedClassC,
otherDisease: card.diseaseCode === 'OTHER' ? (card.otherDisease || '') : (card.otherDisease || ''),
diseaseType: card.diseaseType || '',
reportOrg: card.reportOrg || '',
reportOrgPhone: card.reportOrgPhone || '',
reportDoc: card.reportDoc || '',
reportDate: card.reportDate || '',
correctName: card.correctName || '',
withdrawReason: card.withdrawReason || '',
remark: card.remark || '',
encounterId: card.visitId || '',
patientId: card.patId || '',
diagnosisId: card.diagId || '',
};
// 设置地址级联选择器
const provName = card.addressProv || '';
const cityName = card.addressCity || '';
const countyName = card.addressCounty || '';
const townName = card.addressTown || '';
let names = [];
if (municipalities.includes(provName)) {
names = [provName, countyName, townName].filter(n => n);
} else {
names = [provName, cityName, countyName, townName].filter(n => n);
}
addressCodes.value = findCodesByNames(names);
}
// 组件挂载时初始化
onMounted(() => {
if (props.patientInfo && Object.keys(props.patientInfo).length > 0) {
if ((props.mode === 'view' || props.mode === 'edit') && props.cardData) {
initFormFromCard(props.cardData);
} else if (props.mode === 'create' && props.patientInfo && Object.keys(props.patientInfo).length > 0) {
initForm();
}
});
// 暴露方法供父组件调用
defineExpose({
initForm
initForm,
initFormFromCard
});
</script>
<style scoped>
.infectious-report-container {
height: 100%;
height: auto;
min-height: 100%;
display: flex;
flex-direction: column;
background: #fff;
@@ -1130,8 +1251,7 @@ defineExpose({
/* 表单区域样式 */
.report-form {
flex: 1;
overflow-y: auto;
overflow-y: visible;
padding-right: 8px;
}

View File

@@ -1,31 +1,33 @@
<template>
<div class="my-card-management-container">
<!-- 顶部标题 -->
<div class="page-header">
<h2>我的报卡</h2>
<h2 class="page-title">我的报卡</h2>
</div>
<!-- 统计卡片区 -->
<div class="statistics-section">
<div class="stat-card total">
<div class="stat-icon">
<el-icon><Document /></el-icon>
<div class="stat-card">
<div class="stat-icon-wrap stat-icon-purple">
<el-icon class="stat-icon-inner"><DataAnalysis /></el-icon>
</div>
<div class="stat-content">
<div class="stat-value">{{ statistics.totalCount || 0 }}</div>
<div class="stat-label">总报卡数</div>
</div>
</div>
<div class="stat-card pending">
<div class="stat-icon">
<el-icon><Clock /></el-icon>
<div class="stat-card">
<div class="stat-icon-wrap stat-icon-red">
<el-icon class="stat-icon-inner"><Warning /></el-icon>
</div>
<div class="stat-content">
<div class="stat-value">{{ statistics.pendingFailedCount || 0 }}</div>
<div class="stat-label">处理/失败</div>
<div class="stat-label">提交</div>
</div>
</div>
<div class="stat-card success">
<div class="stat-icon">
<el-icon><CircleCheck /></el-icon>
<div class="stat-card">
<div class="stat-icon-wrap stat-icon-green">
<el-icon class="stat-icon-inner"><CircleCheck /></el-icon>
</div>
<div class="stat-content">
<div class="stat-value">{{ statistics.reportedCount || 0 }}</div>
@@ -34,21 +36,25 @@
</div>
</div>
<!-- 高级筛选区 -->
<div class="filter-section">
<el-form :model="queryParams" :inline="true">
<el-form-item label="日期范围">
<div class="filter-title">高级筛选</div>
<div class="filter-row">
<div class="filter-item">
<span class="filter-label">日期范围</span>
<el-date-picker
v-model="queryParams.dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
start-placeholder="//"
end-placeholder="//"
value-format="YYYY-MM-DD"
style="width: 240px"
class="filter-date-picker"
/>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryParams.status" placeholder="全部状态" clearable style="width: 140px">
</div>
<div class="filter-item">
<span class="filter-label">状态</span>
<el-select v-model="queryParams.status" placeholder="全部状态" clearable class="filter-select">
<el-option label="全部状态" value="" />
<el-option label="待提交" value="0" />
<el-option label="已提交" value="1" />
@@ -57,101 +63,65 @@
<el-option label="失败" value="4" />
<el-option label="作废" value="6" />
</el-select>
</el-form-item>
<el-form-item label="关键词">
</div>
<div class="filter-item">
<span class="filter-label">报卡名称</span>
<el-input
v-model="queryParams.keyword"
placeholder="患者姓名/报卡名称"
placeholder="输入报卡名称..."
clearable
style="width: 180px"
class="filter-input"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>
</div>
<div class="filter-actions">
<el-button class="btn-apply" type="primary" @click="handleQuery">
<el-icon><Check /></el-icon>
应用筛选
</el-button>
<el-button @click="handleReset">
<el-icon><Refresh /></el-icon>
<el-button class="btn-reset" @click="handleReset">
<el-icon><RefreshRight /></el-icon>
重置条件
</el-button>
</el-form-item>
</el-form>
</div>
<div class="action-section">
<el-checkbox v-model="isAllSelected" @change="handleSelectAll">全选</el-checkbox>
<el-button type="primary" :disabled="selectedRows.length === 0" @click="handleBatchSubmit">
批量提交
</el-button>
<el-button type="danger" :disabled="selectedRows.length === 0" @click="handleBatchDelete">
批量删除
</el-button>
</div>
</div>
</div>
<!-- 数据表格区 -->
<div class="table-section">
<el-table
ref="tableRef"
v-loading="loading"
:data="cardList"
@selection-change="handleSelectionChange"
border
stripe
:height="tableHeight"
virtualized
:virtualized-row-height="48"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="卡片ID" prop="cardNo" min-width="150" />
<el-table-column label="患者姓名" prop="patName" width="100" />
<el-table-column label="身份证号" prop="idNo" min-width="180" />
<el-table-column label="联系电话" prop="phone" width="130" />
<el-table-column label="就诊卡号" prop="visitCardNo" width="130" />
<el-table-column label="报卡名称" prop="cardName" min-width="200" />
<el-table-column label="提交时间" prop="submitTime" width="160" align="center" />
<el-table-column label="状态" prop="status" width="100" align="center">
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="卡片ID" prop="cardNo" min-width="155" />
<el-table-column label="患者姓名" prop="patName" width="90" />
<el-table-column label="身份证号" prop="idNo" min-width="170" />
<el-table-column label="联系电话" prop="phone" width="120" />
<el-table-column label="就诊卡号" prop="visitCardNo" width="110" />
<el-table-column label="报卡名称" prop="cardName" min-width="120" />
<el-table-column label="提交时间" prop="submitTime" width="110" align="center" />
<el-table-column label="状态" prop="status" width="80" align="center">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status)" size="small">
<span :class="['status-tag', 'status-' + row.status]">
{{ getStatusName(row.status) }}
</el-tag>
</span>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center" fixed="right">
<el-table-column label="操作" width="160" align="center" fixed="right">
<template #default="{ row }">
<el-button type="primary" link size="small" @click="handleView(row)">查看</el-button>
<el-button
v-if="row.status === '0'"
type="primary"
link
size="small"
@click="handleEdit(row)"
>
编辑
</el-button>
<el-button
v-if="row.status === '0'"
type="success"
link
size="small"
@click="handleSubmit(row)"
>
提交
</el-button>
<el-button
v-if="row.status === '1'"
type="warning"
link
size="small"
@click="handleWithdraw(row)"
>
撤回
</el-button>
<el-button
v-if="row.status === '3'"
type="info"
link
size="small"
@click="handleExport(row)"
>
导出
</el-button>
<el-button v-if="row.status == 0" type="primary" link size="small" @click="handleEdit(row)">编辑</el-button>
<el-button v-if="row.status == 0" type="success" link size="small" @click="handleSubmit(row)">提交</el-button>
<el-button v-if="row.status == 1" type="warning" link size="small" @click="handleWithdraw(row)">撤回</el-button>
<el-button v-if="row.status == 3" type="primary" link size="small" @click="handleExport(row)">导出</el-button>
</template>
</el-table-column>
</el-table>
@@ -169,149 +139,43 @@
</div>
</div>
<!-- 底部批量操作栏 -->
<div class="bottom-action-bar">
<div class="bottom-action-left">
<el-checkbox v-model="isAllSelected" @change="handleSelectAll">全选</el-checkbox>
<span class="selected-info">已选择 {{ selectedRows.length }} 个项目</span>
</div>
<div class="bottom-action-right">
<el-button class="btn-batch-delete" :disabled="selectedRows.length === 0" @click="handleBatchDelete">批量删除</el-button>
<el-button class="btn-batch-submit" type="primary" :disabled="selectedRows.length === 0" @click="handleBatchSubmit">批量提交</el-button>
</div>
</div>
<el-dialog
v-model="detailVisible"
:title="detailMode === 'view' ? '报卡详情' : '编辑报卡'"
width="800px"
width="900px"
destroy-on-close
class="card-detail-dialog"
>
<el-descriptions v-if="detailMode === 'view'" :column="2" border>
<el-descriptions-item label="卡片编号">{{ currentCard.cardNo }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="getStatusType(currentCard.status)">{{ getStatusName(currentCard.status) }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="患者姓名">{{ currentCard.patName }}</el-descriptions-item>
<el-descriptions-item label="身份证号">{{ currentCard.idNo }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ currentCard.phone }}</el-descriptions-item>
<el-descriptions-item label="性别">{{ currentCard.sex === '1' ? '男' : currentCard.sex === '2' ? '女' : '未知' }}</el-descriptions-item>
<el-descriptions-item label="年龄">{{ currentCard.age }}{{ getAgeUnit(currentCard.ageUnit) }}</el-descriptions-item>
<el-descriptions-item label="疾病名称">{{ currentCard.diseaseName }}</el-descriptions-item>
<el-descriptions-item label="发病日期">{{ currentCard.onsetDate }}</el-descriptions-item>
<el-descriptions-item label="诊断日期">{{ currentCard.diagDate }}</el-descriptions-item>
<el-descriptions-item label="报告单位">{{ currentCard.reportOrg }}</el-descriptions-item>
<el-descriptions-item label="报告医生">{{ currentCard.reportDoc }}</el-descriptions-item>
<el-descriptions-item label="填卡日期">{{ currentCard.reportDate }}</el-descriptions-item>
<el-descriptions-item label="现住址" :span="2">
{{ currentCard.addressProv }}{{ currentCard.addressCity }}{{ currentCard.addressCounty }}{{ currentCard.addressTown }}{{ currentCard.addressVillage }}{{ currentCard.addressHouse }}
</el-descriptions-item>
</el-descriptions>
<el-form v-else ref="editFormRef" :model="editForm" :rules="editFormRules" label-width="100px">
<!-- 患者基本信息 -->
<el-divider content-position="left">患者基本信息</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="患者姓名" prop="patName">
<el-input v-model="editForm.patName" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="身份证号" prop="idNo">
<el-input v-model="editForm.idNo" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="性别" prop="sex">
<el-input :value="editForm.sex === '1' ? '男' : editForm.sex === '2' ? '女' : '未知'" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="年龄" prop="age">
<el-input :value="`${editForm.age}${getAgeUnit(editForm.ageUnit)}`" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话" prop="phone" required>
<el-input v-model="editForm.phone" />
</el-form-item>
</el-col>
</el-row>
<!-- 临床信息 -->
<el-divider content-position="left">临床信息</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="疾病名称" prop="diseaseName">
<el-input v-model="editForm.diseaseName" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发病日期" prop="onsetDate">
<el-date-picker v-model="editForm.onsetDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="诊断日期" prop="diagDate" required>
<el-date-picker v-model="editForm.diagDate" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="传染病类别" prop="diseaseType">
<el-select v-model="editForm.diseaseType" placeholder="请选择传染病类别" style="width: 100%">
<el-option label="甲类传染病" value="A" />
<el-option label="乙类传染病" value="B" />
<el-option label="丙类传染病" value="C" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 报告信息 -->
<el-divider content-position="left">报告信息</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="报告单位" prop="reportOrg">
<el-input v-model="editForm.reportOrg" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="报告医生" prop="reportDoc">
<el-input v-model="editForm.reportDoc" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="填卡日期" prop="reportDate">
<el-input v-model="editForm.reportDate" disabled />
</el-form-item>
</el-col>
</el-row>
<!-- 现住址 -->
<el-divider content-position="left">现住址</el-divider>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="省" prop="addressProv">
<el-input v-model="editForm.addressProv" placeholder="请输入省份" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="市" prop="addressCity">
<el-input v-model="editForm.addressCity" placeholder="请输入城市" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="区县" prop="addressCounty">
<el-input v-model="editForm.addressCounty" placeholder="请输入区县" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="详细地址" prop="addressHouse">
<el-input v-model="editForm.addressHouse" type="textarea" placeholder="请输入详细地址" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<InfectiousReport
:mode=" detailMode"
:card-data="currentCard"
@submit-edit="handleSaveEdit"
style="max-height: 70vh; overflow-y: auto;"
/>
<template #footer>
<el-button @click="detailVisible = false">取消</el-button>
<el-button v-if="detailMode === 'edit'" type="primary" @click="handleSaveEdit">保存</el-button>
<el-button @click="detailVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { ref, reactive, onMounted, onActivated, onBeforeUnmount, nextTick, computed } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Document, Clock, CircleCheck, Search, Refresh } from '@element-plus/icons-vue';
import { DataAnalysis, Warning, CircleCheck, Check, RefreshRight } from '@element-plus/icons-vue';
import InfectiousReport from '../components/infectiousReport/index.vue';
import {
getDoctorCardStatistics,
getDoctorCardList,
@@ -329,6 +193,7 @@ const cardList = ref([]);
const total = ref(0);
const selectedRows = ref([]);
const isAllSelected = ref(false);
const tableRef = ref(null);
const statistics = ref({
totalCount: 0,
@@ -347,28 +212,25 @@ const queryParams = reactive({
const detailVisible = ref(false);
const detailMode = ref('view');
const currentCard = ref({});
const editForm = reactive({});
const editFormRef = ref(null);
// 编辑表单验证规则
const editFormRules = {
phone: [
{ required: true, message: '请输入联系电话', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
],
diagDate: [
{ required: true, message: '请选择诊断日期', trigger: 'change' }
]
};
// 计算表格高度:根据视口高度动态调整
const tableHeight = computed(() => {
// 视口高度 - 顶部导航(约60px) - 标题(40px) - 统计卡片(120px) - 筛选区(100px)
// - 分页(60px) - 底部操作栏(60px) - 上下边距(80px) - 额外缓冲(20px)
return window.innerHeight - 540;
});
// 监听窗口大小变化,确保表格高度自适应
let resizeObserver = null;
const statusMap = {
'0': { name: '待提交', type: 'warning' },
'1': { name: '已提交', type: 'primary' },
'2': { name: '已审核', type: 'success' },
'3': { name: '已上报', type: 'success' },
'4': { name: '失败', type: 'danger' },
'5': { name: '退回', type: 'danger' },
'6': { name: '作废', type: 'info' },
0: { name: '待提交', type: 'warning' },
1: { name: '已提交', type: 'primary' },
2: { name: '已审核', type: 'success' },
3: { name: '已上报', type: 'success' },
4: { name: '失败', type: 'danger' },
5: { name: '退回', type: 'danger' },
6: { name: '作废', type: 'info' },
};
const ageUnitMap = {
@@ -454,11 +316,11 @@ function handleSelectionChange(selection) {
}
function handleSelectAll(val) {
if (val) {
selectedRows.value = [...cardList.value];
} else {
selectedRows.value = [];
}
nextTick(() => {
if (tableRef.value) {
tableRef.value.toggleAllSelection();
}
});
}
async function handleView(row) {
@@ -478,7 +340,7 @@ async function handleEdit(row) {
try {
const res = await getCardDetail(row.cardNo);
if (res.code === 200) {
Object.assign(editForm, res.data || {});
currentCard.value = res.data || {};
detailMode.value = 'edit';
detailVisible.value = true;
}
@@ -487,26 +349,33 @@ async function handleEdit(row) {
}
}
async function handleSaveEdit() {
// 验证表单
try {
await editFormRef.value.validate();
} catch (error) {
ElMessage.error('表单验证失败,请检查输入');
return;
}
async function handleSaveEdit(submitData) {
// submitData 来自 InfectiousReport 组件的 submit-edit 事件
try {
const updateData = {
cardNo: editForm.cardNo,
phone: editForm.phone,
onsetDate: editForm.onsetDate,
diagDate: editForm.diagDate,
diseaseType: editForm.diseaseType,
addressProv: editForm.addressProv,
addressCity: editForm.addressCity,
addressCounty: editForm.addressCounty,
addressHouse: editForm.addressHouse,
cardNo: submitData.cardNo,
phone: submitData.phone,
contactPhone: submitData.contactPhone,
onsetDate: submitData.onsetDate,
diagDate: submitData.diagDate,
diseaseCode: submitData.diseaseCode,
diseaseType: submitData.diseaseType,
otherDisease: submitData.otherDisease,
caseClass: submitData.caseClass,
occupation: submitData.occupation,
patientBelong: submitData.patientBelong,
addressProv: submitData.addressProv,
addressCity: submitData.addressCity,
addressCounty: submitData.addressCounty,
addressTown: submitData.addressTown,
addressVillage: submitData.addressVillage,
addressHouse: submitData.addressHouse,
workplace: submitData.workplace,
parentName: submitData.parentName,
deathDate: submitData.deathDate,
correctName: submitData.correctName,
withdrawReason: submitData.withdrawReason,
remark: submitData.remark,
};
const res = await updateDoctorCard(updateData);
@@ -567,7 +436,7 @@ async function handleWithdraw(row) {
}
async function handleBatchSubmit() {
const validRows = selectedRows.value.filter(row => row.status === '0');
const validRows = selectedRows.value.filter(row => row.status == 0);
if (validRows.length === 0) {
ElMessage.warning('只能提交待提交状态的报卡');
return;
@@ -595,7 +464,7 @@ async function handleBatchSubmit() {
}
async function handleBatchDelete() {
const validRows = selectedRows.value.filter(row => row.status === '0');
const validRows = selectedRows.value.filter(row => row.status == 0);
if (validRows.length === 0) {
ElMessage.warning('只能删除待提交状态的报卡');
return;
@@ -638,9 +507,35 @@ async function handleExport(row) {
}
onMounted(() => {
console.log('页面首次加载 - onMounted');
getStatistics();
getList();
// 添加窗口大小变化监听
window.addEventListener('resize', handleResize);
});
// 处理 keep-alive 缓存场景:从其他页面返回时重新加载数据
onActivated(() => {
console.log('页面被激活 - onActivated');
getStatistics();
getList();
});
// 组件卸载时移除事件监听
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize);
});
// 窗口大小变化处理函数
function handleResize() {
// 触发计算属性重新计算
nextTick(() => {
if (tableRef.value) {
tableRef.value.doLayout();
}
});
}
</script>
<style scoped>
@@ -648,109 +543,274 @@ onMounted(() => {
padding: 20px;
background-color: #f5f7fa;
min-height: calc(100vh - 84px);
display: flex;
flex-direction: column;
gap: 16px;
}
/* 页面标题 */
.page-header {
margin-bottom: 20px;
margin-bottom: 0;
}
.page-header h2 {
.page-title {
font-size: 20px;
font-weight: 600;
color: #1e293b;
margin: 0;
}
/* 统计卡片区 */
.statistics-section {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-bottom: 20px;
gap: 16px;
}
.stat-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: #fff;
border-radius: 12px;
padding: 24px;
padding: 20px 24px;
display: flex;
align-items: center;
gap: 16px;
color: #fff;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
border: 1px solid #f0f0f0;
transition: box-shadow 0.2s;
}
.stat-card:hover {
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.1);
}
.stat-card.pending {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
box-shadow: 0 4px 12px rgba(245, 87, 108, 0.3);
}
.stat-card.success {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
box-shadow: 0 4px 12px rgba(0, 242, 254, 0.3);
}
.stat-icon {
width: 56px;
height: 56px;
background: rgba(255, 255, 255, 0.2);
border-radius: 12px;
/* 图标容器 */
.stat-icon-wrap {
width: 52px;
height: 52px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
flex-shrink: 0;
}
.stat-icon-purple {
background: linear-gradient(135deg, #818cf8 0%, #6366f1 100%);
}
.stat-icon-red {
background: linear-gradient(135deg, #fca5a5 0%, #ef4444 100%);
}
.stat-icon-green {
background: linear-gradient(135deg, #6ee7b7 0%, #10b981 100%);
}
.stat-icon-inner {
font-size: 24px;
color: #fff;
}
.stat-content {
flex: 1;
}
.stat-value {
font-size: 32px;
font-weight: bold;
font-size: 28px;
font-weight: 700;
color: #1e293b;
line-height: 1.2;
}
.stat-label {
font-size: 14px;
opacity: 0.9;
font-size: 13px;
color: #64748b;
margin-top: 4px;
}
/* 高级筛选区 */
.filter-section {
background: #fff;
border-radius: 8px;
padding: 16px 20px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
border: 1px solid #f0f0f0;
}
.action-section {
background: #fff;
border-radius: 8px;
padding: 12px 20px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
.filter-title {
font-size: 14px;
font-weight: 600;
color: #1e293b;
margin-bottom: 14px;
}
.filter-row {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
gap: 12px;
}
.filter-item {
display: flex;
align-items: center;
gap: 8px;
}
.filter-label {
font-size: 13px;
color: #475569;
white-space: nowrap;
flex-shrink: 0;
}
.filter-date-picker {
width: 240px;
}
.filter-select {
width: 140px;
}
.filter-input {
width: 180px;
}
.filter-actions {
display: flex;
gap: 8px;
margin-left: auto;
}
.btn-apply {
background-color: #6366f1;
border-color: #6366f1;
}
.btn-apply:hover {
background-color: #4f46e5;
border-color: #4f46e5;
}
.btn-reset {
color: #475569;
}
/* 表格区 */
.table-section {
background: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
border: 1px solid #f0f0f0;
}
/* 状态标签 */
.status-tag {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
line-height: 1.6;
}
/* 已上报 - 绿 */
.status-3 {
color: #16a34a;
background: #dcfce7;
border: 1px solid #bbf7d0;
}
/* 已审核 - 蓝 */
.status-2 {
color: #2563eb;
background: #dbeafe;
border: 1px solid #bfdbfe;
}
/* 失败 - 红 */
.status-4 {
color: #dc2626;
background: #fee2e2;
border: 1px solid #fecaca;
}
/* 已提交 - 绿 */
.status-1 {
color: #16a34a;
background: #dcfce7;
border: 1px solid #bbf7d0;
}
/* 待提交 - 橙 */
.status-0 {
color: #d97706;
background: #fef3c7;
border: 1px solid #fde68a;
}
/* 作废 - 灰 */
.status-6 {
color: #64748b;
background: #f1f5f9;
border: 1px solid #e2e8f0;
}
/* 退回 - 红 */
.status-5 {
color: #dc2626;
background: #fee2e2;
border: 1px solid #fecaca;
}
/* 分页 */
.pagination-section {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
/* 底部批量操作栏 */
.bottom-action-bar {
background: #fff;
border-radius: 8px;
padding: 12px 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
border: 1px solid #f0f0f0;
display: flex;
align-items: center;
justify-content: space-between;
}
.bottom-action-left {
display: flex;
align-items: center;
gap: 12px;
}
.selected-info {
font-size: 13px;
color: #64748b;
}
.bottom-action-right {
display: flex;
gap: 8px;
}
.btn-batch-delete {
color: #dc2626;
border-color: #fecaca;
background: #fff;
}
.btn-batch-delete:hover {
background: #fee2e2;
border-color: #fca5a5;
}
.btn-batch-submit {
background-color: #6366f1;
border-color: #6366f1;
font-weight: 500;
}
.btn-batch-submit:hover {
background-color: #4f46e5;
border-color: #4f46e5;
}
@media (max-width: 992px) {
.statistics-section {
grid-template-columns: 1fr;
}
.filter-row {
flex-direction: column;
align-items: flex-start;
}
.filter-actions {
margin-left: 0;
}
}
/* 报卡详情弹窗 */
:deep(.card-detail-dialog .el-dialog__body) {
padding: 0;
overflow: hidden;
}
:deep(.card-detail-dialog .infectious-report-container) {
padding: 16px;
height: auto;
max-height: 70vh;
overflow-y: auto;
}
</style>