21 Commits

Author SHA1 Message Date
陈琳
2cedcefacb Fix Bug #496: 【住院医生工作站-检查申请】检查申请列表字段命名不规范及单号生成规则不符合医疗行业标准
1. 前端列标题:处方号 → 申请单号(表格列 + 详情弹窗)
2. 后端单号生成:原用 PAR 处方号前缀 → 改为 JCZ + yyMMdd + 5位顺序号
3. 新增 AssignSeqEnum.CHECK_APPLY_NO 枚举项(JCZ 前缀)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
关羽
4feac503a0 Fix Bug #496: 【住院医生工作站-检查申请】检查申请列表字段命名不规范及单号生成规则不符合医疗行业标准
1. 前端 examineApplication.vue:列表表头和详情弹窗中"处方号"改为"申请单号"
2. 后端 RequestFormManageAppServiceImpl:检查申请单单号生成规则由 PAR+流水号 改为 JCZ+yyMMdd+5位顺序号(如:JCZ26051300001),其他类型申请单保持原有PAR规则不变

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
关羽
d747b0b380 Fix Bug #400: 门诊医生站点击【完诊】后,triage_queue_item 表 status 字段未按规范更新为 30
根因:完诊时使用 triageQueueItemService.updateById(queueItem) 更新队列状态,
依赖 MyBatis Plus 的实体级更新策略,可能因字段级更新策略导致 status 字段未实际写入数据库。

修复策略:改用 LambdaUpdateWrapper 直接生成 UPDATE SQL,明确指定 SET status=30,
绕过实体级更新策略,确保 status 字段必定写入数据库。同时增加更新失败日志。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
荀彧
af0a0957d0 Fix Bug #401: 门诊完诊审计日志错误:div_log 表中 pool_id 与 slot_id 存值与设计规范不符
调整完诊时 div_log 的 pool_id/slot_id 获取优先级:优先使用 triage_queue_item
(挂号时录入的号源信息,为权威来源),队列项不存在或值缺失时回退使用
encounter → order → slot → pool 链路

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
赵云
6964f4f0a0 Fix Bug #428: 门诊医生站-检查申请:未实现分类联动检查方法及套餐明细展示与勾选逻辑
根本原因:
1. 分类联动加载检查方法时,未提取后端返回的 packageId 字段
2. 勾选检查方法后,未从方法中获取套餐信息(isPackage/packageId)
3. 选中带套餐的检查方法后,未调用 loadPackageDetailsForItem 预加载套餐明细

修复内容(4处手术式修改):
- handleCategoryExpand:方法映射增加 packageId 字段
- handleRowClick:回充已有申请单时,从匹配的方法中获取套餐信息
- handleMethodSelect:从方法获取套餐信息并预加载套餐明细
- handleItemSelect:方法映射增加 packageId 字段

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
赵云
b8d663d5fa Fix Bug #412: 门诊医生站:传染病报告卡保存失败,提示报错
根因分析:
- 前端在 buildSubmitData() 中使用 formData.diagnosisId || null 将空字符串转为 null
- 后端 InfectiousDiseaseReportDto.diagId 有 @NotNull 校验,导致 null 值被拒绝
- diagnosisId 来源于 show() 中 diagnosisData?.conditionId || diagnosisData?.definitionId
  使用 || 运算符会将 0 等假值跳过,可能导致 ID 丢失

修复内容:
1. show() 函数:使用显式 null/空字符串检查替代 || 运算符,确保 conditionId/definitionId 正确映射
2. handleSubmit():提交前增加 diagnosisId 非空校验,提前拦截并给出友好提示
3. buildSubmitData():diagId 使用 Number() 显式转换,确保发送正确的 Long 值

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
张飞
e32ef40f48 Fix Bug #362: 住院护士站:入出转管理双击查看详情时,"入科时间"字段显示当前系统时间而非实际入科时间
在 selectAdmissionPatientInfo SQL 中,startTime 原取自 bed.start_time(床位级别的位置记录),
当该 LEFT JOIN 无匹配记录时返回 NULL,前端 fallback 到当前系统时间。
改为 COALESCE(bed.start_time, ae.start_time),无床位记录时回退到 encounter 的入院时间。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
关羽
04903dc0b9 Fix Bug #492: 【门诊手术安排】关闭"手术计费"主弹窗后,项目字典选择列表依然残留悬浮在界面上
根因:el-popover 使用 :visible 受控模式,closeAllPopovers() 将 showPopover 设为 false
后,Vue 尚未完成 DOM 更新时 showChargeDialog 已被设为 false,导致弹窗组件被销毁
而 popover 的 visible 状态变更未传播到 DOM,弹窗消失但 popover 残留。

修复:在 closeChargeDialog 中使用 nextTick 等待 Vue 完成 popover 关闭的 DOM 更新后,
再设置 showChargeDialog = false 关闭主弹窗。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
诸葛亮
cd9dddb948 Fix Bug #494: 住院医生工作站-检查申请:"申请单名称"字段显示为通用名称,未展示具体检查项目名称
根因:medicalExaminations.vue 保存检查申请单时,name 字段硬编码为 "检查申请单",
导致列表页所有记录的申请单名称均显示为固定字符串,无法区分具体检查项目。

修复:将 name 从硬编码改为从已选项目集合中提取 adviceName 并用顿号连接,
如选择 CT、超声两项则显示为 "CT、超声"。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
赵云
14ebcea2e6 Fix Bug #493: 【住院医生工作站-临床医嘱-检验申请】项目未维护执行科室时,医生手动选择发往科室后仍报错且数据被清空
根因:projectWithDepartment 函数定义时遗漏了 type 参数,导致函数体内引用 type 变量时报 ReferenceError(未定义),type === 2 的判断永远无法正确执行。用户在提交时手动选择的发往科室被清空且无法提交。

修复:在函数签名中添加 type 参数,与 working 版本 medicalExaminations.vue 保持一致。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
刘备
7694122409 Fix Bug #489: 【医嘱闭环】医生站签发单条长期药品医嘱,护士校对界面生成重复(两条)待校对记录
在住院医生站签发流程的 handMedication() 方法中增加去重逻辑,
与门诊医生站保持一致,使用 patientId+encounterId+adviceDefinitionId+dose+methodCode+rateCode
作为唯一键,防止前端重复提交导致数据库产生重复医嘱记录。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
刘备
dd0cdf0af3 Fix Bug #491: 【执行科室配置】保存配置时系统报错 - 修复Organization.getName()空指针异常
根因分析:
1. organizationLocationInit() 中 Organization.getName() 未做空值过滤,若数据库存在name为null的科室记录会NPE
2. addOrEditOrgLoc() 中 activityDefinitionMapper.selectById().getName() 未对selectById返回null做防护
3. addOrEditOrgLoc() 缺少organizationId前置校验,空值传入可能导致后续流程异常

修复内容:
- 第74行:stream中增加 .filter(organization -> organization != null && organization.getName() != null)
- 第136-138行:增加organizationId为null时的校验,返回友好错误提示
- 第145-147行:将activityDefinitionMapper.selectById结果先赋值再判空,避免NPE

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
赵云
c6a29aa7f4 Fix Bug #488: 【临床医嘱】双击编辑待签发医嘱,医嘱类型回显为数字且点击确认报接口错误
修复 clickRowDb 函数中编辑条件过于严格的问题:原条件 `row.statusEnum == 1 && !row.requestId`
只允许"待保存"(无requestId)的医嘱进入编辑,导致"待签发"(有requestId)的医嘱无法编辑。
改为 `row.statusEnum == 1`,允许所有待签发状态的医嘱编辑。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
张飞
19b3bf5f3a Fix Bug #477: 住院医生工作站-住院检查申请详情弹窗中"发往科室"字段显示为短横线(-),未正常获取数据
根因分析:
1. 前端组件使用了错误的API获取科室列表:表单使用getDepartmentList(/app-common/department-list)
   保存的targetDepartment ID,但详情弹窗使用getOrgList(/base-data-manage/organization/organization)
   查询,两个接口返回的数据结构和ID不同,导致recursionFun无法匹配到科室名称
2. recursionFun中obj.children可能为null/undefined,直接遍历会抛TypeError
3. getLocationInfo是异步调用,handleViewDetail可能在科室数据加载完成前被调用

修复:
- 统一使用getDepartmentList(@/api/public.js)获取科室数据,与表单组件保持一致
- recursionFun增加children空值保护
- handleViewDetail改为async,打开详情前确保科室数据已加载
2026-05-13 11:03:19 +08:00
刘备
51a75a6787 Fix Bug #468: [住院医生工作站-检验申请] 列表页缺失【单据状态】列,无法闭环管理检验医嘱执行进度
前后端完整链路修复:
- 后端 Mapper: LEFT JOIN wor_service_request 表,通过 CASE MIN(status_enum) 映射单据状态
- 后端 Mapper: 新增状态筛选和关键字搜索(申请单号/检验项目模糊匹配)
- 后端 Service/Controller: 新增 status 和 keyword 参数传递
- 前端 Vue: 列表页添加【单据状态】列,绑定 status 字段
- 前端 Vue: 移除中间状态选项(已采集/已收样),与后端 CASE 映射保持一致

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
荀彧
79a91f0a77 Fix Bug #464: [目录管理-诊疗目录] 新增项目时"零售价"未与"诊疗子项"合计总价自动同步
根本原因: calculateTotalPrice() 中同步零售价的条件只检查了第一个子项 (treatmentItems.value[0]),当第一个子项被清空但其他子项有效时,零售价不会同步。submitForm() 中存在相同问题。

修复内容:
1. calculateTotalPrice(): 使用 Array.some() 检查是否有任何有效子项,而非只检查第一个
2. 当无有效子项时,将 retailPrice 重置为 undefined 避免残留旧值
3. submitForm(): childrenJson 序列化和零售价同步同样改用 some() 检查
4. addItem(): 补充缺失的 name 字段,与初始值结构保持一致

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
荀彧
f1e30bb3a7 Fix Bug #457: 门诊收费:已签发的手术类医嘱在门诊收费列表中不显示项目名称
根因分析:门诊医生站处方列表查询(DoctorStationAdviceAppMapper.xml)中,
手术类医嘱(category_enum=4)的 advice_table_name 固定返回 'wor_activity_definition',
而非 'cli_surgery'。当医生通过"签发"按钮处理手术医嘱时,handService() 据此创建
ChargeItem,导致 product_table = 'wor_activity_definition',但 product_id 实际指向
cli_surgery 表中的手术记录。

门诊收费SQL查询的CASE语句仅匹配 product_table = 'cli_surgery' 的手术项,
因此这些手术医嘱无法匹配,item_name 返回 NULL。

修复方案:在 selectEncounterPatientPrescription 和 selectEncounterPatientPrescriptionWithPrice
的 item_name CASE 表达式中新增兜底分支:
  WHEN context_enum = #{activity} AND service_table = 'wor_service_request'
  THEN COALESCE(T9.surgery_name, wsr.content_json->>'surgeryName',
                wsr.content_json->>'adviceName', T2."name")

按优先级回退获取手术名称:cli_surgery表 → content_json手术名称 → content_json医嘱名称 → 诊疗定义名称

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
刘备
49a2313d7a Fix Bug #458: 门诊医生站:诊疗类医嘱保存成功后,列表"医嘱类型"列显示为空值
根因:mapAdviceTypeLabel 函数依赖 drord_doctor_type 字典数据进行类型映射,
当字典中缺少 value=3(诊疗)的条目时,find() 返回 undefined,函数返回空字符串,
导致保存后刷新列表时"医嘱类型"列显示为空白。

修复:在 mapAdviceTypeLabel 中为诊疗/手术类医嘱(wor_activity_definition 表)
添加兜底映射逻辑:type 3→诊疗, 6→手术, 4→手术, 1→检验, 2→检查, 5→其他,
确保即使字典缺失对应条目也能正确显示类型标签。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
赵云
677106afc2 Fix Bug #448: 门诊划价模块-项目分类过滤失效,选择"耗材"类型时仍能检索出药品
根因: adviceBaseList.vue 中 adviceQueryParams 的 watch 在 popoverVisible=false 时
直接 return,未将参数同步到 queryParams。当 handleFocus 同时修改 adviceQueryParams
和 showPopover 时,Vue 的 watch 触发顺序不确定:
- 若 adviceQueryParams watch 先触发(popoverVisible 仍为 false),则 queryParams 保持旧值
- 随后 popoverVisible watch 触发时虽然会同步参数,但存在时序竞态导致查询参数不正确

修复: 将参数同步逻辑移至 early return 之前,确保 queryParams 始终与 adviceQueryParams
保持一致,API 请求仍在 popoverVisible=true 时才触发。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
华佗
925f3dde41 Fix Bug #442: 手术计费:点击"删除"待签发耗材时异常报错,导致操作失败
根因:DoctorStationAdviceAppMapper.xml 中 getRequestBaseInfo SQL 的第二个 UNION 查询(手术计费耗材从 adm_charge_item 关联 wor_device_request)中,biz_request_flag 和 requester_id 使用了 CI.enterer_id(计费录入人),而非 DR.requester_id(设备申请创建人)。当录入人与当前操作人不一致时,biz_request_flag 为 '0',导致删除操作被后端拒绝。

修复:将 CI.enterer_id 改为 COALESCE(DR.requester_id, CI.enterer_id),优先使用 DeviceRequest 的 requester_id,确保 biz_request_flag 基于正确的创建人计算。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:19 +08:00
陈琳
ae805eb89c Fix Bug #428: 门诊医生站-检查申请:未实现分类联动检查方法及套餐明细展示与勾选逻辑
根因分析:
1. handleCategoryExpand 加载了 cat.methods 但模板从未渲染,用户展开分类后看不到检查方法
2. 缺少 isMethodSelected/handleMethodSelect 函数,无法通过勾选检查方法来联动添加到已选择列表
3. 套餐明细展示缺少 CSS 样式(package-details-list/detail-row/detail-name/detail-info)

修复内容:
- 模板: 在分类折叠区域添加 cat.methods 的渲染(检查方法列表 + 勾选框 + 价格)
- 逻辑: 新增 isMethodSelected 和 handleMethodSelect 函数,支持直接勾选检查方法添加到已选择列表
- 样式: 添加套餐明细列表样式 + 检查方法区域样式

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 23:01:48 +08:00
53 changed files with 377 additions and 1512 deletions

View File

