Compare commits
27 Commits
赵云
...
bug464-fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c083cc698 | ||
|
|
6367654ada | ||
|
|
360256e589 | ||
|
|
feb033b857 | ||
|
|
79cce458ee | ||
|
|
1140912f3a | ||
| 250f9ce258 | |||
| 0d6f891b47 | |||
|
|
e68be3be79 | ||
|
|
eab0119c19 | ||
|
|
3ad9ff85d4 | ||
|
|
ab2f580d60 | ||
|
|
665d4ae47a | ||
|
|
d43a06c343 | ||
|
|
a7a33eb5f6 | ||
|
|
444397e868 | ||
|
|
d964155fb8 | ||
|
|
b88e011459 | ||
|
|
492a51d282 | ||
|
|
34774411eb | ||
|
|
0f1b29fcea | ||
|
|
d64ca5b8ee | ||
|
|
faa0b1a61f | ||
|
|
33c76c786c | ||
|
|
1a770ca0ee | ||
|
|
ba9c18b6a4 | ||
|
|
ecc5c75418 |
@@ -85,18 +85,13 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService {
|
||||
String trimmedKey = searchKey.trim();
|
||||
return dictDataMapper.selectDictDataByTypeWithSearch(dictType, trimmedKey);
|
||||
}
|
||||
|
||||
// 否则使用原有方法(带缓存)
|
||||
List<SysDictData> dictDatas = DictUtils.getDictCache(dictType);
|
||||
if (StringUtils.isNotEmpty(dictDatas)) {
|
||||
return dictDatas;
|
||||
}
|
||||
dictDatas = dictDataMapper.selectDictDataByType(dictType);
|
||||
|
||||
// 直接查询数据库,避免缓存中为空数据导致前端下拉框显示"无数据"
|
||||
List<SysDictData> dictDatas = dictDataMapper.selectDictDataByType(dictType);
|
||||
if (StringUtils.isNotEmpty(dictDatas)) {
|
||||
DictUtils.setDictCache(dictType, dictDatas);
|
||||
return dictDatas;
|
||||
}
|
||||
return null;
|
||||
return dictDatas;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -366,7 +366,7 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
|
||||
serviceRequest.setTherapyEnum(TherapyTimeType.TEMPORARY.getValue());// 治疗类型
|
||||
serviceRequest.setQuantity(BigDecimal.valueOf(1)); // 请求数量
|
||||
serviceRequest.setUnitCode("次"); // 请求单位编码
|
||||
serviceRequest.setCategoryEnum(4); // 请求类型:4-手术
|
||||
serviceRequest.setCategoryEnum(24); // 请求类型:24-手术(新值域,避开 adviceType 碰撞)
|
||||
serviceRequest.setActivityId(surgeryId); // 手术ID作为诊疗定义id
|
||||
serviceRequest.setPatientId(surgeryDto.getPatientId()); // 患者
|
||||
serviceRequest.setRequesterId(practitionerId); // 开方医生
|
||||
|
||||
@@ -7,7 +7,6 @@ import com.core.common.core.domain.R;
|
||||
import com.core.common.core.domain.model.LoginUser;
|
||||
import com.core.common.utils.SecurityUtils;
|
||||
import com.openhis.administration.domain.Patient;
|
||||
import com.openhis.administration.service.IOrganizationService;
|
||||
import com.openhis.administration.service.IPatientService;
|
||||
import com.openhis.clinical.domain.Surgery;
|
||||
import com.openhis.clinical.service.ISurgeryService;
|
||||
@@ -28,7 +27,6 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URLEncoder;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -204,6 +202,8 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
|
||||
return R.fail("新增手术安排失败");
|
||||
}
|
||||
|
||||
syncSurgeryIncisionLevel(opSchedule.getOperCode(), opCreateScheduleDto.getIncisionLevel());
|
||||
|
||||
// Bug #247 修复:更新手术申请单状态为已排期 (1)
|
||||
if (opCreateScheduleDto.getApplyId() != null) {
|
||||
try {
|
||||
@@ -300,6 +300,8 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
|
||||
return R.fail("修改手术安排失败");
|
||||
}
|
||||
|
||||
syncSurgeryIncisionLevel(opScheduleDto.getOperCode(), opScheduleDto.getIncisionLevel());
|
||||
|
||||
return R.ok("修改手术安排成功");
|
||||
}
|
||||
|
||||
@@ -433,6 +435,28 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
|
||||
return scheduleDate.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步手术申请表中的切口类型
|
||||
*/
|
||||
private void syncSurgeryIncisionLevel(String surgeryNo, Integer incisionLevel) {
|
||||
if (surgeryNo == null || surgeryNo.isEmpty() || incisionLevel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<Surgery> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(Surgery::getSurgeryNo, surgeryNo)
|
||||
.eq(Surgery::getDeleteFlag, "0");
|
||||
Surgery surgery = surgeryService.getOne(queryWrapper);
|
||||
if (surgery == null) {
|
||||
log.warn("未找到需要同步切口类型的手术申请记录 - surgeryNo: {}", surgeryNo);
|
||||
return;
|
||||
}
|
||||
|
||||
surgery.setIncisionLevel(incisionLevel);
|
||||
surgery.setUpdateTime(new Date());
|
||||
surgeryService.updateById(surgery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充手术申请中缺失的名称字段
|
||||
* 在创建手术安排时调用,确保关联的cli_surgery表中的名称字段有值
|
||||
|
||||
@@ -4,7 +4,6 @@ import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@@ -85,6 +84,11 @@ public class OpCreateScheduleDto {
|
||||
*/
|
||||
private String surgerySite;
|
||||
|
||||
/**
|
||||
* 切口类型
|
||||
*/
|
||||
private Integer incisionLevel;
|
||||
|
||||
/**
|
||||
* 入院时间
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.openhis.surgicalschedule.domain.OpSchedule;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDate;
|
||||
@@ -93,6 +94,12 @@ public class OpScheduleDto extends OpSchedule {
|
||||
* 手术类型
|
||||
*/
|
||||
private String surgeryType;
|
||||
|
||||
/**
|
||||
* 切口类型
|
||||
*/
|
||||
private Integer incisionLevel;
|
||||
|
||||
/**
|
||||
* 申请科室
|
||||
*/
|
||||
@@ -107,8 +114,4 @@ public class OpScheduleDto extends OpSchedule {
|
||||
*/
|
||||
private String createByName;
|
||||
|
||||
/**
|
||||
* 费用类别
|
||||
*/
|
||||
private String feeType;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import com.openhis.medication.service.IMedicationDispenseService;
|
||||
import com.openhis.medication.service.IMedicationRequestService;
|
||||
import com.openhis.web.chargemanage.mapper.OutpatientRegistrationAppMapper;
|
||||
import com.openhis.web.doctorstation.appservice.IDoctorStationAdviceAppService;
|
||||
import com.openhis.web.doctorstation.appservice.IDoctorStationInspectionLabApplyService;
|
||||
import com.openhis.web.doctorstation.dto.*;
|
||||
import com.openhis.web.doctorstation.mapper.DoctorStationAdviceAppMapper;
|
||||
import com.openhis.web.doctorstation.utils.AdviceUtils;
|
||||
@@ -47,12 +48,15 @@ import com.openhis.workflow.domain.InventoryItem;
|
||||
import com.openhis.workflow.domain.ServiceRequest;
|
||||
import com.openhis.workflow.service.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -62,6 +66,9 @@ import java.util.stream.Collectors;
|
||||
@Service
|
||||
public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAppService {
|
||||
|
||||
private static final Pattern INSPECTION_APPLY_NO_JSON =
|
||||
Pattern.compile("\"applyNo\"\\s*:\\s*\"([^\"]+)\"");
|
||||
|
||||
@Resource
|
||||
AssignSeqUtil assignSeqUtil;
|
||||
|
||||
@@ -118,6 +125,13 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
@Resource
|
||||
IInventoryItemService inventoryItemService;
|
||||
|
||||
/**
|
||||
* 与检验申请实现存在循环依赖,需延迟注入;删除诊疗医嘱时按 contentJson 级联作废检验申请单。
|
||||
*/
|
||||
@Resource
|
||||
@Lazy
|
||||
private IDoctorStationInspectionLabApplyService iDoctorStationInspectionLabApplyService;
|
||||
|
||||
// 缓存 key 前缀
|
||||
private static final String ADVICE_BASE_INFO_CACHE_PREFIX = "advice:base:info:";
|
||||
// 缓存过期时间(小时)
|
||||
@@ -1529,12 +1543,24 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
deviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), 4));
|
||||
}
|
||||
deviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
||||
deviceRequest.setPrescriptionNo(adviceSaveDto.getSourceBillNo()); // 来源业务单据号(手术单号)
|
||||
deviceRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
|
||||
deviceRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
|
||||
deviceRequest.setLotNumber(adviceSaveDto.getLotNumber());// 产品批号
|
||||
|
||||
deviceRequest.setCategoryEnum(adviceSaveDto.getCategoryEnum()); // 请求类型
|
||||
deviceRequest.setDeviceDefId(adviceSaveDto.getAdviceDefinitionId());// 耗材定义id
|
||||
// 🔧 BugFix #498: categoryEnum=22(检查) 走 ServiceRequest,不走 DeviceRequest
|
||||
// 检查申请单的诊疗定义ID存在 activityId,不在 adviceDefinitionId
|
||||
// deviceDefId 对应耗材定义ID,不能用诊疗定义ID填充
|
||||
if (adviceSaveDto.getCategoryEnum() == 22) {
|
||||
log.info("handDevice skip - 检查申请单(categoryEnum=22) 走 ServiceRequest 路径,跳过 DeviceRequest 保存");
|
||||
continue; // 跳过本次循环,不走耗材请求路径
|
||||
} else if (adviceSaveDto.getAdviceDefinitionId() != null) {
|
||||
deviceRequest.setDeviceDefId(adviceSaveDto.getAdviceDefinitionId());// 耗材定义id
|
||||
} else {
|
||||
log.warn("handDevice - deviceDefId 为空,adviceDefinitionId=null, categoryEnum={}", adviceSaveDto.getCategoryEnum());
|
||||
}
|
||||
|
||||
deviceRequest.setPatientId(adviceSaveDto.getPatientId()); // 患者
|
||||
deviceRequest.setRequesterId(adviceSaveDto.getPractitionerId()); // 开方医生
|
||||
deviceRequest.setOrgId(adviceSaveDto.getFounderOrgId());// 开方人科室
|
||||
@@ -1695,6 +1721,21 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从诊疗医嘱 contentJson 中解析检验申请单号(检验保存时写入形如 {"applyNo":"..."})。
|
||||
*/
|
||||
private String extractInspectionApplyNoFromContentJson(String contentJson) {
|
||||
if (StringUtils.isBlank(contentJson) || !contentJson.contains("applyNo")) {
|
||||
return null;
|
||||
}
|
||||
Matcher m = INSPECTION_APPLY_NO_JSON.matcher(contentJson);
|
||||
if (!m.find()) {
|
||||
return null;
|
||||
}
|
||||
String applyNo = m.group(1).trim();
|
||||
return StringUtils.isBlank(applyNo) ? null : applyNo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理诊疗
|
||||
*/
|
||||
@@ -1743,6 +1784,8 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
}
|
||||
}
|
||||
}
|
||||
// 检验申请单在医嘱 contentJson 中写入 applyNo;从医嘱删除时需先级联作废检验单,避免检验页签仍显示孤儿申请
|
||||
Map<String, List<Long>> labApplyNoToRequestIds = new LinkedHashMap<>();
|
||||
for (AdviceSaveDto adviceSaveDto : deleteList) {
|
||||
Long requestId = adviceSaveDto.getRequestId();
|
||||
// 🔧 Bug #442: 跳过 requestId 为 null 的记录,避免删除不存在的诊疗请求
|
||||
@@ -1751,6 +1794,35 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
continue;
|
||||
}
|
||||
iServiceRequestService.removeById(requestId);// 删除诊疗
|
||||
ServiceRequest existing = iServiceRequestService.getById(adviceSaveDto.getRequestId());
|
||||
if (existing == null) {
|
||||
continue;
|
||||
}
|
||||
String applyNo = extractInspectionApplyNoFromContentJson(existing.getContentJson());
|
||||
if (StringUtils.isNotBlank(applyNo)) {
|
||||
labApplyNoToRequestIds.computeIfAbsent(applyNo, k -> new ArrayList<>())
|
||||
.add(adviceSaveDto.getRequestId());
|
||||
}
|
||||
}
|
||||
Set<Long> labCascadeSkippedRequestIds = new HashSet<>();
|
||||
for (Map.Entry<String, List<Long>> e : labApplyNoToRequestIds.entrySet()) {
|
||||
R<?> delLab = iDoctorStationInspectionLabApplyService.deleteInspectionLabApply(e.getKey());
|
||||
if (delLab != null && R.isSuccess(delLab)) {
|
||||
labCascadeSkippedRequestIds.addAll(e.getValue());
|
||||
log.info("handService - 级联作废检验申请单 applyNo={},已跳过重复删除的医嘱 requestIds={}",
|
||||
e.getKey(), e.getValue());
|
||||
} else {
|
||||
String msg = delLab != null && StringUtils.isNotEmpty(delLab.getMsg()) ? delLab.getMsg() : "删除检验申请单失败";
|
||||
log.warn("handService - 级联作废检验申请单未成功 applyNo={} msg={},将回退为仅删除当前医嘱记录",
|
||||
e.getKey(), msg);
|
||||
}
|
||||
}
|
||||
for (AdviceSaveDto adviceSaveDto : deleteList) {
|
||||
if (labCascadeSkippedRequestIds.contains(adviceSaveDto.getRequestId())) {
|
||||
continue;
|
||||
}
|
||||
Long requestId = adviceSaveDto.getRequestId();
|
||||
iServiceRequestService.removeById(requestId);// 删除诊疗
|
||||
iServiceRequestService.remove(
|
||||
new LambdaQueryWrapper<ServiceRequest>().eq(ServiceRequest::getParentId,
|
||||
requestId));// 删除诊疗套餐对应的子项
|
||||
@@ -1835,6 +1907,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
serviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.SERVICE_RES_NO.getPrefix(), 4));
|
||||
}
|
||||
serviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
||||
serviceRequest.setPrescriptionNo(adviceSaveDto.getSourceBillNo()); // 来源业务单据号(手术单号)
|
||||
serviceRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
|
||||
serviceRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
|
||||
|
||||
@@ -2030,10 +2103,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
CommonConstants.TableName.MED_MEDICATION_REQUEST, CommonConstants.TableName.WOR_DEVICE_REQUEST,
|
||||
CommonConstants.TableName.WOR_SERVICE_REQUEST, practitionerId, Whether.NO.getCode(),
|
||||
sourceEnum, sourceBillNo);
|
||||
// 手术计费场景:sourceBillNo 不为空时,只保留诊疗请求(3/6),过滤掉药品(1)和耗材(2)
|
||||
// 手术计费场景:sourceBillNo 不为空时,过滤掉药品(1),保留耗材(2)和诊疗(3/6)
|
||||
if (sourceBillNo != null && !sourceBillNo.isEmpty()) {
|
||||
requestBaseInfo.removeIf(dto -> dto.getAdviceType() != null
|
||||
&& (dto.getAdviceType() == 1 || dto.getAdviceType() == 2));
|
||||
&& dto.getAdviceType() == 1);
|
||||
}
|
||||
for (RequestBaseDto requestBaseDto : requestBaseInfo) {
|
||||
// 请求状态
|
||||
|
||||
@@ -31,6 +31,7 @@ public class NursingRecordController {
|
||||
* 获取住院患者信息 分页显示
|
||||
*
|
||||
* @param nursingSearchParam 查询参数
|
||||
*
|
||||
* @param searchKey 模糊查询
|
||||
* @param pageNo 当前页码
|
||||
* @param pageSize 查询条数
|
||||
|
||||
@@ -63,4 +63,20 @@ public interface IRequestFormManageAppService {
|
||||
* @return 申请单
|
||||
*/
|
||||
IPage<RequestFormPageDto> getRequestFormPage(RequestFormDto requestFormDto);
|
||||
|
||||
/**
|
||||
* 删除申请单(仅待签发状态可删除)
|
||||
*
|
||||
* @param requestFormId 申请单ID
|
||||
* @return 结果
|
||||
*/
|
||||
R<?> deleteRequestForm(Long requestFormId);
|
||||
|
||||
/**
|
||||
* 撤回申请单(已签发状态撤回至待签发)
|
||||
*
|
||||
* @param requestFormId 申请单ID
|
||||
* @return 结果
|
||||
*/
|
||||
R<?> withdrawRequestForm(Long requestFormId);
|
||||
}
|
||||
|
||||
@@ -761,6 +761,8 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
||||
if (is_sign) {
|
||||
deviceRequest.setReqAuthoredTime(authoredTime); // 医嘱签发时间
|
||||
}
|
||||
// 保存或签发时都需要设置耗材定义ID(防止 sign 分支 deviceDefId 为空触发 NOT NULL 约束)
|
||||
deviceRequest.setDeviceDefId(regAdviceSaveDto.getAdviceDefinitionId());
|
||||
// 保存时处理的字段属性
|
||||
if (is_save) {
|
||||
deviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), 4));
|
||||
@@ -798,6 +800,8 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
||||
if (is_sign) {
|
||||
deviceRequest.setReqAuthoredTime(authoredTime); // 医嘱签发时间
|
||||
}
|
||||
// 保存或签发时都需要设置耗材定义ID(防止 sign 分支 deviceDefId 为空触发 NOT NULL 约束)
|
||||
deviceRequest.setDeviceDefId(regAdviceSaveDto.getAdviceDefinitionId());
|
||||
// 保存时处理的字段属性
|
||||
if (is_save) {
|
||||
deviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), 4));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.openhis.web.regdoctorstation.appservice.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.core.common.core.domain.R;
|
||||
@@ -87,8 +88,11 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
||||
throw new ServiceException("请先配置当前时间段的执行科室");
|
||||
}
|
||||
|
||||
// 逐个校验activityList中的项目是否都配置了执行科室,避免部分通过后在循环中抛异常导致事务复杂化
|
||||
// 逐个校验activityList中的项目是否都配置了执行科室,并收集positionId供后续使用
|
||||
// 必须在任何数据库操作之前完成全部校验,避免部分保存后异常导致脏数据
|
||||
List<ActivitySaveDto> activityList = requestFormSaveDto.getActivityList();
|
||||
// 缓存校验结果,避免主循环中重复查询和可能出现的数据不一致
|
||||
java.util.Map<Long, Long> activityIdToPositionIdMap = new java.util.HashMap<>();
|
||||
if (activityList != null && !activityList.isEmpty()) {
|
||||
for (ActivitySaveDto activitySaveDto : activityList) {
|
||||
Long positionId = activityOrganizationConfig.stream()
|
||||
@@ -97,6 +101,7 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
||||
if (positionId == null) {
|
||||
throw new ServiceException(activitySaveDto.getAdviceDefinitionName() + "未配置当前时间段的执行科室");
|
||||
}
|
||||
activityIdToPositionIdMap.put(activitySaveDto.getAdviceDefinitionId(), positionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,9 +184,7 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
||||
serviceRequest.setEncounterId(encounterId); // 就诊id
|
||||
serviceRequest.setAuthoredTime(curDate); // 请求签发时间
|
||||
|
||||
Long positionId = activityOrganizationConfig.stream()
|
||||
.filter(dto -> activitySaveDto.getAdviceDefinitionId().equals(dto.getActivityDefinitionId()))
|
||||
.map(ActivityOrganizationConfigDto::getOrganizationId).findFirst().orElse(null);
|
||||
Long positionId = activityIdToPositionIdMap.get(activitySaveDto.getAdviceDefinitionId());
|
||||
if (positionId == null) {
|
||||
throw new ServiceException(activitySaveDto.getAdviceDefinitionName() + "未配置当前时间段的执行科室");
|
||||
}
|
||||
@@ -292,7 +295,7 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
||||
surgeryServiceRequest.setTherapyEnum(TherapyTimeType.TEMPORARY.getValue());
|
||||
surgeryServiceRequest.setQuantity(BigDecimal.valueOf(1));
|
||||
surgeryServiceRequest.setUnitCode("次");
|
||||
surgeryServiceRequest.setCategoryEnum(4); // 4-手术
|
||||
surgeryServiceRequest.setCategoryEnum(24); // 24-手术(新值域,避开 adviceType 碰撞)
|
||||
// 优先从 activityList 获取手术 ID
|
||||
if (activityList != null && !activityList.isEmpty()) {
|
||||
Long activityId = activityList.get(0).getAdviceDefinitionId();
|
||||
@@ -488,4 +491,90 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
||||
return requestFormManageAppMapper.getRequestFormPage(requestFormDto, page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<?> deleteRequestForm(Long requestFormId) {
|
||||
if (requestFormId == null) {
|
||||
return R.fail("申请单ID不能为空");
|
||||
}
|
||||
RequestForm requestForm = iRequestFormService.getById(requestFormId);
|
||||
if (requestForm == null) {
|
||||
return R.fail("申请单不存在");
|
||||
}
|
||||
String prescriptionNo = requestForm.getPrescriptionNo();
|
||||
|
||||
// 查询该申请单下所有 ServiceRequest(含子项)
|
||||
List<ServiceRequest> serviceRequests = iServiceRequestService.list(
|
||||
new LambdaQueryWrapper<ServiceRequest>()
|
||||
.eq(ServiceRequest::getPrescriptionNo, prescriptionNo));
|
||||
if (serviceRequests == null || serviceRequests.isEmpty()) {
|
||||
return R.fail("未找到关联的诊疗医嘱");
|
||||
}
|
||||
|
||||
// 校验:只有待签发(status=0)的申请单可删除
|
||||
boolean allDraft = serviceRequests.stream()
|
||||
.allMatch(sr -> RequestStatus.DRAFT.getValue().equals(sr.getStatusEnum()));
|
||||
if (!allDraft) {
|
||||
return R.fail("只有待签发状态的申请单可删除");
|
||||
}
|
||||
|
||||
List<Long> serviceRequestIds = serviceRequests.stream()
|
||||
.map(ServiceRequest::getId).collect(Collectors.toList());
|
||||
|
||||
// 1. 删除关联的费用项
|
||||
for (Long srId : serviceRequestIds) {
|
||||
iChargeItemService.deleteByServiceTableAndId(
|
||||
CommonConstants.TableName.WOR_SERVICE_REQUEST, srId);
|
||||
}
|
||||
// 2. 删除子项 ServiceRequest(parentId 非空)
|
||||
iServiceRequestService.remove(
|
||||
new LambdaQueryWrapper<ServiceRequest>()
|
||||
.in(ServiceRequest::getId, serviceRequestIds)
|
||||
.isNotNull(ServiceRequest::getParentId));
|
||||
// 3. 删除主项 ServiceRequest
|
||||
iServiceRequestService.removeByIds(serviceRequestIds);
|
||||
// 4. 删除申请单
|
||||
iRequestFormService.removeById(requestFormId);
|
||||
|
||||
log.info("检查申请单删除成功,requestFormId={}, prescriptionNo={}", requestFormId, prescriptionNo);
|
||||
return R.ok("删除成功");
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<?> withdrawRequestForm(Long requestFormId) {
|
||||
if (requestFormId == null) {
|
||||
return R.fail("申请单ID不能为空");
|
||||
}
|
||||
RequestForm requestForm = iRequestFormService.getById(requestFormId);
|
||||
if (requestForm == null) {
|
||||
return R.fail("申请单不存在");
|
||||
}
|
||||
String prescriptionNo = requestForm.getPrescriptionNo();
|
||||
|
||||
// 查询该申请单下所有 ServiceRequest
|
||||
List<ServiceRequest> serviceRequests = iServiceRequestService.list(
|
||||
new LambdaQueryWrapper<ServiceRequest>()
|
||||
.eq(ServiceRequest::getPrescriptionNo, prescriptionNo));
|
||||
if (serviceRequests == null || serviceRequests.isEmpty()) {
|
||||
return R.fail("未找到关联的诊疗医嘱");
|
||||
}
|
||||
|
||||
// 校验:只有已签发(status=2)的申请单可撤回
|
||||
boolean allActive = serviceRequests.stream()
|
||||
.allMatch(sr -> RequestStatus.ACTIVE.getValue().equals(sr.getStatusEnum()));
|
||||
if (!allActive) {
|
||||
return R.fail("只有已签发状态的申请单可撤回");
|
||||
}
|
||||
|
||||
// 将所有 ServiceRequest 状态改回待签发(DRAFT=0)
|
||||
List<Long> serviceRequestIds = serviceRequests.stream()
|
||||
.map(ServiceRequest::getId).collect(Collectors.toList());
|
||||
iServiceRequestService.update(
|
||||
new ServiceRequest().setStatusEnum(RequestStatus.DRAFT.getValue()),
|
||||
new LambdaUpdateWrapper<ServiceRequest>()
|
||||
.in(ServiceRequest::getId, serviceRequestIds));
|
||||
|
||||
log.info("检查申请单撤回成功,requestFormId={}, prescriptionNo={}", requestFormId, prescriptionNo);
|
||||
return R.ok("撤回成功");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 申请单管理 controller
|
||||
@@ -185,4 +186,26 @@ public class RequestFormManageController {
|
||||
public R<IPage<RequestFormPageDto>> getRequestFormPage(@RequestBody RequestFormDto requestFormDto) {
|
||||
return R.ok(iRequestFormManageAppService.getRequestFormPage(requestFormDto));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除申请单(仅待签发状态可删除)
|
||||
*
|
||||
* @param data 包含 requestFormId 的请求体
|
||||
* @return 结果
|
||||
*/
|
||||
@PostMapping(value = "/delete")
|
||||
public R<?> deleteRequestForm(@RequestBody Map<String, Long> data) {
|
||||
return iRequestFormManageAppService.deleteRequestForm(data.get("requestFormId"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回申请单(已签发状态撤回至待签发)
|
||||
*
|
||||
* @param data 包含 requestFormId 的请求体
|
||||
* @return 结果
|
||||
*/
|
||||
@PostMapping(value = "/withdraw")
|
||||
public R<?> withdrawRequestForm(@RequestBody Map<String, Long> data) {
|
||||
return iRequestFormManageAppService.withdrawRequestForm(data.get("requestFormId"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,10 @@
|
||||
CASE
|
||||
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.surgery_name
|
||||
WHEN T1.context_enum = 6 AND T1.product_table = 'cli_surgery' THEN T9.surgery_name
|
||||
WHEN T1.context_enum = 6 AND T1.service_table = 'wor_service_request' THEN COALESCE(
|
||||
wsr.content_json::json->>'surgeryName',
|
||||
wsr.content_json::json->>'adviceName',
|
||||
T9sr.surgery_name)
|
||||
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
|
||||
WHEN T1.context_enum = 6 THEN T2."name"
|
||||
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
|
||||
@@ -108,6 +112,7 @@
|
||||
CASE
|
||||
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN NULL
|
||||
WHEN T1.context_enum = 6 AND T1.product_table = 'cli_surgery' THEN NULL
|
||||
WHEN T1.context_enum = 6 AND T1.service_table = 'wor_service_request' THEN NULL
|
||||
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN NULL
|
||||
WHEN T1.context_enum = 6 THEN T2.yb_no
|
||||
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN NULL
|
||||
@@ -118,6 +123,7 @@
|
||||
CASE
|
||||
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.id
|
||||
WHEN T1.context_enum = 6 AND T1.product_table = 'cli_surgery' THEN T9.id
|
||||
WHEN T1.context_enum = 6 AND T1.service_table = 'wor_service_request' THEN COALESCE(T9sr.id, wsr.activity_id)
|
||||
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN 0
|
||||
WHEN T1.context_enum = 6 THEN T2.id
|
||||
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN 0
|
||||
@@ -159,6 +165,11 @@
|
||||
LEFT JOIN med_medication_request AS mmr ON mmr.id = T1.service_id AND mmr.delete_flag = '0'
|
||||
LEFT JOIN wor_device_request AS wdr ON wdr.id = T1.service_id AND wdr.delete_flag = '0'
|
||||
LEFT JOIN wor_service_request AS wsr ON wsr.id = T1.service_id AND wsr.delete_flag = '0'
|
||||
LEFT JOIN cli_surgery AS T9sr ON T1.context_enum = 6
|
||||
AND T1.service_table = 'wor_service_request'
|
||||
AND wsr.activity_id IS NOT NULL
|
||||
AND wsr.activity_id = T9sr.id
|
||||
AND T9sr.delete_flag = '0'
|
||||
LEFT JOIN wor_service_request AS wsrp ON wsrp.id = wsr.parent_id AND wsrp.delete_flag = '0'
|
||||
WHERE T1.encounter_id = #{encounterId}
|
||||
AND T1.status_enum IN (0
|
||||
@@ -223,6 +234,10 @@
|
||||
CASE
|
||||
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.surgery_name
|
||||
WHEN T1.context_enum = 6 AND T1.product_table = 'cli_surgery' THEN T9.surgery_name
|
||||
WHEN T1.context_enum = 6 AND T1.service_table = 'wor_service_request' THEN COALESCE(
|
||||
wsr.content_json::json->>'surgeryName',
|
||||
wsr.content_json::json->>'adviceName',
|
||||
T9sr.surgery_name)
|
||||
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
|
||||
WHEN T1.context_enum = 6 THEN T2."name"
|
||||
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
|
||||
@@ -234,6 +249,7 @@
|
||||
CASE
|
||||
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN NULL
|
||||
WHEN T1.context_enum = 6 AND T1.product_table = 'cli_surgery' THEN NULL
|
||||
WHEN T1.context_enum = 6 AND T1.service_table = 'wor_service_request' THEN NULL
|
||||
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN NULL
|
||||
WHEN T1.context_enum = 6 THEN T2.yb_no
|
||||
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN NULL
|
||||
@@ -244,6 +260,7 @@
|
||||
CASE
|
||||
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.id
|
||||
WHEN T1.context_enum = 6 AND T1.product_table = 'cli_surgery' THEN T9.id
|
||||
WHEN T1.context_enum = 6 AND T1.service_table = 'wor_service_request' THEN COALESCE(T9sr.id, wsr.activity_id)
|
||||
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN 0
|
||||
WHEN T1.context_enum = 6 THEN T2.id
|
||||
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN 0
|
||||
@@ -286,6 +303,11 @@
|
||||
LEFT JOIN med_medication_request AS mmr ON mmr.id = T1.service_id AND mmr.delete_flag = '0'
|
||||
LEFT JOIN wor_device_request AS wdr ON wdr.id = T1.service_id AND wdr.delete_flag = '0'
|
||||
LEFT JOIN wor_service_request AS wsr ON wsr.id = T1.service_id AND wsr.delete_flag = '0'
|
||||
LEFT JOIN cli_surgery AS T9sr ON T1.context_enum = 6
|
||||
AND T1.service_table = 'wor_service_request'
|
||||
AND wsr.activity_id IS NOT NULL
|
||||
AND wsr.activity_id = T9sr.id
|
||||
AND T9sr.delete_flag = '0'
|
||||
WHERE T1.encounter_id = #{encounterId}
|
||||
AND T1.status_enum IN (0
|
||||
, #{planned}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
</if>
|
||||
AND os.delete_flag = '0'
|
||||
</where>
|
||||
ORDER BY os.create_time DESC
|
||||
ORDER BY os.create_time DESC, os.schedule_id DESC
|
||||
</select>
|
||||
<!-- 根据ID查询手术安排详情-->
|
||||
<select id="getSurgeryScheduleDetail" resultType="com.openhis.web.clinicalmanage.dto.OpScheduleDto">
|
||||
@@ -89,6 +89,8 @@
|
||||
cs.apply_doctor_name AS apply_doctor_name,
|
||||
drf.create_time AS apply_time,
|
||||
os.surgery_nature AS surgeryType,
|
||||
cs.incision_level AS incisionLevel,
|
||||
fc.contract_name AS feeType,
|
||||
os.fee_type AS feeType,
|
||||
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo
|
||||
FROM op_schedule os
|
||||
@@ -183,7 +185,7 @@
|
||||
<if test="dto.applyDeptId != null and dto.applyDeptId != ''"> AND cs.apply_dept_id = #{dto.applyDeptId}</if>
|
||||
<if test="dto.patientName != null and dto.patientName != ''"> AND ap.name LIKE CONCAT('%', #{dto.patientName}, '%')</if>
|
||||
</where>
|
||||
ORDER BY os.create_time DESC
|
||||
ORDER BY os.create_time DESC, os.schedule_id DESC
|
||||
</select>
|
||||
<!-- 查询时间段内该手术室是否被占用-->
|
||||
<select id="isScheduleConflict" resultType="java.lang.Boolean">
|
||||
|
||||
@@ -539,7 +539,8 @@
|
||||
AND T1.refund_medicine_id IS NULL
|
||||
ORDER BY T1.status_enum,T1.sort_number)
|
||||
UNION ALL
|
||||
-- 🔧 新增:查询门诊术中计费生成的耗材数据(这些数据存在于 adm_charge_item 和 wor_device_request)
|
||||
-- 🔧 查询仅存在于 adm_charge_item 的"孤儿"耗材数据(DeviceRequest 缺失或 generate_source_enum 未设置)
|
||||
-- 正常 DeviceRequest(generate_source_enum 已赋值)由下方 Part 3 统一负责,此处不做重复覆盖避免 UNION ALL 重复行
|
||||
(SELECT 2 AS advice_type,
|
||||
CI.service_id AS request_id,
|
||||
CI.service_id || '-ci-dev' AS unique_key,
|
||||
@@ -584,7 +585,7 @@
|
||||
WHERE CI.delete_flag = '0'
|
||||
AND CI.service_table = 'wor_device_request'
|
||||
<if test="generateSourceEnum != null">
|
||||
AND (DR.generate_source_enum IS NULL OR DR.generate_source_enum = #{generateSourceEnum})
|
||||
AND DR.generate_source_enum IS NULL <!-- 仅匹配孤儿记录,normal DeviceRequest 由 Part 3 负责,避免 UNION ALL 重复 -->
|
||||
</if>
|
||||
<if test="historyFlag == '0'.toString()">
|
||||
AND CI.encounter_id = #{encounterId}
|
||||
|
||||
@@ -264,7 +264,6 @@
|
||||
WHERE ael.status_enum = #{active}
|
||||
AND ael.delete_flag = '0'
|
||||
AND ael.form_enum = #{bed}
|
||||
LIMIT 1
|
||||
) AS bed ON bed.encounter_id = ae.id
|
||||
LEFT JOIN (
|
||||
SELECT ael.encounter_id,
|
||||
@@ -275,7 +274,6 @@
|
||||
WHERE ael.status_enum = #{active}
|
||||
AND ael.delete_flag = '0'
|
||||
AND ael.form_enum = #{house}
|
||||
LIMIT 1
|
||||
) AS house ON house.encounter_id = ae.id
|
||||
LEFT JOIN (
|
||||
SELECT ael.encounter_id,
|
||||
@@ -286,7 +284,6 @@
|
||||
WHERE ael.status_enum = #{active}
|
||||
AND ael.delete_flag = '0'
|
||||
AND ael.form_enum = #{ward}
|
||||
LIMIT 1
|
||||
) AS ward ON ward.encounter_id = ae.id
|
||||
LEFT JOIN (
|
||||
SELECT aep.encounter_id,
|
||||
|
||||
@@ -288,7 +288,7 @@
|
||||
AND T1.refund_device_id IS NULL
|
||||
ORDER BY T1.status_enum)
|
||||
UNION ALL
|
||||
(SELECT CASE WHEN T1.category_enum = 4 THEN 6 ELSE 3 END AS advice_type,
|
||||
(SELECT CASE WHEN T1.category_enum IN (4, 24) THEN 6 ELSE 3 END AS advice_type,
|
||||
T1.id AS request_id,
|
||||
T1.id || '-3' AS unique_key,
|
||||
T1.requester_id AS requester_id,
|
||||
|
||||
@@ -13,7 +13,17 @@
|
||||
drf.requester_id,
|
||||
drf.create_time,
|
||||
ap.NAME AS patient_name,
|
||||
drf.status
|
||||
CASE MIN(wsr.status_enum)
|
||||
WHEN 1 THEN 0
|
||||
WHEN 2 THEN 1
|
||||
WHEN 3 THEN 4
|
||||
WHEN 4 THEN 4
|
||||
WHEN 5 THEN 5
|
||||
WHEN 6 THEN 5
|
||||
WHEN 7 THEN 5
|
||||
WHEN 8 THEN 6
|
||||
ELSE NULL
|
||||
END AS status
|
||||
FROM doc_request_form AS drf
|
||||
LEFT JOIN adm_encounter AS ae ON ae.ID = drf.encounter_id
|
||||
AND ae.delete_flag = '0'
|
||||
@@ -31,7 +41,17 @@
|
||||
AND drf.create_time <= (#{endDate}::date + INTERVAL '1 day' - INTERVAL '1 second')
|
||||
</if>
|
||||
<if test="status != null and status != ''">
|
||||
AND drf.status = #{status}::integer
|
||||
AND CASE MIN(wsr.status_enum)
|
||||
WHEN 1 THEN 0
|
||||
WHEN 2 THEN 1
|
||||
WHEN 3 THEN 4
|
||||
WHEN 4 THEN 4
|
||||
WHEN 5 THEN 5
|
||||
WHEN 6 THEN 5
|
||||
WHEN 7 THEN 5
|
||||
WHEN 8 THEN 6
|
||||
ELSE NULL
|
||||
END = #{status}::integer
|
||||
</if>
|
||||
<if test="keyword != null and keyword != ''">
|
||||
AND (drf.prescription_no ILIKE '%' || #{keyword} || '%'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
|
||||
// 日期格式化
|
||||
export function parseTime(time, pattern) {
|
||||
if (arguments.length === 0 || !time) {
|
||||
@@ -275,30 +277,13 @@ export function blobValidate(data) {
|
||||
|
||||
// 按照频次天数计算总数量
|
||||
export function calculateQuantityByDays(frequency, days) {
|
||||
// const dict = useDict('rate_code').rate_code.value
|
||||
// const rate = dict.find(item => item.value === frequency).remark
|
||||
// if(rate){
|
||||
// return Math.floor(Number(rate) * days)
|
||||
// } else {
|
||||
// return undefined
|
||||
// }
|
||||
const frequencyMap = {
|
||||
ST: 1,
|
||||
QD: 1, // 每日一次
|
||||
BID: 2, // 每日两次
|
||||
TID: 3, // 每日三次
|
||||
QID: 4, // 每日四次
|
||||
QN: 1, // 每晚一次
|
||||
QOD: 1 / 2, // 每隔一日一次
|
||||
QW: 1 / 7, // 每周一次
|
||||
BIW: 2 / 7, // 每周两次
|
||||
TIW: 3 / 7, // 每周三次
|
||||
QOW: 1 / 14, // 隔周一次
|
||||
};
|
||||
if (!frequencyMap[frequency]) {
|
||||
return;
|
||||
}
|
||||
const quantity = frequencyMap[frequency] * days;
|
||||
const dicts = useDictStore().getDict('rate_code');
|
||||
if (!dicts) return;
|
||||
const dict = dicts.find(item => item.value === frequency);
|
||||
if (!dict?.remark) return;
|
||||
const rate = Number(dict.remark);
|
||||
if (isNaN(rate) || !rate) return;
|
||||
const quantity = rate * days;
|
||||
return quantity < 1 ? 1 : Math.ceil(quantity);
|
||||
}
|
||||
|
||||
|
||||
@@ -178,22 +178,25 @@ service.interceptors.request.use(config => {
|
||||
}
|
||||
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
|
||||
} else if (code === 500) {
|
||||
// 检查是否需要跳过错误提示
|
||||
if (!res.config?.skipErrorMsg) {
|
||||
ElMessage({ message: msg, type: 'error' })
|
||||
// 检查是否需要跳过错误提示(静默请求:返回响应让.then()处理)
|
||||
if (res.config?.skipErrorMsg) {
|
||||
return Promise.resolve(res.data)
|
||||
}
|
||||
ElMessage({ message: msg, type: 'error' })
|
||||
return Promise.reject(new Error(msg))
|
||||
} else if (code === 601) {
|
||||
// 检查是否需要跳过错误提示
|
||||
if (!res.config?.skipErrorMsg) {
|
||||
ElMessage({ message: msg, type: 'warning' })
|
||||
// 检查是否需要跳过错误提示(静默请求:返回响应让.then()处理)
|
||||
if (res.config?.skipErrorMsg) {
|
||||
return Promise.resolve(res.data)
|
||||
}
|
||||
ElMessage({ message: msg, type: 'warning' })
|
||||
return Promise.reject(new Error(msg))
|
||||
} else if (code !== 200) {
|
||||
// 检查是否需要跳过错误提示
|
||||
if (!res.config?.skipErrorMsg) {
|
||||
ElNotification.error({ title: msg })
|
||||
// 检查是否需要跳过错误提示(静默请求:返回响应让.then()处理)
|
||||
if (res.config?.skipErrorMsg) {
|
||||
return Promise.resolve(res.data)
|
||||
}
|
||||
ElNotification.error({ title: msg })
|
||||
return Promise.reject('error')
|
||||
} else {
|
||||
return Promise.resolve(res.data)
|
||||
|
||||
@@ -473,15 +473,12 @@ function calculateTotalPrice() {
|
||||
}
|
||||
});
|
||||
totalPrice.value = sum.toFixed(2);
|
||||
// Bug #464: 零售价与诊疗子项合计总价实时同步
|
||||
// Bug #464: 零售价与诊疗子项合计总价实时同步,直接赋值不使用nextTick避免多调用方竞争
|
||||
const hasValidItem = treatmentItems.value.some(
|
||||
(item) => item.adviceDefinitionId && item.adviceDefinitionId !== ''
|
||||
);
|
||||
if (hasValidItem) {
|
||||
// 使用 nextTick 确保总价更新后零售价才更新,避免 Vue 响应式时序问题
|
||||
nextTick(() => {
|
||||
form.value.retailPrice = parseFloat(totalPrice.value) || 0;
|
||||
});
|
||||
form.value.retailPrice = parseFloat(totalPrice.value) || 0;
|
||||
} else {
|
||||
form.value.retailPrice = undefined;
|
||||
}
|
||||
@@ -763,10 +760,7 @@ function selectRow(row, index) {
|
||||
treatmentItems.value[index].adviceDefinitionId = row.id;
|
||||
treatmentItems.value[index].retailPrice = row.retailPrice || 0;
|
||||
medicineSearchKey.value = '';
|
||||
// 使用 nextTick 确保 DOM 更新后再计算总价
|
||||
nextTick(() => {
|
||||
calculateTotalPrice();
|
||||
});
|
||||
calculateTotalPrice();
|
||||
}
|
||||
|
||||
// 清空诊疗子项
|
||||
|
||||
@@ -461,7 +461,7 @@ watch(
|
||||
console.log(prescriptionList.value,"prescriptionList.value")
|
||||
if(newValue&&newValue.length>0){
|
||||
let saveList = prescriptionList.value.filter((item) => {
|
||||
return item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
|
||||
return item.check && item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
|
||||
})
|
||||
console.log(saveList,"prescriptionList.value")
|
||||
if (saveList.length == 0) {
|
||||
@@ -1015,7 +1015,7 @@ function handleSave() {
|
||||
return;
|
||||
}
|
||||
let saveList = prescriptionList.value.filter((item) => {
|
||||
return item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
|
||||
return item.check && item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
|
||||
});
|
||||
// let saveList = prescriptionList.value
|
||||
// .filter((item) => {
|
||||
@@ -1080,42 +1080,44 @@ function handleSaveSign(row, index) {
|
||||
proxy.$modal.msgWarning('诊疗项目必须选择执行科室');
|
||||
return;
|
||||
}
|
||||
isSaving.value = true; // #437 立即加锁,消除 TOCTOU 竞态
|
||||
proxy.$refs['formRef' + index].validate((valid) => {
|
||||
if (valid) {
|
||||
isSaving.value = true; // #437 加锁
|
||||
row.isEdit = false;
|
||||
isAdding.value = false;
|
||||
expandOrder.value = [];
|
||||
row.patientId = props.patientInfo.patientId;
|
||||
row.encounterId = props.patientInfo.encounterId;
|
||||
row.accountId = props.patientInfo.accountId;
|
||||
const cleanRow = JSON.parse(JSON.stringify(row));
|
||||
cleanRow.contentJson = JSON.stringify(cleanRow);
|
||||
cleanRow.dbOpType = cleanRow.requestId ? '2' : '1';
|
||||
cleanRow.minUnitQuantity = cleanRow.quantity * cleanRow.partPercent;
|
||||
cleanRow.categoryEnum = cleanRow.adviceType
|
||||
// 如果是手术计费,设置生成来源和来源业务单据号
|
||||
if (props.patientInfo.sourceBillNo) {
|
||||
cleanRow.generateSourceEnum = 6; // 手术计费
|
||||
cleanRow.sourceBillNo = props.patientInfo.sourceBillNo;
|
||||
}
|
||||
console.log('cleanRow', cleanRow)
|
||||
savePrescription({ adviceSaveList: [cleanRow] }).then((res) => {
|
||||
if (res.code === 200) {
|
||||
proxy.$modal.msgSuccess('保存成功');
|
||||
getListInfo(false);
|
||||
nextId.value = 1;
|
||||
// 🔧 Bug Fix #238: 如果诊疗项目缺少执行科室,标记为需要修复的脏数据
|
||||
if (row.adviceType === 3 && !row.orgId) {
|
||||
console.warn('Bug #238: 检测到诊疗项目保存时缺少执行科室,请手动编辑修正:', cleanRow);
|
||||
proxy.$modal.msgWarning('诊疗项目执行科室信息不完整,请编辑后重新保存');
|
||||
}
|
||||
}
|
||||
}).finally(() => {
|
||||
isSaving.value = false; // #437 释放锁
|
||||
});
|
||||
if (!valid) {
|
||||
isSaving.value = false; // 验证失败释放锁
|
||||
return;
|
||||
}
|
||||
});
|
||||
row.isEdit = false;
|
||||
isAdding.value = false;
|
||||
expandOrder.value = [];
|
||||
row.patientId = props.patientInfo.patientId;
|
||||
row.encounterId = props.patientInfo.encounterId;
|
||||
row.accountId = props.patientInfo.accountId;
|
||||
const cleanRow = JSON.parse(JSON.stringify(row));
|
||||
cleanRow.contentJson = JSON.stringify(cleanRow);
|
||||
cleanRow.dbOpType = cleanRow.requestId ? '2' : '1';
|
||||
cleanRow.minUnitQuantity = cleanRow.quantity * cleanRow.partPercent;
|
||||
cleanRow.categoryEnum = cleanRow.adviceType
|
||||
// 如果是手术计费,设置生成来源和来源业务单据号
|
||||
if (props.patientInfo.sourceBillNo) {
|
||||
cleanRow.generateSourceEnum = 6; // 手术计费
|
||||
cleanRow.sourceBillNo = props.patientInfo.sourceBillNo;
|
||||
}
|
||||
console.log('cleanRow', cleanRow)
|
||||
savePrescription({ adviceSaveList: [cleanRow] }, '1').then((res) => {
|
||||
if (res.code === 200) {
|
||||
proxy.$modal.msgSuccess('保存成功');
|
||||
getListInfo(false);
|
||||
nextId.value = 1;
|
||||
// 🔧 Bug Fix #238: 如果诊疗项目缺少执行科室,标记为需要修复的脏数据
|
||||
if (row.adviceType === 3 && !row.orgId) {
|
||||
console.warn('Bug #238: 检测到诊疗项目保存时缺少执行科室,请手动编辑修正:', cleanRow);
|
||||
proxy.$modal.msgWarning('诊疗项目执行科室信息不完整,请编辑后重新保存');
|
||||
}
|
||||
}
|
||||
}).finally(() => {
|
||||
isSaving.value = false; // #437 释放锁
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// 签退
|
||||
|
||||
@@ -49,6 +49,40 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 性别、出生日期、或实足年龄 -->
|
||||
<el-row :gutter="16" class="form-row">
|
||||
<el-col :span="7" class="form-item">
|
||||
<span class="form-label required">性别</span>
|
||||
<el-radio-group v-model="form.sex" class="gender-radio-group">
|
||||
<el-radio value="男">男</el-radio>
|
||||
<el-radio value="女">女</el-radio>
|
||||
<el-radio value="未知">未知</el-radio>
|
||||
</el-radio-group>
|
||||
</el-col>
|
||||
<el-col :span="10" class="form-item">
|
||||
<span class="form-label required">出生日期</span>
|
||||
<div class="birth-input-group">
|
||||
<el-input v-model="form.birthYear" class="birth-input year" placeholder="年" maxlength="4" />
|
||||
<span class="birth-separator">年</span>
|
||||
<el-input v-model="form.birthMonth" class="birth-input month" placeholder="月" maxlength="2" />
|
||||
<span class="birth-separator">月</span>
|
||||
<el-input v-model="form.birthDay" class="birth-input day" placeholder="日" maxlength="2" />
|
||||
<span class="birth-separator">日</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="7" class="form-item">
|
||||
<span class="form-label">或 实足年龄</span>
|
||||
<div class="age-input-group">
|
||||
<el-input v-model="form.age" class="age-input" placeholder="年龄" />
|
||||
<el-select v-model="form.ageUnit" class="age-unit-select">
|
||||
<el-option label="岁" value="岁" />
|
||||
<el-option label="月" value="月" />
|
||||
<el-option label="天" value="天" />
|
||||
</el-select>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 联系电话、紧急联系人电话 -->
|
||||
<el-row :gutter="16" class="form-row">
|
||||
<el-col :span="12" class="form-item">
|
||||
@@ -1992,4 +2026,53 @@ defineExpose({ show, showReport, close: handleClose });
|
||||
display: flex !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
/* 性别单选按钮组 */
|
||||
.gender-radio-group {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
/* 出生日期输入组 */
|
||||
.birth-input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.birth-input {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.birth-input.year {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.birth-input.month,
|
||||
.birth-input.day {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.birth-separator {
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
/* 年龄输入组 */
|
||||
.age-input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.age-input {
|
||||
width: 70px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.age-unit-select {
|
||||
width: 65px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -278,16 +278,33 @@
|
||||
<el-input v-model="scope.row.applyPart" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="检查方法" min-width="120">
|
||||
<el-table-column label="检查方法" min-width="160">
|
||||
<template #default="scope">
|
||||
<!-- Bug #384修复: 显示检查方法名称,不显示套餐名称 -->
|
||||
<span v-if="scope.row.selectedMethod">
|
||||
{{ scope.row.selectedMethod.name }}
|
||||
</span>
|
||||
<span v-else-if="scope.row.methods && scope.row.methods.length > 0" style="color: #909399;">
|
||||
未选择
|
||||
</span>
|
||||
<span v-else style="color: #c0c4cc;">-</span>
|
||||
<el-select
|
||||
v-if="scope.row.methods && scope.row.methods.length > 1"
|
||||
:model-value="scope.row.selectedMethod"
|
||||
value-key="id"
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
placeholder="选择方法"
|
||||
@update:model-value="(val) => onDetailMethodChange(scope.row, val)"
|
||||
>
|
||||
<el-option
|
||||
v-for="meth in scope.row.methods"
|
||||
:key="meth.id"
|
||||
:label="`${meth.name}${meth.packagePrice != null ? ' ¥' + formatDetailAmount(meth.packagePrice) : ''}`"
|
||||
:value="meth"
|
||||
/>
|
||||
</el-select>
|
||||
<template v-else>
|
||||
<span v-if="scope.row.selectedMethod">
|
||||
{{ scope.row.selectedMethod.name }}
|
||||
</span>
|
||||
<span v-else-if="scope.row.methods && scope.row.methods.length > 0" style="color: #909399;">
|
||||
未选择
|
||||
</span>
|
||||
<span v-else style="color: #c0c4cc;">-</span>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" prop="unit" width="55" align="center" />
|
||||
@@ -412,58 +429,48 @@
|
||||
v-for="(item, idx) in selectedItems"
|
||||
:key="idx"
|
||||
class="selected-item-card"
|
||||
:class="{ 'is-expanded': item.expanded }"
|
||||
>
|
||||
<!-- Bug #384修复 + #426修复: 项目卡片头部,可展开/收起 -->
|
||||
<div class="card-header" @click="toggleItemExpand(item)">
|
||||
<el-tag v-if="item.isPackage || item.packageName" size="small" type="warning" style="margin-right: 4px; flex-shrink: 0;">套餐</el-tag>
|
||||
<span class="card-name">{{ item.name }}</span>
|
||||
<span class="card-price">¥{{ item.price }}</span>
|
||||
<!-- 展开/收起图标 -->
|
||||
<el-icon class="expand-icon" :class="{ expanded: item.expanded }">
|
||||
<ArrowRight />
|
||||
<el-tooltip :content="item.name" placement="top" :show-after="400">
|
||||
<span class="card-name">{{ item.name }}</span>
|
||||
</el-tooltip>
|
||||
<span class="card-price">¥{{ formatDetailAmount(item.price) }}</span>
|
||||
<el-icon :class="['expand-icon', { expanded: item.expanded }]">
|
||||
<ArrowDown v-if="!item.expanded" />
|
||||
<ArrowUp v-if="item.expanded" />
|
||||
</el-icon>
|
||||
<!-- 删除按钮 -->
|
||||
<el-button link type="danger" size="small" @click.stop="handleRemoveItem(idx, item)">
|
||||
<el-icon><Close /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- Bug #428修复 + #426修复: 展开后显示套餐明细或检查方法 -->
|
||||
<div v-show="item.expanded" class="expanded-content">
|
||||
<!-- 显示套餐明细 -->
|
||||
<div v-if="(item.isPackage || item.packageName) && item.packageDetails && item.packageDetails.length > 0" class="package-details-list">
|
||||
<div class="detail-row" v-for="detail in item.packageDetails" :key="detail.id">
|
||||
<span class="detail-name">{{ detail.name }}</span>
|
||||
<span class="detail-info">数量: {{ detail.quantity }} 单价: ¥{{ detail.price }}</span>
|
||||
<!-- Bug #428: 有套餐 ID 时默认展开;加载中/空/明细均在本区域展示 -->
|
||||
<div v-if="item.expanded && shouldShowPackageBody(item)" class="selected-card-body">
|
||||
<div v-if="item.packageDetailsLoading" class="package-details-loading">加载中...</div>
|
||||
<template v-else>
|
||||
<div v-if="getPackageDetailsList(item).length === 0" class="package-details-empty">
|
||||
暂无套餐明细
|
||||
</div>
|
||||
</div>
|
||||
<!-- 套餐明细加载中 -->
|
||||
<div v-else-if="(item.isPackage || item.packageName) && item.packageDetailsLoading" class="package-loading-hint">
|
||||
加载中...
|
||||
</div>
|
||||
<!-- 显示检查方法 -->
|
||||
<div v-else-if="item.methods && item.methods.length > 0" class="method-list">
|
||||
<div v-for="method in item.methods" :key="method.id" class="method-option">
|
||||
<el-checkbox :model-value="item.selectedMethod?.id === method.id" @change="(val) => selectMethodCheckbox(val, item, method)">
|
||||
<span class="method-name">{{ method.name }}</span>
|
||||
<span class="method-price">¥{{ method.packagePrice || item.price }}</span>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<!-- 选中方法后,显示对应的套餐明细 -->
|
||||
<div v-if="item.selectedMethod && item.methodPackageDetails && item.methodPackageDetails.length > 0" class="method-package-details">
|
||||
<div class="method-package-header">
|
||||
<span class="method-package-title">套餐明细 - {{ item.selectedMethod.name }}</span>
|
||||
</div>
|
||||
<div v-for="detail in item.methodPackageDetails" :key="detail.id" class="method-option">
|
||||
<el-checkbox v-model="detail.checked">
|
||||
<span class="method-name">{{ detail.name }}</span>
|
||||
<span class="method-price">数量: {{ detail.quantity }} ¥{{ detail.price }}</span>
|
||||
</el-checkbox>
|
||||
<div v-else class="package-details-list">
|
||||
<div class="package-details-head">套餐明细</div>
|
||||
<div
|
||||
v-for="(detail, dIdx) in getPackageDetailsList(item)"
|
||||
:key="detail.id ?? detail.itemCode ?? `d-${dIdx}`"
|
||||
class="detail-row"
|
||||
>
|
||||
<el-tooltip :content="detail.name" placement="top" :show-after="500">
|
||||
<span class="detail-name">{{ detail.name }}</span>
|
||||
</el-tooltip>
|
||||
<div class="detail-meta">
|
||||
<span class="detail-qty">×{{ detail.quantity || 1 }}</span>
|
||||
<span class="detail-price">¥{{ formatDetailAmount(detail.price) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.selectedMethod && item.methodPackageLoading" class="method-package-loading">
|
||||
加载套餐明细中...
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -477,7 +484,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, computed, watch, onMounted, nextTick } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Printer, Delete, ArrowDown, ArrowUp, Close, ArrowRight } from '@element-plus/icons-vue';
|
||||
import { Printer, Delete, ArrowDown, ArrowUp, Close } from '@element-plus/icons-vue';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import request from '@/utils/request';
|
||||
import { listCheckMethod, searchCheckMethod, listCheckPackage } from '@/api/system/checkType';
|
||||
@@ -568,21 +575,22 @@ handleResetSearch();
|
||||
|
||||
// 🔧 BugFix#426: 懒加载套餐明细
|
||||
async function loadPackageDetails(row, treeNode, resolve) {
|
||||
if (!row.isPackage || !row.packageId) {
|
||||
if (!row.packageId) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await request({
|
||||
url: `/system/package/${row.packageId}/details`,
|
||||
url: `/system/check-type/package/${row.packageId}/details`,
|
||||
method: 'get'
|
||||
});
|
||||
if (res.code === 200 && res.data) {
|
||||
const children = res.data.map(item => ({
|
||||
...item,
|
||||
name: item.name || item.itemName,
|
||||
unit: item.unit || '次',
|
||||
price: item.price || item.itemPrice || 0,
|
||||
if (res.code === 200) {
|
||||
const list = parsePackageDetailsPayload(res);
|
||||
const children = list.map((child) => ({
|
||||
...child,
|
||||
name: child.name || child.itemName,
|
||||
unit: child.unit || '次',
|
||||
price: child.price ?? child.unitPrice ?? child.itemPrice ?? 0,
|
||||
quantity: row.quantity || 1,
|
||||
isPackageDetail: true
|
||||
}));
|
||||
@@ -597,16 +605,68 @@ async function loadPackageDetails(row, treeNode, resolve) {
|
||||
}
|
||||
|
||||
// #428修复 + #426修复: 为已选择项目加载套餐明细(通过packageId或packageName查询)
|
||||
/** 套餐明细挂在「部位」或已选的「检查方法」上(方法可带 packageId) */
|
||||
function getPackageCarrier(item) {
|
||||
return item?.selectedMethod?.packageId ? item.selectedMethod : item;
|
||||
}
|
||||
|
||||
function getPackageDetailsList(item) {
|
||||
// 明细挂在行对象上,避免仅写入 methods 内嵌对象时首帧不触发视图更新(体感需点两次才展开)
|
||||
if (Array.isArray(item?.packageDetailsDisplay)) {
|
||||
return item.packageDetailsDisplay;
|
||||
}
|
||||
const carrier = getPackageCarrier(item);
|
||||
return Array.isArray(carrier?.packageDetails) ? carrier.packageDetails : [];
|
||||
}
|
||||
|
||||
/** 有套餐 ID 的已选行才展示右侧套餐区(加载中 / 空 / 明细列表) */
|
||||
function shouldShowPackageBody(item) {
|
||||
return !!getPackageCarrier(item)?.packageId;
|
||||
}
|
||||
|
||||
/** 金额展示:统一两位小数 */
|
||||
function formatDetailAmount(value) {
|
||||
const n = Number(value ?? 0);
|
||||
return Number.isFinite(n) ? n.toFixed(2) : '0.00';
|
||||
}
|
||||
|
||||
/** 默认检查方法:优先与部位 packageId 一致的方法,否则取首个带套餐的方法,否则取第一个 */
|
||||
function pickDefaultMethod(methods, partItem) {
|
||||
if (!methods?.length) return null;
|
||||
if (methods.length === 1) return methods[0];
|
||||
const pid = partItem?.packageId ?? null;
|
||||
if (pid != null && pid !== '') {
|
||||
const matched = methods.find(
|
||||
(x) => x.packageId != null && String(x.packageId) === String(pid)
|
||||
);
|
||||
if (matched) return matched;
|
||||
}
|
||||
const withPkg = methods.find((x) => x.packageId != null);
|
||||
if (withPkg) return withPkg;
|
||||
return methods[0];
|
||||
}
|
||||
|
||||
function parsePackageDetailsPayload(res) {
|
||||
const raw =
|
||||
res?.data?.data ??
|
||||
res?.data?.records ??
|
||||
res?.data ??
|
||||
res?.rows ??
|
||||
res;
|
||||
if (!Array.isArray(raw)) return [];
|
||||
return raw;
|
||||
}
|
||||
|
||||
// #428: 为已选择项目加载套餐明细(后端:CheckTypeController /system/check-type/package/{id}/details)
|
||||
async function loadPackageDetailsForItem(item) {
|
||||
// 只要有 packageName 就认为是套餐,不强制要求 isPackage 或 packageId
|
||||
if (!item.packageName && !item.packageId) {
|
||||
const carrier = getPackageCarrier(item);
|
||||
let packageId = item.packageId || carrier?.packageId;
|
||||
if (!packageId && !item.packageName) {
|
||||
return;
|
||||
}
|
||||
item.packageDetailsLoading = true;
|
||||
try {
|
||||
let packageId = item.packageId;
|
||||
if (!packageId && item.packageName) {
|
||||
// CheckPart 没有 packageId 字段,需要通过 packageName 查询获取
|
||||
const pkgRes = await listCheckPackage({ packageName: item.packageName });
|
||||
let packages = pkgRes?.data || [];
|
||||
if (!Array.isArray(packages)) {
|
||||
@@ -614,31 +674,48 @@ async function loadPackageDetailsForItem(item) {
|
||||
}
|
||||
if (packages.length === 0) {
|
||||
item.packageDetails = [];
|
||||
item.packageDetailsDisplay = [];
|
||||
return;
|
||||
}
|
||||
packageId = packages[0].id;
|
||||
item.packageId = packageId;
|
||||
}
|
||||
if (!packageId) {
|
||||
item.packageDetails = [];
|
||||
item.packageDetailsDisplay = [];
|
||||
return;
|
||||
}
|
||||
const res = await request({
|
||||
url: `/system/package/${packageId}/details`,
|
||||
url: `/system/check-type/package/${packageId}/details`,
|
||||
method: 'get'
|
||||
});
|
||||
const list = parsePackageDetailsPayload(res);
|
||||
const mapped = list.map((detail) => ({
|
||||
...detail,
|
||||
name: detail.name || detail.itemName,
|
||||
unit: detail.unit || '次',
|
||||
price: detail.price ?? detail.unitPrice ?? detail.itemPrice ?? 0,
|
||||
quantity: detail.quantity || 1
|
||||
}));
|
||||
item.packageDetailsDisplay = mapped;
|
||||
carrier.packageDetails = mapped;
|
||||
if (res.code === 200 && res.data) {
|
||||
item.packageDetails = res.data.map(detail => ({
|
||||
...detail,
|
||||
name: detail.name || detail.itemName,
|
||||
unit: detail.unit || '次',
|
||||
price: detail.price || detail.unitPrice || 0,
|
||||
quantity: detail.quantity || 1
|
||||
}));
|
||||
item.packageDetails = Array.isArray(res.data)
|
||||
? res.data.map((detail) => ({
|
||||
...detail,
|
||||
name: detail.name || detail.itemName,
|
||||
unit: detail.unit || '次',
|
||||
price: detail.price || detail.unitPrice || 0,
|
||||
quantity: detail.quantity || 1
|
||||
}))
|
||||
: mapped;
|
||||
} else {
|
||||
item.packageDetails = [];
|
||||
item.packageDetails = mapped;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载套餐明细失败:', err);
|
||||
item.packageDetailsDisplay = [];
|
||||
carrier.packageDetails = [];
|
||||
item.packageDetails = [];
|
||||
} finally {
|
||||
item.packageDetailsLoading = false;
|
||||
@@ -1174,7 +1251,10 @@ function handleRowClick(row) {
|
||||
nationalCode: '', checked: true,
|
||||
methods: [],
|
||||
selectedMethod: null,
|
||||
expanded: false // Bug #384修复: 添加展开状态
|
||||
expanded: false,
|
||||
packageDetailsLoading: false,
|
||||
isPackage: false,
|
||||
packageId: null
|
||||
};
|
||||
// 加载该项目的检查方法
|
||||
if (m.bodyPartCode) {
|
||||
@@ -1205,6 +1285,13 @@ function handleRowClick(row) {
|
||||
item.packageId = item.selectedMethod.packageId;
|
||||
}
|
||||
}
|
||||
if (!item.selectedMethod && item.methods.length) {
|
||||
item.selectedMethod = pickDefaultMethod(item.methods, { packageId: item.packageId });
|
||||
}
|
||||
if (item.selectedMethod?.packageId) {
|
||||
item.packageId = item.selectedMethod.packageId;
|
||||
item.isPackage = true;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载检查方法失败', err);
|
||||
@@ -1214,6 +1301,12 @@ function handleRowClick(row) {
|
||||
return item;
|
||||
}));
|
||||
selectedItems.value = itemsWithMethods;
|
||||
for (const it of selectedItems.value) {
|
||||
if (getPackageCarrier(it)?.packageId) {
|
||||
await loadPackageDetailsForItem(it);
|
||||
}
|
||||
it.expanded = !!getPackageCarrier(it)?.packageId;
|
||||
}
|
||||
syncCategoryChecked();
|
||||
// Bug #384修复: 回充后更新检查方法显示
|
||||
updateMethodDisplay();
|
||||
@@ -1375,7 +1468,7 @@ async function handleItemSelect(checked, item, cat) {
|
||||
}
|
||||
}
|
||||
|
||||
selectedItems.value.push({
|
||||
const newRow = {
|
||||
id: item.id, name: item.name,
|
||||
price: item.price, quantity: 1,
|
||||
serviceFee: item.serviceFee || 0,
|
||||
@@ -1386,11 +1479,27 @@ async function handleItemSelect(checked, item, cat) {
|
||||
checked: true,
|
||||
methods: methods,
|
||||
selectedMethod: null,
|
||||
expanded: false, // Bug #384修复: 新增展开状态,默认不展开
|
||||
isPackage: !!item.packageName, // Bug #428修复: 标记是否为套餐
|
||||
packageName: item.packageName || null, // Bug #426修复: 套餐名称,用于查找packageId
|
||||
packageId: item.packageId || null // Bug #428修复: 套餐ID
|
||||
});
|
||||
expanded: false,
|
||||
isPackage: !!(item.packageId || item.packageName),
|
||||
packageName: item.packageName || null,
|
||||
packageDetailsLoading: false,
|
||||
packageId: item.packageId || null
|
||||
};
|
||||
selectedItems.value.push(newRow);
|
||||
// 必须用数组里的响应式行,不能继续改局部 newRow:push 后列表内是 proxy,改 raw 对象不会触发右侧卡片更新(会一直卡在「加载中」)
|
||||
const row = selectedItems.value[selectedItems.value.length - 1];
|
||||
|
||||
// 右侧不再展示「检查方法」列表:自动选默认方法(保存、计价仍依赖 selectedMethod)
|
||||
if (methods.length >= 1) {
|
||||
row.selectedMethod = pickDefaultMethod(methods, item);
|
||||
}
|
||||
updateMethodDisplay();
|
||||
|
||||
// 有套餐 ID 时默认展开(先显示加载区,明细写入行对象 packageDetailsDisplay)
|
||||
row.expanded = !!getPackageCarrier(row)?.packageId;
|
||||
if (getPackageCarrier(row)?.packageId) {
|
||||
await loadPackageDetailsForItem(row);
|
||||
}
|
||||
|
||||
// 自动回填执行科室:按检查项目类型 → 检查类型管理里配置的执行科室
|
||||
if (selectedItems.value.length === 1 && cat?.performDeptName) {
|
||||
@@ -1414,16 +1523,23 @@ async function handleItemSelect(checked, item, cat) {
|
||||
// Bug #384修复 + #426修复: 展开/收起项目卡片
|
||||
async function toggleItemExpand(item) {
|
||||
item.expanded = !item.expanded;
|
||||
// 如果是展开且该项目是套餐(通过 isPackage 或 packageName 判断),加载套餐明细
|
||||
if (item.expanded && (item.isPackage || item.packageName) && (!item.packageDetails || item.packageDetails.length === 0) && !item.packageDetailsLoading) {
|
||||
await loadPackageDetailsForItem(item);
|
||||
}
|
||||
if (item.expanded && shouldShowPackageBody(item)) {
|
||||
if (getPackageDetailsList(item).length === 0 && !item.packageDetailsLoading) {
|
||||
await loadPackageDetailsForItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bug #384修复: 勾选框选择检查方法(单选逻辑)
|
||||
async function selectMethodCheckbox(checked, item, method) {
|
||||
if (checked) {
|
||||
item.selectedMethod = method;
|
||||
if (item.expanded && method.packageId) {
|
||||
loadPackageDetailsForItem(item);
|
||||
}
|
||||
// 动态加载该方法对应的套餐明细
|
||||
await loadMethodPackageDetails(item, method);
|
||||
} else {
|
||||
@@ -1483,6 +1599,28 @@ async function loadMethodPackageDetails(item, method) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 检查明细表格中切换检查方法 */
|
||||
async function onDetailMethodChange(row, val) {
|
||||
row.selectedMethod = val || null;
|
||||
if (val?.packageId) {
|
||||
row.packageId = val.packageId;
|
||||
row.isPackage = true;
|
||||
}
|
||||
row.packageDetailsDisplay = undefined;
|
||||
const carrier = getPackageCarrier(row);
|
||||
if (carrier) {
|
||||
carrier.packageDetails = undefined;
|
||||
}
|
||||
updateMethodDisplay();
|
||||
row.expanded = !!getPackageCarrier(row)?.packageId;
|
||||
if (getPackageCarrier(row)?.packageId) {
|
||||
await loadPackageDetailsForItem(row);
|
||||
}
|
||||
nextTick(() => {
|
||||
form.totalAmount = totalAmountCalc.value;
|
||||
});
|
||||
}
|
||||
|
||||
// Bug #384修复: 更新检查方法显示字段(联动)
|
||||
function updateMethodDisplay() {
|
||||
// 找到第一个有选中检查方法的项目
|
||||
@@ -1627,7 +1765,7 @@ defineExpose({ getList });
|
||||
|
||||
/* 右:分类面板 */
|
||||
.category-panel {
|
||||
width: 380px;
|
||||
width: 420px;
|
||||
flex-shrink: 0;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
@@ -1763,8 +1901,11 @@ defineExpose({ getList });
|
||||
}
|
||||
|
||||
/* 已选择 tags */
|
||||
/* 已选择:加宽,避免套餐明细挤成一团 */
|
||||
.selected-panel {
|
||||
width: 140px; /* Bug #384修复: 加宽以适应展开内容 */
|
||||
width: 220px;
|
||||
min-width: 200px;
|
||||
max-width: 280px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1772,9 +1913,11 @@ defineExpose({ getList });
|
||||
.selected-tags {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gap: 8px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
.selected-tag {
|
||||
max-width: 100%;
|
||||
@@ -1787,30 +1930,40 @@ defineExpose({ getList });
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Bug #384修复: 已选择项目卡片(可展开) */
|
||||
/* 已选择项目卡片 */
|
||||
.selected-item-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #F5F5F5;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e4e7ed;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.selected-item-card .card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 10px;
|
||||
padding: 10px 10px;
|
||||
cursor: pointer;
|
||||
gap: 4px;
|
||||
gap: 8px;
|
||||
background: linear-gradient(180deg, #f8fafc 0%, #f0f4f8 100%);
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
.selected-item-card .card-header:hover {
|
||||
background: #E6F7FF;
|
||||
background: linear-gradient(180deg, #ecf5ff 0%, #e3eef8 100%);
|
||||
}
|
||||
|
||||
.selected-item-card.is-expanded .card-header {
|
||||
border-bottom-color: #ebeef5;
|
||||
}
|
||||
|
||||
.card-name {
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
min-width: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -1818,14 +1971,17 @@ defineExpose({ getList });
|
||||
}
|
||||
|
||||
.card-price {
|
||||
font-size: 12px;
|
||||
color: #1890FF;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.expand-icon {
|
||||
font-size: 12px;
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
transition: transform 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
transition: transform 0.2s;
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
@@ -1834,19 +1990,6 @@ defineExpose({ getList });
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
/* Bug #426修复: 展开内容容器 */
|
||||
.expanded-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Bug #426修复: 套餐明细加载提示 */
|
||||
.package-loading-hint {
|
||||
padding: 8px 10px;
|
||||
font-size: 11px;
|
||||
color: #c0c4cc;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Bug #428修复: 套餐明细列表样式 */
|
||||
.package-details-list {
|
||||
padding: 6px 10px;
|
||||
@@ -1882,61 +2025,83 @@ defineExpose({ getList });
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Bug #384修复: 检查方法勾选框列表 */
|
||||
.method-list {
|
||||
padding: 6px 10px;
|
||||
background: #fff;
|
||||
border-top: 1px solid #e4e7ed;
|
||||
/* 展开区域 */
|
||||
.selected-card-body {
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.package-details-loading,
|
||||
.package-details-empty {
|
||||
padding: 12px 10px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.package-details-empty {
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.package-details-list {
|
||||
padding: 10px 10px 12px;
|
||||
}
|
||||
|
||||
.package-details-head {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #909399;
|
||||
letter-spacing: 0.02em;
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px dashed #dcdfe6;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 8px 12px;
|
||||
align-items: start;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.detail-row:last-of-type {
|
||||
border-bottom: none;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.detail-name {
|
||||
font-size: 12px;
|
||||
color: #303133;
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.detail-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.method-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.method-option :deep(.el-checkbox__label) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.method-option .method-name {
|
||||
.detail-qty {
|
||||
font-size: 11px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.method-option .method-price {
|
||||
font-size: 11px;
|
||||
color: #e6a23c;
|
||||
font-weight: 500;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* 选中方法后显示的套餐明细 */
|
||||
.method-package-details {
|
||||
margin-top: 4px;
|
||||
padding: 4px 0;
|
||||
border-top: 1px dashed #dcdfe6;
|
||||
}
|
||||
|
||||
.method-package-header {
|
||||
padding: 2px 0 4px 24px;
|
||||
}
|
||||
|
||||
.method-package-title {
|
||||
font-size: 10px;
|
||||
color: #909399;
|
||||
font-weight: 500;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.method-package-loading {
|
||||
padding: 4px 0 4px 24px;
|
||||
font-size: 10px;
|
||||
color: #c0c4cc;
|
||||
.detail-price {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #e6a23c;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* 折叠组件细节 */
|
||||
|
||||
@@ -315,6 +315,7 @@
|
||||
data-prop="dispensePerDuration">
|
||||
<el-input-number v-model="scope.row.dispensePerDuration" style="width: 80px" :min="1"
|
||||
controls-position="right" :controls="false" :ref="(el) => (inputRefs.dispensePerDuration = el)"
|
||||
@input="calculateTotalAmount(scope.row, scope.$index)"
|
||||
@change="calculateTotalAmount(scope.row, scope.$index)"
|
||||
@keyup.enter.prevent="
|
||||
handleEnter('dispensePerDuration', scope.row, scope.$index)
|
||||
@@ -874,7 +875,7 @@ import { ArrowDown, Search, Memo, Minus, Plus, Edit, Delete } from '@element-plu
|
||||
import printUtils, { getPrinterList, PRINT_TEMPLATE, savePrinterToCache, } from '@/utils/printUtils';
|
||||
import Template from "@/views/inpatientDoctor/home/emr/components/template.vue";
|
||||
|
||||
const emit = defineEmits(['selectDiagnosis']);
|
||||
const emit = defineEmits(['selectDiagnosis', 'inspectionListRefresh']);
|
||||
const total = ref(0);
|
||||
const queryParams = ref({});
|
||||
const prescriptionList = ref([]);
|
||||
@@ -2083,6 +2084,21 @@ function getOrgList() {
|
||||
});
|
||||
}
|
||||
|
||||
/** 诊疗医嘱关联检验申请时 contentJson 含 applyNo */
|
||||
function getInspectionApplyNoFromAdviceRow(row) {
|
||||
if (!row || row.adviceType !== 3) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const raw = row.contentJson;
|
||||
const j = raw ? (typeof raw === 'string' ? JSON.parse(raw) : raw) : {};
|
||||
const no = j && j.applyNo != null ? String(j.applyNo).trim() : '';
|
||||
return no || null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
let selectRows = prescriptionRef.value.getSelectionRows();
|
||||
console.log('BugFix#219: handleDelete called, selectRows=', selectRows);
|
||||
@@ -2262,12 +2278,31 @@ function handleDelete() {
|
||||
}
|
||||
|
||||
if (deleteList.length > 0) {
|
||||
savePrescription({ adviceSaveList: deleteList }).then((res) => {
|
||||
if (res.code == 200) {
|
||||
proxy.$modal.msgSuccess('删除成功');
|
||||
getListInfo(false);
|
||||
}
|
||||
const hasLabLinked = deleteList.some((d) => {
|
||||
const row = normalRows.find((r) => r.requestId === d.requestId);
|
||||
return row && getInspectionApplyNoFromAdviceRow(row);
|
||||
});
|
||||
const runApiDelete = () => {
|
||||
savePrescription({ adviceSaveList: deleteList }).then((res) => {
|
||||
if (res.code == 200) {
|
||||
proxy.$modal.msgSuccess('删除成功');
|
||||
getListInfo(false);
|
||||
emit('inspectionListRefresh');
|
||||
}
|
||||
});
|
||||
};
|
||||
if (hasLabLinked) {
|
||||
proxy.$modal
|
||||
.confirm(
|
||||
'删除此医嘱将同时作废关联的检验申请单(检验页签中的同单申请及同单下相关医嘱)。是否继续?',
|
||||
'删除确认',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
.then(runApiDelete)
|
||||
.catch(() => {});
|
||||
} else {
|
||||
runApiDelete();
|
||||
}
|
||||
} else if (consultationRows.length == 0) {
|
||||
proxy.$modal.msgWarning('所选医嘱不可删除,请先撤回后再删除');
|
||||
return;
|
||||
@@ -3418,7 +3453,12 @@ async function setValue(row) {
|
||||
console.log('[BugFix] setValue - prescriptionList[rowIndex].adviceType_dictText:', prescriptionList.value[rowIndex.value].adviceType_dictText);
|
||||
// 🔧 Bug #455: 诊疗医嘱(adviceType=3)的执行科室默认使用患者就诊科室,
|
||||
// 不使用positionId(诊疗目录配置的执行科室),避免配置ID不在机构树中导致显示原始ID
|
||||
if (Number(row.adviceType) != 3) {
|
||||
if (Number(row.adviceType) == 3) {
|
||||
// 覆盖 catalog 传来的 positionId/orgId,使用患者就诊科室
|
||||
prescriptionList.value[rowIndex.value].orgId = props.patientInfo?.orgId;
|
||||
prescriptionList.value[rowIndex.value].positionId = props.patientInfo?.orgId;
|
||||
prescriptionList.value[rowIndex.value].positionName = findOrgNameById(props.patientInfo?.orgId) || props.patientInfo?.orgName || '';
|
||||
} else {
|
||||
prescriptionList.value[rowIndex.value].orgId = row.positionId || props.patientInfo?.orgId;
|
||||
}
|
||||
prescriptionList.value[rowIndex.value].dose = row.dose || row.doseQuantity;
|
||||
@@ -3652,7 +3692,10 @@ function handleSaveGroup(orderGroupList) {
|
||||
unitCode_dictText: item.unitCodeName || '',
|
||||
statusEnum: 1,
|
||||
// 🔧 修复执行科室逻辑:优先使用 orgId(所属科室),其次 positionId
|
||||
orgId: item.orderDetailInfos?.orgId || mergedDetail.orgId || item.positionId || item.orderDetailInfos?.positionId || mergedDetail.positionId,
|
||||
// 🔧 Bug #455: 诊疗类(adviceType=3)使用患者就诊科室,不使用目录配置的ID
|
||||
orgId: item.adviceType === 3
|
||||
? props.patientInfo?.orgId
|
||||
: (item.orderDetailInfos?.orgId || mergedDetail.orgId || item.positionId || item.orderDetailInfos?.positionId || mergedDetail.positionId),
|
||||
dbOpType: prescriptionList.value[rowIndex.value].requestId ? '2' : '1',
|
||||
conditionId: conditionId.value,
|
||||
conditionDefinitionId: conditionDefinitionId.value,
|
||||
|
||||
@@ -1142,13 +1142,7 @@ function submitForm() {
|
||||
// 保存麻醉方式
|
||||
sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum)
|
||||
open.value = false
|
||||
emit('saved') // 通知父组件刷新医嘱列表
|
||||
// 刷新手术申请列表,使用 nextTick 确保数据一致性
|
||||
proxy.$nextTick(() => {
|
||||
if (props.patientInfo?.encounterId) {
|
||||
getList()
|
||||
}
|
||||
})
|
||||
emit('saved') // 通知父组件刷新医嘱列表及手术申请列表
|
||||
} else {
|
||||
proxy.$modal.msgError(res.msg || '新增手术失败,请检查表单信息')
|
||||
}
|
||||
@@ -1164,13 +1158,7 @@ function submitForm() {
|
||||
// 保存麻醉方式
|
||||
sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum)
|
||||
open.value = false
|
||||
emit('saved') // 通知父组件刷新医嘱列表
|
||||
// 刷新手术申请列表,使用 nextTick 确保数据一致性
|
||||
proxy.$nextTick(() => {
|
||||
if (props.patientInfo?.encounterId) {
|
||||
getList()
|
||||
}
|
||||
})
|
||||
emit('saved') // 通知父组件刷新医嘱列表及手术申请列表
|
||||
} else {
|
||||
proxy.$modal.msgError(res.msg || '更新手术失败,请检查表单信息')
|
||||
}
|
||||
|
||||
@@ -138,7 +138,8 @@
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="医嘱" name="prescription">
|
||||
<prescriptionlist :patientInfo="patientInfo" ref="prescriptionRef" :activeTab="activeTab"
|
||||
:outpatientEmrSaved="outpatientEmrSaved" />
|
||||
:outpatientEmrSaved="outpatientEmrSaved"
|
||||
@inspectionListRefresh="refreshInspectionListFromAdvice" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="中医" name="tcm">
|
||||
<tcmAdvice :patientInfo="patientInfo" ref="tcmRef" />
|
||||
@@ -312,6 +313,9 @@ const patientDrawerRef = ref();
|
||||
const prescriptionRef = ref();
|
||||
const tcmRef = ref();
|
||||
const inspectionRef = ref();
|
||||
function refreshInspectionListFromAdvice() {
|
||||
inspectionRef.value?.getList?.();
|
||||
}
|
||||
const examinationRef = ref();
|
||||
const surgeryRef = ref();
|
||||
const emrRef = ref();
|
||||
|
||||
@@ -131,7 +131,8 @@ function getList() {
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((err) => {
|
||||
console.warn('医嘱基础信息加载失败:', err);
|
||||
adviceBaseList.value = [];
|
||||
})
|
||||
.finally(() => {
|
||||
|
||||
@@ -92,45 +92,42 @@
|
||||
<el-table-column prop="requesterId_dictText" label="申请者" width="120" />
|
||||
<el-table-column label="申请单状态" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ parseStatus(scope.row.status) }}</span>
|
||||
<el-tag :type="getStatusTagType(scope.row.status)" effect="plain" round>
|
||||
{{ parseStatus(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="280" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<!-- 待签发:详情、修改、删除 -->
|
||||
<!-- 详情 - 所有状态都显示 -->
|
||||
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
|
||||
<!-- 待签发:修改、删除 -->
|
||||
<template v-if="scope.row.status === '0' || scope.row.status === 0">
|
||||
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
|
||||
<el-button link type="primary" @click="handleModify(scope.row)">修改</el-button>
|
||||
<el-button link type="primary" @click="handleEdit(scope.row)">修改</el-button>
|
||||
<el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
<!-- 已签发:详情、撤回 -->
|
||||
<!-- 已签发:撤回 -->
|
||||
<template v-else-if="scope.row.status === '1' || scope.row.status === 1">
|
||||
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
|
||||
<el-button link type="warning" @click="handleWithdraw(scope.row)">撤回</el-button>
|
||||
<el-button link type="warning" @click="handleRecall(scope.row)">撤回</el-button>
|
||||
</template>
|
||||
<!-- 已校对/待接收:详情、打印 -->
|
||||
<!-- 已校对/待接收:打印 -->
|
||||
<template v-else-if="scope.row.status === '2' || scope.row.status === 2 || scope.row.status === '3' || scope.row.status === 3">
|
||||
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
|
||||
<el-button link type="primary" @click="handlePrint(scope.row)">打印</el-button>
|
||||
</template>
|
||||
<!-- 已接收/已检查:详情、看报告 -->
|
||||
<!-- 已接收/已检查:看报告 -->
|
||||
<template v-else-if="scope.row.status === '4' || scope.row.status === 4 || scope.row.status === '5' || scope.row.status === 5">
|
||||
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
|
||||
<el-button link type="success" @click="handleViewReport(scope.row)">看报告</el-button>
|
||||
</template>
|
||||
<!-- 已出报告:详情、打印、看报告 -->
|
||||
<!-- 已出报告:打印、看报告 -->
|
||||
<template v-else-if="scope.row.status === '6' || scope.row.status === 6">
|
||||
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
|
||||
<el-button link type="primary" @click="handlePrint(scope.row)">打印</el-button>
|
||||
<el-button link type="success" @click="handleViewReport(scope.row)">看报告</el-button>
|
||||
</template>
|
||||
<!-- 已作废:详情 -->
|
||||
<!-- 已作废:无额外按钮 -->
|
||||
<template v-else-if="scope.row.status === '7' || scope.row.status === 7">
|
||||
<el-button link type="info" @click="handleViewDetail(scope.row)">详情</el-button>
|
||||
</template>
|
||||
<!-- 其他/未知状态:仅详情 -->
|
||||
<template v-else>
|
||||
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -203,15 +200,102 @@
|
||||
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 修改申请单弹窗 - 复用检查申请单组件 -->
|
||||
<el-dialog
|
||||
v-model="editDialogVisible"
|
||||
title="修改检查申请"
|
||||
width="1200px"
|
||||
destroy-on-close
|
||||
top="5vh"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<MedicalExaminations
|
||||
v-if="editDialogVisible"
|
||||
ref="editFormRef"
|
||||
:is-edit-mode="true"
|
||||
:edit-data="editingRow"
|
||||
:external-patient-info="patientInfo"
|
||||
/>
|
||||
<template #footer>
|
||||
<el-button @click="editDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleEditSubmit">确认</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看报告弹窗 -->
|
||||
<el-dialog
|
||||
v-model="reportDialogVisible"
|
||||
:title="'检查报告 - ' + (reportData?.prescriptionNo || '')"
|
||||
width="900px"
|
||||
destroy-on-close
|
||||
top="5vh"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div v-loading="reportLoading" class="report-viewer">
|
||||
<!-- 报告基本信息 -->
|
||||
<div v-if="reportData" class="report-viewer-container">
|
||||
<el-descriptions title="报告信息" :column="2" border size="small">
|
||||
<el-descriptions-item label="患者姓名">{{ reportData.patientName || reportRow?.patientName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请单号">{{ reportData.prescriptionNo || reportRow?.prescriptionNo || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请单名称">{{ reportData.name || reportRow?.name || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报告时间">{{ reportData.reportTime || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="诊断意见" :span="2">{{ reportData.diagnosis || reportData.conclusion || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 报告详细内容 -->
|
||||
<div v-if="reportData.content" class="report-content-section">
|
||||
<div class="section-title">报告内容</div>
|
||||
<div class="report-content-text">{{ reportData.content }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 影像预览 - PACS链接 -->
|
||||
<div v-if="reportData.imageUrl || reportData.pacsUrl" class="report-image-section">
|
||||
<div class="section-title">
|
||||
影像预览
|
||||
<el-button
|
||||
v-if="reportData.imageUrl || reportData.pacsUrl"
|
||||
type="primary"
|
||||
size="small"
|
||||
plain
|
||||
style="margin-left: 12px;"
|
||||
@click="openPacsLink"
|
||||
>
|
||||
<el-icon><Link /></el-icon>
|
||||
打开PACS影像
|
||||
</el-button>
|
||||
</div>
|
||||
<iframe
|
||||
v-if="reportData.imageUrl"
|
||||
:src="reportData.imageUrl"
|
||||
class="report-iframe"
|
||||
frameborder="0"
|
||||
/>
|
||||
<el-empty v-else-if="!reportData.imageUrl && reportData.pacsUrl" description="点击上方按钮打开PACS影像" :image-size="60" />
|
||||
</div>
|
||||
|
||||
<!-- 完全无数据时的提示 -->
|
||||
<el-empty v-if="!reportData.content && !reportData.imageUrl && !reportData.pacsUrl" description="暂无详细报告数据" :image-size="60" />
|
||||
</div>
|
||||
|
||||
<!-- 未获取到报告 -->
|
||||
<el-empty v-else description="暂未生成报告" :image-size="80" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="reportDialogVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {computed, getCurrentInstance, ref, watch} from 'vue';
|
||||
import {Refresh, Search} from '@element-plus/icons-vue';
|
||||
import {computed, getCurrentInstance, ref, watch, nextTick} from 'vue';
|
||||
import {Refresh, Search, Link} from '@element-plus/icons-vue';
|
||||
import {patientInfo} from '../../store/patient.js';
|
||||
import {getCheck, deleteRequestForm, withdrawRequestForm, getTestResult} from './api';
|
||||
import {getDepartmentList} from '@/api/public.js';
|
||||
import {getApplicationList, saveCheckd} from '../order/applicationForm/api';
|
||||
import MedicalExaminations from '../order/applicationForm/medicalExaminations.vue';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
@@ -221,6 +305,24 @@ const detailDialogVisible = ref(false);
|
||||
const currentDetail = ref(null);
|
||||
const descJsonData = ref(null);
|
||||
const orgOptions = ref([]);
|
||||
const editForm = ref({
|
||||
name: '',
|
||||
targetDepartment: '',
|
||||
symptom: '',
|
||||
sign: '',
|
||||
clinicalDiagnosis: '',
|
||||
otherDiagnosis: '',
|
||||
relatedResult: '',
|
||||
attention: '',
|
||||
examinationPurpose: '',
|
||||
medicalHistorySummary: '',
|
||||
});
|
||||
|
||||
// 报告弹窗相关
|
||||
const reportDialogVisible = ref(false);
|
||||
const reportLoading = ref(false);
|
||||
const reportData = ref(null);
|
||||
const reportRow = ref(null);
|
||||
|
||||
// 获取近7天的日期范围作为默认值
|
||||
const getDefaultDateRange = () => {
|
||||
@@ -268,7 +370,9 @@ const fetchData = async () => {
|
||||
if (res.code === 200 && res.data) {
|
||||
const raw = res.data?.records || res.data;
|
||||
const list = Array.isArray(raw) ? raw : [raw];
|
||||
console.log('API返回的原始数据:', JSON.stringify(list, null, 2));
|
||||
tableData.value = list.filter(Boolean);
|
||||
console.log('tableData设置后的第一条:', tableData.value[0]);
|
||||
} else {
|
||||
tableData.value = [];
|
||||
}
|
||||
@@ -325,6 +429,25 @@ const parseStatus = (status) => {
|
||||
return statusMap[String(status)] || '-';
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取状态标签类型 - 参考临床医嘱样式
|
||||
* @param {string|number} status - 状态码
|
||||
* @returns {string} el-tag type
|
||||
*/
|
||||
const getStatusTagType = (status) => {
|
||||
const typeMap = {
|
||||
'0': 'primary', // 待签发 - 蓝色
|
||||
'1': 'success', // 已签发 - 绿色
|
||||
'2': 'success', // 已校对 - 绿色
|
||||
'3': 'primary', // 待接收 - 蓝色
|
||||
'4': 'primary', // 已接收 - 蓝色
|
||||
'5': 'success', // 已检查 - 绿色
|
||||
'6': 'success', // 已出报告 - 绿色
|
||||
'7': 'danger', // 已作废 - 红色
|
||||
};
|
||||
return typeMap[String(status)] || 'info';
|
||||
};
|
||||
|
||||
const labelMap = {
|
||||
categoryType: '项目类别',
|
||||
targetDepartment: '发往科室',
|
||||
@@ -418,87 +541,391 @@ const handleViewDetail = async (row) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改申请单(仅待签发状态)
|
||||
* 删除申请单 - 仅待签发状态可用
|
||||
*/
|
||||
const handleModify = (row) => {
|
||||
proxy.$modal?.msgWarning?.('修改功能需后端支持,请联系管理员');
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除申请单(仅待签发状态)
|
||||
*/
|
||||
const handleDelete = (row) => {
|
||||
proxy.$confirm?.('确认删除该检查申请单吗?删除后不可恢复。', '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(async () => {
|
||||
try {
|
||||
const res = await deleteRequestForm({ requestFormId: row.requestFormId || row.id });
|
||||
if (res?.code === 200) {
|
||||
proxy.$modal?.msgSuccess?.('删除成功');
|
||||
await fetchData();
|
||||
} else {
|
||||
proxy.$modal?.msgError?.(res?.msg || '删除失败');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('删除申请单失败(可能后端未实现):', e.message);
|
||||
proxy.$modal?.msgError?.('删除失败,后端服务可能未支持此功能');
|
||||
}
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
/**
|
||||
* 撤回申请单(已签发状态撤回至待签发)
|
||||
*/
|
||||
const handleWithdraw = (row) => {
|
||||
proxy.$confirm?.('确认撤回该检查申请单吗?撤回后状态将变为待签发。', '撤回确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(async () => {
|
||||
try {
|
||||
const res = await withdrawRequestForm({ requestFormId: row.requestFormId || row.id });
|
||||
if (res?.code === 200) {
|
||||
proxy.$modal?.msgSuccess?.('撤回成功');
|
||||
await fetchData();
|
||||
} else {
|
||||
proxy.$modal?.msgError?.(res?.msg || '撤回失败');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('撤回申请单失败(可能后端未实现):', e.message);
|
||||
proxy.$modal?.msgError?.('撤回失败,后端服务可能未支持此功能');
|
||||
}
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
/**
|
||||
* 打印申请单
|
||||
*/
|
||||
const handlePrint = (row) => {
|
||||
// 使用浏览器原生打印功能
|
||||
window.print();
|
||||
};
|
||||
|
||||
/**
|
||||
* 查看检查报告
|
||||
*/
|
||||
const handleViewReport = async (row) => {
|
||||
const handleDelete = async (row) => {
|
||||
try {
|
||||
const res = await getTestResult({ encounterId: row.encounterId || patientInfo.value?.encounterId });
|
||||
if (res?.code === 200 && res.data) {
|
||||
const reportUrl = Array.isArray(res.data) ? res.data[0]?.reportUrl : res.data?.reportUrl;
|
||||
if (reportUrl) {
|
||||
window.open(reportUrl, '_blank');
|
||||
} else {
|
||||
proxy.$modal?.msgWarning?.('暂无检查报告');
|
||||
}
|
||||
await proxy.$modal?.confirm?.('确认删除该检查申请单?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
const res = await deleteRequestForm({ requestFormId: row.requestFormId });
|
||||
if (res.code === 200) {
|
||||
proxy.$modal?.msgSuccess?.('删除成功');
|
||||
fetchData();
|
||||
} else {
|
||||
proxy.$modal?.msgWarning?.('暂无检查报告');
|
||||
proxy.$modal?.msgError?.(res.msg || '删除失败');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('查看检查报告失败:', e.message);
|
||||
proxy.$modal?.msgError?.('获取检查报告失败');
|
||||
// 用户取消操作,不做处理
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 撤回申请单 - 仅已签发状态可用
|
||||
*/
|
||||
const handleRecall = async (row) => {
|
||||
try {
|
||||
await proxy.$modal?.confirm?.('确认撤回该申请单?撤回后状态将变更为"待签发"。', '提示', {
|
||||
confirmButtonText: '确定撤回',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
const res = await withdrawRequestForm({ requestFormId: row.requestFormId });
|
||||
if (res.code === 200) {
|
||||
proxy.$modal?.msgSuccess?.('撤回成功');
|
||||
fetchData();
|
||||
} else {
|
||||
proxy.$modal?.msgError?.(res.msg || '撤回失败');
|
||||
}
|
||||
} catch (e) {
|
||||
// 用户取消操作,不做处理
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 打印申请单 - 已校对/待接收/已接收/已检查状态可用
|
||||
* 打印内容与详情展示一致,并包含申请单条码
|
||||
*/
|
||||
const handlePrint = async (row) => {
|
||||
try {
|
||||
proxy.$modal?.msgInfo?.('正在生成打印预览...');
|
||||
|
||||
// 确保科室数据已加载,用于解析发往科室名称
|
||||
if (!orgOptions.value || orgOptions.value.length === 0) {
|
||||
await new Promise((resolve) => {
|
||||
getDepartmentList().then((res) => {
|
||||
orgOptions.value = res.data || [];
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 解析 descJson
|
||||
let descData = {};
|
||||
if (row.descJson) {
|
||||
try {
|
||||
const obj = JSON.parse(row.descJson);
|
||||
// 将发往科室ID转换为名称
|
||||
if (obj.targetDepartment) {
|
||||
obj.targetDepartment = recursionFun(obj.targetDepartment);
|
||||
}
|
||||
descData = obj;
|
||||
} catch (e) {
|
||||
console.error('解析 descJson 失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 构建诊疗项目表格行
|
||||
let detailRowsHtml = '';
|
||||
const detailList = row.requestFormDetailList || [];
|
||||
if (detailList.length > 0) {
|
||||
detailList.forEach((item, index) => {
|
||||
detailRowsHtml += `
|
||||
<tr>
|
||||
<td style="text-align:center;padding:6px 8px;border:1px solid #ddd;">${index + 1}</td>
|
||||
<td style="padding:6px 8px;border:1px solid #ddd;">${item.adviceName || '-'}</td>
|
||||
<td style="text-align:center;padding:6px 8px;border:1px solid #ddd;">${item.quantity || '-'}</td>
|
||||
<td style="padding:6px 8px;border:1px solid #ddd;">${item.unitCode_dictText || '-'}</td>
|
||||
<td style="text-align:right;padding:6px 8px;border:1px solid #ddd;">${item.totalPrice != null ? '¥' + Number(item.totalPrice).toFixed(2) : '-'}</td>
|
||||
</tr>`;
|
||||
});
|
||||
}
|
||||
|
||||
// 构建 descJson 字段行(与详情弹窗展示的字段一致)
|
||||
const fieldKeys = ['targetDepartment', 'symptom', 'sign', 'clinicalDiagnosis', 'otherDiagnosis', 'relatedResult', 'attention'];
|
||||
let descFieldsHtml = '';
|
||||
fieldKeys.forEach((key) => {
|
||||
const label = labelMap[key] || key;
|
||||
if (descData[key] != null && descData[key] !== '') {
|
||||
descFieldsHtml += `
|
||||
<div class="info-row">
|
||||
<span class="label">${label}:</span>
|
||||
<span class="value">${descData[key]}</span>
|
||||
</div>`;
|
||||
}
|
||||
});
|
||||
|
||||
// 构建完整打印HTML
|
||||
const printContent = `
|
||||
<div class="print-wrapper">
|
||||
<!-- 标题 -->
|
||||
<div class="print-header">
|
||||
<div class="print-title">检查申请单</div>
|
||||
<div class="print-meta">打印时间:${new Date().toLocaleString()}</div>
|
||||
</div>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<div class="print-section">
|
||||
<div class="section-title">基本信息</div>
|
||||
<div class="info-grid">
|
||||
<div class="info-row"><span class="label">患者姓名:</span><span class="value">${row.patientName || '-'}</span></div>
|
||||
<div class="info-row"><span class="label">申请单名称:</span><span class="value">${row.name || '-'}</span></div>
|
||||
<div class="info-row"><span class="label">申请单状态:</span><span class="value">${parseStatus(row.status)}</span></div>
|
||||
<div class="info-row"><span class="label">创建时间:</span><span class="value">${row.createTime || '-'}</span></div>
|
||||
<div class="info-row"><span class="label">申请单号:</span><span class="value">${row.prescriptionNo || '-'}</span></div>
|
||||
<div class="info-row"><span class="label">申请者:</span><span class="value">${row.requesterId_dictText || '-'}</span></div>
|
||||
<div class="info-row"><span class="label">就诊ID:</span><span class="value">${row.encounterId || '-'}</span></div>
|
||||
<div class="info-row"><span class="label">申请单ID:</span><span class="value">${row.requestFormId || '-'}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${descFieldsHtml ? `
|
||||
<!-- 申请单描述 -->
|
||||
<div class="print-section">
|
||||
<div class="section-title">申请单描述</div>
|
||||
${descFieldsHtml}
|
||||
</div>` : ''}
|
||||
|
||||
${detailRowsHtml ? `
|
||||
<!-- 诊疗项目 -->
|
||||
<div class="print-section">
|
||||
<div class="section-title">诊疗项目</div>
|
||||
<table class="detail-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:50px;padding:8px;border:1px solid #ddd;background:#f5f7fa;text-align:center;">序号</th>
|
||||
<th style="padding:8px;border:1px solid #ddd;background:#f5f7fa;text-align:left;">医嘱名称</th>
|
||||
<th style="width:60px;padding:8px;border:1px solid #ddd;background:#f5f7fa;text-align:center;">数量</th>
|
||||
<th style="width:60px;padding:8px;border:1px solid #ddd;background:#f5f7fa;text-align:center;">单位</th>
|
||||
<th style="width:80px;padding:8px;border:1px solid #ddd;background:#f5f7fa;text-align:right;">总价</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>${detailRowsHtml}</tbody>
|
||||
</table>
|
||||
</div>` : ''}
|
||||
|
||||
<!-- 条码区 -->
|
||||
<div class="barcode-section">
|
||||
<div class="barcode-container">
|
||||
<div class="barcode-number">${row.prescriptionNo || ''}</div>
|
||||
<div class="barcode-label">申请单号 / 扫码核验</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="print-footer">
|
||||
<div class="footer-line">本申请单仅供院内使用,请勿外传</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 打开新窗口打印
|
||||
const printWindow = window.open('', '_blank');
|
||||
if (!printWindow) {
|
||||
proxy.$modal?.msgError?.('无法打开打印窗口,请检查浏览器弹窗设置');
|
||||
return;
|
||||
}
|
||||
|
||||
printWindow.document.write(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>检查申请单 - 打印</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
padding: 20px;
|
||||
}
|
||||
.print-wrapper {
|
||||
max-width: 210mm;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.print-header {
|
||||
text-align: center;
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 2px solid #333;
|
||||
}
|
||||
.print-title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 4px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.print-meta {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
}
|
||||
.print-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 4px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 6px 16px;
|
||||
}
|
||||
.info-row {
|
||||
font-size: 12px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.info-row .label {
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
}
|
||||
.info-row .value {
|
||||
color: #333;
|
||||
}
|
||||
.detail-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.detail-table th {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
}
|
||||
.detail-table td {
|
||||
font-size: 12px;
|
||||
}
|
||||
.barcode-section {
|
||||
margin-top: 24px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px dashed #ccc;
|
||||
text-align: center;
|
||||
}
|
||||
.barcode-container {
|
||||
display: inline-block;
|
||||
padding: 12px 24px;
|
||||
border: 2px solid #333;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
}
|
||||
.barcode-number {
|
||||
font-family: 'Libre Barcode 128', 'Libre Barcode 39', 'Code128', 'C', monospace;
|
||||
font-size: 42px;
|
||||
letter-spacing: 4px;
|
||||
color: #000;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.barcode-label {
|
||||
font-size: 10px;
|
||||
color: #888;
|
||||
margin-top: 4px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
.print-footer {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.footer-line {
|
||||
font-size: 10px;
|
||||
color: #aaa;
|
||||
}
|
||||
@media print {
|
||||
body { padding: 0; }
|
||||
.print-wrapper { max-width: none; }
|
||||
.barcode-section { page-break-inside: avoid; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
${printContent}
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
|
||||
printWindow.document.close();
|
||||
// 等待内容渲染后打印
|
||||
printWindow.onload = function() {
|
||||
setTimeout(() => {
|
||||
printWindow.print();
|
||||
proxy.$modal?.closeAll?.();
|
||||
}, 300);
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('打印失败:', error);
|
||||
proxy.$modal?.msgError?.('打印失败: ' + (error.message || '未知错误'));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 查看报告 - 仅已出报告状态可用
|
||||
* 调用报告查询接口,弹窗展示结构化报告或影像预览(PACS链接)
|
||||
*/
|
||||
const handleViewReport = async (row) => {
|
||||
reportRow.value = row;
|
||||
reportDialogVisible.value = true;
|
||||
reportLoading.value = true;
|
||||
reportData.value = null;
|
||||
try {
|
||||
const res = await getTestResult({ prescriptionNo: row.prescriptionNo });
|
||||
if (res.code === 200) {
|
||||
if (res.data) {
|
||||
// 支持两种返回格式:
|
||||
// 1. res.data 为对象(结构化报告):含 patientName, prescriptionNo, reportTime, diagnosis/content, imageUrl/pacsUrl
|
||||
// 2. res.data 为字符串(报告URL):映射到 imageUrl 以支持iframe预览
|
||||
if (typeof res.data === 'string') {
|
||||
reportData.value = {
|
||||
prescriptionNo: row.prescriptionNo,
|
||||
imageUrl: res.data,
|
||||
};
|
||||
} else if (typeof res.data === 'object') {
|
||||
reportData.value = {
|
||||
...res.data,
|
||||
prescriptionNo: res.data.prescriptionNo || row.prescriptionNo,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
reportData.value = null;
|
||||
}
|
||||
} else {
|
||||
reportData.value = null;
|
||||
proxy.$modal?.msgWarning?.(res.msg || '暂未生成报告');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取报告失败:', e);
|
||||
reportData.value = null;
|
||||
proxy.$modal?.msgError?.('获取报告失败');
|
||||
} finally {
|
||||
reportLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开PACS影像链接
|
||||
*/
|
||||
const openPacsLink = () => {
|
||||
const url = reportData.value?.pacsUrl || reportData.value?.imageUrl;
|
||||
if (url) {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
// ========== 修改申请单相关 ==========
|
||||
const editDialogVisible = ref(false);
|
||||
const editFormRef = ref(null);
|
||||
const editingRow = ref(null);
|
||||
|
||||
// 修改申请单 - 复用检查申请单弹窗
|
||||
const handleEdit = (row) => {
|
||||
editingRow.value = { ...row };
|
||||
editDialogVisible.value = true;
|
||||
// 弹窗打开后,手动调用getList确保数据加载
|
||||
nextTick(() => {
|
||||
editFormRef.value?.getList?.();
|
||||
editFormRef.value?.getLocationInfo?.();
|
||||
editFormRef.value?.getDiagnosisList?.();
|
||||
});
|
||||
};
|
||||
|
||||
// 编辑弹窗确认提交
|
||||
const handleEditSubmit = () => {
|
||||
// 调用MedicalExaminations组件的submit方法
|
||||
if (editFormRef.value?.submit) {
|
||||
editFormRef.value.submit();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -626,4 +1053,96 @@ defineExpose({
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// 报告弹窗样式
|
||||
.report-viewer {
|
||||
min-height: 200px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.report-viewer-container {
|
||||
.report-content-section {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #eee;
|
||||
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
color: #303133;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.report-content-text {
|
||||
font-size: 13px;
|
||||
line-height: 1.8;
|
||||
color: #606266;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.report-image-section {
|
||||
margin-top: 16px;
|
||||
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
color: #303133;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.report-iframe {
|
||||
width: 100%;
|
||||
height: 480px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 状态标签样式 - 参考临床医嘱
|
||||
:deep(.el-tag) {
|
||||
border-radius: 2px;
|
||||
padding: 0 8px;
|
||||
height: 24px;
|
||||
line-height: 22px;
|
||||
font-size: 12px;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
:deep(.el-tag--info.is-plain) {
|
||||
background: #f4f4f5;
|
||||
border-color: #e9e9eb;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
:deep(.el-tag--primary.is-plain) {
|
||||
background: #ecf5ff;
|
||||
border-color: #d9ecff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
:deep(.el-tag--success.is-plain) {
|
||||
background: #f0f9eb;
|
||||
border-color: #e1f3d8;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
:deep(.el-tag--warning.is-plain) {
|
||||
background: #fdf6ec;
|
||||
border-color: #faecd8;
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
:deep(.el-tag--danger.is-plain) {
|
||||
background: #fef0f0;
|
||||
border-color: #fde2e2;
|
||||
color: #f56c6c;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -249,7 +249,7 @@ const submit = () => {
|
||||
requestFormId: '', // 申请单ID
|
||||
name: '输血申请单',
|
||||
descJson: JSON.stringify(form),
|
||||
categoryEnum: '3', // 1 检验 2 检查 3 输血 4 手术
|
||||
categoryEnum: '23', // 21 检验 22 检查 23 输血 24 手术(避开 adviceType 1-6 碰撞)
|
||||
}).then((res) => {
|
||||
if (res.code === 200) {
|
||||
proxy.$message.success(res.msg);
|
||||
|
||||
@@ -6,9 +6,26 @@
|
||||
<template>
|
||||
<div class="LaboratoryTests-container">
|
||||
<div v-loading="loading" class="transfer-wrapper">
|
||||
<!-- 远程搜索框 -->
|
||||
<div class="search-bar">
|
||||
<el-input
|
||||
v-model="searchKey"
|
||||
placeholder="输入项目代码/名称搜索"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
@clear="handleSearch"
|
||||
style="width: 300px; margin-bottom: 10px"
|
||||
>
|
||||
<template #append>
|
||||
<el-button @click="handleSearch">搜索</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<span v-if="!searchKey" class="total-count">共 {{ totalCount }} 项</span>
|
||||
<span v-else class="total-count">搜索到 {{ filteredCount }} 项 / 共 {{ totalCount }} 项</span>
|
||||
</div>
|
||||
<el-transfer
|
||||
v-model="transferValue"
|
||||
:data="applicationList"
|
||||
:data="transferData"
|
||||
filter-placeholder="项目代码/名称"
|
||||
filterable
|
||||
:titles="['未选择', '已选择']"
|
||||
@@ -117,7 +134,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup name="LaboratoryTests">
|
||||
import {getCurrentInstance, onBeforeMount, onMounted, reactive, watch} from 'vue';
|
||||
import {getCurrentInstance, onMounted, reactive, ref, watch, computed} from 'vue';
|
||||
import {patientInfo} from '../../../store/patient.js';
|
||||
import {getApplicationList, saveInspection} from './api';
|
||||
import {getOrgList} from '@/views/doctorstation/components/api.js';
|
||||
@@ -140,61 +157,85 @@ const findTreeItem = (list, id) => {
|
||||
const emits = defineEmits(['submitOk']);
|
||||
const props = defineProps({});
|
||||
const state = reactive({});
|
||||
const applicationListAll = ref();
|
||||
const applicationList = ref();
|
||||
const applicationListAll = ref([]);
|
||||
const loading = ref(false);
|
||||
const orgOptions = ref([]); // 科室选项
|
||||
const getList = async () => {
|
||||
const orgOptions = ref([]);
|
||||
const searchKey = ref('');
|
||||
const totalCount = ref(0);
|
||||
|
||||
// 将已加载的全部数据转为 transfer 组件所需的格式
|
||||
const buildTransferData = (records) => {
|
||||
return records.map((item) => {
|
||||
const priceInfo = item.priceList?.[0] || {};
|
||||
const price = priceInfo.price != null ? Number(priceInfo.price).toFixed(2) : '0.00';
|
||||
const unit = item.unitCode_dictText || item.unitCode || '';
|
||||
return {
|
||||
adviceDefinitionId: item.adviceDefinitionId,
|
||||
orgId: item.orgId,
|
||||
label: item.adviceName + ' (¥' + price + '/' + unit + ')',
|
||||
key: item.adviceDefinitionId,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// 加载全部数据(不分页,一次性拉取)
|
||||
const loadAllData = async () => {
|
||||
if (!patientInfo.value?.inHospitalOrgId) {
|
||||
applicationList.value = [];
|
||||
applicationListAll.value = [];
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const allRecords = [];
|
||||
let currentPage = 1;
|
||||
const pageSize = 500;
|
||||
|
||||
// 分页拉取全部数据(后端单页最多500条)
|
||||
while (true) {
|
||||
const res = await getApplicationList({
|
||||
pageSize,
|
||||
pageNo: currentPage,
|
||||
categoryCode: '22',
|
||||
organizationId: patientInfo.value.inHospitalOrgId,
|
||||
adviceTypes: [3], // 1 药品 2 耗材 3 诊疗
|
||||
});
|
||||
if (res.code !== 200) {
|
||||
proxy.$message.error(res.message);
|
||||
applicationList.value = [];
|
||||
return;
|
||||
}
|
||||
const records = res.data?.records || [];
|
||||
allRecords.push(...records);
|
||||
// 当前页不足 pageSize 或已无数据,说明已全部拉取
|
||||
if (records.length < pageSize) break;
|
||||
currentPage++;
|
||||
}
|
||||
|
||||
applicationListAll.value = allRecords;
|
||||
applicationList.value = allRecords.map((item) => {
|
||||
const priceInfo = item.priceList?.[0] || {};
|
||||
const price = priceInfo.price != null ? Number(priceInfo.price).toFixed(2) : '0.00';
|
||||
const unit = item.unitCode_dictText || item.unitCode || '';
|
||||
return {
|
||||
adviceDefinitionId: item.adviceDefinitionId,
|
||||
orgId: item.orgId,
|
||||
label: item.adviceName + ' (¥' + price + '/' + unit + ')',
|
||||
key: item.adviceDefinitionId,
|
||||
};
|
||||
// 使用大 pageSize 一次性拉取所有启用状态的检验类诊疗项目
|
||||
const res = await getApplicationList({
|
||||
pageSize: 9999,
|
||||
pageNo: 1,
|
||||
categoryCode: '22',
|
||||
organizationId: patientInfo.value.inHospitalOrgId,
|
||||
adviceTypes: [3], // 1 药品 2 耗材 3 诊疗
|
||||
});
|
||||
if (res.code !== 200) {
|
||||
proxy.$message.error(res.message);
|
||||
applicationListAll.value = [];
|
||||
return;
|
||||
}
|
||||
applicationListAll.value = res.data?.records || [];
|
||||
totalCount.value = res.data?.total || 0;
|
||||
} catch (e) {
|
||||
proxy.$message.error('获取检验项目列表失败');
|
||||
applicationList.value = [];
|
||||
applicationListAll.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 根据搜索关键词过滤数据
|
||||
const filterData = (key) => {
|
||||
if (!key || key.trim() === '') {
|
||||
return applicationListAll.value;
|
||||
}
|
||||
const lowerKey = key.toLowerCase().trim();
|
||||
return applicationListAll.value.filter((item) => {
|
||||
return (
|
||||
item.adviceName?.toLowerCase().includes(lowerKey) ||
|
||||
item.pyStr?.toLowerCase().includes(lowerKey) ||
|
||||
item.adviceBusNo?.toLowerCase().includes(lowerKey)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// transfer 组件实际显示的数据(受搜索词影响)
|
||||
const transferData = computed(() => buildTransferData(filterData(searchKey.value)));
|
||||
// 当前显示的条数
|
||||
const filteredCount = computed(() => filterData(searchKey.value).length);
|
||||
|
||||
const getList = async () => {
|
||||
await loadAllData();
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
// 搜索时保持已选中的项目不受影响
|
||||
};
|
||||
const transferValue = ref([]);
|
||||
const form = reactive({
|
||||
// categoryType: '', // 项目类别
|
||||
@@ -212,7 +253,6 @@ const form = reactive({
|
||||
otherDiagnosisList: [], //其他断目录
|
||||
});
|
||||
const rules = reactive({});
|
||||
onBeforeMount(() => {});
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
@@ -225,12 +265,22 @@ const projectWithDepartment = (selectProjectIds, type) => {
|
||||
let isRelease = true;
|
||||
// 选中项目的数组
|
||||
const arr = [];
|
||||
// 根据选中的项目id查找对应的项目
|
||||
// 根据选中的项目id查找对应的项目(从全部原始数据中查找)
|
||||
selectProjectIds.forEach((element) => {
|
||||
const searchData = applicationList.value.find((item) => {
|
||||
const searchData = applicationListAll.value.find((item) => {
|
||||
return element == item.adviceDefinitionId;
|
||||
});
|
||||
arr.push(searchData);
|
||||
if (searchData) {
|
||||
const priceInfo = searchData.priceList?.[0] || {};
|
||||
const price = priceInfo.price != null ? Number(priceInfo.price).toFixed(2) : '0.00';
|
||||
const unit = searchData.unitCode_dictText || searchData.unitCode || '';
|
||||
arr.push({
|
||||
adviceDefinitionId: searchData.adviceDefinitionId,
|
||||
orgId: searchData.orgId,
|
||||
label: searchData.adviceName + ' (¥' + price + '/' + unit + ')',
|
||||
key: searchData.adviceDefinitionId,
|
||||
});
|
||||
}
|
||||
});
|
||||
// 保存用户手动选择的发往科室(提交时需要保留)
|
||||
const manualDept = type === 2 ? form.targetDepartment : '';
|
||||
@@ -316,12 +366,12 @@ const submit = () => {
|
||||
requestFormId: '', // 申请单ID
|
||||
name: '检验申请单',
|
||||
descJson: JSON.stringify(form),
|
||||
categoryEnum: '1', // 1 检验 2 检查 3 输血 4 手术
|
||||
categoryEnum: '21', // 21 检验 22 检查 23 输血 24 手术(避开 adviceType 1-6 碰撞)
|
||||
};
|
||||
saveInspection(params).then((res) => {
|
||||
if (res.code === 200) {
|
||||
proxy.$message.success(res.msg);
|
||||
applicationList.value = [];
|
||||
transferValue.value = [];
|
||||
emits('submitOk');
|
||||
} else {
|
||||
proxy.$message.error(res.message);
|
||||
@@ -378,6 +428,19 @@ defineExpose({ state, submit, getLocationInfo, getDiagnosisList, getList });
|
||||
.transfer-wrapper {
|
||||
position: relative;
|
||||
min-height: 300px;
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.total-count {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-transfer {
|
||||
|
||||
@@ -5,36 +5,25 @@
|
||||
-->
|
||||
<template>
|
||||
<div class="medicalExaminations-container">
|
||||
<!-- 顶部标题栏 -->
|
||||
<div class="form-header">
|
||||
<div class="header-left">
|
||||
<el-icon class="header-icon"><Files /></el-icon>
|
||||
<span class="header-title">检查申请单</span>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<span class="urgency-label">紧急程度</span>
|
||||
<el-radio-group v-model="form.urgencyLevel" @change="handleUrgencyChange" class="urgency-radio-group">
|
||||
<!-- 主体内容 -->
|
||||
<div class="form-body">
|
||||
<!-- 右上角:紧急程度 -->
|
||||
<div class="urgency-bar">
|
||||
<span class="urgency-bar-label">紧急程度:</span>
|
||||
<el-radio-group v-model="form.urgencyLevel" @change="handleUrgencyChange" size="small">
|
||||
<el-radio-button label="routine">普通</el-radio-button>
|
||||
<el-radio-button label="emergency">急诊</el-radio-button>
|
||||
</el-radio-group>
|
||||
<transition name="el-fade-in-linear">
|
||||
<span v-if="form.urgencyLevel === 'emergency'" class="emergency-tip">
|
||||
<span v-if="form.urgencyLevel === 'emergency'" class="emergency-tip-inline">
|
||||
<el-icon><WarningFilled /></el-icon>
|
||||
急诊单将进入绿色通道
|
||||
绿色通道
|
||||
</span>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主体内容区 -->
|
||||
<div class="form-body">
|
||||
<!-- 选择检查项目 -->
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<el-icon><Document /></el-icon>
|
||||
<span>选择检查项目</span>
|
||||
</div>
|
||||
<div v-loading="loading" class="transfer-wrapper">
|
||||
<div class="transfer-wrapper">
|
||||
<el-transfer
|
||||
v-model="transferValue"
|
||||
:data="applicationList"
|
||||
@@ -45,165 +34,150 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 申请信息 -->
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<el-icon><EditPen /></el-icon>
|
||||
<span>申请信息</span>
|
||||
</div>
|
||||
<el-form :model="form" :rules="rules" ref="formRef" label-position="top" class="info-form">
|
||||
<!-- 第一行:发往科室 + 紧急程度 + 期望检查时间 -->
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="发往科室" prop="targetDepartment">
|
||||
<el-tree-select
|
||||
clearable
|
||||
style="width: 100%"
|
||||
v-model="form.targetDepartment"
|
||||
filterable
|
||||
:data="orgOptions"
|
||||
:props="{ value: 'id', label: 'name', children: 'children' }"
|
||||
value-key="id"
|
||||
check-strictly
|
||||
placeholder="请选择执行科室"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="期望检查时间">
|
||||
<el-date-picker
|
||||
v-model="form.expectedExaminationTime"
|
||||
type="datetime"
|
||||
placeholder="默认当前时间"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
:disabled-date="disabledFutureDate"
|
||||
:default-value="new Date()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form :model="form" :rules="rules" ref="formRef" label-position="top" class="info-form">
|
||||
<!-- 第一行:发往科室 + 期望检查时间 -->
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="发往科室" prop="targetDepartment">
|
||||
<el-tree-select
|
||||
clearable
|
||||
style="width: 100%"
|
||||
v-model="form.targetDepartment"
|
||||
filterable
|
||||
:data="orgOptions"
|
||||
:props="{ value: 'id', label: 'name', children: 'children' }"
|
||||
value-key="id"
|
||||
check-strictly
|
||||
placeholder="请选择执行科室"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="期望检查时间">
|
||||
<el-date-picker
|
||||
v-model="form.expectedExaminationTime"
|
||||
type="datetime"
|
||||
placeholder="默认当前时间"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
:disabled-date="disabledFutureDate"
|
||||
:default-value="new Date()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 第二行:症状 + 体征 -->
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="症状">
|
||||
<el-input v-model="form.symptom" autocomplete="off" type="textarea" :rows="2" placeholder="请输入患者症状" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="体征">
|
||||
<el-input v-model="form.sign" autocomplete="off" type="textarea" :rows="2" placeholder="请输入患者体征" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第二行:症状 + 体征 -->
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="症状">
|
||||
<el-input v-model="form.symptom" autocomplete="off" type="textarea" :rows="2" placeholder="请输入患者症状" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="体征">
|
||||
<el-input v-model="form.sign" autocomplete="off" type="textarea" :rows="2" placeholder="请输入患者体征" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 第三行:临床诊断 + 其他诊断 -->
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="临床诊断">
|
||||
<el-input disabled v-model="form.clinicalDiagnosis" placeholder="自动带入主诊断" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="其他诊断">
|
||||
<el-input disabled v-model="form.otherDiagnosis" placeholder="自动带入其他诊断" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第三行:临床诊断 + 其他诊断 -->
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="临床诊断">
|
||||
<el-input disabled v-model="form.clinicalDiagnosis" placeholder="自动带入主诊断" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="其他诊断">
|
||||
<el-input disabled v-model="form.otherDiagnosis" placeholder="自动带入其他诊断" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 第四行:相关结果 + 注意事项 -->
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="相关结果">
|
||||
<el-input v-model="form.relatedResult" autocomplete="off" type="textarea" :rows="2" placeholder="请输入相关检验结果" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="注意事项">
|
||||
<el-input v-model="form.attention" autocomplete="off" type="textarea" :rows="2" placeholder="请输入检查注意事项" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第四行:相关结果 + 注意事项 -->
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="相关结果">
|
||||
<el-input v-model="form.relatedResult" autocomplete="off" type="textarea" :rows="2" placeholder="请输入相关检验结果" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="注意事项">
|
||||
<el-input v-model="form.attention" autocomplete="off" type="textarea" :rows="2" placeholder="请输入检查注意事项" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<!-- 过敏史卡片 -->
|
||||
<div class="section-card allergy-card">
|
||||
<div class="section-header">
|
||||
<el-icon><Warning /></el-icon>
|
||||
<span>过敏史</span>
|
||||
<span v-if="form.allergyHistory" class="header-count">{{ form.allergyHistory.length }}字</span>
|
||||
</div>
|
||||
<div class="allergy-content">
|
||||
<div class="allergy-input-row">
|
||||
<!-- 第五行:检查目的 + 病史摘要 -->
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="检查目的" prop="examinationPurpose">
|
||||
<el-input
|
||||
v-model="form.allergyHistory"
|
||||
v-model="form.examinationPurpose"
|
||||
autocomplete="off"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
:class="{ 'allergy-danger': isSevereAllergy }"
|
||||
placeholder="如:造影剂过敏史等(系统将自动从患者档案带入)"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="请输入检查目的,如:明确诊断、术后复查、疗效评估等"
|
||||
/>
|
||||
<span v-if="isSevereAllergy" class="allergy-severe-tag">
|
||||
<el-icon><WarningFilled /></el-icon>
|
||||
严重过敏
|
||||
</span>
|
||||
</div>
|
||||
<div class="allergy-confirm">
|
||||
<el-checkbox v-model="form.allergyConfirmed" size="small">
|
||||
已通过口头询问确认无过敏史
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="病史摘要" prop="medicalHistorySummary">
|
||||
<div class="history-field-wrapper">
|
||||
<el-input
|
||||
v-model="form.medicalHistorySummary"
|
||||
autocomplete="off"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请简要描述患者病史摘要"
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
size="small"
|
||||
class="history-sync-btn"
|
||||
@click="handleSyncHistory"
|
||||
:loading="syncingHistory"
|
||||
>
|
||||
<el-icon><Refresh /></el-icon>
|
||||
同步
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 检查目的卡片 -->
|
||||
<div class="section-card purpose-card">
|
||||
<div class="section-header">
|
||||
<el-icon><Aim /></el-icon>
|
||||
<span>检查目的</span>
|
||||
<span class="required-mark">必填</span>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="form.examinationPurpose"
|
||||
autocomplete="off"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="请输入检查目的,如:明确诊断、术后复查、疗效评估等"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 病史摘要卡片 -->
|
||||
<div class="section-card history-card">
|
||||
<div class="section-header">
|
||||
<el-icon><DocumentCopy /></el-icon>
|
||||
<span>病史摘要</span>
|
||||
<span class="required-mark">必填</span>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
size="small"
|
||||
class="sync-btn"
|
||||
@click="handleSyncHistory"
|
||||
:loading="syncingHistory"
|
||||
>
|
||||
<el-icon><Refresh /></el-icon>
|
||||
同步现病史/体征
|
||||
</el-button>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="form.medicalHistorySummary"
|
||||
autocomplete="off"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请简要描述患者病史摘要"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 第六行:过敏史 -->
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="过敏史">
|
||||
<div class="allergy-wrapper">
|
||||
<el-input
|
||||
v-model="form.allergyHistory"
|
||||
autocomplete="off"
|
||||
type="textarea"
|
||||
:rows="1"
|
||||
:class="{ 'allergy-danger': isSevereAllergy }"
|
||||
placeholder="如:造影剂过敏史等(系统将自动从患者档案带入)"
|
||||
/>
|
||||
<div class="allergy-actions">
|
||||
<span v-if="isSevereAllergy" class="allergy-severe-tag-inline">
|
||||
<el-icon><WarningFilled /></el-icon>
|
||||
严重过敏
|
||||
</span>
|
||||
<el-checkbox v-model="form.allergyConfirmed" size="small">
|
||||
已通过口头询问确认无过敏史
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 急诊确认弹窗 -->
|
||||
@@ -227,7 +201,8 @@
|
||||
</template>
|
||||
|
||||
<script setup name="MedicalExaminations">
|
||||
import {getCurrentInstance, onMounted, reactive, ref, watch, computed} from 'vue';
|
||||
import {getCurrentInstance, onMounted, reactive, ref, watch, computed, nextTick} from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import {patientInfo} from '../../../store/patient.js';
|
||||
import {getDepartmentList} from '@/api/public.js';
|
||||
import {getEncounterDiagnosis} from '../../api.js';
|
||||
@@ -251,7 +226,26 @@ const findTreeItem = (list, id) => {
|
||||
};
|
||||
|
||||
const emits = defineEmits(['submitOk']);
|
||||
const props = defineProps({});
|
||||
const props = defineProps({
|
||||
isEditMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
editData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
// 支持通过props传入patientInfo
|
||||
externalPatientInfo: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
// 优先使用外部传入的patientInfo,否则使用store中的
|
||||
const effectivePatientInfo = computed(() => {
|
||||
return props.externalPatientInfo || patientInfo.value;
|
||||
});
|
||||
const orgOptions = ref([]);
|
||||
const state = reactive({});
|
||||
const applicationListAll = ref();
|
||||
@@ -272,7 +266,9 @@ const isSevereAllergy = computed(() => {
|
||||
});
|
||||
|
||||
const getList = () => {
|
||||
if (!patientInfo.value?.inHospitalOrgId) {
|
||||
console.log('getList called, effectivePatientInfo:', effectivePatientInfo.value);
|
||||
if (!effectivePatientInfo.value?.inHospitalOrgId) {
|
||||
console.log('inHospitalOrgId is missing, setting empty list');
|
||||
applicationList.value = [];
|
||||
return;
|
||||
}
|
||||
@@ -281,7 +277,7 @@ const getList = () => {
|
||||
pageSize: 500,
|
||||
pageNum: 1,
|
||||
categoryCode: '23',
|
||||
organizationId: patientInfo.value.inHospitalOrgId,
|
||||
organizationId: effectivePatientInfo.value.inHospitalOrgId,
|
||||
adviceTypes: [3],
|
||||
})
|
||||
.then((res) => {
|
||||
@@ -298,6 +294,24 @@ const getList = () => {
|
||||
key: item.adviceDefinitionId,
|
||||
};
|
||||
});
|
||||
// 编辑模式下,加载完数据后设置已选项目
|
||||
if (props.isEditMode && props.editData?.requestFormDetailList?.length) {
|
||||
nextTick(() => {
|
||||
// 使用 adviceName 匹配
|
||||
const selectedNames = props.editData.requestFormDetailList.map(item => item.adviceName);
|
||||
console.log('getList completed, selectedNames:', selectedNames);
|
||||
const selectedIds = [];
|
||||
applicationList.value?.forEach(app => {
|
||||
// 匹配时去掉价格部分,只比较名称
|
||||
const appName = app.label?.split(' (')[0];
|
||||
if (selectedNames.includes(appName)) {
|
||||
selectedIds.push(app.key);
|
||||
}
|
||||
});
|
||||
console.log('getList completed, matched selectedIds:', selectedIds);
|
||||
transferValue.value = selectedIds;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
proxy.$message.error(res.message);
|
||||
applicationList.value = [];
|
||||
@@ -316,7 +330,7 @@ const form = reactive({
|
||||
allergyHistory: '',
|
||||
examinationPurpose: '',
|
||||
medicalHistorySummary: '',
|
||||
expectedExaminationTime: '',
|
||||
expectedExaminationTime: dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'),
|
||||
symptom: '',
|
||||
sign: '',
|
||||
clinicalDiagnosis: '',
|
||||
@@ -387,12 +401,65 @@ const handleSyncHistory = async () => {
|
||||
|
||||
// 自动带入患者过敏史
|
||||
const loadPatientAllergyHistory = () => {
|
||||
if (!patientInfo.value?.patientId) return;
|
||||
if (!effectivePatientInfo.value?.patientId) return;
|
||||
};
|
||||
|
||||
// 加载编辑数据
|
||||
const loadEditData = () => {
|
||||
if (!props.isEditMode || !props.editData?.requestFormId) return;
|
||||
|
||||
console.log('loadEditData called, editData:', props.editData);
|
||||
|
||||
// 解析已有的descJson填充表单
|
||||
if (props.editData.descJson) {
|
||||
try {
|
||||
const obj = JSON.parse(props.editData.descJson);
|
||||
form.targetDepartment = obj.targetDepartment || '';
|
||||
form.urgencyLevel = obj.urgencyLevel || 'routine';
|
||||
form.allergyHistory = obj.allergyHistory || '';
|
||||
form.examinationPurpose = obj.examinationPurpose || '';
|
||||
form.medicalHistorySummary = obj.medicalHistorySummary || '';
|
||||
form.expectedExaminationTime = obj.expectedExaminationTime || '';
|
||||
form.symptom = obj.symptom || '';
|
||||
form.sign = obj.sign || '';
|
||||
form.clinicalDiagnosis = obj.clinicalDiagnosis || '';
|
||||
form.otherDiagnosis = obj.otherDiagnosis || '';
|
||||
form.relatedResult = obj.relatedResult || '';
|
||||
form.attention = obj.attention || '';
|
||||
form.allergyConfirmed = obj.allergyConfirmed || false;
|
||||
} catch (e) {
|
||||
console.error('解析descJson失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置已选项目(从requestFormDetailList获取)
|
||||
// 注意:后端返回的字段是 adviceName,不是 adviceDefinitionId
|
||||
console.log('requestFormDetailList:', props.editData.requestFormDetailList);
|
||||
if (props.editData.requestFormDetailList && props.editData.requestFormDetailList.length) {
|
||||
// 使用 adviceName 匹配
|
||||
const selectedNames = props.editData.requestFormDetailList.map(item => item.adviceName);
|
||||
console.log('setting transferValue by adviceName to:', selectedNames);
|
||||
// 通过名称匹配找到对应的 key
|
||||
const selectedIds = [];
|
||||
applicationList.value?.forEach(app => {
|
||||
if (selectedNames.includes(app.label?.split(' (')[0])) {
|
||||
selectedIds.push(app.key);
|
||||
}
|
||||
});
|
||||
console.log('matched selectedIds:', selectedIds);
|
||||
transferValue.value = selectedIds;
|
||||
} else {
|
||||
console.log('requestFormDetailList is empty or undefined');
|
||||
}
|
||||
};
|
||||
|
||||
const projectWithDepartment = (selectProjectIds, type) => {
|
||||
let isRelease = true;
|
||||
const arr = [];
|
||||
// 确保 applicationList 存在
|
||||
if (!applicationList.value || !Array.isArray(applicationList.value)) {
|
||||
return true;
|
||||
}
|
||||
selectProjectIds.forEach((element) => {
|
||||
const searchData = applicationList.value.find((item) => {
|
||||
return element == item.adviceDefinitionId;
|
||||
@@ -429,9 +496,28 @@ const projectWithDepartment = (selectProjectIds, type) => {
|
||||
};
|
||||
|
||||
watch(() => transferValue.value, (newValue) => {
|
||||
console.log('transferValue changed:', newValue);
|
||||
console.log('applicationList length:', applicationList.value?.length);
|
||||
projectWithDepartment(newValue, 1);
|
||||
});
|
||||
|
||||
// 监听 applicationList 加载完成,重新设置已选项目
|
||||
watch(() => applicationList.value, (newList) => {
|
||||
if (newList && newList.length > 0 && props.isEditMode && props.editData?.requestFormDetailList?.length) {
|
||||
console.log('applicationList loaded, re-setting transferValue');
|
||||
// 使用 adviceName 匹配
|
||||
const selectedNames = props.editData.requestFormDetailList.map(item => item.adviceName);
|
||||
const selectedIds = [];
|
||||
newList.forEach(app => {
|
||||
const appName = app.label?.split(' (')[0];
|
||||
if (selectedNames.includes(appName)) {
|
||||
selectedIds.push(app.key);
|
||||
}
|
||||
});
|
||||
transferValue.value = selectedIds;
|
||||
}
|
||||
});
|
||||
|
||||
const getPriorityCode = () => {
|
||||
return form.urgencyLevel === 'emergency' ? 1 : 0;
|
||||
};
|
||||
@@ -470,28 +556,32 @@ const submit = () => {
|
||||
adviceType: item.adviceType,
|
||||
definitionId: item.priceList[0].definitionId,
|
||||
definitionDetailId: item.priceList[0].definitionDetailId,
|
||||
accountId: patientInfo.value.accountId,
|
||||
accountId: effectivePatientInfo.value.accountId,
|
||||
};
|
||||
});
|
||||
|
||||
const submitForm = { ...form, priorityCode: getPriorityCode() };
|
||||
console.log('提交 descJson:', JSON.stringify(submitForm));
|
||||
|
||||
// 如果是编辑模式,带上requestFormId
|
||||
const requestFormId = props.isEditMode ? props.editData?.requestFormId : '';
|
||||
|
||||
saveCheckd({
|
||||
activityList: applicationListAllFilter,
|
||||
patientId: patientInfo.value.patientId,
|
||||
encounterId: patientInfo.value.encounterId,
|
||||
organizationId: patientInfo.value.inHospitalOrgId,
|
||||
requestFormId: '',
|
||||
name: transferValue.value.map(id => {
|
||||
const item = applicationListAll.value?.find(i => i.adviceDefinitionId === id);
|
||||
return item?.adviceName || '';
|
||||
}).filter(Boolean).join('、'),
|
||||
patientId: effectivePatientInfo.value.patientId,
|
||||
encounterId: effectivePatientInfo.value.encounterId,
|
||||
organizationId: effectivePatientInfo.value.inHospitalOrgId,
|
||||
requestFormId: requestFormId,
|
||||
name: applicationListAllFilter.map(item => item.adviceName).join('、'),
|
||||
descJson: JSON.stringify(submitForm),
|
||||
categoryEnum: '2',
|
||||
categoryEnum: '22',
|
||||
}).then((res) => {
|
||||
if (res.code === 200) {
|
||||
proxy.$message.success(res.msg);
|
||||
if (props.isEditMode) {
|
||||
proxy.$message.success(res.msg || '修改成功');
|
||||
} else {
|
||||
proxy.$message.success(res.msg);
|
||||
}
|
||||
applicationList.value = [];
|
||||
resetForm();
|
||||
emits('submitOk');
|
||||
@@ -507,7 +597,7 @@ const resetForm = () => {
|
||||
form.allergyHistory = '';
|
||||
form.examinationPurpose = '';
|
||||
form.medicalHistorySummary = '';
|
||||
form.expectedExaminationTime = '';
|
||||
form.expectedExaminationTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss');
|
||||
form.symptom = '';
|
||||
form.sign = '';
|
||||
form.clinicalDiagnosis = '';
|
||||
@@ -527,7 +617,7 @@ const getLocationInfo = () => {
|
||||
};
|
||||
|
||||
function getDiagnosisList() {
|
||||
getEncounterDiagnosis(patientInfo.value.encounterId).then((res) => {
|
||||
getEncounterDiagnosis(effectivePatientInfo.value.encounterId).then((res) => {
|
||||
if (res.code == 200) {
|
||||
const datas = (res.data || []).map((item) => {
|
||||
let obj = { ...item };
|
||||
@@ -548,9 +638,24 @@ onMounted(() => {
|
||||
getList();
|
||||
getLocationInfo();
|
||||
loadPatientAllergyHistory();
|
||||
loadEditData();
|
||||
});
|
||||
|
||||
defineExpose({ state, submit, getLocationInfo, getDiagnosisList, resetForm });
|
||||
// 监听编辑模式变化,重新加载数据
|
||||
watch(
|
||||
() => props.isEditMode,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
nextTick(() => {
|
||||
getList();
|
||||
getLocationInfo();
|
||||
loadEditData();
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
defineExpose({ state, submit, getLocationInfo, getDiagnosisList, resetForm, getList });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -575,81 +680,13 @@ $bg-color: #f5f7fa;
|
||||
background: $bg-color;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||
|
||||
// 顶部标题栏
|
||||
.form-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 14px 20px;
|
||||
background: linear-gradient(135deg, #fff 0%, #f0f7ff 100%);
|
||||
border-bottom: 1px solid $border-color;
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
.header-icon {
|
||||
font-size: 24px;
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: $text-primary;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.urgency-label {
|
||||
font-size: 13px;
|
||||
color: $text-secondary;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.urgency-radio-group {
|
||||
:deep(.el-radio-button__inner) {
|
||||
border-radius: 4px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.el-radio-button:first-child .el-radio-button__inner) {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-radio-button:last-child .el-radio-button__inner) {
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.emergency-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: $danger-color;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
background: #fef0f0;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #fde2e2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 主体内容区
|
||||
// 主体内容区 - 紧凑布局
|
||||
.form-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
@@ -666,47 +703,30 @@ $bg-color: #f5f7fa;
|
||||
}
|
||||
}
|
||||
|
||||
// 卡片通用样式
|
||||
// 紧急程度栏 - 右上角
|
||||
.urgency-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 4px 0;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.urgency-bar-label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: $text-regular;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// 卡片通用样式 - 紧凑
|
||||
.section-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
border: 1px solid rgba(0, 0, 0, 0.04);
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px dashed $border-color;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $text-primary;
|
||||
|
||||
> i {
|
||||
font-size: 16px;
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
.header-count {
|
||||
margin-left: auto;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
.required-mark {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
background: $danger-color;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.transfer-wrapper {
|
||||
@@ -720,10 +740,23 @@ $bg-color: #f5f7fa;
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
}
|
||||
// 信息表单
|
||||
|
||||
// 穿梭框按钮垂直居中
|
||||
:deep(.el-transfer__buttons) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
:deep(.el-transfer__button) {
|
||||
margin: 4px 0;
|
||||
}
|
||||
// 信息表单 - 紧凑
|
||||
.info-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 14px;
|
||||
margin-bottom: 6px;
|
||||
|
||||
.el-form-item__label {
|
||||
font-size: 13px;
|
||||
@@ -753,53 +786,10 @@ $bg-color: #f5f7fa;
|
||||
}
|
||||
}
|
||||
|
||||
// 过敏史卡片
|
||||
.allergy-card {
|
||||
.allergy-content {
|
||||
.allergy-input-row {
|
||||
position: relative;
|
||||
|
||||
:deep(.el-textarea) {
|
||||
.el-textarea__inner.allergy-danger {
|
||||
border-color: $danger-color !important;
|
||||
background-color: #fef0f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.allergy-severe-tag {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: $danger-color;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
background: #fef0f0;
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #fde2e2;
|
||||
}
|
||||
|
||||
.allergy-confirm {
|
||||
margin-top: 10px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 病史摘要卡片
|
||||
.history-card {
|
||||
.section-header {
|
||||
.sync-btn {
|
||||
margin-left: auto;
|
||||
font-size: 12px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
}
|
||||
// 过敏史危险输入样式
|
||||
:deep(.el-textarea__inner.allergy-danger) {
|
||||
border-color: $danger-color !important;
|
||||
background-color: #fef0f0;
|
||||
}
|
||||
|
||||
// 急诊确认弹窗
|
||||
@@ -838,4 +828,64 @@ $bg-color: #f5f7fa;
|
||||
.fade-in-linear-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 紧急程度行内布局 */
|
||||
.urgency-inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.emergency-tip-inline {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
color: $danger-color;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
background: #fef0f0;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 过敏史包装 */
|
||||
.allergy-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.allergy-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.allergy-severe-tag-inline {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
color: $danger-color;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
background: #fef0f0;
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* 病史摘要同步按钮 */
|
||||
.history-field-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.history-sync-btn {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: -28px;
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
height: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -267,7 +267,7 @@ const submit = () => {
|
||||
requestFormId: '', // 申请单ID
|
||||
name: '手术申请单',
|
||||
descJson: JSON.stringify(form),
|
||||
categoryEnum: '4', // 1 检验 2 检查 3 输血 4 手术
|
||||
categoryEnum: '24', // 21 检验 22 检查 23 输血 24 手术(避开 adviceType 1-6 碰撞)
|
||||
}).then((res) => {
|
||||
if (res.code === 200) {
|
||||
proxy.$message.success(res.msg);
|
||||
|
||||
@@ -952,7 +952,9 @@ function handleChange(value) {
|
||||
// 修复Bug #486:当行没有显式选择医嘱类型时(row?.adviceType为undefined),
|
||||
// 不传categoryCode,让搜索在全药库中进行;只有行已选择类型时才用对应categoryCode过滤
|
||||
const categoryCode = row?.adviceType !== undefined ? (selectedItem ? selectedItem.categoryCode : '') : '';
|
||||
tableRef.refresh(adviceType, categoryCode, value);
|
||||
// 修复Bug #453:当adviceType为空字符串或NaN时,不传具体类型,让refresh函数根据searchKey决定搜索范围
|
||||
const effectiveAdviceType = (adviceType && !isNaN(Number(adviceType))) ? adviceType : '';
|
||||
tableRef.refresh(effectiveAdviceType, categoryCode, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1223,7 +1225,7 @@ function handleSave() {
|
||||
groupId: item.groupId,
|
||||
uniqueKey: undefined,
|
||||
// 确保 therapyEnum 被正确传递
|
||||
therapyEnum: parsedContent.therapyEnum || item.therapyEnum || '1',
|
||||
therapyEnum: parsedContent?.therapyEnum || item.therapyEnum || '1',
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -519,17 +519,31 @@ watch(
|
||||
}
|
||||
);
|
||||
|
||||
// 加载科室选项
|
||||
// 加载科室选项(支持树形/扁平两种数据结构)
|
||||
function loadDepartmentOptions() {
|
||||
getOrgList()
|
||||
.then((res) => {
|
||||
if (res.data && res.data.records && res.data.records.length > 0) {
|
||||
const firstRecord = res.data.records[0];
|
||||
// 优先使用 children(树形结构),回退到 records 本身(扁平结构)
|
||||
departmentOptions.value = (firstRecord.children && firstRecord.children.length > 0)
|
||||
? firstRecord.children
|
||||
: res.data.records;
|
||||
if (res.data) {
|
||||
// 尝试从树形结构中取:records[0].children
|
||||
if (res.data.records && res.data.records.length > 0) {
|
||||
if (res.data.records[0].children && res.data.records[0].children.length > 0) {
|
||||
departmentOptions.value = res.data.records[0].children;
|
||||
return;
|
||||
}
|
||||
// 如果 records[0] 有 id 和 name(非树根节点),直接用所有 records
|
||||
if (res.data.records[0].id) {
|
||||
departmentOptions.value = res.data.records;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 兜底:如果 records 不存在或为空,尝试直接使用 data 本身
|
||||
if (Array.isArray(res.data)) {
|
||||
departmentOptions.value = res.data;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 所有方式都失败,置空
|
||||
departmentOptions.value = [];
|
||||
})
|
||||
.catch(() => {
|
||||
console.warn('科室列表加载失败(可能无权限)');
|
||||
@@ -593,12 +607,14 @@ function getItemType_Text(type) {
|
||||
}
|
||||
function getUnitCodeOptions(row) {
|
||||
const unitCodes = [
|
||||
{ code: String(row.unitCode), codeText: row.unitCode_dictText },
|
||||
{ code: String(row.minUnitCode), codeText: row.minUnitCode_dictText },
|
||||
].filter(item => item.code);
|
||||
{ code: row.unitCode != null ? String(row.unitCode) : null, codeText: row.unitCode_dictText },
|
||||
{ code: row.minUnitCode != null ? String(row.minUnitCode) : null, codeText: row.minUnitCode_dictText },
|
||||
];
|
||||
// 过滤掉 code 为空的单位选项
|
||||
const validUnitCodes = unitCodes.filter(item => item.code != null && item.code !== '');
|
||||
// 使用 Set 来跟踪已经存在的 code
|
||||
const seenCodes = new Set();
|
||||
const uniqueUnitCodes = unitCodes.filter((item) => {
|
||||
const uniqueUnitCodes = validUnitCodes.filter((item) => {
|
||||
// 如果 Set 中没有这个 code,就保留它,并把它加入 Set
|
||||
if (!seenCodes.has(item.code)) {
|
||||
seenCodes.add(item.code);
|
||||
@@ -667,14 +683,36 @@ function selectChange(row) {
|
||||
defaultUnitCode = String(row.minUnitCode);
|
||||
} else if (row.unitCode) {
|
||||
defaultUnitCode = String(row.unitCode);
|
||||
} else if (row.minUnitCode_dictText) {
|
||||
// 兜底:如果 minUnitCode 为空但字典文本存在,使用文本作为选项值
|
||||
defaultUnitCode = row.minUnitCode_dictText;
|
||||
} else if (row.unitCode_dictText) {
|
||||
defaultUnitCode = row.unitCode_dictText;
|
||||
}
|
||||
// 如果默认单位不在 uniqueUnitCodes 中,添加兜底选项
|
||||
if (defaultUnitCode && !uniqueUnitCodes.some(u => u.code === defaultUnitCode)) {
|
||||
uniqueUnitCodes.push({ code: defaultUnitCode, codeText: defaultUnitCode });
|
||||
}
|
||||
// 设置默认执行科室/位置(统一转为字符串,避免 el-select 类型不匹配)
|
||||
let defaultPositionId = undefined;
|
||||
if (row.adviceType === 3 && departmentOptions.value.length > 0) {
|
||||
// 诊疗:优先使用患者所在科室,否则取第一个科室
|
||||
const patientOrgId = props.patientInfo.organizationId;
|
||||
const matched = departmentOptions.value.find(d => String(d.id) === String(patientOrgId));
|
||||
defaultPositionId = matched ? String(matched.id) : String(departmentOptions.value[0].id);
|
||||
// 诊疗:
|
||||
// 1. 优先使用诊疗目录项目的"所属科室"(row.orgId)
|
||||
// 2. 其次使用患者当前病房科室(patientInfo.organizationId)
|
||||
// 3. 最后取第一个科室
|
||||
const orgIdPriority = [row.orgId, props.patientInfo.organizationId];
|
||||
for (const id of orgIdPriority) {
|
||||
if (id) {
|
||||
const matched = departmentOptions.value.find(d => String(d.id) === String(id));
|
||||
if (matched) {
|
||||
defaultPositionId = String(matched.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!defaultPositionId) {
|
||||
defaultPositionId = String(departmentOptions.value[0].id);
|
||||
}
|
||||
} else if (row.adviceType === 2 && locationOptions.value.length > 0) {
|
||||
// 耗材:默认取第一个药房/耗材房
|
||||
defaultPositionId = String(locationOptions.value[0].value);
|
||||
|
||||
@@ -458,7 +458,9 @@ const loadPatientInfo = () => {
|
||||
'YYYY-MM-DD HH:mm:ss'
|
||||
);
|
||||
} else {
|
||||
interventionForm.value.startTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss');
|
||||
// 已有患者(entranceType == 1)不自动填充当前时间,避免覆盖历史数据
|
||||
// 新入科患者由后端默认返回当前时间,或由用户手动选择
|
||||
interventionForm.value.startTime = '';
|
||||
}
|
||||
interventionForm.value.height = res.data.height;
|
||||
interventionForm.value.weight = res.data.weight;
|
||||
|
||||
@@ -459,10 +459,15 @@ function handleGetPrescription(skipAutoSelectAll = false) {
|
||||
// 执行
|
||||
function handleExecute() {
|
||||
let list = getSelectRows();
|
||||
if (list.length === 0) {
|
||||
proxy.$modal.msgWarning('请选择需要执行的医嘱');
|
||||
return;
|
||||
}
|
||||
let encounterIds = patientInfoList.value.map((i) => i.encounterId).join(',');
|
||||
list = list.map((item) => {
|
||||
return {
|
||||
requestId: item.requestId,
|
||||
encounterId: item.encounterId,
|
||||
accountId: item.accountId,
|
||||
adviceTable: item.adviceTable,
|
||||
executeTimes: item.executeTimes,
|
||||
@@ -473,14 +478,23 @@ function handleExecute() {
|
||||
if (res.code == 200) {
|
||||
// 仅当选中医嘱中包含诊疗类医嘱(可能绑定耗材)时,才调用耗材批号匹配
|
||||
// adviceTable 取值为 med_medication_request(药品)或 wor_service_request(诊疗/耗材)
|
||||
// 原代码用 includes('device') 判断有误,两个表名均不含 "device" 字符串
|
||||
const hasServiceRequest = list.some((item) =>
|
||||
String(item.adviceTable || '') === 'wor_service_request',
|
||||
);
|
||||
if (hasServiceRequest) {
|
||||
lotNumberMatch({ encounterIdList: encounterIds }, { skipErrorMsg: true }).catch((error) => {
|
||||
console.warn('lotNumberMatch failed after adviceExecute:', error);
|
||||
});
|
||||
// 仅传入选中医嘱对应的 encounterId,避免其他患者的耗材记录干扰
|
||||
const selectedEncounterIds = [...new Set(list.map((item) => item.encounterId).filter(Boolean))];
|
||||
if (selectedEncounterIds.length > 0) {
|
||||
lotNumberMatch({ encounterIdList: selectedEncounterIds }, { skipErrorMsg: true })
|
||||
.then((matchRes) => {
|
||||
if (matchRes && matchRes.code !== 200) {
|
||||
console.warn('lotNumberMatch returned error:', matchRes.msg);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('lotNumberMatch failed after adviceExecute:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
// 刷新列表(不自动全选,保持用户操作前的选择状态)
|
||||
handleGetPrescription(true);
|
||||
@@ -488,6 +502,9 @@ function handleExecute() {
|
||||
} else {
|
||||
proxy.$modal.msgError(res.msg || '医嘱执行失败');
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error('医嘱执行接口调用失败:', error);
|
||||
proxy.$modal.msgError('医嘱执行失败,请稍后重试');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,11 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
/** 表头所选出库仓库:传入后药品列表只含该仓有库存的行,避免选到别仓批号导致 inventory-item-info 一直为 0 */
|
||||
orgLocationId: {
|
||||
type: [String, Number],
|
||||
default: undefined,
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['selectRow']);
|
||||
const queryParams = ref({
|
||||
@@ -89,11 +93,14 @@ watch(
|
||||
queryParams.value.searchKey = newValue.searchKey;
|
||||
queryParams.value.itemType = newValue.itemType;
|
||||
queryParams.value.purchaseFlag = 0;
|
||||
// queryParams.value.sourceLocationId = newValue.sourceLocationId;
|
||||
// queryParams.value.purposeLocationId = newValue.purposeLocationId;
|
||||
if (newValue.orgLocationId != null && newValue.orgLocationId !== '') {
|
||||
queryParams.value.orgLocationId = newValue.orgLocationId;
|
||||
} else {
|
||||
delete queryParams.value.orgLocationId;
|
||||
}
|
||||
throttledGetList();
|
||||
},
|
||||
{ immdiate: true, deep: true }
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
getList();
|
||||
|
||||
@@ -133,6 +133,7 @@
|
||||
filterable
|
||||
style="width: 200px"
|
||||
:disabled="data.isEdit"
|
||||
@change="onHeaderWarehouseChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in purposeTypeListOptions"
|
||||
@@ -222,6 +223,7 @@
|
||||
@selectRow="(row) => selectRow(row, scope.$index)"
|
||||
:searchKey="medicineSearchKey"
|
||||
:itemType="itemType"
|
||||
:orgLocationId="receiptHeaderForm.headerLocationId"
|
||||
/>
|
||||
</template>
|
||||
</PopoverList>
|
||||
@@ -483,6 +485,46 @@ import {useStore} from '@/store/store';
|
||||
import useTagsViewStore from '@/store/modules/tagsView';
|
||||
import TraceNoDialog from '@/components/OpenHis/TraceNoDialog/index.vue'
|
||||
|
||||
/** 领用保存 IssueDto:后端 Jackson 只认 yyyy-MM-dd HH:mm:ss;库存接口可能回传 2025/4/2 00:00:00 等 */
|
||||
function toIssueDateTimeStr(val) {
|
||||
if (val == null || val === '') return undefined;
|
||||
if (typeof val === 'string') {
|
||||
const s = val.trim();
|
||||
if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(s)) return s;
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return `${s} 00:00:00`;
|
||||
}
|
||||
const d = val instanceof Date ? val : new Date(val);
|
||||
if (Number.isNaN(d.getTime())) return undefined;
|
||||
return formatDate(d);
|
||||
}
|
||||
|
||||
/** 总库存 totalQuantity 为最小单位;领用数量按当前计量单位折算成最小单位后再比较 */
|
||||
function getRequisitionQtyInMinUnit(r) {
|
||||
const q = Number(r.itemQuantity ?? 0);
|
||||
if (!Number.isFinite(q) || q < 0) return NaN;
|
||||
const minCode = r.unitList?.minUnitCode;
|
||||
if (!minCode || r.unitCode === minCode) return q;
|
||||
const part = Number(r.partPercent ?? 1);
|
||||
return q * part;
|
||||
}
|
||||
|
||||
function validateRequisitionQtyVsStock(r, lineNo) {
|
||||
const cap = Number(r.totalQuantity ?? 0);
|
||||
const reqMin = getRequisitionQtyInMinUnit(r);
|
||||
if (!Number.isFinite(reqMin)) {
|
||||
return `第${lineNo}行:领用数量请输入有效数字`;
|
||||
}
|
||||
if (cap > 0 && reqMin > cap + 1e-9) {
|
||||
const name = r.name || `第${lineNo}行`;
|
||||
return `${name}:领用数量(折合最小单位)不能超过当前仓库可领库存 ${cap},请修改后再保存。`;
|
||||
}
|
||||
if (cap <= 0 && reqMin > 0) {
|
||||
const name = r.name || `第${lineNo}行`;
|
||||
return `${name}:当前仓库可领库存为 0,不能填写正数领用数量。`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const tagsViewStore = useTagsViewStore();
|
||||
const store = useStore();
|
||||
|
||||
@@ -568,6 +610,8 @@ const data = reactive({
|
||||
medicationType: [{ required: true, message: '请选择药品类型', trigger: 'change' }],
|
||||
locationId: [{ required: true, message: '请选择领用部门', trigger: 'change' }],
|
||||
practitionerId: [{ required: true, message: '请选择部门经手人', trigger: 'change' }],
|
||||
// 领用出库按「表头仓库」查 /app-common/inventory-item-info,未选仓库会查不到库存并误报「仓库数量为0」
|
||||
headerLocationId: [{ required: true, message: '请先选择仓库(按仓库查询可领用库存)', trigger: 'change' }],
|
||||
},
|
||||
tableRules: {
|
||||
name: [{ required: true, message: '项目不能为空', trigger: 'change' }],
|
||||
@@ -988,10 +1032,9 @@ function selectRow(rowValue, index) {
|
||||
form.purchaseinventoryList[index].unitList = rowValue.unitList[0];
|
||||
form.purchaseinventoryList[index].lotNumber = rowValue.lotNumber;
|
||||
form.purchaseinventoryList[index].ybNo = rowValue.ybNo;
|
||||
// #439 fix: 不清空sourceLocationId,保留handleAddRow设置的仓库ID
|
||||
if (!form.purchaseinventoryList[index].sourceLocationId) {
|
||||
form.purchaseinventoryList[index].sourceLocationId = receiptHeaderForm.headerLocationId || '';
|
||||
}
|
||||
// 出库仓库:优先表头当前所选仓库(避免先选药后选仓时行上一直为空)
|
||||
form.purchaseinventoryList[index].sourceLocationId =
|
||||
receiptHeaderForm.headerLocationId || form.purchaseinventoryList[index].sourceLocationId || '';
|
||||
getPharmacyCabinetList().then((res) => {
|
||||
purposeTypeListOptions.value = res.data;
|
||||
handleLocationClick(1, rowValue, index)
|
||||
@@ -1004,7 +1047,30 @@ function selectRow(rowValue, index) {
|
||||
});
|
||||
}
|
||||
|
||||
// 选择仓库
|
||||
/** 多条库存记录时取可领数量最大的一条(避免仅取 res.data[0] 恰好为 0) */
|
||||
function pickBestOrgQuantityRow(list) {
|
||||
if (!Array.isArray(list) || list.length === 0) return null;
|
||||
return list.reduce((best, cur) => {
|
||||
const cq = Number(cur?.orgQuantity ?? 0);
|
||||
const bq = Number(best?.orgQuantity ?? 0);
|
||||
return cq > bq ? cur : best;
|
||||
});
|
||||
}
|
||||
|
||||
/** 表头「仓库」变化:同步每行 sourceLocationId 并重新拉库存(修复先选药品后选仓库行上仍无仓库 ID) */
|
||||
function onHeaderWarehouseChange() {
|
||||
const hid = receiptHeaderForm.headerLocationId;
|
||||
form.purchaseinventoryList.forEach((r) => {
|
||||
r.sourceLocationId = hid || '';
|
||||
});
|
||||
form.purchaseinventoryList.forEach((r, idx) => {
|
||||
if (hid && r.itemId) {
|
||||
handleLocationClick(1, {}, idx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 选择仓库 / 选药品后拉取该仓库存
|
||||
function handleLocationClick(item, row, index) {
|
||||
getCount({
|
||||
itemId: form.purchaseinventoryList[index].itemId,
|
||||
@@ -1013,67 +1079,100 @@ function handleLocationClick(item, row, index) {
|
||||
if (res.data && res.data.length > 0) {
|
||||
form.purchaseinventoryList[index].itemTable = res.data[0].itemTable || '';
|
||||
form.purchaseinventoryList[index].totalQuantity = res.data[0].orgQuantity || 0;
|
||||
const r = form.purchaseinventoryList[index];
|
||||
let orgLocationId = r.sourceLocationId || receiptHeaderForm.headerLocationId || '';
|
||||
if (!orgLocationId) {
|
||||
proxy.$message.warning('请先在表头选择「仓库」。库存按仓库维度查询,未选仓库无法匹配您看到的总库存。');
|
||||
r.totalQuantity = 0;
|
||||
r.price = 0;
|
||||
return;
|
||||
}
|
||||
if (!r.sourceLocationId) {
|
||||
r.sourceLocationId = orgLocationId;
|
||||
}
|
||||
|
||||
if (res.data[0].price) {
|
||||
form.purchaseinventoryList[index].price = res.data[0].price.toFixed(4);
|
||||
} else {
|
||||
form.purchaseinventoryList[index].price = 0;
|
||||
}
|
||||
// 获取供应商id
|
||||
form.purchaseinventoryList[index].supplierId = res.data[0].supplierId || '';
|
||||
// 生产日期
|
||||
form.purchaseinventoryList[index].startTime = res.data[0].productionDate;
|
||||
// 有效期
|
||||
form.purchaseinventoryList[index].endTime = res.data[0].expirationDate;
|
||||
form.purchaseinventoryList[index].unitCode =
|
||||
form.purchaseinventoryList[index].unitList.minUnitCode;
|
||||
form.purchaseinventoryList[index].unitCode_dictText =
|
||||
form.purchaseinventoryList[index].unitList.minUnitCode_dictText;
|
||||
// 单价 大单位单价
|
||||
console.log(
|
||||
form.purchaseinventoryList[index].unitCode ==
|
||||
form.purchaseinventoryList[index].unitList.minUnitCode,
|
||||
1212121
|
||||
);
|
||||
if (
|
||||
form.purchaseinventoryList[index].unitCode ==
|
||||
form.purchaseinventoryList[index].unitList.minUnitCode
|
||||
) {
|
||||
form.purchaseinventoryList[index].price =
|
||||
res.data[0].price / form.purchaseinventoryList[index].partPercent || '';
|
||||
form.purchaseinventoryList[index].price =
|
||||
form.purchaseinventoryList[index].price.toFixed(4);
|
||||
// parseFloat(form.purchaseinventoryList[index].price.toFixed(4))
|
||||
} else {
|
||||
console.log(
|
||||
form.purchaseinventoryList[index].price > 1,
|
||||
1212,
|
||||
form.purchaseinventoryList[index].price
|
||||
);
|
||||
if (form.purchaseinventoryList[index].price > 1) {
|
||||
form.purchaseinventoryList[index].price =
|
||||
form.purchaseinventoryList[index].price.toFixed(4);
|
||||
const lotTrimmed =
|
||||
r.lotNumber != null && String(r.lotNumber).trim() !== '' ? String(r.lotNumber).trim() : null;
|
||||
|
||||
const runGet = (withLot) => {
|
||||
const params = { itemId: r.itemId, orgLocationId };
|
||||
if (withLot && lotTrimmed) {
|
||||
params.lotNumber = lotTrimmed;
|
||||
}
|
||||
return getCount(params);
|
||||
};
|
||||
|
||||
const applyFromDto = (d, syncLotFromPick) => {
|
||||
if (syncLotFromPick && d.lotNumber != null && d.lotNumber !== '') {
|
||||
r.lotNumber = d.lotNumber;
|
||||
}
|
||||
r.itemTable = d.itemTable || '';
|
||||
r.totalQuantity = d.orgQuantity || 0;
|
||||
if (d.price) {
|
||||
r.price = d.price.toFixed(4);
|
||||
} else {
|
||||
r.price = 0;
|
||||
}
|
||||
r.supplierId = d.supplierId || '';
|
||||
r.startTime = toIssueDateTimeStr(d.productionDate) || '';
|
||||
r.endTime = toIssueDateTimeStr(d.expirationDate) || '';
|
||||
r.unitCode = r.unitList.minUnitCode;
|
||||
r.unitCode_dictText = r.unitList.minUnitCode_dictText;
|
||||
if (r.unitCode == r.unitList.minUnitCode) {
|
||||
r.price = d.price / r.partPercent || '';
|
||||
r.price = r.price.toFixed(4);
|
||||
} else if (r.price > 1) {
|
||||
r.price = r.price.toFixed(4);
|
||||
}
|
||||
};
|
||||
|
||||
const persistStore = () => {
|
||||
store.setCurrentDataLYCK({
|
||||
purchaseinventoryList: form.purchaseinventoryList,
|
||||
receiptHeaderForm: receiptHeaderForm,
|
||||
});
|
||||
};
|
||||
|
||||
runGet(true)
|
||||
.then((res) => {
|
||||
const list = res.data || [];
|
||||
const d = pickBestOrgQuantityRow(list);
|
||||
const strictOk = d && Number(d.orgQuantity ?? 0) > 0;
|
||||
if (strictOk) {
|
||||
applyFromDto(d, false);
|
||||
if (Number(r.totalQuantity) <= 0) {
|
||||
proxy.$message.warning('仓库数量为0,无法调用!');
|
||||
}
|
||||
}
|
||||
if (form.purchaseinventoryList[index].totalQuantity == 0) {
|
||||
proxy.$message.warning('仓库数量为0,无法调用!');
|
||||
persistStore();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
form.purchaseinventoryList[index].totalQuantity = 0;
|
||||
form.purchaseinventoryList[index].price = 0;
|
||||
// if(form.purchaseinventoryList[index].totalQuantity==0){
|
||||
if (lotTrimmed) {
|
||||
return runGet(false).then((res2) => {
|
||||
const list2 = res2.data || [];
|
||||
const d2 = pickBestOrgQuantityRow(list2);
|
||||
if (d2 && Number(d2.orgQuantity ?? 0) > 0) {
|
||||
applyFromDto(d2, true);
|
||||
proxy.$message.info(
|
||||
'所选批号在本仓库无对应库存或批号不一致,已按仓库实物回显批号与可领数量,请核对。'
|
||||
);
|
||||
} else {
|
||||
r.totalQuantity = 0;
|
||||
r.price = 0;
|
||||
proxy.$message.warning('仓库数量为0,无法调用!');
|
||||
}
|
||||
persistStore();
|
||||
});
|
||||
}
|
||||
r.totalQuantity = 0;
|
||||
r.price = 0;
|
||||
proxy.$message.warning('仓库数量为0,无法调用!');
|
||||
// }
|
||||
}
|
||||
}).catch(() => {
|
||||
form.purchaseinventoryList[index].totalQuantity = 0;
|
||||
form.purchaseinventoryList[index].price = 0;
|
||||
});
|
||||
store.setCurrentDataLYCK({
|
||||
purchaseinventoryList: form.purchaseinventoryList,
|
||||
receiptHeaderForm: receiptHeaderForm,
|
||||
});
|
||||
persistStore();
|
||||
})
|
||||
.catch(() => {
|
||||
r.totalQuantity = 0;
|
||||
r.price = 0;
|
||||
persistStore();
|
||||
});
|
||||
}
|
||||
// 切换仓库类型获取药房/药库列表
|
||||
// function handleChangeLocationType(value) {
|
||||
@@ -1230,20 +1329,19 @@ function getMaxCounts(row, index, counts) {
|
||||
}
|
||||
// 计算总价
|
||||
function handleTotalPrice(index) {
|
||||
form.purchaseinventoryList[index].olditemQuantity =
|
||||
form.purchaseinventoryList[index].itemQuantity * row.partPercent;
|
||||
form.purchaseinventoryList[index].itemMaxQuantity =
|
||||
form.purchaseinventoryList[index].itemQuantity;
|
||||
let purchaseItem = form.purchaseinventoryList[index];
|
||||
const r = form.purchaseinventoryList[index];
|
||||
r.olditemQuantity = r.itemQuantity * (r.partPercent ?? 1);
|
||||
r.itemMaxQuantity = r.itemQuantity;
|
||||
let purchaseItem = r;
|
||||
if (purchaseItem.price > 0 && purchaseItem.itemQuantity > 0) {
|
||||
form.purchaseinventoryList[index].totalPrice = purchaseItem.price * purchaseItem.itemQuantity;
|
||||
form.purchaseinventoryList[index].totalPrice =
|
||||
form.purchaseinventoryList[index].totalPrice.toFixed(4);
|
||||
// parseFloat(form.purchaseinventoryList[index].totalPrice.toFixed(4))
|
||||
r.totalPrice = purchaseItem.price * purchaseItem.itemQuantity;
|
||||
r.totalPrice = r.totalPrice.toFixed(4);
|
||||
}
|
||||
if (form.purchaseinventoryList[index].itemQuantity == 0) {
|
||||
form.purchaseinventoryList[index].totalPrice = 0;
|
||||
if (r.itemQuantity == 0) {
|
||||
r.totalPrice = 0;
|
||||
}
|
||||
const qtyErr = validateRequisitionQtyVsStock(r, index + 1);
|
||||
r.error = !!qtyErr;
|
||||
store.setCurrentDataLYCK({
|
||||
purchaseinventoryList: form.purchaseinventoryList,
|
||||
receiptHeaderForm: receiptHeaderForm,
|
||||
@@ -1252,6 +1350,15 @@ function handleTotalPrice(index) {
|
||||
// 保存
|
||||
function handleSave(row, index) {
|
||||
rowList.value = [];
|
||||
for (let i = 0; i < form.purchaseinventoryList.length; i++) {
|
||||
const line = form.purchaseinventoryList[i];
|
||||
if (!line) continue;
|
||||
const err = validateRequisitionQtyVsStock(line, i + 1);
|
||||
if (err) {
|
||||
proxy.$message.warning(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
form.purchaseinventoryList.map((row, index) => {
|
||||
if (row) {
|
||||
// 触发校验
|
||||
@@ -1299,7 +1406,13 @@ function handleSave(row, index) {
|
||||
});
|
||||
}
|
||||
function addTransferProducts(rowList) {
|
||||
addTransferProduct(JSON.parse(JSON.stringify(rowList))).then((res) => {
|
||||
const payload = (Array.isArray(rowList) ? rowList : []).map((item) => ({
|
||||
...item,
|
||||
startTime: toIssueDateTimeStr(item.startTime),
|
||||
endTime: toIssueDateTimeStr(item.endTime),
|
||||
occurrenceTime: toIssueDateTimeStr(item.occurrenceTime) ?? item.occurrenceTime,
|
||||
}));
|
||||
addTransferProduct(JSON.parse(JSON.stringify(payload))).then((res) => {
|
||||
// 当前行没有id视为首次新增
|
||||
// if (!row.id) {
|
||||
// data.isAdding = false; // 允许新增下一行
|
||||
|
||||
@@ -966,7 +966,7 @@ const form = reactive({
|
||||
allergyRemark: undefined,
|
||||
surgeryNature: undefined,
|
||||
surgerySite: undefined,
|
||||
incisionLevel: undefined,
|
||||
incisionType: undefined,
|
||||
surgeryLevel: undefined,
|
||||
|
||||
admissionTime: undefined,
|
||||
@@ -1552,6 +1552,8 @@ function handleMedicalAdvice(row) {
|
||||
// 过滤掉名称为空的项目
|
||||
const medicineName = item.adviceName || item.advice_name;
|
||||
if (!medicineName || medicineName.trim() === '') return false;
|
||||
// 🔧 修复 Bug #445: 过滤掉已生成医嘱的项目(已有 requestId 的不应出现在"待生成"列表)
|
||||
if (item.requestId) return false;
|
||||
// 根据药品请求ID去重,避免重复显示
|
||||
const itemId = item.requestId || item.id;
|
||||
if (itemId && seenIds.has(itemId)) return false;
|
||||
@@ -1739,15 +1741,27 @@ function handleTemporaryMedicalSubmit(data) {
|
||||
}
|
||||
})
|
||||
|
||||
// 同步更新计费药品列表:移除已生成医嘱的项目,避免数据重复显示
|
||||
const submittedIds = new Set(
|
||||
(data.temporaryAdvices || []).map(a => a.originalMedicine?.requestId || a.originalMedicine?.chargeItemId).filter(Boolean)
|
||||
// 🔧 修复 Bug #445: 使用稳定的字段组合匹配已提交项目,而不是依赖可能为空的 requestId/chargeItemId
|
||||
// 构建已提交项目的匹配键集合(药品名称 + 规格 + 数量)
|
||||
const submittedKeys = new Set(
|
||||
(data.temporaryAdvices || [])
|
||||
.map(a => {
|
||||
const om = a.originalMedicine || {}
|
||||
const name = om.medicineName || om.adviceName || om.advice_name || a.adviceName || ''
|
||||
const spec = om.specification || om.volume || ''
|
||||
const qty = om.quantity || 0
|
||||
return `${name}|||${spec}|||${qty}`
|
||||
})
|
||||
.filter(k => k !== '|||0') // 过滤掉空项
|
||||
)
|
||||
if (submittedIds.size > 0) {
|
||||
temporaryBillingMedicines.value = (data.billingMedicines || []).filter(
|
||||
m => !submittedIds.has(m.requestId || m.chargeItemId)
|
||||
)
|
||||
|
||||
if (submittedKeys.size > 0) {
|
||||
temporaryBillingMedicines.value = (temporaryBillingMedicines.value || []).filter(m => {
|
||||
const key = `${m.medicineName || ''}|||${m.specification || ''}|||${m.quantity || 0}`
|
||||
return !submittedKeys.has(key)
|
||||
})
|
||||
} else {
|
||||
// 如果没有任何匹配键,清空待生成列表(所有项目都已提交)
|
||||
temporaryBillingMedicines.value = []
|
||||
}
|
||||
|
||||
@@ -1807,7 +1821,8 @@ function handleQuoteBilling() {
|
||||
temporaryBillingMedicines.value = []
|
||||
temporaryAdvices.value = []
|
||||
|
||||
// 只保留药品类型(adviceType=1),过滤掉耗材(2)和诊疗项目(3)
|
||||
// 🔧 修复 Bug #445: 只保留药品类型(adviceType=1),过滤掉耗材(2)和诊疗项目(3)
|
||||
// 同时过滤掉已有 requestId 的项目(已生成医嘱的不需要再次显示在"待生成"列表中)
|
||||
const filteredItems = res.data.filter(item => {
|
||||
// 匹配 encounterId
|
||||
if (item.encounterId !== temporaryPatientInfo.value.visitId) return false;
|
||||
@@ -1815,7 +1830,10 @@ function handleQuoteBilling() {
|
||||
if (item.adviceType !== 1) return false;
|
||||
// 过滤掉名称为空的项目
|
||||
const medicineName = item.adviceName || item.advice_name;
|
||||
return medicineName && medicineName.trim() !== '';
|
||||
if (!medicineName || medicineName.trim() === '') return false;
|
||||
// 🔧 修复 Bug #445: 过滤掉已生成医嘱的项目(已有 requestId)
|
||||
if (item.requestId) return false;
|
||||
return true;
|
||||
})
|
||||
// 🔧 修复:限制返回数量,最多显示前100条,避免数据过多导致页面卡死
|
||||
const maxItems = 100
|
||||
@@ -2032,7 +2050,12 @@ function resetForm() {
|
||||
function submitForm() {
|
||||
proxy.$refs['surgeryRef'].validate((valid) => {
|
||||
if (valid) {
|
||||
const submitData = { ...form, orgId: userStore.orgId }
|
||||
const submitData = {
|
||||
...form,
|
||||
orgId: userStore.orgId,
|
||||
incisionLevel: form.incisionType
|
||||
}
|
||||
delete submitData.incisionType
|
||||
if (!form.scheduleId) {
|
||||
// 新增手术安排
|
||||
addSurgerySchedule(submitData).then((res) => {
|
||||
|
||||
Reference in New Issue
Block a user