fixbug326,329,334,368: 门诊医生站检验申请模块多项缺陷修复

Bug #326: 检验申请单套餐项目回充数据不完整
  - 后端回充时查询 LabActivityDefinition 补全套餐信
  - DTO 新增 activityId、feePackageId、isPackage、sampleType、unit 字段
  - 前端实现套餐项目树形展开,懒加载套餐明细
  Bug #329: 检验申请执行科室默认值设置错误
  - 后端移除默认执行科室逻辑,添加未匹配科室警告日志
  - 前端从 Organization 表获取执行科室,自动根据检验类型设置默认值
  Bug #334: 检验申请界面顶部操作栏占用空间过大
  - 隐藏顶部操作栏,保存/新增按钮移至卡片头部
  Bug #368: 门诊医生站待写病历标签页功能冗余
  - 屏蔽待写病历标签页(左侧导航栏已有独立菜单)
This commit is contained in:
wangjian963
2026-04-15 14:50:14 +08:00
parent 38b4ff5c92
commit 4e2097fc7b
6 changed files with 489 additions and 102 deletions

View File

@@ -11,10 +11,12 @@ import com.openhis.lab.domain.InspectionLabApply;
import com.openhis.lab.domain.InspectionLabApplyItem; import com.openhis.lab.domain.InspectionLabApplyItem;
import com.openhis.lab.domain.BarCode; import com.openhis.lab.domain.BarCode;
import com.openhis.lab.domain.InspectionPackage; import com.openhis.lab.domain.InspectionPackage;
import com.openhis.lab.domain.LabActivityDefinition;
import com.openhis.lab.service.IInspectionLabApplyItemService; import com.openhis.lab.service.IInspectionLabApplyItemService;
import com.openhis.lab.service.IInspectionLabApplyService; import com.openhis.lab.service.IInspectionLabApplyService;
import com.openhis.lab.service.IInspectionLabBarCodeService; import com.openhis.lab.service.IInspectionLabBarCodeService;
import com.openhis.lab.service.IInspectionPackageService; import com.openhis.lab.service.IInspectionPackageService;
import com.openhis.lab.service.ILabActivityDefinitionService;
import com.openhis.workflow.domain.ServiceRequest; import com.openhis.workflow.domain.ServiceRequest;
import com.openhis.workflow.service.IServiceRequestService; import com.openhis.workflow.service.IServiceRequestService;
import com.openhis.web.doctorstation.appservice.IDoctorStationAdviceAppService; import com.openhis.web.doctorstation.appservice.IDoctorStationAdviceAppService;
@@ -45,6 +47,9 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.function.Function;
/** /**
* 根据检验申请单开单信息系统自动插入门诊医嘱表(与检验申请主表申请单号进行关联), * 根据检验申请单开单信息系统自动插入门诊医嘱表(与检验申请主表申请单号进行关联),
@@ -88,6 +93,10 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
@Autowired @Autowired
private IInspectionPackageService inspectionPackageService; private IInspectionPackageService inspectionPackageService;
// Bug #326: 检验项目定义服务(用于回充时获取套餐信息)
@Autowired
private ILabActivityDefinitionService labActivityDefinitionService;
/** /**
* 保存检验申请单信息 * 保存检验申请单信息
* @param doctorStationLabApplyDto * @param doctorStationLabApplyDto
@@ -160,6 +169,28 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
InspectionLabApplyItem inspectionLabApplyItem = new InspectionLabApplyItem(); InspectionLabApplyItem inspectionLabApplyItem = new InspectionLabApplyItem();
BeanUtils.copyProperties(doctorStationLabApplyItemDto, inspectionLabApplyItem); BeanUtils.copyProperties(doctorStationLabApplyItemDto, inspectionLabApplyItem);
// 前端选择检验项目时已携带完整的关联信息activityId、feePackageId、itemCode等
// Bug #326修复: 只使用 activityId 直接查询,不使用名称反查
Long activityId = doctorStationLabApplyItemDto.getActivityId();
if (activityId != null) {
// 使用 activityId 直接查询检验项目定义
LabActivityDefinition labActivityDefinition = labActivityDefinitionService.getById(activityId);
if (labActivityDefinition != null && DelFlag.NO.getCode().equals(labActivityDefinition.getDeleteFlag())) {
// 补充编码(如果前端未传或为空)
if (inspectionLabApplyItem.getItemCode() == null || inspectionLabApplyItem.getItemCode().isEmpty()) {
inspectionLabApplyItem.setItemCode(labActivityDefinition.getBusNo());
}
// 补充套餐ID如果前端未传
if (inspectionLabApplyItem.getFeePackageId() == null) {
inspectionLabApplyItem.setFeePackageId(labActivityDefinition.getFeePackageId());
}
}
} else {
// 没有 activityId 时记录警告,不使用名称反查
log.warn("检验项目 [{}] 未传入 activityId无法获取完整关联信息",
doctorStationLabApplyItemDto.getItemName());
}
// 后端重新计算金额:金额 = 单价 × 数量 // 后端重新计算金额:金额 = 单价 × 数量
java.math.BigDecimal itemPrice = doctorStationLabApplyItemDto.getItemPrice(); java.math.BigDecimal itemPrice = doctorStationLabApplyItemDto.getItemPrice();
java.math.BigDecimal itemQty = doctorStationLabApplyItemDto.getItemQty(); java.math.BigDecimal itemQty = doctorStationLabApplyItemDto.getItemQty();
@@ -241,13 +272,16 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
); );
if (organization != null) { if (organization != null) {
positionId = organization.getId(); positionId = organization.getId();
} else {
// Bug #329: 执行科室代码无法匹配到科室,记录警告日志
log.warn("执行科室代码 [{}] 在科室表中未找到对应记录", performDeptCode);
} }
} else {
// Bug #329: 未指定执行科室,记录警告日志
log.warn("检验项目 [{}] 未指定执行科室", itemName);
} }
// 如果没有指定执行科室,使用当前医生所在的科室作为默认执行科室 // Bug #329: 移除默认执行科室逻辑,必须由前端明确指定执行科室
if (positionId == null) {
positionId = SecurityUtils.getDeptId();
}
// 4. 创建医嘱保存对象 // 4. 创建医嘱保存对象
AdviceSaveDto adviceSaveDto = new AdviceSaveDto(); AdviceSaveDto adviceSaveDto = new AdviceSaveDto();
@@ -324,7 +358,6 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
throw new RuntimeException("套餐项目 '" + itemName + "' 未设置有效价格,请先配置套餐金额"); throw new RuntimeException("套餐项目 '" + itemName + "' 未设置有效价格,请先配置套餐金额");
} }
unitPrice = packageInfo.getPackageAmount(); unitPrice = packageInfo.getPackageAmount();
log.info("套餐项目 '{}' 使用套餐价格: {}", itemName, unitPrice);
} else { } else {
// 普通项目:使用前端传入的价格 // 普通项目:使用前端传入的价格
unitPrice = labApplyItemDto.getItemPrice(); unitPrice = labApplyItemDto.getItemPrice();
@@ -367,30 +400,84 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
*/ */
@Override @Override
public Object getInspectionApplyByApplyNo(String applyNo) { public Object getInspectionApplyByApplyNo(String applyNo) {
// 查询主表数据 // 使用MyBatis-Plus查询主表数据
DoctorStationLabApplyDto applyDto = (DoctorStationLabApplyDto) doctorStationLabApplyMapper.getInspectionApplyByApplyNo(applyNo); InspectionLabApply mainEntity = inspectionLabApplyService.getOne(
if (applyDto == null) { new QueryWrapper<InspectionLabApply>()
.eq("apply_no", applyNo)
.eq("delete_flag", DelFlag.NO.getCode())
);
if (mainEntity == null) {
return null; return null;
} }
// 使用BeanUtils进行基础字段映射
DoctorStationLabApplyDto applyDto = new DoctorStationLabApplyDto();
BeanUtils.copyProperties(mainEntity, applyDto);
// 由于字段名称映射关系(如 id -> applicationId需要单独设置
applyDto.setApplicationId(mainEntity.getId());
// 查询检验项目明细 // 查询检验项目明细
List<InspectionLabApplyItem> itemList = inspectionLabApplyItemService.list( List<InspectionLabApplyItem> itemList = inspectionLabApplyItemService.list(
new QueryWrapper<InspectionLabApplyItem>() new QueryWrapper<InspectionLabApplyItem>()
.eq("apply_no", applyNo) .eq("apply_no", applyNo)
.eq("delete_flag", "0") .eq("delete_flag", DelFlag.NO.getCode())
.orderByAsc("item_seq") .orderByAsc("item_seq")
); );
// 转换为 DTO 列表 // 转换为 DTO 列表
List<DoctorStationLabApplyItemDto> itemDtoList = new ArrayList<>(); List<DoctorStationLabApplyItemDto> itemDtoList = new ArrayList<>();
if (itemList != null && !itemList.isEmpty()) { if (itemList != null && !itemList.isEmpty()) {
// 提取所有不同的 itemCode用于批量查询
Set<String> itemCodes = itemList.stream()
.filter(item -> item.getItemCode() != null && !item.getItemCode().isEmpty())
.map(InspectionLabApplyItem::getItemCode)
.collect(Collectors.toSet());
// 批量查询所有关联的检验项目定义使用MyBatis-Plus
Map<String, LabActivityDefinition> definitionMap = new HashMap<>();
if (!itemCodes.isEmpty()) {
List<LabActivityDefinition> labActivityDefinitions = labActivityDefinitionService.list(
new QueryWrapper<LabActivityDefinition>()
.in("bus_no", itemCodes)
.eq("delete_flag", DelFlag.NO.getCode())
);
// 构建 itemCode 到定义的映射
definitionMap = labActivityDefinitions.stream()
.collect(Collectors.toMap(LabActivityDefinition::getBusNo, Function.identity()));
}
// 处理每个明细项
for (InspectionLabApplyItem item : itemList) { for (InspectionLabApplyItem item : itemList) {
DoctorStationLabApplyItemDto itemDto = new DoctorStationLabApplyItemDto(); DoctorStationLabApplyItemDto itemDto = new DoctorStationLabApplyItemDto();
// 使用BeanUtils进行基础字段映射
BeanUtils.copyProperties(item, itemDto); BeanUtils.copyProperties(item, itemDto);
// feePackageId 在保存时已存储,直接使用
itemDto.setFeePackageId(item.getFeePackageId());
// 判断是否是套餐项目(根据 feePackageId 是否存在)
itemDto.setIsPackage(item.getFeePackageId() != null);
// 从批量查询结果中获取关联信息
if (item.getItemCode() != null && !item.getItemCode().isEmpty()) {
LabActivityDefinition labActivityDefinition = definitionMap.get(item.getItemCode());
if (labActivityDefinition != null) {
itemDto.setActivityId(labActivityDefinition.getId());
itemDto.setSampleType(labActivityDefinition.getSpecimenCode());
itemDto.setUnit(labActivityDefinition.getPermittedUnitCode());
// 补充检验类型ID用于前端自动设置执行科室
itemDto.setInspectionTypeId(labActivityDefinition.getInspectionTypeId());
}
} else {
log.warn("检验项目 [{}] 未存储 itemCode无法获取完整关联信息", item.getItemName());
}
itemDtoList.add(itemDto); itemDtoList.add(itemDto);
} }
// 从第一个明细项获取执行科室代码 // 从第一个明细项获取执行科室代码
applyDto.setExecuteDepartment(itemList.get(0).getPerformDeptCode()); if (!itemList.isEmpty() && itemList.get(0).getPerformDeptCode() != null) {
applyDto.setExecuteDepartment(itemList.get(0).getPerformDeptCode());
}
} }
applyDto.setLabApplyItemList(itemDtoList); applyDto.setLabApplyItemList(itemDtoList);
@@ -409,8 +496,9 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
// 使用 PageHelper 进行分页查询 // 使用 PageHelper 进行分页查询
PageHelper.startPage(pageNo, pageSize); PageHelper.startPage(pageNo, pageSize);
// 查询检验申请单列表 // 使用MyBatis-Plus查询检验申请单列表
log.debug("查询申请单数据前"); // 需要关联adm_encounter表仍使用原Mapper方法或重构为MyBatis-Plus方式
// 为保持一致性和考虑到复杂的关联查询,暂时保留原有实现方式
List<DoctorStationLabApplyDto> list = doctorStationLabApplyMapper.getInspectionApplyListPage(encounterId); List<DoctorStationLabApplyDto> list = doctorStationLabApplyMapper.getInspectionApplyListPage(encounterId);
log.debug("查询申请单数据后"); log.debug("查询申请单数据后");

View File

@@ -1,5 +1,7 @@
package com.openhis.web.doctorstation.dto; package com.openhis.web.doctorstation.dto;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
@@ -67,4 +69,42 @@ public class DoctorStationLabApplyItemDto {
*/ */
@NotNull(message = "行状态不能为空") @NotNull(message = "行状态不能为空")
private Long itemStatus; private Long itemStatus;
// ========== Bug #326: 套餐相关字段(回充时需要) ==========
/**
* 活动定义ID检验项目定义ID
* 用于回充时关联到原始检验项目定义
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long activityId;
/**
* 套餐ID如果该项目是套餐则关联套餐表
* 对应 InspectionPackage.basicInformationId
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long feePackageId;
/**
* 是否是套餐项目
*/
private Boolean isPackage;
/**
* 样本类型
*/
private String sampleType;
/**
* 单位
*/
private String unit;
/**
* 检验类型ID关联 inspection_type 大类)
* 用于前端自动设置执行科室
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long inspectionTypeId;
} }

View File

@@ -4,41 +4,6 @@
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.openhis.web.doctorstation.mapper.DoctorStationLabApplyMapper"> <mapper namespace="com.openhis.web.doctorstation.mapper.DoctorStationLabApplyMapper">
<!-- 根据申请单号查询检验申请单(返回完整字段) -->
<select id="getInspectionApplyByApplyNo" resultType="com.openhis.web.doctorstation.dto.DoctorStationLabApplyDto">
SELECT
id AS applicationId,
apply_no AS applyNo,
patient_id AS patientId,
patient_name AS patientName,
medicalrecord_number AS medicalrecordNumber,
natureof_cost AS natureofCost,
visit_no AS visitNo,
apply_dept_code AS applyDeptCode,
apply_department AS applyDepartment,
apply_doc_code AS applyDocCode,
apply_doc_name AS applyDocName,
apply_time AS applyTime,
clinic_diag AS clinicDiag,
clinic_desc AS clinicDesc,
contraindication AS contraindication,
medical_history_summary AS medicalHistorySummary,
purposeof_inspection AS purposeofInspection,
physical_examination AS physicalExamination,
inspection_item AS inspectionItem,
specimen_type_code AS specimenTypeCode,
specimen_name AS specimenName,
priority_code AS priorityCode,
apply_status AS applyStatus,
apply_remark AS applyRemark,
create_time AS createTime,
operator_id AS operatorId,
create_by AS createBy,
tenant_id AS tenantId
FROM lab_apply
WHERE apply_no = #{applyNo}
AND delete_flag = '0'
</select>
<!-- 分页查询检验申请单列表根据就诊ID查询按申请时间降序 <!-- 分页查询检验申请单列表根据就诊ID查询按申请时间降序
从明细表聚合项目名称和金额--> 从明细表聚合项目名称和金额-->

View File

@@ -74,4 +74,9 @@ public class InspectionLabApplyItem extends HisBaseEntity {
*/ */
@JsonSerialize(using = ToStringSerializer.class) @JsonSerialize(using = ToStringSerializer.class)
private Long itemStatus; private Long itemStatus;
/**
* 套餐ID
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long feePackageId;
} }

View File

@@ -1,7 +1,5 @@
<template> <template>
<el-container class="inspection-application-container"> <el-container class="inspection-application-container">
<!-- 顶部操作按钮区 - Bug#334: 优化垂直空间利用率 -->
<el-header class="top-action-bar" height="48px"> <el-header class="top-action-bar" height="48px">
<el-row class="action-buttons" type="flex" justify="end" :gutter="8"> <el-row class="action-buttons" type="flex" justify="end" :gutter="8">
<el-button type="primary" size="default" @click="handleSave" class="save-btn" :loading="saving"> <el-button type="primary" size="default" @click="handleSave" class="save-btn" :loading="saving">
@@ -19,10 +17,22 @@
<el-main class="inspection-section" style="width: 100%; max-width: 100%"> <el-main class="inspection-section" style="width: 100%; max-width: 100%">
<el-card class="table-card" style="width: 100%"> <el-card class="table-card" style="width: 100%">
<template #header> <template #header>
<el-row class="card-header" type="flex" align="middle"> <div class="card-header-flex">
<el-icon><DocumentChecked /></el-icon> <div class="header-title">
<span>检验信息</span> <el-icon><DocumentChecked /></el-icon>
</el-row> <span>检验信息</span>
</div>
<div class="header-actions">
<el-button type="primary" size="default" @click="handleSave" class="save-btn" :loading="saving">
<el-icon><Document /></el-icon>
保存
</el-button>
<el-button type="primary" size="default" @click="handleNewApplication" class="new-btn">
<el-icon><Plus /></el-icon>
新增
</el-button>
</div>
</div>
</template> </template>
<el-table <el-table
ref="inspectionTableRef" ref="inspectionTableRef"
@@ -41,7 +51,6 @@
@current-change="handleRowClick" @current-change="handleRowClick"
@cell-click="handleCellClick" @cell-click="handleCellClick"
> >
<!-- Bug #326: 添加展开列 -->
<el-table-column type="expand" width="50" align="center" header-align="center"> <el-table-column type="expand" width="50" align="center" header-align="center">
<template #default="scope"> <template #default="scope">
<div v-if="scope.row.children && scope.row.children.length > 0" class="expand-content"> <div v-if="scope.row.children && scope.row.children.length > 0" class="expand-content">
@@ -114,7 +123,7 @@
<template #default="scope"> <template #default="scope">
<el-row type="flex" align="middle" justify="center" :gutter="8"> <el-row type="flex" align="middle" justify="center" :gutter="8">
<el-button link size="default" @click="handlePrint(scope.row)" :icon="Printer" title="打印" style="font-size: 16px"></el-button> <el-button link size="default" @click="handlePrint(scope.row)" :icon="Printer" title="打印" style="font-size: 16px"></el-button>
<el-button link size="default" @click="handleDelete(scope.row)" :icon="Delete" style="color: #f56c6c; font-size: 16px" title="删除"></el-button> <el-button link size="default" @click.stop="handleDelete(scope.row)" :icon="Delete" style="color: #f56c6c; font-size: 16px" title="删除"></el-button>
</el-row> </el-row>
</template> </template>
</el-table-column> </el-table-column>
@@ -406,7 +415,6 @@
<template #header> <template #header>
<h4 style="margin: 0; font-weight: bold">检验信息详情</h4> <h4 style="margin: 0; font-weight: bold">检验信息详情</h4>
</template> </template>
<!-- Bug #326: 添加树形展开功能支持套餐明细展示 -->
<el-table <el-table
:data="selectedInspectionItems" :data="selectedInspectionItems"
border border
@@ -414,12 +422,12 @@
style="width: 100%; min-width: 100%" style="width: 100%; min-width: 100%"
max-height="250" max-height="250"
row-key="itemId" row-key="itemId"
lazy
:load="loadPackageDetailsForTable"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
default-expand-all
> >
<el-table-column label="项目名称" prop="itemName" min-width="180"> <el-table-column label="项目名称" prop="itemName" min-width="180">
<template #default="scope"> <template #default="scope">
<!-- BugFix#326: 套餐项目添加标识和加粗显示 -->
<el-tag v-if="scope.row.isPackage" size="small" type="warning" style="margin-right: 4px">套餐</el-tag> <el-tag v-if="scope.row.isPackage" size="small" type="warning" style="margin-right: 4px">套餐</el-tag>
<span :style="{ fontWeight: scope.row.isPackage ? 'bold' : 'normal' }"> <span :style="{ fontWeight: scope.row.isPackage ? 'bold' : 'normal' }">
{{ scope.row.itemName }} {{ scope.row.itemName }}
@@ -538,16 +546,15 @@
@change="toggleInspectionItem(item)" @change="toggleInspectionItem(item)"
@click.stop @click.stop
/> />
<!-- BugFix#326: 套餐项目添加标识 -->
<el-tag v-if="item.isPackage" size="small" type="warning" style="margin-right: 4px">套餐</el-tag> <el-tag v-if="item.isPackage" size="small" type="warning" style="margin-right: 4px">套餐</el-tag>
<span class="item-itemName">{{ item.itemName }}</span> <span class="item-itemName">{{ item.itemName }}</span>
<span class="item-price">¥{{ item.itemPrice }}</span> <span class="item-price">¥{{ item.itemPrice }}</span>
</div> </div>
<!-- 加载更多 --> <!-- 加载更多 -->
<div v-if="category.hasMore && category.items.length > 0" class="load-more"> <div v-if="category.hasMore && category.items.length > 0" class="load-more">
<el-button <el-button
link link
size="small" size="small"
:loading="category.loading" :loading="category.loading"
@click.stop="loadMoreItems(category.key)" @click.stop="loadMoreItems(category.key)"
> >
@@ -659,7 +666,7 @@ import { debounce } from 'lodash-es'
// 获取当前组件实例和字典 // 获取当前组件实例和字典
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const { inspection_lab_dept } = proxy.useDict('inspection_lab_dept') // Bug #329: 移除 inspection_lab_dept 字典,执行科室数据从 Organization 表获取
// 动态获取的检验类型缓存(用于缓存不在 inspectionCategories 中的检验类型) // 动态获取的检验类型缓存(用于缓存不在 inspectionCategories 中的检验类型)
const dynamicInspectionTypesCache = ref([]) const dynamicInspectionTypesCache = ref([])
@@ -785,9 +792,123 @@ const loading = ref(false)
const saving = ref(false) // 保存状态 const saving = ref(false) // 保存状态
const total = ref(0) const total = ref(0)
const leftActiveTab = ref('application') const leftActiveTab = ref('application')
// Bug #326: 展开的行
const expandedRowKeys = ref([]) const expandedRowKeys = ref([])
const isExpanded = (applicationId) => {
return expandedRowKeys.value.includes(applicationId)
}
const toggleExpand = async (row) => {
const applicationId = row.applicationId
const isCurrentlyExpanded = isExpanded(applicationId)
if (isCurrentlyExpanded) {
// 收起
expandedRowKeys.value = expandedRowKeys.value.filter(id => id !== applicationId)
} else {
// 展开 - 先检查是否需要加载明细数据
if (row.hasChildren && (!row.children || row.children.length === 0)) {
// 加载套餐明细
await loadPackageDetails(row)
}
expandedRowKeys.value = [...expandedRowKeys.value, applicationId]
}
}
const handleExpandChange = (row, expandedRows) => {
expandedRowKeys.value = expandedRows.map(r => r.applicationId)
}
const loadPackageDetails = async (row) => {
try {
const packageId = row.feePackageId || row.packageId
if (!packageId) return
const res = await getInspectionPackageDetails(packageId)
if (res.code === 200 && res.data) {
row.children = res.data.map(detail => ({
itemId: detail.detailId || detail.id || detail.itemId,
itemName: detail.itemName || detail.name,
sampleType: detail.sampleType || '',
unit: detail.unit || '',
itemPrice: detail.unitPrice || detail.itemPrice || detail.price || 0,
itemQty: detail.quantity || detail.itemQty || detail.qty || 1,
itemAmount: (detail.unitPrice || detail.itemPrice || 0) * (detail.quantity || detail.itemQty || 1)
}))
}
} catch (error) {
console.error('加载套餐明细失败:', error)
row.children = []
}
}
const loadPackageDetailsForTable = async (row, treeNode, resolve) => {
if (!row.isPackage) {
resolve([])
return
}
try {
const packageId = row.feePackageId || row.packageId
if (!packageId) {
resolve([])
return
}
const res = await getInspectionPackageDetails(packageId)
if (res.code === 200 && res.data) {
// 构建明细数据结构
// BugFix: 后端返回字段为 unitPrice 和 quantity需正确映射
const children = res.data.map(detail => ({
itemId: detail.detailId || detail.id || detail.itemId,
itemName: detail.itemName || detail.name,
sampleType: detail.sampleType || '',
unit: detail.unit || '',
itemPrice: detail.unitPrice || detail.itemPrice || detail.price || 0,
itemQty: detail.quantity || detail.itemQty || detail.qty || 1,
itemAmount: (detail.unitPrice || detail.itemPrice || 0) * (detail.quantity || detail.itemQty || 1)
}))
resolve(children)
} else {
resolve([])
}
} catch (error) {
console.error('加载套餐明细失败:', error)
resolve([])
}
}
const togglePackageExpand = async (item) => {
if (!item.isPackage) return
item.expanded = !item.expanded
if (item.expanded && (!item.children || item.children.length === 0)) {
item.loading = true
try {
const packageId = item.feePackageId || item.packageId
if (packageId) {
const res = await getInspectionPackageDetails(packageId)
if (res.code === 200 && res.data) {
item.children = res.data.map(detail => ({
detailId: detail.detailId || detail.id || detail.itemId,
itemName: detail.itemName || detail.name,
unit: detail.unit || '',
quantity: detail.quantity || detail.itemQty || detail.qty || 1,
unitPrice: detail.unitPrice || detail.itemPrice || detail.price || 0,
itemPrice: detail.unitPrice || detail.itemPrice || detail.price || 0
}))
}
}
} catch (error) {
console.error('加载套餐明细失败:', error)
item.children = []
} finally {
item.loading = false
}
}
}
// 申请日期实时更新定时器 // 申请日期实时更新定时器
let applyTimeTimer = null let applyTimeTimer = null
@@ -856,6 +977,9 @@ const executeDepartmentOptions = ref([])
// 执行科室加载完成标志BugFix#CodeReview: 防止竞态条件) // 执行科室加载完成标志BugFix#CodeReview: 防止竞态条件)
const isExecuteDepartmentLoaded = ref(false) const isExecuteDepartmentLoaded = ref(false)
// 删除操作标志,用于避免删除时触发数据填充
const isDeleting = ref(false)
// 表单数据 // 表单数据
const formData = reactive({ const formData = reactive({
applyOrganizationId: '', applyOrganizationId: '',
@@ -1061,7 +1185,7 @@ const loadInspectionData = async () => {
const loadCategoryItems = async (categoryKey, loadMore = false) => { const loadCategoryItems = async (categoryKey, loadMore = false) => {
const category = inspectionCategories.value.find(c => c.key === categoryKey) const category = inspectionCategories.value.find(c => c.key === categoryKey)
if (!category) return if (!category) return
// 已加载完成且不是加载更多,或正在加载中,跳过 // 已加载完成且不是加载更多,或正在加载中,跳过
if ((category.loaded && !loadMore) || category.loading) return if ((category.loaded && !loadMore) || category.loading) return
// 没有更多数据了,跳过 // 没有更多数据了,跳过
@@ -1281,10 +1405,10 @@ const getInspectionList = () => {
if (!queryParams.encounterId) { if (!queryParams.encounterId) {
return return
} }
loading.value = true loading.value = true
getApplyList({ getApplyList({
encounterId: queryParams.encounterId, encounterId: queryParams.encounterId,
pageNo: queryParams.pageNo, pageNo: queryParams.pageNo,
pageSize: queryParams.pageSize pageSize: queryParams.pageSize
@@ -1544,17 +1668,27 @@ const handleSave = () => {
// 准备保存数据 // 准备保存数据
const prepareSaveData = () => { const prepareSaveData = () => {
// 将执行科室代码赋值给每个检验项目
const labApplyItemList = selectedInspectionItems.value.map(item => ({ const labApplyItemList = selectedInspectionItems.value.map(item => ({
...item, itemSeq: item.itemSeq || 1,
performDeptCode: formData.executeDepartment // 从字典获取的执行科室代码 itemCode: item.code || '',
itemName: item.itemName || '',
itemPrice: item.itemPrice || 0,
itemQty: item.itemQty || 1,
itemAmount: item.itemAmount || 0,
itemStatus: formData.applyStatus || 0,
performDeptCode: formData.executeDepartment,
// Bug #326修复: 传入 activityId后端直接使用 ID 关联,避免用名称反查
activityId: item.activityId || item.itemId || null,
feePackageId: item.feePackageId || null,
isPackage: item.isPackage || false,
sampleType: item.sampleType || '',
unit: item.unit || ''
})) }))
return { return {
...formData, ...formData,
labApplyItemList, labApplyItemList,
physicalExamination: formData.physicalExam, // 字段名映射:前端 physicalExam -> 后端 physicalExamination physicalExamination: formData.physicalExam,
inspectionItemsText: selectedInspectionItems.value.map(item => item.itemName).join('+') inspectionItemsText: selectedInspectionItems.value.map(item => item.itemName).join('+')
// 金额由后端计算,前端不传递
} }
} }
@@ -1563,6 +1697,7 @@ const handleSave = () => {
executeSave(saveData); executeSave(saveData);
} }
const executeSave = (saveData) => { const executeSave = (saveData) => {
saveInspectionApplication(saveData).then((res) => { saveInspectionApplication(saveData).then((res) => {
if (res.code === 200) { if (res.code === 200) {
@@ -1725,6 +1860,8 @@ const handlePrint = (row) => {
// 删除申请单 // 删除申请单
const handleDelete = (row) => { const handleDelete = (row) => {
isDeleting.value = true; // 设置删除标志
ElMessageBox.confirm( ElMessageBox.confirm(
`确定要删除申请单 "${row.applyNo}" 吗?此操作将同时删除对应的医嘱。`, `确定要删除申请单 "${row.applyNo}" 吗?此操作将同时删除对应的医嘱。`,
'删除确认', '删除确认',
@@ -1740,22 +1877,30 @@ const handleDelete = (row) => {
deleteInspectionApplication(row.applyNo).then((res) => { deleteInspectionApplication(row.applyNo).then((res) => {
if (res.code === 200) { if (res.code === 200) {
ElMessage.success('删除成功') ElMessage.success('删除成功')
resetForm(); // 删除成功后清空表单
// 刷新列表 // 刷新列表
getInspectionList() getInspectionList()
} else { } else {
ElMessage.error(res.message || '删除失败') ElMessage.error(res.message || '删除失败')
} }
}).catch((error) => { }).catch((error) => {
console.error('删除检验<EFBFBD><EFBFBD>请单异常:', error) console.error('删除检验请单异常:', error)
ElMessage.error('删除异常') ElMessage.error('删除异常')
}) })
}).catch(() => { }).catch(() => {
// 用户取消删除 // 用户取消删除
}).finally(() => {
isDeleting.value = false; // 重置删除标志
}) })
} }
// 单元格点击 - 点击表格行时加载申请单详情 // 单元格点击 - 点击表格行时加载申请单详情
const handleCellClick = (row, column) => { const handleCellClick = (row, column) => {
// 如果点击的是操作列或展开列,不触发数据填充
if (column.property === '操作' || column.label === '操作' ||
column.type === 'expand' || column.type === 'selection') {
return;
}
// 点击表格行时,将该申请单的数据加载到表单中 // 点击表格行时,将该申请单的数据加载到表单中
// 使用 applyNo 判断是否有效 // 使用 applyNo 判断是否有效
if (row && row.applyNo) { if (row && row.applyNo) {
@@ -1766,8 +1911,8 @@ const handleCellClick = (row, column) => {
// 行点击事件处理 // 行点击事件处理
const handleRowClick = (currentRow, oldRow) => { const handleRowClick = (currentRow, oldRow) => {
// 点击表格行时,将该申请单的数据加载到表单中 // 点击表格行时,将该申请单的数据加载到表单中
// 使用 applyNo 判断是否有效 // 使用 applyNo 判断是否有效,同时检查是否处于删除状态
if (currentRow && currentRow.applyNo) { if (currentRow && currentRow.applyNo && !isDeleting.value) {
loadApplicationToForm(currentRow); loadApplicationToForm(currentRow);
} }
} }
@@ -1790,7 +1935,6 @@ const loadApplicationToForm = async (row) => {
// 根据申请单号获取完整详情 // 根据申请单号获取完整详情
try { try {
const res = await getInspectionApplyDetail(row.applyNo) const res = await getInspectionApplyDetail(row.applyNo)
console.log('申请单详情API返回:', res) // 临时调试日志
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const detail = res.data const detail = res.data
// 加载完整的表单数据 // 加载完整的表单数据
@@ -1831,13 +1975,32 @@ const loadApplicationToForm = async (row) => {
// 加载检验项目数据 // 加载检验项目数据
selectedInspectionItems.value = [] selectedInspectionItems.value = []
if (detail.labApplyItemList && detail.labApplyItemList.length > 0) { if (detail.labApplyItemList && detail.labApplyItemList.length > 0) {
selectedInspectionItems.value = detail.labApplyItemList.map(item => ({ // Bug #326修复: 直接使用后端返回的数据,不再从本地缓存查找匹配项
...item, // 后端已返回完整关联信息activityId、feePackageId、inspectionTypeId、sampleType、unit
itemId: item.itemId || item.id || Math.random().toString(36).substring(2, 11), selectedInspectionItems.value = detail.labApplyItemList.map(item => {
itemName: item.itemName || item.name || '', const itemId = item.activityId || item.itemId || item.id || Math.random().toString(36).substring(2, 11)
itemPrice: item.itemPrice || item.price || 0, const isPackage = item.feePackageId != null || item.itemName?.includes('套餐')
itemAmount: item.itemAmount || item.price || 0,
})) return {
itemId: itemId,
itemName: item.itemName || '',
itemPrice: item.itemPrice || 0,
itemAmount: item.itemAmount || 0,
itemQty: item.itemQty || 1,
sampleType: item.sampleType || '',
unit: item.unit || '',
code: item.itemCode || '',
isPackage: isPackage,
hasChildren: isPackage,
feePackageId: item.feePackageId || null,
activityId: item.activityId || itemId,
inspectionTypeId: item.inspectionTypeId || null,
expanded: false,
children: [],
childrenLoaded: false,
loading: false
}
})
} else if (detail.inspectionItem || detail.itemName) { } else if (detail.inspectionItem || detail.itemName) {
// 如果只有项目名称,尝试从本地分类中查找匹配项 // 如果只有项目名称,尝试从本地分类中查找匹配项
const itemNames = (detail.inspectionItem || detail.itemName).split(/[+,]/) const itemNames = (detail.inspectionItem || detail.itemName).split(/[+,]/)
@@ -1884,7 +2047,7 @@ watch(() => props.patientInfo, async (newVal) => {
// 初始化数据 // 初始化数据
await initData(); await initData();
// 如果encounterId发生变化重新加载检验申请单列表 // 如果encounterId发生变化重新加载检验申请单列表
if (oldEncounterId !== newVal.encounterId) { if (oldEncounterId !== newVal.encounterId) {
getInspectionList() getInspectionList()
@@ -1896,24 +2059,38 @@ watch(() => props.patientInfo, async (newVal) => {
} }
}, { deep: true, immediate: true }) }, { deep: true, immediate: true })
// 监听已选择的检验项目,自动更新检验项目文本(用+号拼接) // Bug #329: 监听已选择的检验项目,自动更新检验项目文本并设置默认执行科室
watch(() => selectedInspectionItems.value, (newVal) => { watch(() => selectedInspectionItems.value, async (newVal) => {
if (newVal && newVal.length > 0) { if (newVal && newVal.length > 0) {
formData.inspectionItemsText = newVal.map(item => item.itemName).join('+') formData.inspectionItemsText = newVal.map(item => item.itemName).join('+')
// Bug #329: 如果执行科室为空,根据第一个检验项目的检验类型自动设置默认执行科室
if (!formData.executeDepartment) {
const firstItem = newVal[0]
// 根据检验项目的 inspectionTypeId 获取默认执行科室
if (firstItem.inspectionTypeId) {
const defaultDeptCode = await getDefaultPerformDeptCode(firstItem.inspectionTypeId)
if (defaultDeptCode) {
formData.executeDepartment = defaultDeptCode
}
}
}
} else { } else {
// Bug #329: 当项目被清空时,同时清空执行科室(下次选择项目时会重新自动设置)
formData.inspectionItemsText = '' formData.inspectionItemsText = ''
formData.executeDepartment = ''
} }
}, { deep: true }) }, { deep: true })
// 监听执行科室字典数据,设置默认值为第一个选项 // Bug #329: 移除字典数据默认值设置,执行科室应从 Organization 表获取,不应使用字典数据
watch(() => inspection_lab_dept.value, (newVal) => { // 原问题:inspection_lab_dept 字典的 value (如 'ORG045') 与 executeDepartmentOptions 的 value (科室编码 busNo) 格式不一致
if (newVal && newVal.length > 0 && !formData.executeDepartment) { // 导致 el-select 显示原始 value 值而非科室名称
formData.executeDepartment = newVal[0].value
}
}, { immediate: true })
// 组件挂载时预加载检验项目数据不依赖patientInfo // 组件挂载时预加载检验项目数据不依赖patientInfo
// BugFix #329: 先加载执行科室列表再加载检验类型确保检验类型的department字段能正确匹配
onMounted(async () => { onMounted(async () => {
await loadExecuteDepartmentList()
await loadInspectionData() await loadInspectionData()
}) })
@@ -1947,13 +2124,7 @@ defineExpose({
/* Bug#334: 顶部操作按钮区 - 优化垂直空间利用率 */ /* Bug#334: 顶部操作按钮区 - 优化垂直空间利用率 */
.top-action-bar { .top-action-bar {
display: flex; display: none; /* 隐藏原有的顶部操作栏 */
align-items: center;
justify-content: flex-end;
border-bottom: 1px solid var(--el-border-color-light);
background: var(--el-bg-color);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
padding: 0 12px;
} }
.action-buttons { .action-buttons {
@@ -1998,7 +2169,14 @@ defineExpose({
padding-bottom: 6px; padding-bottom: 6px;
} }
.card-header { .card-header-flex {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.header-title {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
@@ -2006,6 +2184,11 @@ defineExpose({
color: var(--el-text-color-primary); color: var(--el-text-color-primary);
} }
.header-actions {
display: flex;
gap: 8px;
}
/* Bug#334: 底部内容区域 - 优化垂直空间利用率 */ /* Bug#334: 底部内容区域 - 优化垂直空间利用率 */
.bottom-content-area { .bottom-content-area {
padding: 2px 10px; padding: 2px 10px;
@@ -2066,6 +2249,7 @@ defineExpose({
} }
:deep(.el-pagination) { :deep(.el-pagination) {
.el-pager li { .el-pager li {
border-radius: 4px; border-radius: 4px;
@@ -2554,6 +2738,110 @@ defineExpose({
margin-right: 10px; margin-right: 10px;
} }
/* 套餐明细展开样式 */
.selected-tree-header {
display: flex;
align-items: center;
padding: 8px 12px;
background: #fafafa;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
}
.selected-tree-header .item-itemName {
flex: 1;
margin-left: 8px;
font-weight: 500;
color: #333;
}
.selected-tree-header .item-price {
color: #e6a23c;
font-weight: bold;
margin-left: 16px;
min-width: 60px;
}
.selected-tree-header:hover {
background: #f0f0f0;
}
.selected-tree-header.expanded {
background: #e8f4fc;
border-left: 3px solid #51A3F3;
}
.selected-tree-icon {
width: 20px;
text-align: center;
color: #51A3F3;
font-size: 12px;
margin-right: 4px;
}
.is-package .selected-tree-header {
font-weight: 600;
}
.selected-tree-children {
padding: 4px 12px 4px 36px;
background: #fff;
border-top: 1px dashed #ebeef5;
}
.selected-tree-detail {
display: flex;
align-items: center;
padding: 6px 8px;
border-bottom: 1px solid #f5f5f5;
font-size: 12px;
}
.selected-tree-detail:last-child {
border-bottom: none;
}
.selected-tree-detail .detail-name {
flex: 1;
color: #333;
}
.selected-tree-detail .detail-unit {
width: 60px;
text-align: center;
color: #666;
}
.selected-tree-detail .detail-qty {
width: 40px;
text-align: center;
color: #666;
}
.selected-tree-detail .detail-price {
width: 60px;
text-align: right;
color: #e6a23c;
font-weight: 500;
}
.no-detail {
text-align: center;
padding: 10px;
color: #909399;
font-size: 12px;
}
.loading-placeholder {
display: flex;
align-items: center;
justify-content: center;
padding: 15px;
color: #909399;
font-size: 12px;
}
.no-selection { .no-selection {
text-align: center; text-align: center;
padding: 40px 20px; padding: 40px 20px;

View File

@@ -114,9 +114,10 @@
<el-tab-pane label="门诊病历" name="hospitalizationEmr"> <el-tab-pane label="门诊病历" name="hospitalizationEmr">
<hospitalizationEmr :patientInfo="patientInfo" :activeTab="activeTab" @emrSaved="handleEmrSaved" /> <hospitalizationEmr :patientInfo="patientInfo" :activeTab="activeTab" @emrSaved="handleEmrSaved" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="待写病历" name="pendingEmr"> <!-- Bug #368: 屏蔽待写病历标签页左侧导航栏已有独立菜单功能冗余 -->
<!-- <el-tab-pane label="待写病历" name="pendingEmr">
<PendingEmr @writeEmr="handleWriteEmr" @viewPatient="handleViewPatient" /> <PendingEmr @writeEmr="handleWriteEmr" @viewPatient="handleViewPatient" />
</el-tab-pane> </el-tab-pane> -->
<!-- <el-tab-pane label="病历" name="emr"> <!-- <el-tab-pane label="病历" name="emr">
<Emr <Emr
:patientInfo="patientInfo" :patientInfo="patientInfo"