@@ -2,13 +2,9 @@ package com.openhis.web.clinicalmanage.controller;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.openhis.common.enums.ActivityDefCategory;
import com.openhis.web.clinicalmanage.appservice.ISurgicalScheduleAppService; import com.openhis.web.clinicalmanage.appservice.ISurgicalScheduleAppService;
import com.openhis.web.clinicalmanage.dto.OpCreateScheduleDto; import com.openhis.web.clinicalmanage.dto.OpCreateScheduleDto;
import com.openhis.web.clinicalmanage.dto.OpScheduleDto; import com.openhis.web.clinicalmanage.dto.OpScheduleDto;
import com.openhis.web.regdoctorstation.appservice.IRequestFormManageAppService;
import com.openhis.web.regdoctorstation.dto.RequestFormDto;
import com.openhis.web.regdoctorstation.dto.RequestFormPageDto;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -30,7 +26,6 @@ import java.util.Map;
public class SurgicalScheduleController { public class SurgicalScheduleController {
private final ISurgicalScheduleAppService surgicalScheduleAppService; private final ISurgicalScheduleAppService surgicalScheduleAppService;
private final IRequestFormManageAppService requestFormManageAppService;
/** /**
* 分页查询手术安排列表 * 分页查询手术安排列表
@@ -92,27 +87,6 @@ public class SurgicalScheduleController {
return surgicalScheduleAppService.deleteSurgerySchedule(scheduleId); return surgicalScheduleAppService.deleteSurgerySchedule(scheduleId);
} }
/**
* 分页查询待排期手术申请列表
*
* @param requestFormDto 查询条件
* @return 手术申请列表
*/
@PostMapping(value = "/apply-list")
public R<IPage<RequestFormPageDto>> getSurgeryApplyList(@RequestBody RequestFormDto requestFormDto) {
if (requestFormDto.getPageNo() == null) {
requestFormDto.setPageNo(1);
}
if (requestFormDto.getPageSize() == null) {
requestFormDto.setPageSize(10);
}
//虽然很想这么写但是库里的手术申请单的type_code都是直接写的SURGERY
// requestFormDto.setTypeCode(ActivityDefCategory.PROCEDURE.getCode());
//只查询手术申请单
requestFormDto.setTypeCode("SURGERY");
return R.ok(requestFormManageAppService.getRequestFormPage(requestFormDto));
}
/** /**
* 导出手术安排列表 * 导出手术安排列表
* *

View File

@@ -59,16 +59,6 @@ public interface IDoctorStationAdviceAppService {
*/ */
R<?> getRequestBaseInfo(Long encounterId); R<?> getRequestBaseInfo(Long encounterId);
/**
* 查询医嘱请求数据(支持按生成来源和来源单据号过滤)
*
* @param encounterId 就诊id
* @param generateSourceEnum 生成来源(可选,如手术计费=6
* @param sourceBillNo 来源业务单据号(可选)
* @return 医嘱请求数据
*/
R<?> getRequestBaseInfo(Long encounterId, Integer generateSourceEnum, String sourceBillNo);
/** /**
* 门诊签退医嘱 * 门诊签退医嘱
* *

View File

@@ -228,10 +228,8 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
// 医嘱定义ID集合 // 医嘱定义ID集合
List<Long> adviceDefinitionIdList = adviceBaseDtoList.stream().map(AdviceBaseDto::getAdviceDefinitionId) List<Long> adviceDefinitionIdList = adviceBaseDtoList.stream().map(AdviceBaseDto::getAdviceDefinitionId)
.collect(Collectors.toList()); .collect(Collectors.toList());
// 费用定价主表ID集合过滤null值手术项目无定价定义 // 费用定价主表ID集合
List<Long> chargeItemDefinitionIdList = adviceBaseDtoList.stream() List<Long> chargeItemDefinitionIdList = adviceBaseDtoList.stream().map(AdviceBaseDto::getChargeItemDefinitionId)
.map(AdviceBaseDto::getChargeItemDefinitionId)
.filter(Objects::nonNull)
.collect(Collectors.toList()); .collect(Collectors.toList());
// 判断是否包含药品或耗材类型(只有这些类型才需要库存相关查询) // 判断是否包含药品或耗材类型(只有这些类型才需要库存相关查询)
@@ -277,9 +275,9 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
medLocationConfig = Collections.emptyList(); medLocationConfig = Collections.emptyList();
allowedLocByCategory = Collections.emptyMap(); allowedLocByCategory = Collections.emptyMap();
} }
// 费用定价子表信息 - 仅药品/耗材需要批次定价查询,手术/诊疗无库存概念不需要 // 费用定价子表信息 - 使用分批处理避免大量参数问题
List<AdvicePriceDto> childCharge = new ArrayList<>(); List<AdvicePriceDto> childCharge = new ArrayList<>();
if (hasMedOrDevice && chargeItemDefinitionIdList != null && !chargeItemDefinitionIdList.isEmpty()) { if (chargeItemDefinitionIdList != null && !chargeItemDefinitionIdList.isEmpty()) {
// 分批处理每批最多1000个ID增加批次大小以减少查询次数 // 分批处理每批最多1000个ID增加批次大小以减少查询次数
int batchSize = 1000; int batchSize = 1000;
for (int i = 0; i < chargeItemDefinitionIdList.size(); i += batchSize) { for (int i = 0; i < chargeItemDefinitionIdList.size(); i += batchSize) {
@@ -959,16 +957,11 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
} }
} }
for (AdviceSaveDto adviceSaveDto : deleteList) { for (AdviceSaveDto adviceSaveDto : deleteList) {
Long requestId = adviceSaveDto.getRequestId(); iMedicationRequestService.removeById(adviceSaveDto.getRequestId());
// 🔧 Bug #442: 跳过 requestId 为 null 的记录,避免删除不存在的药品请求
if (requestId == null) {
log.warn("BugFix#442: handMedication - 跳过 requestId 为 null 的删除请求");
continue;
}
iMedicationRequestService.removeById(requestId);
// 删除已经产生的药品发放信息 // 删除已经产生的药品发放信息
iMedicationDispenseService.deleteMedicationDispense(adviceSaveDto.getRequestId()); iMedicationDispenseService.deleteMedicationDispense(adviceSaveDto.getRequestId());
// 🔧 Bug Fix #219: 删除费用项 // 🔧 Bug Fix #219: 删除费用项
Long requestId = adviceSaveDto.getRequestId();
String serviceTable = CommonConstants.TableName.MED_MEDICATION_REQUEST; String serviceTable = CommonConstants.TableName.MED_MEDICATION_REQUEST;
// 直接删除费用项 // 直接删除费用项
iChargeItemService.deleteByServiceTableAndId(serviceTable, requestId); iChargeItemService.deleteByServiceTableAndId(serviceTable, requestId);
@@ -1424,11 +1417,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
log.info("BugFix#219: handDevice - 开始删除循环, deleteList.size={}", deleteList.size()); log.info("BugFix#219: handDevice - 开始删除循环, deleteList.size={}", deleteList.size());
for (AdviceSaveDto adviceSaveDto : deleteList) { for (AdviceSaveDto adviceSaveDto : deleteList) {
Long requestId = adviceSaveDto.getRequestId(); Long requestId = adviceSaveDto.getRequestId();
// 🔧 Bug #442: 跳过 requestId 为 null 的记录,避免删除不存在的耗材请求
if (requestId == null) {
log.warn("BugFix#442: handDevice - 跳过 requestId 为 null 的删除请求");
continue;
}
log.info("BugFix#219: handDevice - 删除开始: requestId={}", requestId); log.info("BugFix#219: handDevice - 删除开始: requestId={}", requestId);
// 1. 删除耗材请求 // 1. 删除耗材请求
@@ -1539,22 +1527,11 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
deviceRequest.setRequesterId(adviceSaveDto.getPractitionerId()); // 开方医生 deviceRequest.setRequesterId(adviceSaveDto.getPractitionerId()); // 开方医生
deviceRequest.setOrgId(adviceSaveDto.getFounderOrgId());// 开方人科室 deviceRequest.setOrgId(adviceSaveDto.getFounderOrgId());// 开方人科室
deviceRequest.setReqAuthoredTime(curDate); // 请求开始时间 deviceRequest.setReqAuthoredTime(curDate); // 请求开始时间
// 发放耗材房若前端未传locationId优先沿用已有DeviceRequest的performLocation否则使用登录用户科室) // 发放耗材房若前端未传locationId使用登录用户科室作为默认值
Long locId = adviceSaveDto.getLocationId(); Long locId = adviceSaveDto.getLocationId();
if (locId == null) { if (locId == null) {
// 尝试从已有DeviceRequest获取原始的performLocation locId = SecurityUtils.getLoginUser().getOrgId();
if (adviceSaveDto.getRequestId() != null) { log.info("耗材locationId为空使用登录用户科室作为默认值: locationId={}", locId);
DeviceRequest existingDevice = iDeviceRequestService.getById(adviceSaveDto.getRequestId());
if (existingDevice != null && existingDevice.getPerformLocation() != null) {
locId = existingDevice.getPerformLocation();
log.info("耗材locationId为空使用已有DeviceRequest的performLocation: locationId={}", locId);
}
}
// 如果已有记录也没有performLocation则使用登录用户科室作为兜底
if (locId == null) {
locId = SecurityUtils.getLoginUser().getOrgId();
log.info("耗材locationId为空且无已有记录使用登录用户科室作为默认值: locationId={}", locId);
}
} }
deviceRequest.setPerformLocation(locId); deviceRequest.setPerformLocation(locId);
deviceRequest.setEncounterId(adviceSaveDto.getEncounterId()); // 就诊id deviceRequest.setEncounterId(adviceSaveDto.getEncounterId()); // 就诊id
@@ -1744,17 +1721,12 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
} }
} }
for (AdviceSaveDto adviceSaveDto : deleteList) { for (AdviceSaveDto adviceSaveDto : deleteList) {
Long requestId = adviceSaveDto.getRequestId(); iServiceRequestService.removeById(adviceSaveDto.getRequestId());// 删除诊疗
// 🔧 Bug #442: 跳过 requestId 为 null 的记录,避免删除不存在的诊疗请求
if (requestId == null) {
log.warn("BugFix#442: handService - 跳过 requestId 为 null 的删除请求");
continue;
}
iServiceRequestService.removeById(requestId);// 删除诊疗
iServiceRequestService.remove( iServiceRequestService.remove(
new LambdaQueryWrapper<ServiceRequest>().eq(ServiceRequest::getParentId, new LambdaQueryWrapper<ServiceRequest>().eq(ServiceRequest::getParentId,
requestId));// 删除诊疗套餐对应的子项 adviceSaveDto.getRequestId()));// 删除诊疗套餐对应的子项
// 🔧 Bug Fix #219: 删除费用项 // 🔧 Bug Fix #219: 删除费用项
Long requestId = adviceSaveDto.getRequestId();
String serviceTable = CommonConstants.TableName.WOR_SERVICE_REQUEST; String serviceTable = CommonConstants.TableName.WOR_SERVICE_REQUEST;
// 直接删除费用项 // 直接删除费用项
iChargeItemService.deleteByServiceTableAndId(serviceTable, requestId); iChargeItemService.deleteByServiceTableAndId(serviceTable, requestId);
@@ -1811,9 +1783,8 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
log.info("handService - 自动补全founderOrgId: founderOrgId={}", adviceSaveDto.getFounderOrgId()); log.info("handService - 自动补全founderOrgId: founderOrgId={}", adviceSaveDto.getFounderOrgId());
} }
// 🔧 Bug Fix #238/#454: 诊疗项目执行科室非空校验(删除操作跳过校验) // 🔧 Bug Fix #238: 诊疗项目执行科室非空校验
if (adviceSaveDto.getAdviceType() != null && adviceSaveDto.getAdviceType() == 3 if (adviceSaveDto.getAdviceType() != null && adviceSaveDto.getAdviceType() == 3) {
&& !DbOpType.DELETE.getCode().equals(adviceSaveDto.getDbOpType())) {
Long effectiveOrgId = adviceSaveDto.getEffectiveOrgId(); Long effectiveOrgId = adviceSaveDto.getEffectiveOrgId();
if (effectiveOrgId == null) { if (effectiveOrgId == null) {
throw new ServiceException("诊疗项目必须选择执行科室"); throw new ServiceException("诊疗项目必须选择执行科室");
@@ -2016,25 +1987,13 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
*/ */
@Override @Override
public R<?> getRequestBaseInfo(Long encounterId) { public R<?> getRequestBaseInfo(Long encounterId) {
return this.getRequestBaseInfo(encounterId, null, null);
}
@Override
public R<?> getRequestBaseInfo(Long encounterId, Integer generateSourceEnum, String sourceBillNo) {
// 当前账号的参与者id // 当前账号的参与者id
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId(); Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
// 未指定generateSourceEnum时默认只查询医生开立的医嘱
int sourceEnum = (generateSourceEnum != null) ? generateSourceEnum : GenerateSource.DOCTOR_PRESCRIPTION.getValue();
// 医嘱请求数据 // 医嘱请求数据
List<RequestBaseDto> requestBaseInfo = doctorStationAdviceAppMapper.getRequestBaseInfo(encounterId, null, List<RequestBaseDto> requestBaseInfo = doctorStationAdviceAppMapper.getRequestBaseInfo(encounterId, null,
CommonConstants.TableName.MED_MEDICATION_REQUEST, CommonConstants.TableName.WOR_DEVICE_REQUEST, CommonConstants.TableName.MED_MEDICATION_REQUEST, CommonConstants.TableName.WOR_DEVICE_REQUEST,
CommonConstants.TableName.WOR_SERVICE_REQUEST, practitionerId, Whether.NO.getCode(), CommonConstants.TableName.WOR_SERVICE_REQUEST, practitionerId, Whether.NO.getCode(),
sourceEnum, sourceBillNo); GenerateSource.DOCTOR_PRESCRIPTION.getValue());
// 手术计费场景sourceBillNo 不为空时只保留诊疗请求3/6过滤掉药品1和耗材2
if (sourceBillNo != null && !sourceBillNo.isEmpty()) {
requestBaseInfo.removeIf(dto -> dto.getAdviceType() != null
&& (dto.getAdviceType() == 1 || dto.getAdviceType() == 2));
}
for (RequestBaseDto requestBaseDto : requestBaseInfo) { for (RequestBaseDto requestBaseDto : requestBaseInfo) {
// 请求状态 // 请求状态
requestBaseDto requestBaseDto
@@ -2155,7 +2114,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
List<RequestBaseDto> requestBaseInfo = doctorStationAdviceAppMapper.getRequestBaseInfo(encounterId, patientId, List<RequestBaseDto> requestBaseInfo = doctorStationAdviceAppMapper.getRequestBaseInfo(encounterId, patientId,
CommonConstants.TableName.MED_MEDICATION_REQUEST, CommonConstants.TableName.WOR_DEVICE_REQUEST, CommonConstants.TableName.MED_MEDICATION_REQUEST, CommonConstants.TableName.WOR_DEVICE_REQUEST,
CommonConstants.TableName.WOR_SERVICE_REQUEST, practitionerId, Whether.YES.getCode(), CommonConstants.TableName.WOR_SERVICE_REQUEST, practitionerId, Whether.YES.getCode(),
GenerateSource.DOCTOR_PRESCRIPTION.getValue(), null); GenerateSource.DOCTOR_PRESCRIPTION.getValue());
for (RequestBaseDto requestBaseDto : requestBaseInfo) { for (RequestBaseDto requestBaseDto : requestBaseInfo) {
// 请求状态 // 请求状态
requestBaseDto requestBaseDto

View File

@@ -580,11 +580,7 @@ public class DoctorStationDiagnosisAppServiceImpl implements IDoctorStationDiagn
@Override @Override
public R<?> saveInfectiousDiseaseReport(InfectiousDiseaseReportDto infectiousDiseaseReportDto) { public R<?> saveInfectiousDiseaseReport(InfectiousDiseaseReportDto infectiousDiseaseReportDto) {
// 检查卡片编号唯一性(新增时检查,编辑时排除当前记录) // 检查卡片编号唯一性(新增时检查,编辑时排除当前记录)
String cardNo = infectiousDiseaseReportDto.getCardNo(); String cardNo = infectiousDiseaseReportDto.getCardNo().trim();
if (cardNo == null || cardNo.trim().isEmpty()) {
return R.fail("卡片编号不能为空");
}
cardNo = cardNo.trim();
LambdaQueryWrapper<InfectiousDiseaseReport> queryWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<InfectiousDiseaseReport> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(InfectiousDiseaseReport::getCardNo, cardNo); queryWrapper.eq(InfectiousDiseaseReport::getCardNo, cardNo);
long count = iInfectiousDiseaseReportService.count(queryWrapper); long count = iInfectiousDiseaseReportService.count(queryWrapper);

View File

@@ -307,15 +307,13 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
if (queueItem != null && queueItem.getPoolId() != null && queueItem.getSlotId() != null) { if (queueItem != null && queueItem.getPoolId() != null && queueItem.getSlotId() != null) {
divPoolId = queueItem.getPoolId(); divPoolId = queueItem.getPoolId();
divSlotId = queueItem.getSlotId(); divSlotId = queueItem.getSlotId();
} } else if (encounter.getOrderId() != null) {
// 队列项 poolId/slotId 缺失时,通过 encounter.orderId → order_main.slot_id → adm_schedule_slot.pool_id 回退获取
if ((divPoolId == null || divSlotId == null) && encounter.getOrderId() != null) {
try { try {
Order order = iOrderService.getById(encounter.getOrderId()); Order order = iOrderService.getById(encounter.getOrderId());
if (order != null && order.getSlotId() != null) { if (order != null && order.getSlotId() != null) {
ScheduleSlot slot = scheduleSlotMapper.selectById(order.getSlotId()); divSlotId = order.getSlotId();
ScheduleSlot slot = scheduleSlotMapper.selectById(divSlotId);
if (slot != null) { if (slot != null) {
divSlotId = slot.getId();
divPoolId = slot.getPoolId(); divPoolId = slot.getPoolId();
} }
} }
@@ -343,24 +341,19 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
} }
// 写入 div_log 审计日志(独立于队列项,确保每次完诊都生成记录) // 写入 div_log 审计日志(独立于队列项,确保每次完诊都生成记录)
// 防重复:若队列项已是 COMPLETED 状态,说明护士站已处理过并写过分诊日志,不再重复写入 try {
boolean queueAlreadyCompleted = queueItem != null LoginUser loginUser = SecurityUtils.getLoginUser();
&& TriageQueueStatus.COMPLETED.getValue().equals(queueItem.getStatus()); DivLog divLog = new DivLog()
if (!queueAlreadyCompleted) { .setPoolId(divPoolId)
try { .setSlotId(divSlotId)
LoginUser loginUser = SecurityUtils.getLoginUser(); .setOpUserId(loginUser != null ? loginUser.getUserId() : null)
DivLog divLog = new DivLog() .setAction("COMPLETE")
.setPoolId(divPoolId) .setCreateTime(LocalDateTime.now())
.setSlotId(divSlotId) .setUpdateAt(LocalDateTime.now())
.setOpUserId(loginUser != null ? loginUser.getUserId() : null) .setCreatedAt(LocalDateTime.now());
.setAction("COMPLETE") divLogService.save(divLog);
.setCreateTime(LocalDateTime.now()) } catch (Exception e) {
.setUpdateAt(LocalDateTime.now()) log.error("写入div_log审计日志失败", e);
.setCreatedAt(LocalDateTime.now());
divLogService.save(divLog);
} catch (Exception e) {
log.error("写入div_log审计日志失败", e);
}
} }
// 4. 更新状态、完成时间以及初复诊标识 // 4. 更新状态、完成时间以及初复诊标识

View File

@@ -112,16 +112,11 @@ public class DoctorStationAdviceController {
* 查询医嘱请求数据 * 查询医嘱请求数据
* *
* @param encounterId 就诊id * @param encounterId 就诊id
* @param generateSourceEnum 生成来源(可选,用于按来源过滤,如手术计费=6
* @param sourceBillNo 来源业务单据号(可选,用于按来源单据过滤)
* @return 医嘱请求数据 * @return 医嘱请求数据
*/ */
@GetMapping(value = "/request-base-info") @GetMapping(value = "/request-base-info")
public R<?> getRequestBaseInfo( public R<?> getRequestBaseInfo(@RequestParam(required = false) Long encounterId) {
@RequestParam(required = false) Long encounterId, return iDoctorStationAdviceAppService.getRequestBaseInfo(encounterId);
@RequestParam(required = false) Integer generateSourceEnum,
@RequestParam(required = false) String sourceBillNo) {
return iDoctorStationAdviceAppService.getRequestBaseInfo(encounterId, generateSourceEnum, sourceBillNo);
} }
/** /**

View File

@@ -122,8 +122,7 @@ public interface DoctorStationAdviceAppMapper {
@Param("MED_MEDICATION_REQUEST") String MED_MEDICATION_REQUEST, @Param("MED_MEDICATION_REQUEST") String MED_MEDICATION_REQUEST,
@Param("WOR_DEVICE_REQUEST") String WOR_DEVICE_REQUEST, @Param("WOR_DEVICE_REQUEST") String WOR_DEVICE_REQUEST,
@Param("WOR_SERVICE_REQUEST") String WOR_SERVICE_REQUEST, @Param("practitionerId") Long practitionerId, @Param("WOR_SERVICE_REQUEST") String WOR_SERVICE_REQUEST, @Param("practitionerId") Long practitionerId,
@Param("historyFlag") String historyFlag, @Param("generateSourceEnum") Integer generateSourceEnum, @Param("historyFlag") String historyFlag, @Param("generateSourceEnum") Integer generateSourceEnum);
@Param("sourceBillNo") String sourceBillNo);
/** /**
* 查询就诊费用性质 * 查询就诊费用性质

View File

@@ -178,26 +178,15 @@ public class AdviceUtils {
// 生命提示信息集合 // 生命提示信息集合
List<String> tipsList = new ArrayList<>(); List<String> tipsList = new ArrayList<>();
for (MedicationRequestUseExe medicationRequestUseExe : medUseExeList) { for (MedicationRequestUseExe medicationRequestUseExe : medUseExeList) {
// 第一步:按 performLocation 匹配指定药房的库存 // 聚合同一位置所有批次的库存总量
List<AdviceInventoryDto> matchedInventories = adviceInventory.stream() List<AdviceInventoryDto> matchedInventories = adviceInventory.stream()
.filter(inventoryDto -> medicationRequestUseExe.getMedicationId().equals(inventoryDto.getItemId()) .filter(inventoryDto -> medicationRequestUseExe.getMedicationId().equals(inventoryDto.getItemId())
&& CommonConstants.TableName.MED_MEDICATION_DEFINITION.equals(inventoryDto.getItemTable()) && CommonConstants.TableName.MED_MEDICATION_DEFINITION.equals(inventoryDto.getItemTable())
&& (medicationRequestUseExe.getPerformLocation() == null && medicationRequestUseExe.getPerformLocation().equals(inventoryDto.getLocationId())
|| medicationRequestUseExe.getPerformLocation().equals(inventoryDto.getLocationId()))
// 如果选择了具体的批次号,校验库存时需要加上批次号的匹配条件 // 如果选择了具体的批次号,校验库存时需要加上批次号的匹配条件
&& (StringUtils.isEmpty(medicationRequestUseExe.getLotNumber()) && (StringUtils.isEmpty(medicationRequestUseExe.getLotNumber())
|| medicationRequestUseExe.getLotNumber().equals(inventoryDto.getLotNumber()))) || medicationRequestUseExe.getLotNumber().equals(inventoryDto.getLotNumber())))
.collect(Collectors.toList()); .collect(Collectors.toList());
// 第二步:如果指定药房没有匹配到库存,则放宽条件查询所有药房的库存
if (matchedInventories.isEmpty()) {
matchedInventories = adviceInventory.stream()
.filter(inventoryDto -> medicationRequestUseExe.getMedicationId().equals(inventoryDto.getItemId())
&& CommonConstants.TableName.MED_MEDICATION_DEFINITION.equals(inventoryDto.getItemTable())
// 如果选择了具体的批次号,校验库存时需要加上批次号的匹配条件
&& (StringUtils.isEmpty(medicationRequestUseExe.getLotNumber())
|| medicationRequestUseExe.getLotNumber().equals(inventoryDto.getLotNumber())))
.collect(Collectors.toList());
}
// 匹配到库存信息 // 匹配到库存信息
if (!matchedInventories.isEmpty()) { if (!matchedInventories.isEmpty()) {
// 聚合所有批次的可用库存 // 聚合所有批次的可用库存

View File

@@ -341,7 +341,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
&& (DbOpType.INSERT.getCode().equals(e.getDbOpType()) || DbOpType.UPDATE.getCode().equals(e.getDbOpType()))) && (DbOpType.INSERT.getCode().equals(e.getDbOpType()) || DbOpType.UPDATE.getCode().equals(e.getDbOpType())))
.collect(Collectors.toList()); .collect(Collectors.toList());
// 防重复保存:对所有医嘱进行去重(包括 INSERT 和 UPDATE 混合场景),避免签发单条医嘱时产生重复记录 // 防重复保存:对新增医嘱进行去重,避免签发单条长期医嘱时产生重复记录
Set<String> longUniqueKeySet = new HashSet<>(); Set<String> longUniqueKeySet = new HashSet<>();
List<RegAdviceSaveDto> longUniqueList = new ArrayList<>(); List<RegAdviceSaveDto> longUniqueList = new ArrayList<>();
for (RegAdviceSaveDto adviceSaveDto : longInsertOrUpdateList) { for (RegAdviceSaveDto adviceSaveDto : longInsertOrUpdateList) {
@@ -351,10 +351,10 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
+ adviceSaveDto.getDose() + "_" + adviceSaveDto.getDose() + "_"
+ adviceSaveDto.getMethodCode() + "_" + adviceSaveDto.getMethodCode() + "_"
+ adviceSaveDto.getRateCode(); + adviceSaveDto.getRateCode();
if (longUniqueKeySet.contains(uniqueKey)) { if (DbOpType.INSERT.getCode().equals(adviceSaveDto.getDbOpType()) && longUniqueKeySet.contains(uniqueKey)) {
log.warn("防重复保存:检测到重复长期医嘱(跨操作类型),跳过 - patientId={}, encounterId={}, adviceDefinitionId={}, dbOpType={}", log.warn("防重复保存:检测到重复长期医嘱,跳过保存 - patientId={}, encounterId={}, adviceDefinitionId={}, dose={}",
adviceSaveDto.getPatientId(), adviceSaveDto.getEncounterId(), adviceSaveDto.getPatientId(), adviceSaveDto.getEncounterId(),
adviceSaveDto.getAdviceDefinitionId(), adviceSaveDto.getDbOpType()); adviceSaveDto.getAdviceDefinitionId(), adviceSaveDto.getDose());
continue; continue;
} }
longUniqueKeySet.add(uniqueKey); longUniqueKeySet.add(uniqueKey);
@@ -429,7 +429,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
&& (DbOpType.INSERT.getCode().equals(e.getDbOpType()) || DbOpType.UPDATE.getCode().equals(e.getDbOpType()))) && (DbOpType.INSERT.getCode().equals(e.getDbOpType()) || DbOpType.UPDATE.getCode().equals(e.getDbOpType())))
.collect(Collectors.toList()); .collect(Collectors.toList());
// 防重复保存:对所有医嘱进行去重(包括 INSERT 和 UPDATE 混合场景),避免签发时产生重复记录 // 防重复保存:对新增医嘱进行去重
Set<String> tempUniqueKeySet = new HashSet<>(); Set<String> tempUniqueKeySet = new HashSet<>();
List<RegAdviceSaveDto> tempUniqueList = new ArrayList<>(); List<RegAdviceSaveDto> tempUniqueList = new ArrayList<>();
for (RegAdviceSaveDto adviceSaveDto : tempInsertOrUpdateList) { for (RegAdviceSaveDto adviceSaveDto : tempInsertOrUpdateList) {
@@ -439,10 +439,10 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
+ adviceSaveDto.getDose() + "_" + adviceSaveDto.getDose() + "_"
+ adviceSaveDto.getMethodCode() + "_" + adviceSaveDto.getMethodCode() + "_"
+ adviceSaveDto.getRateCode(); + adviceSaveDto.getRateCode();
if (tempUniqueKeySet.contains(uniqueKey)) { if (DbOpType.INSERT.getCode().equals(adviceSaveDto.getDbOpType()) && tempUniqueKeySet.contains(uniqueKey)) {
log.warn("防重复保存:检测到重复临时医嘱(跨操作类型),跳过 - patientId={}, encounterId={}, adviceDefinitionId={}, dbOpType={}", log.warn("防重复保存:检测到重复临时医嘱,跳过保存 - patientId={}, encounterId={}, adviceDefinitionId={}, dose={}",
adviceSaveDto.getPatientId(), adviceSaveDto.getEncounterId(), adviceSaveDto.getPatientId(), adviceSaveDto.getEncounterId(),
adviceSaveDto.getAdviceDefinitionId(), adviceSaveDto.getDbOpType()); adviceSaveDto.getAdviceDefinitionId(), adviceSaveDto.getDose());
continue; continue;
} }
tempUniqueKeySet.add(uniqueKey); tempUniqueKeySet.add(uniqueKey);
@@ -710,21 +710,11 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
// 批量更新诊疗医嘱状态(使用 update 确保状态字段必定更新) // 批量更新诊疗医嘱状态(使用 update 确保状态字段必定更新)
if (!processedRequestIds.isEmpty()) { if (!processedRequestIds.isEmpty()) {
// 🔧 Bug #487 修复:签发时额外设置 authoredTime确保签发时间被记录 iServiceRequestService.update(null,
if (is_sign) { new LambdaUpdateWrapper<ServiceRequest>()
iServiceRequestService.update(null, .set(ServiceRequest::getStatusEnum,
new LambdaUpdateWrapper<ServiceRequest>() is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue())
.set(ServiceRequest::getStatusEnum, RequestStatus.ACTIVE.getValue()) .in(ServiceRequest::getId, processedRequestIds));
.set(ServiceRequest::getAuthoredTime, authoredTime)
.set(ServiceRequest::getSignCode, signCode)
.in(ServiceRequest::getId, processedRequestIds));
log.info("签发诊疗医嘱成功requestIds: {}, signCode: {}", processedRequestIds, signCode);
} else {
iServiceRequestService.update(null,
new LambdaUpdateWrapper<ServiceRequest>()
.set(ServiceRequest::getStatusEnum, RequestStatus.DRAFT.getValue())
.in(ServiceRequest::getId, processedRequestIds));
}
} }
} }

View File

@@ -76,34 +76,12 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public R<?> saveRequestForm(RequestFormSaveDto requestFormSaveDto, String typeCode) { public R<?> saveRequestForm(RequestFormSaveDto requestFormSaveDto, String typeCode) {
// 申请单ID前端空字符串可能反序列化为0L需同时判0
Long requestFormId = requestFormSaveDto.getRequestFormId();
boolean isEdit = requestFormId != null && requestFormId != 0L;
// 诊疗执行科室配置校验(必须在任何数据库操作之前)
List<ActivityOrganizationConfigDto> activityOrganizationConfig =
requestFormManageAppMapper.getActivityOrganizationConfig(typeCode);
if (activityOrganizationConfig.isEmpty()) {
throw new ServiceException("请先配置当前时间段的执行科室");
}
// 逐个校验activityList中的项目是否都配置了执行科室避免部分通过后在循环中抛异常导致事务复杂化
List<ActivitySaveDto> activityList = requestFormSaveDto.getActivityList();
if (activityList != null && !activityList.isEmpty()) {
for (ActivitySaveDto activitySaveDto : activityList) {
Long positionId = activityOrganizationConfig.stream()
.filter(dto -> activitySaveDto.getAdviceDefinitionId().equals(dto.getActivityDefinitionId()))
.map(ActivityOrganizationConfigDto::getOrganizationId).findFirst().orElse(null);
if (positionId == null) {
throw new ServiceException(activitySaveDto.getAdviceDefinitionName() + "未配置当前时间段的执行科室");
}
}
}
// 诊疗处方号 // 诊疗处方号
String prescriptionNo; String prescriptionNo;
// 申请单ID
Long requestFormId = requestFormSaveDto.getRequestFormId();
// 编辑场景 // 编辑场景
if (isEdit) { if (requestFormId != null) {
RequestForm requestFormInfo = iRequestFormService.getById(requestFormId); RequestForm requestFormInfo = iRequestFormService.getById(requestFormId);
prescriptionNo = requestFormInfo.getPrescriptionNo(); prescriptionNo = requestFormInfo.getPrescriptionNo();
// 该申请单存在的待发送医嘱个数 // 该申请单存在的待发送医嘱个数
@@ -147,7 +125,7 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
iRequestFormService.saveOrUpdate(requestForm); iRequestFormService.saveOrUpdate(requestForm);
// 编辑场景时,先删除掉原有诊疗项目及账单再新增 // 编辑场景时,先删除掉原有诊疗项目及账单再新增
if (isEdit) { if (requestFormId != null) {
List<Long> serviceRequestIds = iServiceRequestService List<Long> serviceRequestIds = iServiceRequestService
.list(new LambdaQueryWrapper<ServiceRequest>().eq(ServiceRequest::getPrescriptionNo, prescriptionNo)) .list(new LambdaQueryWrapper<ServiceRequest>().eq(ServiceRequest::getPrescriptionNo, prescriptionNo))
.stream().map(ServiceRequest::getId).collect(Collectors.toList()); .stream().map(ServiceRequest::getId).collect(Collectors.toList());
@@ -161,7 +139,15 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
ServiceRequest serviceRequest; ServiceRequest serviceRequest;
ChargeItem chargeItem; ChargeItem chargeItem;
// 诊疗集合
List<ActivitySaveDto> activityList = requestFormSaveDto.getActivityList();
log.info("保存申请单typeCode={}, activityListSize={}, encounterId={}", typeCode, activityList != null ? activityList.size() : 0, encounterId); log.info("保存申请单typeCode={}, activityListSize={}, encounterId={}", typeCode, activityList != null ? activityList.size() : 0, encounterId);
// 诊疗执行科室配置
List<ActivityOrganizationConfigDto> activityOrganizationConfig =
requestFormManageAppMapper.getActivityOrganizationConfig(typeCode);
if (activityOrganizationConfig.isEmpty()) {
throw new ServiceException("请先配置当前时间段的执行科室");
}
for (ActivitySaveDto activitySaveDto : activityList) { for (ActivitySaveDto activitySaveDto : activityList) {
serviceRequest = new ServiceRequest(); serviceRequest = new ServiceRequest();

View File

@@ -16,7 +16,6 @@ import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List; import java.util.List;
/** /**
@@ -82,23 +81,14 @@ public class RequestFormManageController {
* 查询检查申请单 * 查询检查申请单
* *
* @param encounterId 就诊id * @param encounterId 就诊id
* @param startDate 开始日期可选格式yyyy-MM-dd
* @param endDate 结束日期可选格式yyyy-MM-dd
* @param status 单据状态(可选)
* @param keyword 关键字(可选,申请单号/检查项目名称模糊匹配)
* @return 检查申请单 * @return 检查申请单
*/ */
@GetMapping(value = "/get-check") @GetMapping(value = "/get-check")
public R<?> getCheckRequestForm( public R<?> getCheckRequestForm(@RequestParam(required = false) Long encounterId) {
@RequestParam(required = false) Long encounterId,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate,
@RequestParam(required = false) String status,
@RequestParam(required = false) String keyword) {
if (encounterId == null) { if (encounterId == null) {
return R.fail("就诊ID不能为空"); return R.fail("就诊ID不能为空");
} }
return R.ok(iRequestFormManageAppService.getRequestForm(encounterId, ActivityDefCategory.TEST.getCode(), startDate, endDate, status, keyword)); return R.ok(iRequestFormManageAppService.getRequestForm(encounterId, ActivityDefCategory.TEST.getCode()));
} }
/** /**
@@ -152,32 +142,6 @@ public class RequestFormManageController {
return R.ok(iRequestFormManageAppService.getRequestForm(encounterId, ActivityDefCategory.PROCEDURE.getCode())); return R.ok(iRequestFormManageAppService.getRequestForm(encounterId, ActivityDefCategory.PROCEDURE.getCode()));
} }
/** /**
* 分页查询手术申请单全局不需要encounterId用于门诊手术安排查找弹窗
*
* @param surgeryNo 手术单号(模糊查询,可选)
* @param applyTimeStart 申请时间起始(可选)
* @param applyTimeEnd 申请时间截止(可选)
* @param mainDoctorId 主刀医生ID可选
* @param applyDeptId 申请科室ID可选
* @param pageNo 页码默认1
* @param pageSize 每页数量默认10
* @return 分页手术申请单列表
*/
@GetMapping(value = "/get-surgery-page")
public R<IPage<RequestFormPageDto>> getSurgeryRequestFormPage(
@RequestParam(required = false) String surgeryNo,
@RequestParam(required = false) LocalDate applyTimeStart,
@RequestParam(required = false) LocalDate applyTimeEnd,
@RequestParam(required = false) Long mainDoctorId,
@RequestParam(required = false) Long applyDeptId,
@RequestParam(defaultValue = "1") Integer pageNo,
@RequestParam(defaultValue = "10") Integer pageSize) {
RequestFormDto dto = new RequestFormDto(surgeryNo, ActivityDefCategory.PROCEDURE.getCode(), applyTimeStart, applyTimeEnd,
mainDoctorId, applyDeptId, pageNo, pageSize);
return R.ok(iRequestFormManageAppService.getRequestFormPage(dto));
}
/**
* 分页查询申请单 * 分页查询申请单
* @return 申请单 * @return 申请单
*/ */

View File

@@ -14,10 +14,6 @@ public class RequestFormDto {
* 手术单号 * 手术单号
*/ */
private String surgeryNo; private String surgeryNo;
/**
* 申请单类型编码
*/
private String typeCode;
/** /**
* 申请时间开始 * 申请时间开始
*/ */

View File

@@ -138,12 +138,4 @@ public class CurrentDayEncounterTencentDto {
*/ */
private String englishName; private String englishName;
/** 号源池ID用于分诊队列 div_log 审计日志) */
@JsonSerialize(using = ToStringSerializer.class)
private Long poolId;
/** 号源槽位ID用于分诊队列 div_log 审计日志) */
@JsonSerialize(using = ToStringSerializer.class)
private Long slotId;
} }

View File

@@ -89,12 +89,15 @@
cs.apply_doctor_name AS apply_doctor_name, cs.apply_doctor_name AS apply_doctor_name,
drf.create_time AS apply_time, drf.create_time AS apply_time,
os.surgery_nature AS surgeryType, os.surgery_nature AS surgeryType,
os.fee_type AS feeType, fc.contract_name AS feeType,
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo
FROM op_schedule os FROM op_schedule os
LEFT JOIN adm_patient ap ON os.patient_id = ap.id LEFT JOIN adm_patient ap ON os.patient_id = ap.id
INNER JOIN cli_surgery cs ON os.oper_code = cs.surgery_no AND cs.delete_flag = '0' INNER JOIN cli_surgery cs ON os.oper_code = cs.surgery_no AND cs.delete_flag = '0'
LEFT JOIN adm_organization o ON cs.org_id = o.id LEFT JOIN adm_organization o ON cs.org_id = o.id
LEFT JOIN adm_encounter ae ON ae.id = cs.encounter_id AND ae.delete_flag = '0'
LEFT JOIN adm_account aa ON aa.encounter_id = ae.id AND aa.delete_flag = '0'
LEFT JOIN fin_contract fc ON fc.bus_no = aa.contract_no AND fc.delete_flag = '0'
LEFT JOIN doc_request_form drf ON drf.prescription_no=cs.surgery_no LEFT JOIN doc_request_form drf ON drf.prescription_no=cs.surgery_no
LEFT JOIN ( LEFT JOIN (
SELECT patient_id, identifier_no SELECT patient_id, identifier_no

View File

@@ -239,7 +239,7 @@
NULL AS activity_type_dictText, NULL AS activity_type_dictText,
-- 前端"包装单位"列显示使用单位permitted_unit_code -- 前端"包装单位"列显示使用单位permitted_unit_code
T1.permitted_unit_code AS unit_code, T1.permitted_unit_code AS unit_code,
T1.permitted_unit_code AS min_unit_code, '' AS min_unit_code,
'' AS volume, '' AS volume,
'' AS method_code, '' AS method_code,
'' AS rate_code, '' AS rate_code,
@@ -527,9 +527,6 @@
LEFT JOIN cli_condition AS cc ON cc.id = T1.condition_id AND cc.delete_flag = '0' LEFT JOIN cli_condition AS cc ON cc.id = T1.condition_id AND cc.delete_flag = '0'
LEFT JOIN cli_condition_definition AS ccd ON ccd.id = cc.definition_id LEFT JOIN cli_condition_definition AS ccd ON ccd.id = cc.definition_id
WHERE T1.delete_flag = '0' AND T1.tcm_flag = 0 WHERE T1.delete_flag = '0' AND T1.tcm_flag = 0
<if test="generateSourceEnum != null">
AND T1.generate_source_enum = #{generateSourceEnum}
</if>
<if test="historyFlag == '0'.toString()"> <if test="historyFlag == '0'.toString()">
AND T1.encounter_id = #{encounterId} AND T1.encounter_id = #{encounterId}
</if> </if>
@@ -698,9 +695,6 @@
-- based_on_table='med_medication_request' → 输液/皮试执行记录,应排除 -- based_on_table='med_medication_request' → 输液/皮试执行记录,应排除
-- based_on_table='exam_apply'/'lab_apply' → 申请单原始医嘱,应保留 -- based_on_table='exam_apply'/'lab_apply' → 申请单原始医嘱,应保留
AND (T1.based_on_id IS NULL OR T1.based_on_table IS NULL OR T1.based_on_table NOT IN ('med_medication_request', 'med_medication_dispense')) AND (T1.based_on_id IS NULL OR T1.based_on_table IS NULL OR T1.based_on_table NOT IN ('med_medication_request', 'med_medication_dispense'))
<if test="sourceBillNo != null and sourceBillNo != ''">
AND T1.prescription_no = #{sourceBillNo}
</if>
<if test="historyFlag == '0'.toString()"> <if test="historyFlag == '0'.toString()">
AND T1.encounter_id = #{encounterId} AND T1.encounter_id = #{encounterId}
</if> </if>

View File

@@ -155,7 +155,7 @@
ii.performer_check_id, ii.performer_check_id,
ii.category_code, ii.category_code,
ii.dispense_status ii.dispense_status
FROM (( SELECT DISTINCT T1.encounter_id, FROM (( SELECT T1.encounter_id,
T1.tenant_id, T1.tenant_id,
#{medMedicationRequest} AS advice_table, #{medMedicationRequest} AS advice_table,
T1.id AS request_id, T1.id AS request_id,
@@ -293,7 +293,7 @@
T1.sort_number, T1.sort_number,
T1.group_id ) T1.group_id )
UNION UNION
( SELECT DISTINCT T1.encounter_id, ( SELECT T1.encounter_id,
T1.tenant_id, T1.tenant_id,
#{worServiceRequest} AS advice_table, #{worServiceRequest} AS advice_table,
T1.id AS request_id, T1.id AS request_id,

View File

@@ -288,7 +288,7 @@
AND T1.refund_device_id IS NULL AND T1.refund_device_id IS NULL
ORDER BY T1.status_enum) ORDER BY T1.status_enum)
UNION ALL UNION ALL
(SELECT CASE WHEN T1.category_enum = 4 THEN 6 ELSE 3 END AS advice_type, (SELECT CASE WHEN T1.category_enum = 4 THEN 6 ELSE COALESCE(T1.category_enum, 3) END AS advice_type,
T1.id AS request_id, T1.id AS request_id,
T1.id || '-3' AS unique_key, T1.id || '-3' AS unique_key,
T1.requester_id AS requester_id, T1.requester_id AS requester_id,
@@ -373,4 +373,4 @@
</if> </if>
</select> </select>
</mapper> </mapper>

View File

@@ -13,7 +13,16 @@
drf.requester_id, drf.requester_id,
drf.create_time, drf.create_time,
ap.NAME AS patient_name, 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
ELSE NULL
END AS status
FROM doc_request_form AS drf FROM doc_request_form AS drf
LEFT JOIN adm_encounter AS ae ON ae.ID = drf.encounter_id LEFT JOIN adm_encounter AS ae ON ae.ID = drf.encounter_id
AND ae.delete_flag = '0' AND ae.delete_flag = '0'
@@ -31,7 +40,16 @@
AND drf.create_time &lt;= (#{endDate}::date + INTERVAL '1 day' - INTERVAL '1 second') AND drf.create_time &lt;= (#{endDate}::date + INTERVAL '1 day' - INTERVAL '1 second')
</if> </if>
<if test="status != null and status != ''"> <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
ELSE NULL
END = #{status}::integer
</if> </if>
<if test="keyword != null and keyword != ''"> <if test="keyword != null and keyword != ''">
AND (drf.prescription_no ILIKE '%' || #{keyword} || '%' AND (drf.prescription_no ILIKE '%' || #{keyword} || '%'
@@ -143,9 +161,6 @@
<if test="requestFormDto.surgeryNo != null and requestFormDto.surgeryNo != ''"> <if test="requestFormDto.surgeryNo != null and requestFormDto.surgeryNo != ''">
AND drf.prescription_no LIKE CONCAT('%', #{requestFormDto.surgeryNo}, '%') AND drf.prescription_no LIKE CONCAT('%', #{requestFormDto.surgeryNo}, '%')
</if> </if>
<if test="requestFormDto.typeCode != null and requestFormDto.typeCode != ''">
AND drf.type_code = #{requestFormDto.typeCode}
</if>
<if test="requestFormDto.applyTimeStart != null"> <if test="requestFormDto.applyTimeStart != null">
AND drf.create_time >= #{requestFormDto.applyTimeStart} AND drf.create_time >= #{requestFormDto.applyTimeStart}
</if> </if>

View File

@@ -27,9 +27,7 @@
T9.payment_id, T9.payment_id,
T9.picture_url, T9.picture_url,
T9.birth_date, T9.birth_date,
t9.english_name, t9.english_name
t9.slot_id,
t9.pool_id
from ( from (
SELECT T1.tenant_id AS tenant_id, SELECT T1.tenant_id AS tenant_id,
T1.id AS encounter_id, T1.id AS encounter_id,
@@ -53,9 +51,7 @@
T13.id AS payment_id, T13.id AS payment_id,
ai.picture_url AS picture_url, ai.picture_url AS picture_url,
T8.birth_date AS birth_date, T8.birth_date AS birth_date,
tx.staff_english_name AS english_name, tx.staff_english_name AS english_name
om_slot.slot_id AS slot_id,
om_slot.pool_id AS pool_id
FROM adm_encounter AS T1 FROM adm_encounter AS T1
LEFT JOIN adm_organization AS T2 ON T1.organization_id = T2.ID AND T2.delete_flag = '0' LEFT JOIN adm_organization AS T2 ON T1.organization_id = T2.ID AND T2.delete_flag = '0'
LEFT JOIN adm_healthcare_service AS T3 ON T1.service_type_id = T3.ID AND T3.delete_flag = '0' LEFT JOIN adm_healthcare_service AS T3 ON T1.service_type_id = T3.ID AND T3.delete_flag = '0'
@@ -95,8 +91,6 @@
AND T13.status_enum = ${paymentStatus} AND T13.status_enum = ${paymentStatus}
LEFT JOIN adm_invoice AS ai LEFT JOIN adm_invoice AS ai
ON ai.reconciliation_id = T13.id AND ai.delete_flag = '0' ON ai.reconciliation_id = T13.id AND ai.delete_flag = '0'
LEFT JOIN order_main AS om ON T1.order_id = om.id AND om.delete_flag = '0'
LEFT JOIN adm_schedule_slot AS om_slot ON om.slot_id = om_slot.id
WHERE T1.delete_flag = '0' WHERE T1.delete_flag = '0'
AND T1.class_enum = #{classEnum} AND T1.class_enum = #{classEnum}
AND T10.context_enum = #{register} AND T10.context_enum = #{register}

View File

@@ -193,9 +193,6 @@ public class OpSchedule extends HisBaseEntity {
/** 外请专家姓名 */ /** 外请专家姓名 */
private String externalExpertName; private String externalExpertName;
/** 费用类别 */
private String feeType;
/** 备注信息 */ /** 备注信息 */
private String remark; private String remark;

View File

@@ -13,7 +13,7 @@ CREATE TABLE IF NOT EXISTS triage_queue_item (
patient_name VARCHAR(255), patient_name VARCHAR(255),
healthcare_name VARCHAR(255), healthcare_name VARCHAR(255),
practitioner_name VARCHAR(255), practitioner_name VARCHAR(255),
status INTEGER NOT NULL DEFAULT 0, -- 分诊队列状态: 0=WAITING(等待), 10=CALLING(叫号中), 20=IN_CLINIC(诊中), 30=COMPLETED(已完成), 40=SKIPPED(已跳过) status VARCHAR(50) NOT NULL, -- WAITING/CALLING/SKIPPED/COMPLETED
queue_order INTEGER NOT NULL, queue_order INTEGER NOT NULL,
create_time TIMESTAMP, create_time TIMESTAMP,
update_time TIMESTAMP, update_time TIMESTAMP,

View File

@@ -1,30 +0,0 @@
-- Bug #400 修复triage_queue_item.status 字段类型修正
-- 原 DDL 将 status 定义为 VARCHAR(50),但 Java 实体 TriageQueueItem.status 为 Integer 类型,
-- 应用层使用 TriageQueueStatus 枚举值0/10/20/30/40写入类型不匹配导致 MyBatis-Plus
-- 无法正确映射 status 字段,完诊时 status 更新为 30 失败。
--
-- 注意:必须在后端实际连接的 schema 中执行dev=hisdev, test=histest, prd=hisprd
-- 执行前请先确认SET search_path TO hisdev; (或对应的 schema
-- 1. 先将已有的字符串值转换为对应的整数值
-- 如果之前写入的是枚举 code如 'waiting'、'completed'),需要先转换
UPDATE triage_queue_item
SET status = CASE
WHEN status IN ('0', 'waiting', 'WAITING') THEN 0
WHEN status IN ('10', 'calling', 'CALLING') THEN 10
WHEN status IN ('20', 'in-clinic', 'IN_CLINIC', 'in-clinic') THEN 20
WHEN status IN ('30', 'completed', 'COMPLETED') THEN 30
WHEN status IN ('40', 'skipped', 'SKIPPED') THEN 40
WHEN status IN ('50', 'refunded', 'REFUNDED') THEN 50
WHEN status IN ('60', 'follow', 'FOLLOW') THEN 60
ELSE 0
END
WHERE status IS NOT NULL AND status !~ '^[0-9]+$';
-- 2. 修改字段类型为 INTEGER
ALTER TABLE triage_queue_item
ALTER COLUMN status TYPE INTEGER USING status::INTEGER;
-- 3. 设置默认值
ALTER TABLE triage_queue_item
ALTER COLUMN status SET DEFAULT 0;

View File

@@ -163,7 +163,7 @@ export function updateCheckPart(data) {
// 查询检查套餐列表 // 查询检查套餐列表
export function listCheckPackage(query) { export function listCheckPackage(query) {
return request({ return request({
url: '/system/package/list', url: '/system/check-package/list',
method: 'get', method: 'get',
params: query params: query
}) })

View File

@@ -226,14 +226,8 @@ function getList() {
getDiagnosisTreatmentList(queryParams.value).then((res) => { getDiagnosisTreatmentList(queryParams.value).then((res) => {
loading.value = false; loading.value = false;
catagoryList.value = res.data.records.map(record => { catagoryList.value = res.data.records.map(record => {
// 为每一行初始化 filteredOptions确保显示框能正确显示项目名称
const filteredOptions = allImplementDepartmentList.value.slice(0, 100); const filteredOptions = allImplementDepartmentList.value.slice(0, 100);
// 确保后端返回的项目名称选项存在于 filteredOptions 中,避免 el-select 因找不到选项而回显为 ID
if (record.activityDefinitionId && !filteredOptions.some(o => o.value === record.activityDefinitionId)) {
filteredOptions.push({
value: record.activityDefinitionId,
label: record.activityDefinitionId_dictText || record.activityDefinitionId
});
}
return { return {
...record, ...record,
loading: false, loading: false,

View File

@@ -372,6 +372,7 @@ import {
} from './diagnosistreatment'; } from './diagnosistreatment';
import PopoverList from '@/components/OpenHis/popoverList/index.vue'; import PopoverList from '@/components/OpenHis/popoverList/index.vue';
import medicineList from './medicineList.vue'; import medicineList from './medicineList.vue';
import MedicineList from '../components/medicineList.vue';
import {getCurrentInstance, nextTick, watch} from 'vue'; import {getCurrentInstance, nextTick, watch} from 'vue';
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
@@ -466,9 +467,9 @@ function calculateTotalPrice() {
try { try {
let sum = 0; let sum = 0;
treatmentItems.value.forEach((item) => { treatmentItems.value.forEach((item) => {
if (item.adviceDefinitionId && item.adviceDefinitionId !== '') { if (item.adviceDefinitionId && item.retailPrice && item.childrenRequestNum) {
const price = Number(item.retailPrice) || 0; const price = parseFloat(item.retailPrice) || 0;
const count = Number(item.childrenRequestNum) || 0; const count = parseInt(item.childrenRequestNum) || 0;
sum += price * count; sum += price * count;
} }
}); });
@@ -478,10 +479,7 @@ function calculateTotalPrice() {
(item) => item.adviceDefinitionId && item.adviceDefinitionId !== '' (item) => item.adviceDefinitionId && item.adviceDefinitionId !== ''
); );
if (hasValidItem) { if (hasValidItem) {
// 使用 nextTick 确保总价更新后零售价才更新,避免 Vue 响应式时序问题 form.value.retailPrice = parseFloat(totalPrice.value);
nextTick(() => {
form.value.retailPrice = parseFloat(totalPrice.value) || 0;
});
} else { } else {
form.value.retailPrice = undefined; form.value.retailPrice = undefined;
} }
@@ -567,16 +565,15 @@ function edit() {
form.value.pricingFlag = 1; form.value.pricingFlag = 1;
} }
// 处理子项数据确保包含retailPrice和name字段 // 处理子项数据确保包含retailPrice字段
if (props.item.childrenJson) { if (props.item.childrenJson) {
const parsedItems = JSON.parse(props.item.childrenJson); const parsedItems = JSON.parse(props.item.childrenJson);
treatmentItems.value = parsedItems.map((item) => ({ treatmentItems.value = parsedItems.map((item) => ({
...item, ...item,
name: item.name || '',
retailPrice: item.retailPrice || 0, retailPrice: item.retailPrice || 0,
})); }));
} else { } else {
treatmentItems.value = [{ adviceDefinitionId: '', childrenRequestNum: 1, name: '', retailPrice: 0 }]; treatmentItems.value = [{ adviceDefinitionId: '', childrenRequestNum: 1, retailPrice: 0 }];
} }
form.value.permittedUnitCode = form.value.permittedUnitCode form.value.permittedUnitCode = form.value.permittedUnitCode
? form.value.permittedUnitCode.toString() ? form.value.permittedUnitCode.toString()
@@ -621,7 +618,7 @@ function reset() {
chrgitmLv: undefined, //医保等级 chrgitmLv: undefined, //医保等级
pricingFlag: 1, // 划价标记,默认允许划价 pricingFlag: 1, // 划价标记,默认允许划价
}; };
treatmentItems.value = [{ adviceDefinitionId: '', childrenRequestNum: 1, name: '', retailPrice: 0 }]; treatmentItems.value = [{ adviceDefinitionId: '', childrenRequestNum: 1, retailPrice: 0 }];
totalPrice.value = '0.00'; totalPrice.value = '0.00';
proxy.resetForm('diagnosisTreatmentRef'); proxy.resetForm('diagnosisTreatmentRef');
} }
@@ -763,10 +760,7 @@ function selectRow(row, index) {
treatmentItems.value[index].adviceDefinitionId = row.id; treatmentItems.value[index].adviceDefinitionId = row.id;
treatmentItems.value[index].retailPrice = row.retailPrice || 0; treatmentItems.value[index].retailPrice = row.retailPrice || 0;
medicineSearchKey.value = ''; medicineSearchKey.value = '';
// 使用 nextTick 确保 DOM 更新后再计算总价 calculateTotalPrice();
nextTick(() => {
calculateTotalPrice();
});
} }
// 清空诊疗子项 // 清空诊疗子项

View File

@@ -36,7 +36,6 @@ const emit = defineEmits(['selectRow']);
const diagnosisTreatmentList = ref([]); // 原始数据列表 const diagnosisTreatmentList = ref([]); // 原始数据列表
const filteredList = ref([]); // 过滤后的数据列表 const filteredList = ref([]); // 过滤后的数据列表
const hasLoaded = ref(false); // 标记是否已加载数据 const hasLoaded = ref(false); // 标记是否已加载数据
const isLoading = ref(false); // 标记是否正在加载
// 获取诊疗项目列表 // 获取诊疗项目列表
function getList() { function getList() {
@@ -54,7 +53,7 @@ function getList() {
getDiagnosisTreatmentList({ statusEnum: 2, pageSize: 1000, pageNo: 1 }) getDiagnosisTreatmentList({ statusEnum: 2, pageSize: 1000, pageNo: 1 })
.then((res) => { .then((res) => {
diagnosisTreatmentList.value = diagnosisTreatmentList.value =
res.data?.records?.filter((item) => item.childrenJson == null || item.childrenJson === '') || []; res.data?.records?.filter((item) => item.childrenJson == null) || [];
filterList(); // 初始化过滤 filterList(); // 初始化过滤
hasLoaded.value = true; // 标记为已加载 hasLoaded.value = true; // 标记为已加载
}) })
@@ -63,47 +62,6 @@ function getList() {
}); });
} }
// 服务端搜索(当用户输入搜索关键词时)
function searchList(searchKey) {
if (!searchKey || searchKey.trim() === '') return;
isLoading.value = true;
// 使用较大的pageSize确保搜索结果尽可能多
getDiagnosisTreatmentList({ statusEnum: 2, searchKey: searchKey.trim(), pageSize: 5000, pageNo: 1 })
.then((res) => {
diagnosisTreatmentList.value =
res.data?.records?.filter((item) => item.childrenJson == null || item.childrenJson === '') || [];
filterList();
})
.catch((err) => {
console.error('搜索诊疗项目数据失败:', err);
})
.finally(() => {
isLoading.value = false;
});
}
// 获取预加载数据(不带搜索关键词时使用)
function loadPreloadedData() {
if (props.preloadedData && props.preloadedData.length > 0) {
diagnosisTreatmentList.value = props.preloadedData;
filterList();
hasLoaded.value = true;
return;
}
if (hasLoaded.value) return;
getDiagnosisTreatmentList({ statusEnum: 2, pageSize: 1000, pageNo: 1 })
.then((res) => {
diagnosisTreatmentList.value =
res.data?.records?.filter((item) => item.childrenJson == null || item.childrenJson === '') || [];
filterList();
hasLoaded.value = true;
})
.catch((err) => {
console.error('获取诊疗项目数据失败:', err);
});
}
// 监听shouldLoadData属性变化仅在首次为true时加载数据 // 监听shouldLoadData属性变化仅在首次为true时加载数据
watch( watch(
() => props.shouldLoadData, () => props.shouldLoadData,
@@ -128,17 +86,11 @@ watch(
{ immediate: true } { immediate: true }
); );
// 监听搜索关键词变化,有搜索词时走服务端搜索,否则走本地过滤 // 监听搜索关键词变化,实时过滤数据
watch( watch(
() => props.searchKey, () => props.searchKey,
(newVal) => { () => {
if (newVal && newVal.trim() !== '') { filterList();
// 有搜索关键词,走服务端搜索
searchList(newVal);
} else {
// 搜索词为空,使用预加载数据
loadPreloadedData();
}
} }
); );

View File

@@ -115,44 +115,22 @@ function getList() {
console.log('[adviceBaseList] getList() 跳过:未选择患者'); console.log('[adviceBaseList] getList() 跳过:未选择患者');
return; // 不执行API调用 return; // 不执行API调用
} }
// 只有在弹窗打开时才执行查询 // 只有在弹窗打开时才执行查询
if (!props.popoverVisible) { if (!props.popoverVisible) {
console.log('[adviceBaseList] getList() 跳过:弹窗未打开'); console.log('[adviceBaseList] getList() 跳过:弹窗未打开');
return; return;
} }
// 🔧 Bug #448 修复:显式构建请求参数,确保 adviceType 正确传递 queryParams.value.organizationId = props.patientInfo.orgId;
// 不直接使用 queryParams.value避免 undefined 值被发送到后端导致过滤失效 console.log('[adviceBaseList] getList() 请求参数:', JSON.stringify(queryParams.value));
const requestParams = {
pageSize: queryParams.value.pageSize, getAdviceBaseInfo(queryParams.value).then((res) => {
pageNum: queryParams.value.pageNum,
organizationId: props.patientInfo.orgId,
};
// 只在 adviceType 有值时添加0 是无效值undefined/null 会导致后端查询所有类型)
if (queryParams.value.adviceType != null && queryParams.value.adviceType !== 0) {
requestParams.adviceType = queryParams.value.adviceType;
}
// 只在 categoryCode 有值时添加
if (queryParams.value.categoryCode) {
requestParams.categoryCode = queryParams.value.categoryCode;
}
// 只在 searchKey 有值时添加
if (queryParams.value.searchKey) {
requestParams.searchKey = queryParams.value.searchKey;
}
console.log('[adviceBaseList] getList() 请求参数:', JSON.stringify(requestParams));
getAdviceBaseInfo(requestParams).then((res) => {
console.log('[adviceBaseList] getList() 响应数据:', { console.log('[adviceBaseList] getList() 响应数据:', {
total: res.data?.total, total: res.data?.total,
recordsCount: res.data?.records?.length || 0, recordsCount: res.data?.records?.length || 0,
firstRecord: res.data?.records?.[0]?.adviceName || '无数据', firstRecord: res.data?.records?.[0]?.adviceName || '无数据',
adviceType: requestParams.adviceType adviceType: queryParams.value.adviceType
}); });
adviceBaseList.value = res.data.records || []; adviceBaseList.value = res.data.records || [];
total.value = res.data.total || 0; total.value = res.data.total || 0;

View File

@@ -58,17 +58,10 @@ export function singOut(data) {
/** /**
* 获取患者本次就诊处方 * 获取患者本次就诊处方
*/ */
export function getPrescriptionList(encounterId, generateSourceEnum, sourceBillNo) { export function getPrescriptionList(encounterId) {
let url = '/doctor-station/advice/request-base-info?encounterId=' + encounterId // Add timestamp to bypass browser caching and ensure fresh data is loaded
if (generateSourceEnum != null) {
url += '&generateSourceEnum=' + generateSourceEnum
}
if (sourceBillNo != null) {
url += '&sourceBillNo=' + encodeURIComponent(sourceBillNo)
}
url += '&t=' + Date.now()
return request({ return request({
url: url, url: '/doctor-station/advice/request-base-info?encounterId=' + encounterId + '&t=' + Date.now(),
method: 'get', method: 'get',
}) })
} }

View File

@@ -381,14 +381,6 @@ const props = defineProps({
activeTab: { activeTab: {
type: String, type: String,
}, },
generateSourceEnum: {
type: Number,
default: null,
},
sourceBillNo: {
type: String,
default: null,
},
}); });
const isAdding = ref(false); const isAdding = ref(false);
const isSaving = ref(false); // #437 防重复提交锁 const isSaving = ref(false); // #437 防重复提交锁
@@ -476,11 +468,7 @@ watch(
function getListInfo(addNewRow) { function getListInfo(addNewRow) {
isAdding.value = false; isAdding.value = false;
getPrescriptionList( getPrescriptionList(props.patientInfo.encounterId).then((res) => {
props.patientInfo.encounterId,
props.generateSourceEnum ?? undefined,
props.sourceBillNo ?? undefined,
).then((res) => {
// 为每行数据添加 adviceTypeValue 字段,用于类型下拉框显示 // 为每行数据添加 adviceTypeValue 字段,用于类型下拉框显示
prescriptionList.value = (res.data || []).map(item => { prescriptionList.value = (res.data || []).map(item => {
// 根据 adviceType 和 categoryCode 找到对应的 adviceTypeValue // 根据 adviceType 和 categoryCode 找到对应的 adviceTypeValue
@@ -880,27 +868,19 @@ function handleDelete() {
proxy.$modal.msgWarning('请选择要删除的项目'); proxy.$modal.msgWarning('请选择要删除的项目');
return; return;
} }
let deleteList = groupIndexList.value.map((index) => { let deleteList = groupIndexList.value.map((index) => {
const item = prescriptionList.value[index]; const item = prescriptionList.value[index];
// 只删除待签发且未收费的项目 // 只删除待签发且未收费的项目
if (item.statusEnum != 1 || item.chargeStatus == 5) { if (item.statusEnum != 1 || item.chargeStatus == 5) {
return null; return null;
} }
// 🔧 Bug #442: 非本人创建的医嘱不允许删除(与签发/签退逻辑保持一致)
if (Number(item.bizRequestFlag) !== 1 && item.bizRequestFlag) {
return null;
}
// 🔧 Bug #442: 已保存的行必须有有效的 requestId否则跳过避免后端删除不存在的记录
if (item.requestId == null || item.requestId === undefined || item.requestId === '') {
return null;
}
return { return {
requestId: item.requestId, requestId: item.requestId,
dbOpType: '3', dbOpType: '3',
adviceType: item.adviceType, adviceType: item.adviceType,
}; };
}).filter(item => item !== null); // 过滤掉已签发已收费、非本人创建或无 requestId 的项目 }).filter(item => item !== null); // 过滤掉已签发已收费的项目
if (deleteList.length == 0) { if (deleteList.length == 0) {
proxy.$modal.msgWarning('只能删除待签发且未收费的项目'); proxy.$modal.msgWarning('只能删除待签发且未收费的项目');
@@ -1036,14 +1016,6 @@ function handleSave() {
requestId: item.requestId, requestId: item.requestId,
dbOpType: '1', dbOpType: '1',
groupId: item.groupId, groupId: item.groupId,
// 🔧 Bug #443: 补充顶层关键字段(这些不在 contentJson 中,需从 API 响应顶层提取)
encounterId: item.encounterId,
patientId: item.patientId,
locationId: item.positionId,
adviceType: item.adviceType,
adviceTableName: item.adviceTableName,
adviceDefinitionId: item.adviceDefinitionId,
chargeItemId: item.chargeItemId,
}; };
}); });
savePrescriptionSign({ savePrescriptionSign({

View File

@@ -1243,14 +1243,11 @@ async function show(diagnosisData) {
const res = await getNextCardNo(orgCode); const res = await getNextCardNo(orgCode);
if (res.code === 200 && res.data && res.data.length >= 12) { if (res.code === 200 && res.data && res.data.length >= 12) {
cardNo = res.data; cardNo = res.data;
} else {
// API返回失败或不合规时生成临时卡号避免保存时 cardNo 为空导致后端校验失败
cardNo = 'TEMP_' + Date.now();
} }
// API失败或返回不合规时保持为空字符串由用户手动填写或后端自动生成
} catch (err) { } catch (err) {
console.error('获取卡片编号失败:', err); console.error('获取卡片编号失败:', err);
// API调用异常时生成临时卡号 // 保持为空,不使用不合规的临时值
cardNo = 'TEMP_' + Date.now();
} }
form.value = { form.value = {
@@ -1366,9 +1363,6 @@ async function buildSubmitData() {
} else if (formData.otherDisease) { } else if (formData.otherDisease) {
// 其他传染病使用自定义编码 // 其他传染病使用自定义编码
diseaseCode = 'OTHER'; diseaseCode = 'OTHER';
} else if (formData.selectedDiseases && formData.selectedDiseases.length > 0) {
// 兜底:如果 ClassA/B/C 都为空但 selectedDiseases 有值,取第一个作为 diseaseCode
diseaseCode = formData.selectedDiseases[0];
} }
// 转换年龄单位:岁=1, 月=2, 天=3 // 转换年龄单位:岁=1, 月=2, 天=3
@@ -1436,8 +1430,8 @@ async function buildSubmitData() {
function validateFormManually() { function validateFormManually() {
const errors = []; const errors = [];
// 卡片编号验证至少12位后端自动生成16位编号;临时卡号 TEMP_ 开头允许通过 // 卡片编号验证至少12位后端自动生成16位编号
if (form.value.cardNo && !form.value.cardNo.startsWith('TEMP_') && form.value.cardNo.length < 12) { if (form.value.cardNo && form.value.cardNo.length < 12) {
errors.push('卡片编号至少12位'); errors.push('卡片编号至少12位');
} }
@@ -1778,33 +1772,6 @@ defineExpose({ show, showReport, close: handleClose });
color: #999; color: #999;
} }
/* 输入框下划线样式(与 underline-select 保持一致) */
.underline-input :deep(.el-input__wrapper) {
border: none;
border-bottom: 1px solid #dcdfe6;
border-radius: 0;
box-shadow: none;
background: transparent;
}
.underline-input :deep(.el-input__wrapper:hover) {
border-bottom-color: #c0c4cc;
}
.underline-input :deep(.el-input__wrapper.is-focus) {
border-bottom-color: #409eff;
}
.underline-input :deep(.el-input__inner) {
font-size: 12px;
color: #666;
}
.underline-input :deep(.el-input__inner::placeholder) {
font-size: 12px;
color: #999;
}
/* 街道下拉框下划线样式 */ /* 街道下拉框下划线样式 */
.underline-select { .underline-select {
width: 100%; width: 100%;

View File

@@ -3,56 +3,15 @@
<!-- ====== 顶部卡片申请单列表 ====== --> <!-- ====== 顶部卡片申请单列表 ====== -->
<div class="top-section"> <div class="top-section">
<div class="section-header"> <div class="section-header">
<span class="section-title">检查项目 ({{ filteredApplicationList.length }})</span> <span class="section-title">检查项目 ({{ applicationList.length }})</span>
<div class="header-actions"> <div class="header-actions">
<el-button type="primary" @click="handleAdd" icon="Plus">新增</el-button> <el-button type="primary" @click="handleAdd" icon="Plus">新增</el-button>
<el-button type="success" @click="handleSave" icon="Finished">保存</el-button> <el-button type="success" @click="handleSave" icon="Finished">保存</el-button>
</div> </div>
</div> </div>
<!-- Bug #499: 查询过滤工具栏 -->
<div class="search-toolbar">
<el-form :inline="true" size="small">
<el-form-item label="日期范围">
<el-date-picker
v-model="searchForm.dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
style="width: 240px"
/>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="searchForm.applyStatus" placeholder="全部" clearable style="width: 140px">
<el-option
v-for="opt in statusOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
<el-form-item label="关键字">
<el-input
v-model="searchForm.keyword"
placeholder="申请单号 / 检查项目"
clearable
style="width: 200px"
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch" icon="Search">搜索</el-button>
<el-button @click="handleResetSearch" icon="Refresh">重置</el-button>
</el-form-item>
</el-form>
</div>
<el-table <el-table
v-loading="loading" v-loading="loading"
:data="filteredApplicationList" :data="applicationList"
:max-height="200" :max-height="200"
highlight-current-row highlight-current-row
@row-click="handleRowClick" @row-click="handleRowClick"
@@ -376,9 +335,8 @@
加载中... 加载中...
</div> </div>
<!-- Bug #428修复: 渲染分类联动加载的检查方法列表 --> <!-- Bug #428修复: 渲染分类联动加载的检查方法列表 -->
<!-- Bug #500修复: v-if 改为 v-show避免方法列表加载时 DOM 突然插入导致高度跳变 -->
<div <div
v-show="cat.methods && cat.methods.length > 0" v-if="cat.methods && cat.methods.length > 0"
class="method-section" class="method-section"
> >
<div class="method-section-title">检查方法</div> <div class="method-section-title">检查方法</div>
@@ -444,21 +402,6 @@
<span class="method-price">¥{{ method.packagePrice || item.price }}</span> <span class="method-price">¥{{ method.packagePrice || item.price }}</span>
</el-checkbox> </el-checkbox>
</div> </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>
</div>
<div v-if="item.selectedMethod && item.methodPackageLoading" class="method-package-loading">
加载套餐明细中...
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -476,7 +419,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
import { Printer, Delete, ArrowDown, ArrowUp, Close } from '@element-plus/icons-vue'; import { Printer, Delete, ArrowDown, ArrowUp, Close } from '@element-plus/icons-vue';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
import request from '@/utils/request'; import request from '@/utils/request';
import { listCheckMethod, searchCheckMethod, listCheckPackage } from '@/api/system/checkType'; import { listCheckMethod, searchCheckMethod } from '@/api/system/checkType';
import { getEncounterDiagnosis } from '../api.js'; import { getEncounterDiagnosis } from '../api.js';
const props = defineProps({ const props = defineProps({
@@ -494,74 +437,6 @@ const activeDetailTab = ref('applyForm');
const applicationList = ref([]); const applicationList = ref([]);
const selectedItems = ref([]); const selectedItems = ref([]);
// Bug #499: 查询过滤状态
const searchForm = reactive({
dateRange: [],
applyStatus: '',
keyword: ''
});
// 申请单状态选项
const statusOptions = [
{ label: '已开单', value: 0 },
{ label: '已收费', value: 1 },
{ label: '已预约', value: 2 },
{ label: '已签到', value: 3 },
{ label: '部分报告', value: 4 },
{ label: '已完告', value: 5 },
{ label: '已作废', value: 6 }
];
// Bug #499: 过滤后的申请单列表
const filteredApplicationList = computed(() => {
let result = applicationList.value;
// 日期范围过滤
if (searchForm.dateRange && searchForm.dateRange.length === 2) {
const start = searchForm.dateRange[0];
const end = searchForm.dateRange[1];
result = result.filter(item => {
const d = item.applyTime;
if (!d) return false;
const dateStr = d.length > 10 ? d.substring(0, 10) : d;
return dateStr >= start && dateStr <= end;
});
}
// 状态过滤
if (searchForm.applyStatus !== '' && searchForm.applyStatus !== null && searchForm.applyStatus !== undefined) {
result = result.filter(item => item.applyStatus === searchForm.applyStatus);
}
// 关键字过滤(申请单号、申检部位、检查项目名)
if (searchForm.keyword) {
const kw = searchForm.keyword.toLowerCase();
result = result.filter(item => {
return (item.applyNo || '').toLowerCase().includes(kw)
|| (item.inspectionArea || '').toLowerCase().includes(kw);
});
}
return result;
});
// Bug #499: 搜索与重置
function handleSearch() {
// 过滤逻辑由 computed 自动处理
}
function handleResetSearch() {
const now = new Date();
const end = now.toISOString().substring(0, 10);
const start = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString().substring(0, 10);
searchForm.dateRange = [start, end];
searchForm.applyStatus = '';
searchForm.keyword = '';
}
// 初始化默认日期范围为近一周
handleResetSearch();
// 🔧 BugFix#426: 懒加载套餐明细 // 🔧 BugFix#426: 懒加载套餐明细
async function loadPackageDetails(row, treeNode, resolve) { async function loadPackageDetails(row, treeNode, resolve) {
if (!row.isPackage || !row.packageId) { if (!row.isPackage || !row.packageId) {
@@ -570,7 +445,7 @@ async function loadPackageDetails(row, treeNode, resolve) {
} }
try { try {
const res = await request({ const res = await request({
url: `/system/package/${row.packageId}/details`, url: `/exam/package/${row.packageId}/details`,
method: 'get' method: 'get'
}); });
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
@@ -592,28 +467,14 @@ async function loadPackageDetails(row, treeNode, resolve) {
} }
} }
// #428修复: 为已选择项目加载套餐明细通过packageId或packageName查询 // #428: 为已选择项目加载套餐明细
async function loadPackageDetailsForItem(item) { async function loadPackageDetailsForItem(item) {
if (!item.isPackage || (!item.packageId && !item.packageName)) { if (!item.isPackage || !item.packageId) {
return; return;
} }
try { 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)) {
packages = packages.records || packages.data || [];
}
if (packages.length === 0) {
item.packageDetails = [];
return;
}
packageId = packages[0].id;
}
const res = await request({ const res = await request({
url: `/system/package/${packageId}/details`, url: `/exam/package/${item.packageId}/details`,
method: 'get' method: 'get'
}); });
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
@@ -621,7 +482,7 @@ async function loadPackageDetailsForItem(item) {
...detail, ...detail,
name: detail.name || detail.itemName, name: detail.name || detail.itemName,
unit: detail.unit || '次', unit: detail.unit || '次',
price: detail.price || detail.unitPrice || 0, price: detail.price || detail.itemPrice || 0,
quantity: detail.quantity || 1 quantity: detail.quantity || 1
})); }));
} else { } else {
@@ -683,7 +544,7 @@ const categoryList = ref([]); // 原始分类+项目数据
const dictSearchKey = ref(''); const dictSearchKey = ref('');
const activeNames = ref(''); // 当前展开的折叠项 const activeNames = ref(''); // 当前展开的折叠项
const categoryLoadingSet = ref(new Set()); // Bug #500: 正在加载方法的分类集合 const categoryLoadingSet = ref(new Set()); // Bug #500: 正在加载方法的分类集合
const currentActiveCategory = ref(null); // Bug #500: 记录当前激活的分类,忽略过期请求响应 const isAnimating = ref(false); // Bug #500: 防止快速切换时折叠动画重叠导致抖动
const allMethods = ref([]); const allMethods = ref([]);
@@ -800,18 +661,15 @@ const availableMethods = computed(() => {
// 当可选方法列表改变时,如果当前选中的方法不在新列表中,则清空 // 当可选方法列表改变时,如果当前选中的方法不在新列表中,则清空
// #428: 分类展开时联动加载检查方法 // #428: 分类展开时联动加载检查方法
// Bug #500: 使用 categoryLoadingSet 替代 dictLoading避免切换分类时整个区域闪烁 // Bug #500: 使用 categoryLoadingSet 替代 dictLoading避免切换分类时整个区域闪烁
// Bug #500: 添加 currentActiveCategory 守卫,忽略过期请求响应,防止快速切换时数据闪烁
async function handleCategoryExpand(cat) { async function handleCategoryExpand(cat) {
if (!cat || !cat.typeName) return; if (!cat || !cat.typeName) return;
// 如果已加载过或正在加载中,跳过
if ((cat.methods && cat.methods.length > 0) || categoryLoadingSet.value.has(cat.typeId)) return; if ((cat.methods && cat.methods.length > 0) || categoryLoadingSet.value.has(cat.typeId)) return;
categoryLoadingSet.value.add(cat.typeId); categoryLoadingSet.value.add(cat.typeId);
currentActiveCategory.value = cat.typeId;
try { try {
const res = await searchCheckMethod({ checkType: cat.typeName }); const res = await searchCheckMethod({ checkType: cat.typeName });
// 忽略过期请求:用户已切换到其他分类,丢弃当前响应
if (currentActiveCategory.value !== cat.typeId) return;
let data = res?.data?.data || res?.data || res?.rows || res; let data = res?.data?.data || res?.data || res?.rows || res;
if (!Array.isArray(data) && res?.data && Array.isArray(res.data.data)) { if (!Array.isArray(data) && res?.data && Array.isArray(res.data.data)) {
data = res.data.data; data = res.data.data;
@@ -830,21 +688,20 @@ async function handleCategoryExpand(cat) {
})); }));
} }
} catch (err) { } catch (err) {
if (currentActiveCategory.value !== cat.typeId) return;
console.error('加载分类检查方法失败', err); console.error('加载分类检查方法失败', err);
} finally { } finally {
categoryLoadingSet.value.delete(cat.typeId); categoryLoadingSet.value.delete(cat.typeId);
} }
} }
// Bug #500修复: 不阻塞 accordion 状态更新,仅防止重复加载同一分类的方法 // Bug #500: 添加防抖逻辑,快速切换时跳过中间状态的动画,避免高度跳变和白屏闪烁
function handleCollapseChange(activeName) { function handleCollapseChange(activeName) {
// 始终记录当前激活的分类,确保 handleCategoryExpand 能正确忽略过期请求 if (isAnimating.value) return; // 动画进行中,忽略后续点击
currentActiveCategory.value = activeName || null;
isAnimating.value = true;
setTimeout(() => { isAnimating.value = false; }, 300); // 与 CSS 过渡时长一致
if (activeName) { if (activeName) {
// Bug #428修复: 直接从 categoryList原始响应式数组查找分类 const cat = filteredCategoryList.value.find(c => c.typeId == activeName);
// 确保后续 handleCategoryExpand 对 cat.methods 的赋值能正确触发 Vue 响应式更新
const cat = categoryList.value.find(c => c.typeId == activeName);
if (cat && (!cat.methods || cat.methods.length === 0)) { if (cat && (!cat.methods || cat.methods.length === 0)) {
handleCategoryExpand(cat); // 异步加载,不 await handleCategoryExpand(cat); // 异步加载,不 await
} }
@@ -1136,20 +993,12 @@ function handleRowClick(row) {
selectedItems.value = []; selectedItems.value = [];
activeDetailTab.value = 'applyForm'; activeDetailTab.value = 'applyForm';
request({ url: `/exam/apply/${row.applyNo}`, method: 'get' }).then(async res => { request({ url: `/exam/apply/${row.applyNo}`, method: 'get' }).then(async res => {
const resp = res.data || res; const d = res.data || res;
// Bug #408修复: items 在 AjaxResult 顶层(res.items / resp.items),不在 ExamApply 对象内 if (d.data) Object.assign(form, d.data);
// 防御性提取:优先取顶层 items兼容嵌套在 resp.data.items 的情况 if (d.items && Array.isArray(d.items)) {
let rawItems = res.items || resp.items;
if (!rawItems && resp.data && typeof resp.data === 'object') {
rawItems = resp.data.items;
}
rawItems = rawItems || [];
const d = resp.data || resp;
if (d) Object.assign(form, d);
if (Array.isArray(rawItems) && rawItems.length > 0) {
try { try {
// 为每个项目加载检查方法 // 为每个项目加载检查方法
const itemsWithMethods = await Promise.all(rawItems.map(async m => { const itemsWithMethods = await Promise.all(d.items.map(async m => {
const item = { const item = {
id: m.itemCode, name: m.itemName, id: m.itemCode, name: m.itemName,
price: m.itemFee || 0, quantity: 1, price: m.itemFee || 0, quantity: 1,
@@ -1263,8 +1112,7 @@ async function handleMethodSelect(checked, method, cat) {
// 如果该项目不存在,创建一个并关联方法 // 如果该项目不存在,创建一个并关联方法
if (selectedItems.value.length > 0) { if (selectedItems.value.length > 0) {
const currentCategory = selectedItems.value[0].checkType; const currentCategory = selectedItems.value[0].checkType;
// Bug #428修复: 使用 cat.typeName 进行比较(与 newItem.checkType 保持一致) const newCategory = cat.typeCode || '';
const newCategory = cat.typeName || '';
if (currentCategory !== newCategory) { if (currentCategory !== newCategory) {
ElMessage.warning('一个检查单不能同时选择多个项目类型的检查项目'); ElMessage.warning('一个检查单不能同时选择多个项目类型的检查项目');
return; return;
@@ -1349,8 +1197,7 @@ async function handleItemSelect(checked, item, cat) {
if (selectedItems.value.length > 0) { if (selectedItems.value.length > 0) {
const currentCategory = selectedItems.value[0].checkType; const currentCategory = selectedItems.value[0].checkType;
// Bug #428修复: 使用 cat.typeName 进行比较(与 effectiveCheckType 保持一致) const newCategory = cat.typeCode || '';
const newCategory = cat.typeName || '';
if (currentCategory !== newCategory) { if (currentCategory !== newCategory) {
ElMessage.warning('一个检查单不能同时选择多个项目类型的检查项目'); ElMessage.warning('一个检查单不能同时选择多个项目类型的检查项目');
item.checked = false; item.checked = false;
@@ -1371,7 +1218,6 @@ async function handleItemSelect(checked, item, cat) {
selectedMethod: null, selectedMethod: null,
expanded: false, // Bug #384修复: 新增展开状态,默认不展开 expanded: false, // Bug #384修复: 新增展开状态,默认不展开
isPackage: !!item.packageName, // Bug #428修复: 标记是否为套餐 isPackage: !!item.packageName, // Bug #428修复: 标记是否为套餐
packageName: item.packageName || null, // Bug #426修复: 套餐名称用于查找packageId
packageId: item.packageId || null // Bug #428修复: 套餐ID packageId: item.packageId || null // Bug #428修复: 套餐ID
}); });
@@ -1404,68 +1250,21 @@ async function toggleItemExpand(item) {
} }
// Bug #384修复: 勾选框选择检查方法(单选逻辑) // Bug #384修复: 勾选框选择检查方法(单选逻辑)
async function selectMethodCheckbox(checked, item, method) { function selectMethodCheckbox(checked, item, method) {
if (checked) { if (checked) {
item.selectedMethod = method; item.selectedMethod = method;
// 动态加载该方法对应的套餐明细
await loadMethodPackageDetails(item, method);
} else { } else {
item.selectedMethod = null; item.selectedMethod = null;
item.methodPackageDetails = [];
} }
// 联动更新表单检查方法显示字段 // 联动更新表单检查方法显示字段
updateMethodDisplay(); updateMethodDisplay();
// #430: 套餐金额实时同步到申请单 // #430: 套餐金额实时同步到申请单
nextTick(() => { nextTick(() => {
form.totalAmount = totalAmountCalc.value; form.totalAmount = totalAmountCalc.value;
}); });
} }
// 根据检查方法的packageName加载对应的套餐明细
async function loadMethodPackageDetails(item, method) {
item.methodPackageLoading = true;
item.methodPackageDetails = [];
try {
if (!method.packageName) {
item.methodPackageLoading = false;
return;
}
// 通过packageName查询套餐获取packageId
const pkgRes = await listCheckPackage({ packageName: method.packageName });
let packages = pkgRes?.data || [];
if (!Array.isArray(packages)) {
packages = packages.records || packages.data || [];
}
if (packages.length === 0) {
item.methodPackageLoading = false;
return;
}
const packageId = packages[0].id;
// 查询套餐明细
const detailRes = await request({
url: `/system/package/${packageId}/details`,
method: 'get'
});
if (detailRes.code === 200 && detailRes.data) {
item.methodPackageDetails = detailRes.data.map(d => ({
id: d.id,
name: d.itemName || d.name,
quantity: d.quantity || 1,
unit: d.unit || '次',
price: d.unitPrice || d.price || 0,
amount: d.amount || d.total || 0,
checked: true // 默认勾选
}));
}
} catch (err) {
console.error('加载方法套餐明细失败:', err);
item.methodPackageDetails = [];
} finally {
item.methodPackageLoading = false;
}
}
// Bug #384修复: 更新检查方法显示字段(联动) // Bug #384修复: 更新检查方法显示字段(联动)
function updateMethodDisplay() { function updateMethodDisplay() {
// 找到第一个有选中检查方法的项目 // 找到第一个有选中检查方法的项目
@@ -1555,19 +1354,6 @@ defineExpose({ getList });
gap: 8px; gap: 8px;
} }
/* Bug #499: 查询过滤工具栏 */
.search-toolbar {
margin-bottom: 10px;
padding: 8px 0;
border-bottom: 1px solid #ebeef5;
}
.search-toolbar :deep(.el-form-item) {
margin-bottom: 8px;
}
.search-toolbar :deep(.el-form-item__label) {
font-size: 12px;
}
/* 底部区域:左表单 + 右分类 */ /* 底部区域:左表单 + 右分类 */
.bottom-section { .bottom-section {
display: flex; display: flex;
@@ -1645,7 +1431,6 @@ defineExpose({ getList });
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; /* Bug #500: 防止切换时水平方向溢出导致抖动 */ overflow-x: hidden; /* Bug #500: 防止切换时水平方向溢出导致抖动 */
min-height: 120px; /* Bug #500: 固定最小高度,避免分类切换时 flex 容器高度突变 */
} }
.empty-hint { .empty-hint {
color: #909399; color: #909399;
@@ -1885,29 +1670,6 @@ defineExpose({ getList });
margin-left: 8px; 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;
}
.method-package-loading {
padding: 4px 0 4px 24px;
font-size: 10px;
color: #c0c4cc;
}
/* 折叠组件细节 */ /* 折叠组件细节 */
:deep(.el-collapse) { :deep(.el-collapse) {
border: none; border: none;
@@ -1918,10 +1680,10 @@ defineExpose({ getList });
height: auto; height: auto;
line-height: 1.5; line-height: 1.5;
} }
/* Bug #500修复: 折叠内容使用明确属性过渡,避免 transition: all 导致子元素意外动画 */ /* Bug #500: 折叠内容添加平滑过渡动画,避免切换时高度跳变 */
:deep(.el-collapse-item__content) { :deep(.el-collapse-item__content) {
padding-bottom: 4px; padding-bottom: 4px;
transition: height 0.3s ease, max-height 0.3s ease; transition: all 0.3s ease;
} }
/* Bug #500: 折叠面板动画容器,添加 overflow:hidden 防止展开时内容溢出导致闪烁 */ /* Bug #500: 折叠面板动画容器,添加 overflow:hidden 防止展开时内容溢出导致闪烁 */
:deep(.el-collapse-item__wrap) { :deep(.el-collapse-item__wrap) {

View File

@@ -589,46 +589,36 @@ function handleUseOrderGroup(row) {
minUnitPrice: orderDetail.minUnitPrice, minUnitPrice: orderDetail.minUnitPrice,
inventoryList: orderDetail.inventoryList || [], inventoryList: orderDetail.inventoryList || [],
priceList: orderDetail.priceList || [], priceList: orderDetail.priceList || [],
partPercent: orderDetail.partPercent ?? 1, partPercent: orderDetail.partPercent || 1,
partAttributeEnum: orderDetail.partAttributeEnum,
unitConversionRatio: orderDetail.unitConversionRatio,
// 🔧 Bug #218 修复positionId 可能存储在 item 本身,优先使用 item.positionId // 🔧 Bug #218 修复positionId 可能存储在 item 本身,优先使用 item.positionId
positionId: item.positionId ?? orderDetail.positionId, positionId: item.positionId || orderDetail.positionId,
defaultLotNumber: orderDetail.defaultLotNumber, defaultLotNumber: orderDetail.defaultLotNumber,
// 单位信息 // 单位信息
unitCode: item.unitCode ?? orderDetail.unitCode, unitCode: item.unitCode || orderDetail.unitCode,
categoryCode: item.categoryCode ?? orderDetail.categoryCode,
unitCodeName: item.unitCodeName || orderDetail.unitCode_dictText, unitCodeName: item.unitCodeName || orderDetail.unitCode_dictText,
minUnitCode: orderDetail.minUnitCode, minUnitCode: orderDetail.minUnitCode,
doseUnitCode: orderDetail.doseUnitCode, doseUnitCode: orderDetail.doseUnitCode,
// 合并后的完整对象(用于 setValue // 合并后的完整对象(用于 setValue
// 先展开 orderDetail 获取所有药品基础字段categoryCode、minUnitCode、doseUnitCode、
// partPercent、partAttributeEnum、unitConversionRatio、defaultLotNumber 等),
// 再用组套用户覆盖值覆盖,确保单次剂量/频次/用法/用药天数/总量等不被丢失
mergedDetail: { mergedDetail: {
...orderDetail, ...orderDetail,
adviceName: orderDetail.adviceName || item.orderDefinitionName || '未知项目', adviceName: orderDetail.adviceName || item.orderDefinitionName || '未知项目',
adviceType: orderDetail.adviceType, adviceType: orderDetail.adviceType,
quantity: item.quantity, quantity: item.quantity,
unitCode: item.unitCode ?? orderDetail.unitCode, unitCode: item.unitCode || orderDetail.unitCode,
categoryCode: item.categoryCode ?? orderDetail.categoryCode,
unitCodeName: item.unitCodeName, unitCodeName: item.unitCodeName,
dose: item.dose ?? orderDetail.dose, dose: item.dose || orderDetail.dose,
rateCode: item.rateCode ?? orderDetail.rateCode, rateCode: item.rateCode || orderDetail.rateCode,
methodCode: item.methodCode ?? orderDetail.methodCode, methodCode: item.methodCode || orderDetail.methodCode,
dispensePerDuration: item.dispensePerDuration ?? orderDetail.dispensePerDuration, dispensePerDuration: item.dispensePerDuration || orderDetail.dispensePerDuration,
doseQuantity: item.doseQuantity ?? orderDetail.doseQuantity, doseQuantity: item.doseQuantity,
// 🔧 Bug #218 / #403 修复positionId 可能存储在 item 本身,优先使用 item.positionId inventoryList: orderDetail.inventoryList || [],
positionId: item.positionId ?? orderDetail.positionId, priceList: orderDetail.priceList || [],
// 执行科室:优先使用组套明细中保存的 orgId partPercent: orderDetail.partPercent || 1,
orgId: item.orgId ?? orderDetail.orgId, // 🔧 Bug #218 修复positionId 可能存储在 item 本身,优先使用 item.positionId
orgName: item.orgName ?? orderDetail.orgName, positionId: item.positionId || orderDetail.positionId,
// 组号(保留组套中的分组信息) defaultLotNumber: orderDetail.defaultLotNumber,
groupId: item.groupId,
groupOrder: item.groupOrder,
therapyEnum: item.therapyEnum ?? orderDetail.therapyEnum ?? '1',
} }
}; };
}); });

View File

@@ -3326,13 +3326,9 @@ function syncGroupFields(row) {
} }
// 同步执行科室 // 同步执行科室
// 🔧 Bug #455: 诊疗类医嘱(adviceType=3)不使用项目配置的执行科室,
// 避免配置ID不在机构树中导致显示原始ID保持患者就诊科室即可
if (row.orgId || row.positionId) { if (row.orgId || row.positionId) {
if (Number(row.adviceType) != 3) { // 🔧 修复:优先使用项目所属科室(orgId)其次positionId
// 🔧 修复:优先使用项目所属科室(orgId),其次positionId prescriptionList.value[rowIndex.value].orgId = row.orgId || row.positionId;
prescriptionList.value[rowIndex.value].orgId = row.orgId || row.positionId;
}
} }
// 同步皮试标记 // 同步皮试标记
@@ -3416,11 +3412,8 @@ async function setValue(row) {
showPopover: false, // 确保查询框关闭 showPopover: false, // 确保查询框关闭
}; };
console.log('[BugFix] setValue - prescriptionList[rowIndex].adviceType_dictText:', prescriptionList.value[rowIndex.value].adviceType_dictText); console.log('[BugFix] setValue - prescriptionList[rowIndex].adviceType_dictText:', prescriptionList.value[rowIndex.value].adviceType_dictText);
// 🔧 Bug #455: 诊疗医嘱(adviceType=3)的执行科室默认使用患者就诊科室, // 🔧 Bug #455: 执行科室默认逻辑使用positionId(诊疗执行科室配置) → 患者就诊科室不再使用row.orgId(项目所属科室)
// 不使用positionId(诊疗目录配置的执行科室)避免配置ID不在机构树中导致显示原始ID prescriptionList.value[rowIndex.value].orgId = row.positionId || props.patientInfo?.orgId;
if (Number(row.adviceType) != 3) {
prescriptionList.value[rowIndex.value].orgId = row.positionId || props.patientInfo?.orgId;
}
prescriptionList.value[rowIndex.value].dose = row.dose || row.doseQuantity; prescriptionList.value[rowIndex.value].dose = row.dose || row.doseQuantity;
prescriptionList.value[rowIndex.value].quantity = row.quantity || 1; prescriptionList.value[rowIndex.value].quantity = row.quantity || 1;
prescriptionList.value[rowIndex.value].unitCodeList = unitCodeList.value; prescriptionList.value[rowIndex.value].unitCodeList = unitCodeList.value;

View File

@@ -626,19 +626,14 @@ function getList() {
loading.value = false loading.value = false
return return
} }
loading.value = true loading.value = true
getSurgeryPage({ getSurgeryPage({
pageNo: 1, pageNo: 1,
pageSize: 100, pageSize: 100,
encounterId: props.patientInfo.encounterId encounterId: props.patientInfo.encounterId
}).then((res) => { }).then((res) => {
if (res.code === 200) { surgeryList.value = res.data.records || []
surgeryList.value = res.data?.records || []
} else {
proxy.$modal.msgError(res.msg || '数据加载失败,请稍后重试')
surgeryList.value = []
}
}).catch(error => { }).catch(error => {
console.error('获取手术列表失败:', error) console.error('获取手术列表失败:', error)
proxy.$modal.msgError('数据加载失败,请稍后重试') proxy.$modal.msgError('数据加载失败,请稍后重试')
@@ -1138,17 +1133,13 @@ function submitForm() {
// 新增手术 // 新增手术
addSurgery(form.value).then((res) => { addSurgery(form.value).then((res) => {
if (res.code === 200) { if (res.code === 200) {
proxy.$modal.msgSuccess('手术申请提交成功!') proxy.$modal.msgSuccess(res.msg || '手术申请提交成功!')
// 保存麻醉方式 // 保存麻醉方式
sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum) sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum)
open.value = false open.value = false
emit('saved') // 通知父组件刷新医嘱列表 emit('saved') // 通知父组件刷新医嘱列表
// 刷新手术申请列表,使用 nextTick 确保数据一致性 // 刷新手术申请列表
proxy.$nextTick(() => { getList()
if (props.patientInfo?.encounterId) {
getList()
}
})
} else { } else {
proxy.$modal.msgError(res.msg || '新增手术失败,请检查表单信息') proxy.$modal.msgError(res.msg || '新增手术失败,请检查表单信息')
} }
@@ -1160,17 +1151,13 @@ function submitForm() {
// 修改手术 // 修改手术
updateSurgery(form.value).then((res) => { updateSurgery(form.value).then((res) => {
if (res.code === 200) { if (res.code === 200) {
proxy.$modal.msgSuccess('手术申请修改成功!') proxy.$modal.msgSuccess(res.msg || '手术申请修改成功!')
// 保存麻醉方式 // 保存麻醉方式
sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum) sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum)
open.value = false open.value = false
emit('saved') // 通知父组件刷新医嘱列表 emit('saved') // 通知父组件刷新医嘱列表
// 刷新手术申请列表,使用 nextTick 确保数据一致性 // 刷新手术申请列表
proxy.$nextTick(() => { getList()
if (props.patientInfo?.encounterId) {
getList()
}
})
} else { } else {
proxy.$modal.msgError(res.msg || '更新手术失败,请检查表单信息') proxy.$modal.msgError(res.msg || '更新手术失败,请检查表单信息')
} }

View File

@@ -151,8 +151,8 @@
@saved="() => prescriptionRef?.getListInfo()" /> @saved="() => prescriptionRef?.getListInfo()" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="手术申请" name="surgery"> <el-tab-pane label="手术申请" name="surgery">
<surgeryApplication :patientInfo="patientInfo" :activeTab="activeTab" ref="surgeryRef" <surgeryApplication :patientInfo="patientInfo" :activeTab="activeTab" ref="surgeryRef"
@saved="() => { prescriptionRef?.getListInfo(); surgeryRef?.getList() }" /> @saved="() => prescriptionRef?.getListInfo()" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="电子处方" name="eprescription"> <el-tab-pane label="电子处方" name="eprescription">
<eprescriptionlist :patientInfo="patientInfo" ref="eprescriptionRef" /> <eprescriptionlist :patientInfo="patientInfo" ref="eprescriptionRef" />

View File

@@ -51,7 +51,7 @@ const currentSelectRow = ref<any>({});
const queryParams = ref({ const queryParams = ref({
pageSize: 100, pageSize: 100,
pageNo: 1, pageNo: 1,
adviceTypes: [1, 2, 3, 6], adviceTypes: '1,2,3,6',
searchKey: '', searchKey: '',
organizationId: '', organizationId: '',
categoryCode: '', categoryCode: '',
@@ -88,10 +88,10 @@ const tableColumns = computed<TableColumn[]>(() => [
function refresh(adviceType: any, categoryCode: string, searchKey: string) { function refresh(adviceType: any, categoryCode: string, searchKey: string) {
// 有搜索词时跨类型搜索,避免用户输入"级护理"但因当前adviceType为药品而搜不到诊疗类护理项目 // 有搜索词时跨类型搜索,避免用户输入"级护理"但因当前adviceType为药品而搜不到诊疗类护理项目
if (searchKey) { if (searchKey) {
queryParams.value.adviceTypes = [1, 2, 3, 6]; queryParams.value.adviceTypes = '1,2,3,6';
} else { } else {
queryParams.value.adviceTypes = queryParams.value.adviceTypes =
adviceType !== undefined && adviceType !== '' ? [parseInt(adviceType)] : [1, 2, 3, 6]; adviceType !== undefined && adviceType !== '' ? String(adviceType) : '1,2,3,6';
} }
queryParams.value.categoryCode = categoryCode || ''; queryParams.value.categoryCode = categoryCode || '';
queryParams.value.searchKey = searchKey || ''; queryParams.value.searchKey = searchKey || '';

View File

@@ -44,17 +44,6 @@ export function getSurgery(queryParams) {
}); });
} }
/**
* 分页查询手术申请单全局不需要encounterId用于门诊手术安排查找弹窗
*/
export function getSurgeryPage(queryParams) {
return request({
url: '/reg-doctorstation/request-form/get-surgery-page',
method: 'get',
params: queryParams,
});
}
/** /**
* 查询护理医嘱信息 * 查询护理医嘱信息
*/ */

View File

@@ -49,15 +49,6 @@
<el-option label="已作废" value="7" /> <el-option label="已作废" value="7" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="关键字">
<el-input
v-model="filterForm.keyword"
placeholder="申请单号 / 检查项目名称"
clearable
style="width: 220px"
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="handleSearch" :loading="loading"> <el-button type="primary" @click="handleSearch" :loading="loading">
<el-icon><Search /></el-icon> <el-icon><Search /></el-icon>
@@ -95,43 +86,9 @@
<span>{{ parseStatus(scope.row.status) }}</span> <span>{{ parseStatus(scope.row.status) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="280" align="center" fixed="right"> <el-table-column label="操作" align="center" fixed="right">
<template #default="scope"> <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="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>
</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> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -180,7 +137,7 @@
<el-descriptions title="申请单描述" :column="2"> <el-descriptions title="申请单描述" :column="2">
<template v-for="(value, key) in descJsonData" :key="key"> <template v-for="(value, key) in descJsonData" :key="key">
<el-descriptions-item v-if="isFieldMatched(key)" :label="getFieldLabel(key)"> <el-descriptions-item v-if="isFieldMatched(key)" :label="getFieldLabel(key)">
{{ transformField(key, value) || '-' }} {{ value || '-' }}
</el-descriptions-item> </el-descriptions-item>
</template> </template>
</el-descriptions> </el-descriptions>
@@ -210,7 +167,7 @@
import {computed, getCurrentInstance, ref, watch} from 'vue'; import {computed, getCurrentInstance, ref, watch} from 'vue';
import {Refresh, Search} from '@element-plus/icons-vue'; import {Refresh, Search} from '@element-plus/icons-vue';
import {patientInfo} from '../../store/patient.js'; import {patientInfo} from '../../store/patient.js';
import {getCheck, deleteRequestForm, withdrawRequestForm, getTestResult} from './api'; import {getCheck} from './api';
import {getDepartmentList} from '@/api/public.js'; import {getDepartmentList} from '@/api/public.js';
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
@@ -222,19 +179,10 @@ const currentDetail = ref(null);
const descJsonData = ref(null); const descJsonData = ref(null);
const orgOptions = ref([]); const orgOptions = ref([]);
// 获取近7天的日期范围作为默认值
const getDefaultDateRange = () => {
const now = new Date();
const endDate = now.toISOString().split('T')[0];
const startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
return [startDate, endDate];
};
// 筛选表单数据 // 筛选表单数据
const filterForm = ref({ const filterForm = ref({
dateRange: getDefaultDateRange(), // 默认近一周 dateRange: [], // [startDate, endDate]
status: '', // 申请单状态 status: '', // 申请单状态
keyword: '', // 关键字搜索
}); });
const fetchData = async () => { const fetchData = async () => {
@@ -259,11 +207,6 @@ const fetchData = async () => {
params.status = filterForm.value.status; params.status = filterForm.value.status;
} }
// 添加关键字搜索
if (filterForm.value.keyword && filterForm.value.keyword.trim()) {
params.keyword = filterForm.value.keyword.trim();
}
const res = await getCheck(params); const res = await getCheck(params);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const raw = res.data?.records || res.data; const raw = res.data?.records || res.data;
@@ -300,9 +243,8 @@ const handleSearch = async () => {
* 重置按钮处理 * 重置按钮处理
*/ */
const handleReset = () => { const handleReset = () => {
filterForm.value.dateRange = getDefaultDateRange(); filterForm.value.dateRange = [];
filterForm.value.status = ''; filterForm.value.status = '';
filterForm.value.keyword = '';
fetchData(); fetchData();
}; };
@@ -328,12 +270,6 @@ const parseStatus = (status) => {
const labelMap = { const labelMap = {
categoryType: '项目类别', categoryType: '项目类别',
targetDepartment: '发往科室', targetDepartment: '发往科室',
urgencyLevel: '紧急程度',
allergyHistory: '过敏史',
examinationPurpose: '检查目的',
expectedExaminationTime: '期望检查时间',
medicalHistorySummary: '病史摘要',
allergyConfirmed: '过敏确认',
symptom: '症状', symptom: '症状',
sign: '体征', sign: '体征',
clinicalDiagnosis: '临床诊断', clinicalDiagnosis: '临床诊断',
@@ -342,17 +278,6 @@ const labelMap = {
attention: '注意事项', attention: '注意事项',
}; };
// Fields that need value transformation before display
const transformField = (key, value) => {
if (key === 'urgencyLevel') {
return value === 'emergency' ? '急诊' : '普通';
}
if (key === 'allergyConfirmed') {
return value === true || value === 'true' ? '已口头确认' : '未确认';
}
return value;
};
const isFieldMatched = (key) => { const isFieldMatched = (key) => {
return key in labelMap; return key in labelMap;
}; };
@@ -367,45 +292,50 @@ const hasMatchedFields = computed(() => {
}); });
/** 查询科室 */ /** 查询科室 */
const getLocationInfo = async () => { const getLocationInfo = () => {
try { getDepartmentList().then((res) => {
const res = await getDepartmentList(); orgOptions.value = res.data || [];
orgOptions.value = Array.isArray(res.data) ? res.data : []; });
} catch (e) {
console.warn('科室列表加载失败:', e.message);
orgOptions.value = [];
}
}; };
// 递归查找树形科室节点 const recursionFun = (targetDepartment) => {
const findTreeItem = (list, id) => { let name = '';
if (!list || list.length === 0) return null; for (let index = 0; index < orgOptions.value.length; index++) {
for (const item of list) { const obj = orgOptions.value[index];
if (item.id == id) return item; if (obj.id == targetDepartment) {
if (item.children && item.children.length > 0) { name = obj.name;
const found = findTreeItem(item.children, id); }
if (found) return found; const subObjArray = obj['children'];
if (subObjArray && subObjArray.length > 0) {
for (let index = 0; index < subObjArray.length; index++) {
const item = subObjArray[index];
if (item.id == targetDepartment) {
name = item.name;
}
}
} }
} }
return null; return name;
}; };
const handleViewDetail = async (row) => { const handleViewDetail = async (row) => {
// 确保科室数据已加载,以便将 ID 解析为名称 console.log('targetDepartment========>', JSON.stringify(row));
if (!orgOptions.value || orgOptions.value.length === 0) {
await getLocationInfo();
}
currentDetail.value = row; currentDetail.value = row;
// 解析 descJson // 解析 descJson
if (row.descJson) { if (row.descJson) {
try { try {
const obj = JSON.parse(row.descJson); const obj = JSON.parse(row.descJson);
// 将发往科室 ID 转换为名称 // 确保科室数据已加载
if (obj.targetDepartment) { if (!orgOptions.value || orgOptions.value.length === 0) {
const deptItem = findTreeItem(orgOptions.value, obj.targetDepartment); await new Promise((resolve) => {
obj.targetDepartment = deptItem ? deptItem.name : obj.targetDepartment; getDepartmentList().then((res) => {
orgOptions.value = res.data || [];
resolve();
});
});
} }
obj.targetDepartment = recursionFun(obj.targetDepartment);
descJsonData.value = obj; descJsonData.value = obj;
} catch (e) { } catch (e) {
console.error('解析 descJson 失败:', e); console.error('解析 descJson 失败:', e);
@@ -417,91 +347,6 @@ const handleViewDetail = async (row) => {
detailDialogVisible.value = true; detailDialogVisible.value = true;
}; };
/**
* 修改申请单(仅待签发状态)
*/
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) => {
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?.('暂无检查报告');
}
} else {
proxy.$modal?.msgWarning?.('暂无检查报告');
}
} catch (e) {
console.warn('查看检查报告失败:', e.message);
proxy.$modal?.msgError?.('获取检查报告失败');
}
};
watch( watch(
() => patientInfo.value?.encounterId, () => patientInfo.value?.encounterId,
(val) => { (val) => {
@@ -510,9 +355,8 @@ watch(
getLocationInfo(); getLocationInfo();
} else { } else {
tableData.value = []; tableData.value = [];
filterForm.value.dateRange = getDefaultDateRange(); filterForm.value.dateRange = [];
filterForm.value.status = ''; filterForm.value.status = '';
filterForm.value.keyword = '';
} }
}, },
{ immediate: true } { immediate: true }

View File

@@ -82,11 +82,7 @@
</template> </template>
<el-table-column type="index" label="序号" width="60" align="center" /> <el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="patientName" label="患者姓名" width="120" /> <el-table-column prop="patientName" label="患者姓名" width="120" />
<el-table-column label="申请单名称" width="140"> <el-table-column prop="name" label="申请单名称" width="140" />
<template #default="scope">
<span>{{ buildApplicationName(scope.row) }}</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="160" /> <el-table-column prop="createTime" label="创建时间" width="160" />
<el-table-column prop="prescriptionNo" label="申请单号" width="140" /> <el-table-column prop="prescriptionNo" label="申请单号" width="140" />
<el-table-column label="单据状态" width="100" align="center"> <el-table-column label="单据状态" width="100" align="center">
@@ -107,11 +103,11 @@
<el-table-column prop="requesterId_dictText" label="申请者" width="120" /> <el-table-column prop="requesterId_dictText" label="申请者" width="120" />
<el-table-column label="操作" align="center" fixed="right" width="160"> <el-table-column label="操作" align="center" fixed="right" width="160">
<template #default="scope"> <template #default="scope">
<template v-if="scope.row.status == 0"> <template v-if="scope.row.billStatus == 0 || scope.row.status == 0">
<el-button link type="primary" @click="handleEdit(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> <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template> </template>
<template v-else-if="scope.row.status == 1"> <template v-else-if="scope.row.billStatus == 1 || scope.row.status == 1">
<el-button link type="warning" @click="handleWithdraw(scope.row)">撤回</el-button> <el-button link type="warning" @click="handleWithdraw(scope.row)">撤回</el-button>
</template> </template>
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button> <el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
@@ -141,7 +137,7 @@
<el-descriptions-item label="创建时间">{{ <el-descriptions-item label="创建时间">{{
currentDetail.createTime || '-' currentDetail.createTime || '-'
}}</el-descriptions-item> }}</el-descriptions-item>
<el-descriptions-item label="申请单号">{{ <el-descriptions-item label="处方号">{{
currentDetail.prescriptionNo || '-' currentDetail.prescriptionNo || '-'
}}</el-descriptions-item> }}</el-descriptions-item>
<el-descriptions-item label="申请者">{{ <el-descriptions-item label="申请者">{{
@@ -191,7 +187,7 @@ import {computed, getCurrentInstance, ref, watch} from 'vue';
import {Refresh, Search} from '@element-plus/icons-vue'; import {Refresh, Search} from '@element-plus/icons-vue';
import {patientInfo} from '../../store/patient.js'; import {patientInfo} from '../../store/patient.js';
import {getInspection, deleteRequestForm, withdrawRequestForm} from './api'; import {getInspection, deleteRequestForm, withdrawRequestForm} from './api';
import {getDepartmentList} from '@/api/public.js'; import {getOrgList} from '@/views/doctorstation/components/api.js';
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
@@ -289,9 +285,6 @@ const labelMap = {
otherDiagnosis: '其他诊断', otherDiagnosis: '其他诊断',
relatedResult: '相关结果', relatedResult: '相关结果',
attention: '注意事项', attention: '注意事项',
applicationType: '申请类型',
specimenName: '标本类型',
executeTime: '执行时间',
}; };
/** /**
@@ -318,8 +311,8 @@ const parsePriorityCode = (descJson) => {
if (!descJson) return '-'; if (!descJson) return '-';
try { try {
const obj = JSON.parse(descJson); const obj = JSON.parse(descJson);
// applicationType: 0-普通, 1-急 // priorityCode: 0-普通, 1-急
return obj.applicationType === 1 ? '急' : '普通'; return obj.priorityCode === 1 ? '急' : '普通';
} catch (e) { } catch (e) {
console.error('解析 descJson 失败:', e); console.error('解析 descJson 失败:', e);
return '-'; return '-';
@@ -343,24 +336,6 @@ const parseSpecimenType = (descJson) => {
} }
}; };
/**
* 根据申请单详情构建申请单名称
* 单一项目:显示项目名称+数量
* 多个项目:显示首个项目名称+数量+"等X项"
*/
const buildApplicationName = (row) => {
const details = row.requestFormDetailList;
if (!details || details.length === 0) {
return row.name || '-';
}
if (details.length === 1) {
const item = details[0];
return `${item.adviceName}${item.quantity || ''}`;
}
const first = details[0];
return `${first.adviceName}${first.quantity || ''}${details.length}`;
};
const isFieldMatched = (key) => { const isFieldMatched = (key) => {
return key in labelMap; return key in labelMap;
}; };
@@ -416,9 +391,6 @@ const handleViewDetail = async (row) => {
try { try {
const obj = JSON.parse(row.descJson); const obj = JSON.parse(row.descJson);
obj.targetDepartment = recursionFun(obj.targetDepartment); obj.targetDepartment = recursionFun(obj.targetDepartment);
// 转换申请类型编码为可读文本
if (obj.applicationType === 0) obj.applicationType = '普通';
else if (obj.applicationType === 1) obj.applicationType = '急诊';
descJsonData.value = obj; descJsonData.value = obj;
} catch (e) { } catch (e) {
console.error('解析 descJson 失败:', e); console.error('解析 descJson 失败:', e);

View File

@@ -72,45 +72,6 @@
<el-input v-model="form.attention" autocomplete="off" type="textarea" /> <el-input v-model="form.attention" autocomplete="off" type="textarea" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 申请类型 -->
<el-col :span="12">
<el-form-item label="申请类型" prop="applicationType" style="width: 100%">
<el-radio-group v-model="form.applicationType">
<el-radio :value="0">普通</el-radio>
<el-radio :value="1">急诊</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<!-- 标本类型 -->
<el-col :span="12">
<el-form-item label="标本类型" prop="specimenName" style="width: 100%">
<el-select v-model="form.specimenName" placeholder="请选择标本类型" style="width: 100%">
<el-option label="血液" value="血液" />
<el-option label="尿液" value="尿液" />
<el-option label="粪便" value="粪便" />
<el-option label="痰液" value="痰液" />
<el-option label="咽拭子" value="咽拭子" />
<el-option label="脑脊液" value="脑脊液" />
<el-option label="胸腹水" value="胸腹水" />
<el-option label="关节液" value="关节液" />
<el-option label="分泌物" value="分泌物" />
<el-option label="其他" value="其他" />
</el-select>
</el-form-item>
</el-col>
<!-- 执行时间 -->
<el-col :span="12">
<el-form-item label="执行时间" prop="executeTime" style="width: 100%">
<el-date-picker
v-model="form.executeTime"
type="datetime"
placeholder="选择执行时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row> </el-row>
</el-form> </el-form>
</div> </div>
@@ -144,56 +105,42 @@ const applicationListAll = ref();
const applicationList = ref(); const applicationList = ref();
const loading = ref(false); const loading = ref(false);
const orgOptions = ref([]); // 科室选项 const orgOptions = ref([]); // 科室选项
const getList = async () => { const getList = () => {
if (!patientInfo.value?.inHospitalOrgId) { if (!patientInfo.value?.inHospitalOrgId) {
applicationList.value = []; applicationList.value = [];
return; return;
} }
loading.value = true; loading.value = true;
try { getApplicationList({
const allRecords = []; pageSize: 9999,
let currentPage = 1; pageNum: 1,
const pageSize = 500; categoryCode: '22',
organizationId: patientInfo.value.inHospitalOrgId,
// 分页拉取全部数据后端单页最多500条 adviceTypes: [3], //1 药品 2耗材 3诊疗
while (true) { })
const res = await getApplicationList({ .then((res) => {
pageSize, if (res.code === 200) {
pageNo: currentPage, applicationListAll.value = res.data.records;
categoryCode: '22', applicationList.value = res.data.records.map((item) => {
organizationId: patientInfo.value.inHospitalOrgId, const priceInfo = item.priceList?.[0] || {};
adviceTypes: [3], // 1 药品 2 耗材 3 诊疗 const price = priceInfo.price != null ? Number(priceInfo.price).toFixed(2) : '0.00';
}); const unit = item.unitCode_dictText || item.unitCode || '';
if (res.code !== 200) { return {
adviceDefinitionId: item.adviceDefinitionId,
orgId: item.orgId,
label: item.adviceName + ' (¥' + price + '/' + unit + ')',
key: item.adviceDefinitionId,
};
});
console.log('applicationList========>', JSON.stringify(res.data.records));
} else {
proxy.$message.error(res.message); proxy.$message.error(res.message);
applicationList.value = []; applicationList.value = [];
return;
} }
const records = res.data?.records || []; })
allRecords.push(...records); .finally(() => {
// 当前页不足 pageSize 或已无数据,说明已全部拉取 loading.value = false;
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,
};
}); });
} catch (e) {
proxy.$message.error('获取检验项目列表失败');
applicationList.value = [];
} finally {
loading.value = false;
}
}; };
const transferValue = ref([]); const transferValue = ref([]);
const form = reactive({ const form = reactive({
@@ -205,9 +152,6 @@ const form = reactive({
otherDiagnosis: '', // 其他诊断 otherDiagnosis: '', // 其他诊断
relatedResult: '', // 相关结果 relatedResult: '', // 相关结果
attention: '', // 注意事项 attention: '', // 注意事项
applicationType: 0, // 申请类型 0-普通 1-急诊
specimenName: '血液', // 标本类型
executeTime: null, // 执行时间
primaryDiagnosisList: [], //主诊断目录 primaryDiagnosisList: [], //主诊断目录
otherDiagnosisList: [], //其他断目录 otherDiagnosisList: [], //其他断目录
}); });
@@ -256,16 +200,12 @@ const projectWithDepartment = (selectProjectIds, type) => {
if (type === 2 && manualDept) { if (type === 2 && manualDept) {
form.targetDepartment = manualDept; form.targetDepartment = manualDept;
isRelease = true; isRelease = true;
} else if (type === 2 && !manualDept) { } else {
// 提交时用户未手动选择科室,才提示错误
isRelease = false; isRelease = false;
ElMessage({ ElMessage({
type: 'error', type: 'error',
message: '未找到项目执行的科室', message: '未找到项目执行的科室',
}); });
} else {
// type=1(选择项目变化)时,不弹窗,仅清空科室让用户自行选择
isRelease = false;
} }
} }
if (findItem && isRelease) { if (findItem && isRelease) {

View File

@@ -483,10 +483,7 @@ const submit = () => {
encounterId: patientInfo.value.encounterId, encounterId: patientInfo.value.encounterId,
organizationId: patientInfo.value.inHospitalOrgId, organizationId: patientInfo.value.inHospitalOrgId,
requestFormId: '', requestFormId: '',
name: transferValue.value.map(id => { name: applicationListAllFilter.map(item => item.adviceName).join('、'),
const item = applicationListAll.value?.find(i => i.adviceDefinitionId === id);
return item?.adviceName || '';
}).filter(Boolean).join('、'),
descJson: JSON.stringify(submitForm), descJson: JSON.stringify(submitForm),
categoryEnum: '2', categoryEnum: '2',
}).then((res) => { }).then((res) => {

View File

@@ -5,14 +5,16 @@
--> -->
<template> <template>
<div class="surgery-container"> <div class="surgery-container">
<div v-loading="loading" class="transfer-wrapper" style="min-height: 300px;"> <div class="transfer-wrapper">
<el-transfer <div v-loading="loading" style="min-height: 300px;">
v-model="transferValue" <el-transfer
:data="applicationList" v-model="transferValue"
filter-placeholder="项目代码/名称" :data="applicationList"
filterable filter-placeholder="项目代码/名称"
:titles="['未选择', '已选择']" filterable
/> :titles="['未选择', '已选择']"
/>
</div>
</div> </div>
<div class="bloodTransfusion-form"> <div class="bloodTransfusion-form">
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="demo-ruleForm"> <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="demo-ruleForm">
@@ -86,9 +88,6 @@ import {getApplicationList, saveSurgery} from './api';
import {ElMessage} from 'element-plus'; import {ElMessage} from 'element-plus';
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
// 模块级缓存:避免每次打开弹窗都重新请求手术项目列表
let surgeryRecordsCache = null; // 原始 API 记录
let surgeryMappedCache = null; // 映射后的 el-transfer 数据
// 递归查找树形科室节点 // 递归查找树形科室节点
const findTreeItem = (list, id) => { const findTreeItem = (list, id) => {
if (!list || list.length === 0) return null; if (!list || list.length === 0) return null;
@@ -104,8 +103,8 @@ const findTreeItem = (list, id) => {
const emits = defineEmits(['submitOk']); const emits = defineEmits(['submitOk']);
const props = defineProps({}); const props = defineProps({});
const state = reactive({}); const state = reactive({});
const applicationListAll = ref(); const applicationListAll = ref([]);
const applicationList = ref(); const applicationList = ref([]);
const orgOptions = ref([]); // 科室选项 const orgOptions = ref([]); // 科室选项
const loading = ref(false); // 加载状态 const loading = ref(false); // 加载状态
const getList = () => { const getList = () => {
@@ -113,12 +112,6 @@ const getList = () => {
applicationList.value = []; applicationList.value = [];
return; return;
} }
// 命中缓存时直接使用,避免重复请求导致加载缓慢
if (surgeryMappedCache && surgeryMappedCache.length > 0) {
applicationList.value = surgeryMappedCache;
applicationListAll.value = surgeryRecordsCache;
return;
}
loading.value = true; loading.value = true;
getApplicationList({ getApplicationList({
pageSize: 500, pageSize: 500,
@@ -141,9 +134,6 @@ const getList = () => {
key: item.adviceDefinitionId, key: item.adviceDefinitionId,
}; };
}); });
// 写入模块缓存,后续打开弹窗直接复用
surgeryRecordsCache = res.data.records;
surgeryMappedCache = applicationList.value;
} else { } else {
console.warn('获取手术项目列表失败:', res.message); console.warn('获取手术项目列表失败:', res.message);
applicationList.value = []; applicationList.value = [];

View File

@@ -197,7 +197,6 @@
style="width: 62%" style="width: 62%"
v-model="scope.row.adviceName" v-model="scope.row.adviceName"
placeholder="请选择项目" placeholder="请选择项目"
@input="handleChange"
@click="handleFocus(scope.row, scope.$index)" @click="handleFocus(scope.row, scope.$index)"
@keyup.enter.stop="handleFocus(scope.row, scope.$index)" @keyup.enter.stop="handleFocus(scope.row, scope.$index)"
@keydown=" @keydown="
@@ -533,7 +532,6 @@ const statusOption = [
let loadingInstance = undefined; let loadingInstance = undefined;
onMounted(() => { onMounted(() => {
document.addEventListener('keydown', escKeyListener); document.addEventListener('keydown', escKeyListener);
getList();
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
@@ -574,6 +572,7 @@ function handleTotalAmount() {
} }
}, new Decimal(0)); }, new Decimal(0));
} }
getList();
function getList() { function getList() {
getDiagnosisDefinitionList(queryParams.value).then((res) => { getDiagnosisDefinitionList(queryParams.value).then((res) => {
// prescriptionList.value = res.data.records; // prescriptionList.value = res.data.records;
@@ -585,11 +584,6 @@ function refresh() {
} }
// 获取列表信息 // 获取列表信息
function getListInfo(addNewRow) { function getListInfo(addNewRow) {
// 守护:未选择患者时不发起 API 请求,避免页面加载时循环报错
if (!patientInfo.value || !patientInfo.value.encounterId) {
console.warn('⚠️ getListInfo 跳过:未选择患者');
return;
}
loadingInstance = ElLoading.service({ fullscreen: true }); loadingInstance = ElLoading.service({ fullscreen: true });
setTimeout(() => { setTimeout(() => {
loadingInstance.close(); loadingInstance.close();
@@ -715,17 +709,9 @@ function loadConfiguredCategories() {
// 数据过滤 // 数据过滤
const filterPrescriptionList = computed(() => { const filterPrescriptionList = computed(() => {
const pList = prescriptionList.value.filter((item) => { const pList = prescriptionList.value.filter((item) => {
// 修复 Bug #488orderClassCode 可能是复合值 '1-2',需提取 adviceType 部分进行比较
let matchAdviceType = true;
if (orderClassCode.value) {
const filterAdviceType = String(orderClassCode.value).includes('-')
? parseInt(String(orderClassCode.value).split('-')[0])
: orderClassCode.value;
matchAdviceType = filterAdviceType == item.adviceType;
}
return ( return (
(!therapyEnum.value || therapyEnum.value == item.therapyEnum) && (!therapyEnum.value || therapyEnum.value == item.therapyEnum) &&
matchAdviceType && (!orderClassCode.value || orderClassCode.value == item.adviceType) &&
(!orderStatus.value || (orderStatus.value == item.statusEnum && item.requestId)) (!orderStatus.value || (orderStatus.value == item.statusEnum && item.requestId))
); );
}); });
@@ -753,28 +739,12 @@ function getRowDisabled(row) {
/** /**
* 将行的 adviceType + categoryCode 映射为 el-select 的选中值 * 将行的 adviceType + categoryCode 映射为 el-select 的选中值
* 药品子分类使用复合值如 '1-2'adviceType-categoryCode诊疗/手术/全部使用原始值 * 药品子分类使用复合值如 '1-2'adviceType-categoryCode诊疗/手术/全部使用原始值
* 修复 Bug #488当行的 adviceType 在当前配置中找不到匹配选项时,返回最接近的可用值,避免回显为纯数字
*/ */
function getRowSelectValue(row) { function getRowSelectValue(row) {
if (row.adviceType == 1 && row.categoryCode) { if (row.adviceType == 1 && row.categoryCode) {
const compositeValue = '1-' + row.categoryCode; return '1-' + row.categoryCode;
// 检查复合值是否在选项列表中
if (adviceTypeList.value.some(item => item.value === compositeValue)) {
return compositeValue;
}
// 配置的 categoryCode 已变更,回退到第一个药品选项
const firstPharmacy = adviceTypeList.value.find(item => String(item.value).startsWith('1-'));
if (firstPharmacy) {
return firstPharmacy.value;
}
return row.adviceType;
} }
// 诊疗/手术等非药品类型,检查其值是否在选项列表中 return row.adviceType;
if (adviceTypeList.value.some(item => item.value === row.adviceType)) {
return row.adviceType;
}
// 不在选项中的值(如已废弃的 adviceType返回 undefined 让 el-select 显示为空
return undefined;
} }
// 新增医嘱 // 新增医嘱
@@ -1211,27 +1181,19 @@ function handleSave() {
}); });
// 此处签发处方和单行保存处方传参相同后台已经将传参存为JSON字符串此处直接转换为JSON即可 // 此处签发处方和单行保存处方传参相同后台已经将传参存为JSON字符串此处直接转换为JSON即可
loading.value = true; loading.value = true;
let list = []; let list = saveList.map((item) => {
try { const parsedContent = JSON.parse(item.contentJson);
list = saveList.map((item) => { return {
const parsedContent = item.contentJson ? JSON.parse(item.contentJson) || {} : {}; ...parsedContent,
return { adviceType: item.adviceType,
...parsedContent, requestId: item.requestId,
adviceType: item.adviceType, dbOpType: '1',
requestId: item.requestId, groupId: item.groupId,
dbOpType: '1', uniqueKey: undefined,
groupId: item.groupId, // 确保 therapyEnum 被正确传递
uniqueKey: undefined, therapyEnum: parsedContent.therapyEnum || item.therapyEnum || '1',
// 确保 therapyEnum 被正确传递 };
therapyEnum: parsedContent.therapyEnum || item.therapyEnum || '1', });
};
});
} catch (error) {
loading.value = false;
isSaving.value = false;
proxy.$modal.msgError('医嘱内容解析失败,请检查待签发医嘱');
return;
}
// 保存签发按钮 // 保存签发按钮
isSaving.value = true; isSaving.value = true;
console.log('签发处方参数:', { console.log('签发处方参数:', {
@@ -1246,16 +1208,9 @@ function handleSave() {
if (res.code === 200) { if (res.code === 200) {
proxy.$modal.msgSuccess('签发成功'); proxy.$modal.msgSuccess('签发成功');
isSaving.value = false; isSaving.value = false;
// 乐观更新:立即将已签发医嘱的状态设为"已签发",确保列表实时刷新
saveList.forEach((item) => {
const row = prescriptionList.value.find((r) => r.requestId && r.requestId === item.requestId);
if (row) {
row.statusEnum = 2;
}
});
getListInfo(false); getListInfo(false);
bindMethod.value = {}; bindMethod.value = {};
nextId.value = 1; nextId.value == 1;
} else { } else {
proxy.$modal.msgError(res.message); proxy.$modal.msgError(res.message);
isSaving.value = false; isSaving.value = false;
@@ -1356,12 +1311,11 @@ function handleCancelEdit(row, index) {
function handleSaveSign(row, index) { function handleSaveSign(row, index) {
if (row.adviceType != 2) { if (row.adviceType != 2) {
// 修复 Bug #488严格校验 itemNo确保非空且为有效字符串才发起请求
let itemNo = row.adviceType == 1 ? row.methodCode : row.adviceDefinitionId; let itemNo = row.adviceType == 1 ? row.methodCode : row.adviceDefinitionId;
if (!itemNo || String(itemNo).trim() === '') { if (!itemNo) {
console.warn('绑定设备检查跳过itemNo为空adviceType=' + row.adviceType + ', adviceName=' + row.adviceName + ''); console.warn('绑定设备检查跳过itemNo为空adviceType=' + row.adviceType + ', adviceName=' + row.adviceName + '');
} else { } else {
getBindDevice({ typeCode: row.adviceType, itemNo: String(itemNo) }).then((res) => { getBindDevice({ typeCode: row.adviceType, itemNo: itemNo }).then((res) => {
if (res.data.length == 0) { if (res.data.length == 0) {
return; return;
} }
@@ -1422,21 +1376,13 @@ function handleSaveSign(row, index) {
savePrescription({ regAdviceSaveList: [row] }).then((res) => { savePrescription({ regAdviceSaveList: [row] }).then((res) => {
if (res.code === 200) { if (res.code === 200) {
proxy.$modal.msgSuccess('保存成功'); proxy.$modal.msgSuccess('保存成功');
nextId.value = 1; nextId.value == 1;
} }
}); });
} else { } else {
// 新增行:调用保存接口将数据持久化到后端 if (prescriptionList.value[0].adviceName) {
row.dbOpType = '1'; handleAddPrescription();
savePrescription({ regAdviceSaveList: [row] }).then((res) => { }
if (res.code === 200) {
proxy.$modal.msgSuccess('保存成功');
nextId.value = 1;
// 保存成功后刷新列表,确保后端返回的数据带 requestId
getListInfo(false);
}
});
// 不需要再添加空行,保存成功后由 getListInfo 处理
} }
adviceQueryParams.value.adviceType = undefined; adviceQueryParams.value.adviceType = undefined;
} }
@@ -1483,13 +1429,12 @@ function handleSaveBatch() {
if (row) row.isEdit = false; if (row) row.isEdit = false;
}); });
getListInfo(false); getListInfo(false);
nextId.value = 1; nextId.value == 1;
isSaving.value = false; isSaving.value = false;
} }
}) })
.catch((error) => { .catch((error) => {
isSaving.value = false; isSaving.value = false;
proxy.$modal.msgError(error?.msg || '保存失败,请重试');
}); });
} }
@@ -1617,21 +1562,17 @@ function handleSaveGroup(orderGroupList) {
// 🔥 新版组件已经预处理了数据,优先使用 mergedDetail // 🔥 新版组件已经预处理了数据,优先使用 mergedDetail
const mergedDetail = item.mergedDetail || { const mergedDetail = item.mergedDetail || {
...(item.orderDetailInfos || {}), ...(item.orderDetailInfos || {}),
adviceName: item.orderDefinitionName || item.orderDetailInfos?.adviceName || '未知项目', adviceName: item.orderDetailInfos?.adviceName || item.orderDefinitionName || '未知项目',
adviceType: item.orderDetailInfos?.adviceType, adviceType: item.orderDetailInfos?.adviceType,
adviceDefinitionId: item.orderDefinitionId || item.orderDetailInfos?.adviceDefinitionId, adviceDefinitionId: item.orderDefinitionId || item.orderDetailInfos?.adviceDefinitionId,
quantity: item.quantity, quantity: item.quantity,
unitCode: item.unitCode || item.orderDetailInfos?.unitCode, unitCode: item.unitCode || item.orderDetailInfos?.unitCode,
unitCodeName: item.unitCodeName, unitCodeName: item.unitCodeName,
// 🔧 Bug #403 修复dose/doseQuantity/dispensePerDuration 需用 null 检查, dose: item.dose || item.orderDetailInfos?.dose,
// 避免组套中值为 null 时回退到医嘱库的 orderDetailInfos
dose: item.dose !== undefined && item.dose !== null ? item.dose : item.orderDetailInfos?.dose,
rateCode: item.rateCode || item.orderDetailInfos?.rateCode, rateCode: item.rateCode || item.orderDetailInfos?.rateCode,
methodCode: item.methodCode || item.orderDetailInfos?.methodCode, methodCode: item.methodCode || item.orderDetailInfos?.methodCode,
dispensePerDuration: item.dispensePerDuration !== undefined && item.dispensePerDuration !== null dispensePerDuration: item.dispensePerDuration || item.orderDetailInfos?.dispensePerDuration,
? item.dispensePerDuration : item.orderDetailInfos?.dispensePerDuration, doseQuantity: item.doseQuantity,
doseQuantity: item.doseQuantity !== undefined && item.doseQuantity !== null
? item.doseQuantity : item.orderDetailInfos?.doseQuantity,
inventoryList: item.orderDetailInfos?.inventoryList || [], inventoryList: item.orderDetailInfos?.inventoryList || [],
priceList: item.orderDetailInfos?.priceList || [], priceList: item.orderDetailInfos?.priceList || [],
partPercent: item.orderDetailInfos?.partPercent || 1, partPercent: item.orderDetailInfos?.partPercent || 1,
@@ -1650,15 +1591,20 @@ function handleSaveGroup(orderGroupList) {
setValue(mergedDetail); setValue(mergedDetail);
// 创建新的处方项目 // 创建新的处方项目
// 🔧 Bug #403 修复:关键字段使用 null-safe 回退到 mergedDetail已由 setValue 填充完整数据)
// 先取 setValue 填充的行数据作为基础
const baseRow = prescriptionList.value[rowIndex.value];
const newRow = { const newRow = {
...baseRow, ...prescriptionList.value[rowIndex.value],
patientId: patientInfo.value.patientId, patientId: patientInfo.value.patientId,
encounterId: patientInfo.value.encounterId, encounterId: patientInfo.value.encounterId,
accountId: accountId.value, accountId: accountId.value,
quantity: item.quantity,
methodCode: item.methodCode,
rateCode: item.rateCode,
dispensePerDuration: item.dispensePerDuration,
dose: item.dose,
doseQuantity: item.doseQuantity,
executeNum: 1, executeNum: 1,
unitCode: item.unitCode,
unitCode_dictText: item.unitCodeName || '',
statusEnum: 1, statusEnum: 1,
orgId: resolveOrgId(item.orderDetailInfos?.orgId || mergedDetail.orgId || patientInfo.value?.inHospitalOrgId) || '', orgId: resolveOrgId(item.orderDetailInfos?.orgId || mergedDetail.orgId || patientInfo.value?.inHospitalOrgId) || '',
// 🔧 修复:同时保存 orgName确保树匹配不到时仍有中文名称可显示 // 🔧 修复:同时保存 orgName确保树匹配不到时仍有中文名称可显示
@@ -1667,33 +1613,19 @@ function handleSaveGroup(orderGroupList) {
conditionId: conditionId.value, conditionId: conditionId.value,
conditionDefinitionId: conditionDefinitionId.value, conditionDefinitionId: conditionDefinitionId.value,
encounterDiagnosisId: encounterDiagnosisId.value, encounterDiagnosisId: encounterDiagnosisId.value,
therapyEnum: baseRow?.therapyEnum || '1', therapyEnum: prescriptionList.value[rowIndex.value]?.therapyEnum || '1',
}; };
// 覆盖关键字段:优先使用 item 的值,其次 mergedDetail已由 setValue 填充),最后 baseRow
newRow.quantity = item.quantity ?? mergedDetail.quantity ?? baseRow.quantity;
newRow.methodCode = item.methodCode ?? mergedDetail.methodCode ?? baseRow.methodCode;
newRow.rateCode = item.rateCode ?? mergedDetail.rateCode ?? baseRow.rateCode;
newRow.dispensePerDuration = item.dispensePerDuration ?? mergedDetail.dispensePerDuration ?? baseRow.dispensePerDuration;
newRow.dose = item.dose ?? mergedDetail.dose ?? baseRow.dose;
newRow.doseQuantity = item.doseQuantity ?? mergedDetail.doseQuantity ?? baseRow.doseQuantity;
newRow.unitCode = item.unitCode ?? mergedDetail.unitCode ?? baseRow.unitCode;
newRow.unitCode_dictText = item.unitCodeName || mergedDetail.unitCodeName || baseRow.unitCode_dictText || '';
// 计算价格和总量 // 计算价格和总量
// 🔧 Bug #403 修复:使用 newRow.unitCode已由 setValue 填充)而非 item.unitCode const unitInfo = unitCodeList.value.find((k) => k.value == item.unitCode);
// 使用 ?? 替代 || 计算 partPercent确保值为 0 时不会被错误替换
const finalUnitCode = newRow.unitCode;
const unitInfo = unitCodeList.value.find((k) => k.value == finalUnitCode);
const finalQuantity = newRow.quantity;
const partPercent = item.orderDetailInfos?.partPercent ?? mergedDetail.partPercent ?? baseRow.partPercent ?? 1;
if (unitInfo && unitInfo.type == 'minUnit') { if (unitInfo && unitInfo.type == 'minUnit') {
newRow.price = newRow.minUnitPrice; newRow.price = newRow.minUnitPrice;
newRow.totalPrice = ((finalQuantity || 0) * newRow.minUnitPrice).toFixed(6); newRow.totalPrice = (item.quantity * newRow.minUnitPrice).toFixed(6);
newRow.minUnitQuantity = finalQuantity || 0; newRow.minUnitQuantity = item.quantity;
} else { } else {
newRow.price = newRow.unitPrice; newRow.price = newRow.unitPrice;
newRow.totalPrice = ((finalQuantity || 0) * newRow.unitPrice).toFixed(6); newRow.totalPrice = (item.quantity * newRow.unitPrice).toFixed(6);
newRow.minUnitQuantity = (finalQuantity || 0) * partPercent; newRow.minUnitQuantity = item.quantity * (item.orderDetailInfos?.partPercent || mergedDetail.partPercent || 1);
} }
newRow.contentJson = JSON.stringify(newRow); newRow.contentJson = JSON.stringify(newRow);

View File

@@ -296,7 +296,6 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-empty v-if="!groupSetLoading && groupSetList.length === 0" description="暂无划价组套数据" :image-size="80" />
<div style="margin-top: 15px; text-align: right"> <div style="margin-top: 15px; text-align: right">
<el-button @click="groupSetDialogVisible = false">取消</el-button> <el-button @click="groupSetDialogVisible = false">取消</el-button>
<el-button type="primary" @click="applyGroupSet" :disabled="!selectedGroupSet">应用</el-button> <el-button type="primary" @click="applyGroupSet" :disabled="!selectedGroupSet">应用</el-button>
@@ -482,53 +481,18 @@ watch(
(visible) => { (visible) => {
if (visible) { if (visible) {
executeTime.value = formatDateStr(new Date(), 'YYYY-MM-DD HH:mm:ss'); executeTime.value = formatDateStr(new Date(), 'YYYY-MM-DD HH:mm:ss');
// 弹窗打开时重新加载科室和位置选项,确保数据最新
loadDepartmentOptions();
getDiseaseInitLoc(16);
} else { } else {
resetData(); resetData();
} }
} }
); );
// 监听科室选项加载完成,为已添加的诊疗项目设置默认执行科室
watch(
() => departmentOptions.value,
(depts) => {
if (!depts || depts.length === 0) return;
feeItemsList.value.forEach(item => {
if (item.adviceType === 3 && !item.positionId) {
const patientOrgId = props.patientInfo.organizationId;
const matched = depts.find(d => String(d.id) === String(patientOrgId));
item.positionId = matched ? String(matched.id) : String(depts[0].id);
}
});
}
);
// 监听位置选项加载完成,为已添加的耗材项目设置默认位置
watch(
() => locationOptions.value,
(locs) => {
if (!locs || locs.length === 0) return;
feeItemsList.value.forEach(item => {
if (item.adviceType === 2 && !item.positionId) {
item.positionId = String(locs[0].value);
}
});
}
);
// 加载科室选项 // 加载科室选项
function loadDepartmentOptions() { function loadDepartmentOptions() {
getOrgList() getOrgList()
.then((res) => { .then((res) => {
if (res.data && res.data.records && res.data.records.length > 0) { if (res.data && res.data.records && res.data.records.length > 0) {
const firstRecord = res.data.records[0]; departmentOptions.value = res.data.records[0].children || [];
// 优先使用 children树形结构回退到 records 本身(扁平结构)
departmentOptions.value = (firstRecord.children && firstRecord.children.length > 0)
? firstRecord.children
: res.data.records;
} }
}) })
.catch(() => { .catch(() => {
@@ -563,27 +527,23 @@ function getDiseaseInitLoc() {
locationOptions.value = []; locationOptions.value = [];
}); });
} }
// 下拉框模糊搜索过滤自定义filter-method配合element-plus filterable使用 // 下拉框模糊搜索过滤
function filterOptions(val, row, optionsKey) { function filterOptions(val, row, optionsKey) {
const key = row.adviceDefinitionId + '_' + optionsKey; const key = row.adviceDefinitionId + '_' + optionsKey;
if (!val || val.trim() === '') { filterKeywords.value[key] = val;
delete filterKeywords.value[key];
} else {
filterKeywords.value[key] = val.toLowerCase();
}
} }
function getFilteredOptions(row, optionsKey) { function getFilteredOptions(row, optionsKey) {
const key = row.adviceDefinitionId + '_' + optionsKey; const key = row.adviceDefinitionId + '_' + optionsKey;
const keyword = filterKeywords.value[key]; const keyword = filterKeywords.value[key];
const options = optionsKey === 'departmentOptions' ? departmentOptions.value : locationOptions.value; const options = optionsKey === 'departmentOptions' ? departmentOptions.value : locationOptions.value;
if (!keyword) { if (!keyword || keyword.trim() === '') {
return options; return options;
} }
const lower = keyword.toLowerCase();
return options.filter(item => { return options.filter(item => {
const name = (item.name || item.label || '').toLowerCase(); const name = (item.name || item.label || '').toLowerCase();
const id = String(item.id || item.value || '').toLowerCase(); const id = String(item.id || item.value || '').toLowerCase();
const py = (item.pyStr || '').toLowerCase(); return name.includes(lower) || id.includes(lower);
return name.includes(keyword) || id.includes(keyword) || py.includes(keyword);
}); });
} }
// 获取组套类型文本 // 获取组套类型文本
@@ -593,9 +553,9 @@ function getItemType_Text(type) {
} }
function getUnitCodeOptions(row) { function getUnitCodeOptions(row) {
const unitCodes = [ const unitCodes = [
{ code: String(row.unitCode), codeText: row.unitCode_dictText }, { code: row.unitCode, codeText: row.unitCode_dictText },
{ code: String(row.minUnitCode), codeText: row.minUnitCode_dictText }, { code: row.minUnitCode, codeText: row.minUnitCode_dictText },
].filter(item => item.code); ];
// 使用 Set 来跟踪已经存在的 code // 使用 Set 来跟踪已经存在的 code
const seenCodes = new Set(); const seenCodes = new Set();
const uniqueUnitCodes = unitCodes.filter((item) => { const uniqueUnitCodes = unitCodes.filter((item) => {
@@ -615,11 +575,11 @@ function unitCodeChange(row) {
// 获取价格 // 获取价格
const price = row.priceList?.[0]?.price || 0; const price = row.priceList?.[0]?.price || 0;
// 根据选择的单位调整单价(统一用字符串比较) // 根据选择的单位调整单价
if (String(row.selectUnitCode) === String(row.unitCode)) { if (row.selectUnitCode === row.unitCode) {
// 如果选择的是大单位 (如 "") // 如果选择的是大单位 (如 "")
row.unitPrice = price.toFixed(6); // 单价就是原价 row.unitPrice = price.toFixed(6); // 单价就是原价
} else if (String(row.selectUnitCode) === String(row.minUnitCode)) { } else if (row.selectUnitCode === row.minUnitCode) {
// 如果选择的是小单位 (如 "") // 如果选择的是小单位 (如 "")
row.unitPrice = (price / (row.partPercent || 1)).toFixed(6); // 单价 = 原价 / 拆零比 row.unitPrice = (price / (row.partPercent || 1)).toFixed(6); // 单价 = 原价 / 拆零比
} }
@@ -803,7 +763,6 @@ function resetData() {
// 划价组套相关功能 // 划价组套相关功能
function openGroupSetDialog() { function openGroupSetDialog() {
console.log('openGroupSetDialog called');
groupSetDialogVisible.value = true; groupSetDialogVisible.value = true;
groupSetSearchText.value = ''; groupSetSearchText.value = '';
selectedGroupSet.value = null; selectedGroupSet.value = null;
@@ -812,37 +771,19 @@ function openGroupSetDialog() {
function loadGroupSets() { function loadGroupSets() {
groupSetLoading.value = true; groupSetLoading.value = true;
const params = { organizationId: orgId.value }; getOrderGroup({ organizationId: orgId.value })
// 传递搜索关键字,后端 /group-package-for-order 虽不直接支持 searchKey
// 但保持参数传递以便后续扩展
if (groupSetSearchText.value && groupSetSearchText.value.trim()) {
params.searchKey = groupSetSearchText.value.trim();
}
getOrderGroup(params)
.then((res) => { .then((res) => {
const data = res?.data || {}; const data = res?.data || {};
let rawList = [];
if (groupSetRange.value === 1) { if (groupSetRange.value === 1) {
rawList = data.personalList || []; groupSetList.value = data.personalList || [];
} else if (groupSetRange.value === 2) { } else if (groupSetRange.value === 2) {
rawList = data.organizationList || []; groupSetList.value = data.organizationList || [];
} else { } else {
rawList = data.hospitalList || []; groupSetList.value = data.hospitalList || [];
}
// 客户端过滤:根据搜索关键字过滤组套名称
const keyword = groupSetSearchText.value?.trim()?.toLowerCase();
if (keyword) {
groupSetList.value = rawList.filter(item => {
const name = (item.name || item.Name || '').toLowerCase();
return name.includes(keyword);
});
} else {
groupSetList.value = rawList;
} }
}) })
.catch((err) => { .catch(() => {
console.warn('组套列表加载失败(可能无权限):', err); console.warn('组套列表加载失败(可能无权限)');
ElMessage.warning('组套列表加载失败,当前暂无可用组套');
groupSetList.value = []; groupSetList.value = [];
}) })
.finally(() => { .finally(() => {

View File

@@ -28,7 +28,6 @@ export function getOrgList() {
return request({ return request({
url: '/base-data-manage/organization/organization', url: '/base-data-manage/organization/organization',
method: 'get', method: 'get',
params: { pageSize: 100, pageNum: 1 },
}); });
} }
/** /**

View File

@@ -302,29 +302,6 @@ function getSelectRows() {
}); });
return list; return list;
} }
function getTableRef(index) {
return proxy.$refs['tableRef' + index]?.[0];
}
function selectAllRows() {
prescriptionList.value.forEach((item, index) => {
const tableRef = getTableRef(index);
if (!tableRef) {
return;
}
item.forEach((row) => {
tableRef.toggleRowSelection(row, true);
});
});
}
function clearSelection() {
prescriptionList.value.forEach((item, index) => {
getTableRef(index)?.clearSelection();
});
}
function handleRateChange(value, item, row) { function handleRateChange(value, item, row) {
// 拼接当前选中时间 // 拼接当前选中时间
if (value) { if (value) {
@@ -342,8 +319,6 @@ function handleRateChange(value, item, row) {
defineExpose({ defineExpose({
handleGetPrescription, handleGetPrescription,
handleMedicineSummary, handleMedicineSummary,
selectAllRows,
clearSelection,
}); });
</script> </script>

View File

@@ -66,21 +66,13 @@ const props = defineProps({
type: Number, type: Number,
default: 1, default: 1,
}, },
therapyEnum: {
type: Number,
default: undefined,
},
}); });
handleGetPrescription(); handleGetPrescription();
function handleGetPrescription() { function handleGetPrescription() {
loading.value = true; loading.value = true;
let encounterIds = patientInfoList.value.map((i) => i.encounterId).join(','); let encounterIds = patientInfoList.value.map((i) => i.encounterId).join(',');
const params = {}; getMedicineSummary({}).then((res) => {
if (props.therapyEnum !== undefined) {
params.therapyEnum = props.therapyEnum;
}
getMedicineSummary(params).then((res) => {
medicineSummaryFormList.value = res.data.records; medicineSummaryFormList.value = res.data.records;
loading.value = false; loading.value = false;
}); });

View File

@@ -69,11 +69,7 @@
</div> </div>
<div> <div>
<span class="descriptions-item-label">全选</span> <span class="descriptions-item-label">全选</span>
<el-switch <el-switch v-model="chooseAll" @change="handelSwicthChange" />
v-model="chooseAll"
:disabled="isDetails != '1'"
@change="handelSwicthChange"
/>
<el-button class="ml20 mr20" type="primary" @click="handleExecute"> 汇总领药 </el-button> <el-button class="ml20 mr20" type="primary" @click="handleExecute"> 汇总领药 </el-button>
</div> </div>
</div> </div>
@@ -85,7 +81,7 @@
:deadline="deadline" :deadline="deadline"
:therapyEnum="therapyEnum" :therapyEnum="therapyEnum"
/> />
<SummaryMedicineList v-else ref="summaryMedicineRefs" :therapyEnum="therapyEnum" /> <SummaryMedicineList v-else />
<!-- <el-tabs v-model="activeName" class="demo-tabs centered-tabs" @tab-change="handleClick"> <!-- <el-tabs v-model="activeName" class="demo-tabs centered-tabs" @tab-change="handleClick">
<el-tab-pane <el-tab-pane
v-for="tab in prescriptionTabs" v-for="tab in prescriptionTabs"
@@ -129,7 +125,6 @@ const therapyEnum = ref(undefined);
// 存储子组件引用的对象 // 存储子组件引用的对象
const prescriptionRefs = ref(); const prescriptionRefs = ref();
const summaryMedicineRefs = ref();
const navigationButtons = inpatientNurseNavs; const navigationButtons = inpatientNurseNavs;
@@ -165,35 +160,24 @@ function handleClick(tabName) {
} }
function handleGetPrescription() { function handleGetPrescription() {
chooseAll.value = false; prescriptionRefs.value.handleGetPrescription();
if (isDetails.value == '1') {
prescriptionRefs.value?.handleGetPrescription();
} else {
summaryMedicineRefs.value?.handleGetPrescription();
}
} }
function handelSwicthChange(value) { function handelSwicthChange() {
if (!prescriptionRefs.value) { if (chooseAll.value) {
chooseAll.value = false; proxy.$refs['prescriptionRefs'].selectAllRows();
return;
}
if (value) {
prescriptionRefs.value.selectAllRows();
} else { } else {
prescriptionRefs.value.clearSelection(); proxy.$refs['prescriptionRefs'].clearSelection();
} }
} }
function handleRadioChange(value) { function handleRadioChange(value) {
chooseAll.value = false;
if (value == '1') { if (value == '1') {
handleGetPrescription(); handleGetPrescription();
} }
} }
function handleTherapyChange() { function handleTherapyChange() {
chooseAll.value = false;
handleGetPrescription(); handleGetPrescription();
} }
@@ -232,4 +216,4 @@ provide('handleGetPrescription', (value) => {
:deep(.el-tabs__header) { :deep(.el-tabs__header) {
margin: 0; margin: 0;
} }
</style> </style>

View File

@@ -453,10 +453,6 @@ const loadPatientInfo = () => {
interventionForm.value.startTime = dayjs(res.data.startTime).format( interventionForm.value.startTime = dayjs(res.data.startTime).format(
'YYYY-MM-DD HH:mm:ss' 'YYYY-MM-DD HH:mm:ss'
); );
} else if (res.data.inHosTime) {
interventionForm.value.startTime = dayjs(res.data.inHosTime).format(
'YYYY-MM-DD HH:mm:ss'
);
} else { } else {
interventionForm.value.startTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'); interventionForm.value.startTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss');
} }

View File

@@ -471,13 +471,11 @@ function handleExecute() {
console.log(list, 'list'); console.log(list, 'list');
adviceExecute({ exeDate: exeDate.value, adviceExecuteDetailList: list }).then((res) => { adviceExecute({ exeDate: exeDate.value, adviceExecuteDetailList: list }).then((res) => {
if (res.code == 200) { if (res.code == 200) {
// 仅当选中医嘱中包含诊疗类医嘱(可能绑定耗材)时,才调用耗材批号匹配 // 仅当选中医嘱中包含耗材类医嘱时,才调用耗材批号匹配(排除纯药品医嘱场景)
// adviceTable 取值为 med_medication_request药品或 wor_service_request诊疗/耗材) const hasDevice = list.some((item) =>
// 原代码用 includes('device') 判断有误,两个表名均不含 "device" 字符串 String(item.adviceTable || '').includes('device'),
const hasServiceRequest = list.some((item) =>
String(item.adviceTable || '') === 'wor_service_request',
); );
if (hasServiceRequest) { if (hasDevice) {
lotNumberMatch({ encounterIdList: encounterIds }, { skipErrorMsg: true }).catch((error) => { lotNumberMatch({ encounterIdList: encounterIds }, { skipErrorMsg: true }).catch((error) => {
console.warn('lotNumberMatch failed after adviceExecute:', error); console.warn('lotNumberMatch failed after adviceExecute:', error);
}); });

View File

@@ -803,7 +803,7 @@
</el-dialog> </el-dialog>
<!-- 手术计费弹窗 --> <!-- 手术计费弹窗 -->
<el-dialog :title="chargeDialogTitle" v-model="showChargeDialog" width="1400px" @close="closeChargeDialog" append-to-body destroy-on-close> <el-dialog :title="chargeDialogTitle" v-model="showChargeDialog" width="1400px" @close="closeChargeDialog" append-to-body>
<div style="display: flex; justify-content: space-between; height: 80vh"> <div style="display: flex; justify-content: space-between; height: 80vh">
<div style="width: 100%; border: 1px solid #eee; position: relative"> <div style="width: 100%; border: 1px solid #eee; position: relative">
<div style="padding: 10px; border: 1px solid #eee; height: 50px; border-left: 0"> <div style="padding: 10px; border: 1px solid #eee; height: 50px; border-left: 0">
@@ -829,9 +829,7 @@
</el-descriptions> </el-descriptions>
</div> </div>
<div style="padding: 10px"> <div style="padding: 10px">
<prescriptionlist :patientInfo="chargePatientInfo" ref="prescriptionRef" <prescriptionlist v-if="showChargeDialog" :patientInfo="chargePatientInfo" ref="prescriptionRef" />
:generateSourceEnum="1"
:sourceBillNo="chargePatientInfo.sourceBillNo" />
<div class="overlay" v-if="disabled"></div> <div class="overlay" v-if="disabled"></div>
</div> </div>
</div> </div>
@@ -874,59 +872,16 @@ import { Loading } from '@element-plus/icons-vue' // 🔧 新增:导入 Loadin
import { getPrescriptionList } from '@/views/clinicmanagement/bargain/component/api' import { getPrescriptionList } from '@/views/clinicmanagement/bargain/component/api'
// API 导入 // API 导入
import { import { getSurgerySchedulePage, addSurgerySchedule, updateSurgerySchedule, deleteSurgerySchedule, getSurgeryScheduleDetail } from '@/api/surgicalschedule'
getSurgerySchedulePage, import { listUser } from '@/api/system/user'
addSurgerySchedule, import { deptTreeSelect } from '@/api/system/user'
updateSurgerySchedule, import { listOperatingRoom } from '@/api/operatingroom'
deleteSurgerySchedule, import { getSurgery} from '@/views/inpatientDoctor/home/components/applicationShow/api.js'
getSurgeryScheduleDetail import { getTenantPage } from '@/api/system/tenant'
} from '@/api/surgicalschedule'
import { getSurgeryPage} from '@/views/inpatientDoctor/home/components/applicationShow/api.js'
import { getContract } from '@/views/inpatientDoctor/home/components/api.js' import { getContract } from '@/views/inpatientDoctor/home/components/api.js'
import request from '@/utils/request'
import SurgeryCharge from '../charge/surgerycharge/index.vue' import SurgeryCharge from '../charge/surgerycharge/index.vue'
import TemporaryMedical from './temporaryMedical.vue' import TemporaryMedical from './temporaryMedical.vue'
// 静默获取字典列表(跳过拦截器错误提示,手术室护士等角色可能无此权限)
function getTenantPageSilent(query) {
return request({
url: '/system/tenant/page',
method: 'get',
params: query,
skipErrorMsg: true
})
}
// 静默获取科室树(跳过拦截器错误提示)
function deptTreeSelectSilent(params = {}) {
return request({
url: '/base-data-manage/organization/organization',
method: 'get',
params: { typeEnum: 2, ...params },
skipErrorMsg: true
})
}
// 静默获取用户列表(跳过拦截器错误提示)
function listUserSilent(query) {
return request({
url: '/base-data-manage/practitioner/user-practitioner-page',
method: 'get',
params: query,
skipErrorMsg: true
})
}
// 静默获取手术室列表(跳过拦截器错误提示)
function listOperatingRoomSilent(query) {
return request({
url: '/base-data-manage/operating-room/list',
method: 'get',
params: query,
skipErrorMsg: true
})
}
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const userStore = useUserStore() const userStore = useUserStore()
const loading = ref(true) const loading = ref(true)
@@ -1155,7 +1110,7 @@ onMounted(() => {
// 加载卫生机构列表 // 加载卫生机构列表
function loadOrgList() { function loadOrgList() {
getTenantPageSilent({ pageNo: 1, pageSize: 1000 }) getTenantPage({ pageNo: 1, pageSize: 1000 })
.then(res => { .then(res => {
if (res.code === 200) { if (res.code === 200) {
const records = res.data?.records || res.data || [] const records = res.data?.records || res.data || []
@@ -1175,7 +1130,7 @@ function loadOrgList() {
// 加载科室列表 // 加载科室列表
function loadDeptList() { function loadDeptList() {
deptTreeSelectSilent() deptTreeSelect()
.then(res => { .then(res => {
if (res.code === 200) { if (res.code === 200) {
const tree = res.data?.records || res.data || [] const tree = res.data?.records || res.data || []
@@ -1195,7 +1150,7 @@ function loadDeptList() {
// 加载医生列表 // 加载医生列表
function loadDoctorList() { function loadDoctorList() {
listUserSilent({ pageNo: 1, pageSize: 1000 }) listUser({ pageNo: 1, pageSize: 1000 })
.then(res => { .then(res => {
if (res.code === 200) { if (res.code === 200) {
const records = res.data?.records || [] const records = res.data?.records || []
@@ -1215,7 +1170,7 @@ function loadDoctorList() {
// 加载护士列表 // 加载护士列表
function loadNurseList() { function loadNurseList() {
listUserSilent({ pageNo: 1, pageSize: 1000 }) listUser({ pageNo: 1, pageSize: 1000 })
.then(res => { .then(res => {
if (res.code === 200) { if (res.code === 200) {
const records = res.data?.records || [] const records = res.data?.records || []
@@ -1235,7 +1190,7 @@ function loadNurseList() {
// 加载手术室列表 // 加载手术室列表
function loadOperatingRoomList() { function loadOperatingRoomList() {
listOperatingRoomSilent({ pageNo: 1, pageSize: 1000, statusEnum: 1 }) listOperatingRoom({ pageNo: 1, pageSize: 1000, statusEnum: 1 })
.then(res => { .then(res => {
if (res.code === 200) { if (res.code === 200) {
const records = res.data?.records || [] const records = res.data?.records || []
@@ -1433,8 +1388,8 @@ async function handleChargeCharge(row) {
orgId: userStore.organizationId || userStore.orgId || userStore.tenantId || 1, orgId: userStore.organizationId || userStore.orgId || userStore.tenantId || 1,
// 添加账户ID // 添加账户ID
accountId: accountId, accountId: accountId,
// 添加手术单号用于关联对应的手术医嘱 // 添加手术申请单号用于追溯
sourceBillNo: row.operCode, sourceBillNo: row.applyId,
//添加计费标志手术计费 //添加计费标志手术计费
generateSourceEnum: 6 generateSourceEnum: 6
} }
@@ -1456,18 +1411,17 @@ async function handleChargeCharge(row) {
} }
// 关闭计费弹窗 // 关闭计费弹窗
async function closeChargeDialog() { function closeChargeDialog() {
// 先关闭 prescriptionlist 内所有已打开的项目字典 popover // 先关闭 prescriptionlist 内所有已打开的项目字典 popover
if (prescriptionRef.value && prescriptionRef.value.closeAllPopovers) { if (prescriptionRef.value && prescriptionRef.value.closeAllPopovers) {
prescriptionRef.value.closeAllPopovers() prescriptionRef.value.closeAllPopovers()
} }
// 等 Vue 完成 popover 可见性更新的 DOM 操作, // 等 Vue 完成 DOM 更新后再关闭弹窗,确保 popover 先消失
// 因为 el-popover 通过 teleport 渲染在 body 上,需要在 dialog 卸载前完成清理 nextTick(() => {
await nextTick() showChargeDialog.value = false
// 清空数据,避免下次打开时使用缓存 chargePatientInfo.value = {}
showChargeDialog.value = false chargeSurgeryInfo.value = {}
chargePatientInfo.value = {} })
chargeSurgeryInfo.value = {}
} }
// 🔧 新增:标志位,用于区分是"打开"还是"刷新" // 🔧 新增:标志位,用于区分是"打开"还是"刷新"
@@ -1496,8 +1450,7 @@ function handleMedicalAdvice(row) {
role: userStore.roles[0], role: userStore.roles[0],
effectiveOrgId : row.effectiveOrgId, effectiveOrgId : row.effectiveOrgId,
orgId: userStore.orgId, orgId: userStore.orgId,
positionId: userStore.orgId, positionId: userStore.orgId
applyId: row.applyId // 手术申请单ID用于过滤关联医嘱
} }
// 🔧 关键修复:如果已有提交的医嘱数据,并且是同一个患者的就诊,则使用保存的数据 // 🔧 关键修复:如果已有提交的医嘱数据,并且是同一个患者的就诊,则使用保存的数据
@@ -1543,8 +1496,6 @@ function handleMedicalAdvice(row) {
const filteredItems = res.data.filter(item => { const filteredItems = res.data.filter(item => {
// 匹配 encounterId // 匹配 encounterId
if (item.encounterId !== row.visitId) return false; if (item.encounterId !== row.visitId) return false;
// 只保留药品类型adviceType=1过滤掉耗材(2)和诊疗项目(3)
if (item.adviceType !== 1) return false;
// 过滤掉名称为空的项目 // 过滤掉名称为空的项目
const medicineName = item.adviceName || item.advice_name; const medicineName = item.adviceName || item.advice_name;
if (!medicineName || medicineName.trim() === '') return false; if (!medicineName || medicineName.trim() === '') return false;
@@ -1797,18 +1748,16 @@ function handleQuoteBilling() {
// 重新拉取计费药品数据 // 重新拉取计费药品数据
if (temporaryPatientInfo.value.visitId) { if (temporaryPatientInfo.value.visitId) {
temporaryMedicalLoading.value = true // 🔧 新增:开始加载 temporaryMedicalLoading.value = true // 🔧 新增:开始加载
getPrescriptionList(temporaryPatientInfo.value.visitId, 6, temporaryPatientInfo.value.operCode).then((res) => { getPrescriptionList(temporaryPatientInfo.value.visitId).then((res) => {
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
// 🔧 修复:先清空旧数据,避免数据累积 // 🔧 修复:先清空旧数据,避免数据累积
temporaryBillingMedicines.value = [] temporaryBillingMedicines.value = []
temporaryAdvices.value = [] temporaryAdvices.value = []
// 只保留药品类型adviceType=1过滤掉耗材(2)和诊疗项目(3) // 🔧 修复:显示所有药品请求数据,不管有没有计费项目
const filteredItems = res.data.filter(item => { const filteredItems = res.data.filter(item => {
// 匹配 encounterId // 匹配 encounterId
if (item.encounterId !== temporaryPatientInfo.value.visitId) return false; if (item.encounterId !== temporaryPatientInfo.value.visitId) return false;
// 只保留药品类型adviceType=1过滤掉耗材(2)和诊疗项目(3)
if (item.adviceType !== 1) return false;
// 过滤掉名称为空的项目 // 过滤掉名称为空的项目
const medicineName = item.adviceName || item.advice_name; const medicineName = item.adviceName || item.advice_name;
return medicineName && medicineName.trim() !== ''; return medicineName && medicineName.trim() !== '';
@@ -2074,7 +2023,7 @@ function handleFindApply() {
getSurgicalScheduleList() getSurgicalScheduleList()
} }
// 获取手术申请列表(用于查找”弹窗) // 获取手术申请列表(用于查找”弹窗)
function getSurgicalScheduleList() { function getSurgicalScheduleList() {
applyLoading.value = true applyLoading.value = true
const params = { ...applyQueryParams } const params = { ...applyQueryParams }
@@ -2083,7 +2032,8 @@ function getSurgicalScheduleList() {
params.applyTimeEnd = params.applyTimeRange[1] params.applyTimeEnd = params.applyTimeRange[1]
delete params.applyTimeRange delete params.applyTimeRange
} }
getSurgeryPage(params).then((res) => { getSurgery(params).then((res) => {
// Check if data is nested under data.data or directly under data
const responseData = res.data.data || res.data const responseData = res.data.data || res.data
applyList.value = responseData.records || [] applyList.value = responseData.records || []
applyTotal.value = responseData.total || 0 applyTotal.value = responseData.total || 0
@@ -2332,4 +2282,4 @@ function getRowClassName({ row, rowIndex }) {
border-bottom: 1px solid #d9ecff !important; border-bottom: 1px solid #d9ecff !important;
} }
</style> </style>

View File

@@ -312,13 +312,6 @@ const getMethodCodeDict = computed(() => {
// 响应式数据 - isSigned 从父组件传入的 prop 初始化 // 响应式数据 - isSigned 从父组件传入的 prop 初始化
const isSigned = ref(props.isSignedProp) const isSigned = ref(props.isSignedProp)
// 🔧 修复 Bug #446: 同步父组件 isSignedProp 的变化到本地 isSigned
// ref(props.isSignedProp) 只在初始化时读取一次,父组件后续更新不会自动同步
watch(() => props.isSignedProp, (newVal) => {
isSigned.value = newVal
})
const signatureTime = ref('') const signatureTime = ref('')
const showSignDialog = ref(false) const showSignDialog = ref(false)
const signPassword = ref('') const signPassword = ref('')

View File

@@ -1,5 +0,0 @@
-- Bug #434 修复:为 op_schedule 表添加 incision_level 字段
-- 手术安排表需要存储切口类型,以便在编辑弹窗中正确回显和保存
ALTER TABLE op_schedule ADD COLUMN IF NOT EXISTS incision_level INT2;
COMMENT ON COLUMN op_schedule.incision_level IS '手术切口等级 1-I级切口 2-II级切口 3-III级切口 4-IV级切口';