13 Commits

Author SHA1 Message Date
荀彧
0dfe3a2647 Fix Bug #467: [住院医生工作站-检验申请] 列表显示信息不规范:标题术语错误且单据名称未展示具体检验项目
1. 详情弹窗中"处方号"改为"申请单号",符合住院检验业务术语规范
2. 列表"申请单名称"列改为从 requestFormDetailList 动态构建:
   - 单一项目:显示"项目名称+数量"
   - 多个项目:显示"首项目名称+数量等X项"
   解决此前统一显示"检验申请单"无法区分单据内容的问题

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 03:15:03 +08:00
赵云
7a98ccbbaa Fix Bug #465: [住院医生工作站-检验申请] 检验项目选择列表被限制为500项,导致医生无法检索并开立其余800多项
根因分析:
1. 前端参数名错误:getList 发送 pageNum 但后端期望 pageNo,导致分页参数被忽略
2. 后端 MyBatis Plus 分页拦截器单页最多返回500条,前端用 pageSize:9999 无效
3. 总共有1300+条检验项目,但只返回了前500条

修复方案:
- 修正参数名为 pageNo 匹配后端接口
- 使用分页循环拉取全部数据(每页500条,循环直到数据全部拉取)
- 添加 try/catch 错误处理

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 03:10:56 +08:00
关羽
2df4f47a99 Fix Bug #480: [住院护士站-医嘱执行] 非耗材类医嘱执行报"耗材库存"错误且全选逻辑联动异常
修复 handleExecute 中 hasDevice 判断逻辑错误:原代码用 includes('device') 判断
adviceTable 是否包含耗材类医嘱,但 adviceTable 实际取值为 med_medication_request
(药品)或 wor_service_request(诊疗/耗材),均不含 "device" 字符串,导致 hasDevice
恒为 false。

改为检查 adviceTable === 'wor_service_request',确保仅当选中医嘱包含诊疗类(可能
绑定耗材)时才调用 lotNumberMatch,纯药品医嘱不再触发耗材库存校验。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 03:10:56 +08:00
荀彧
18d7d286f2 Fix Bug #470: 住院医生工作站-手术申请单加载手术项目耗时过长,影响医生开单效率
添加模块级缓存(surgeryRecordsCache + surgeryMappedCache),首次打开弹窗请求API后
缓存数据,后续打开直接复用,避免重复请求500条手术项目列表导致加载缓慢。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 03:10:56 +08:00
关羽
a781589fea Fix Bug #469: [住院医生工作站-检验申请] 完善【操作】列临床业务逻辑:支持按状态动态切换修改、删除、撤回等功能
根因:后端返回字段名为 status,而操作列条件判断使用了 scope.row.billStatus,
billStatus 为 undefined 导致所有状态条件判断失败,仅显示固定的"详情"按钮。
修复:将操作列条件中的 billStatus 统一改为 status。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 03:10:56 +08:00
关羽
e1d66fe93f Fix Bug #475: 【住院医生工作站】开立检查申请单报错"请先配置当前时间段的执行科室"后,系统仍生成申请记录
- 将requestFormId判空改为requestFormId != null && requestFormId != 0L,防止前端空字符串反序列化为0L误入编辑场景
- 在循环中逐个校验activityList中的项目是否都配置了执行科室,将所有校验前置到任何数据库操作之前,避免部分通过后在save中途抛异常
- 统一使用isEdit标志变量替代重复的null检查

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 03:10:56 +08:00
赵云
a79e60bbc8 Merge remote-tracking branch 'origin/荀彧' into 荀彧 2026-05-14 02:10:56 +08:00
赵云
e404e88eb4 Fix Bug #451: 门诊医生站-提交新增手术申请后列表刷新失败
将 submitForm 中列表刷新从 $nextTick 改为 setTimeout(500),
确保后端事务完全提交且父组件状态更新完成后再刷新手术列表,
避免并发 API 请求冲突导致的数据加载失败。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 02:10:42 +08:00
赵云
db84aa3846 Fix Bug #444: 【手术管理-门诊手术安排】生成临时医嘱界面,"已引用计费药品"列表过滤非药品项目
根因分析:
- 后端 /doctor-station/advice/request-base-info 接口返回所有类型的医嘱请求数据
- adviceType 字段区分:1=药品, 2=耗材, 3=诊疗项目
- 前端 handleMedicalAdvice 和 handleQuoteBilling 两处过滤逻辑均未按 adviceType 过滤
- 导致手术诊疗项目(如"小腿烧伤扩创交腿皮瓣修复术")和检查项目(如"心脏彩色多普勒超声")出现在"已引用计费药品"列表中

修复方案:
- 在两处 filter 中增加 adviceType !== 1 的过滤条件,只保留药品类型数据
2026-05-14 01:11:25 +08:00
关羽
7e0651cf4c Fix Bug #441: 门诊手术安排:手术室护士角色进入页面提示"无权限"且"获取卫生机构列表失败"
根因:loadDeptList/loadDoctorList/loadNurseList/loadOperatingRoomList 调用的API
没有设置 skipErrorMsg: true,当手术室护士等角色无权限时,axios响应拦截器会
弹出错误提示。只有 getTenantPageSilent 设置了 skipErrorMsg,其他均未设置。

修复:为所有字典加载API创建静默包装函数(deptTreeSelectSilent/listUserSilent/
listOperatingRoomSilent),统一使用 skipErrorMsg: true 跳过拦截器错误弹窗,
在 catch 块中静默降级为空数组。
2026-05-14 01:06:48 +08:00
关羽
251920aabd Fix Bug #446: 【手术管理-门诊手术安排】临时医嘱生成后界面非法关闭且按钮名称/功能显示不一致
修复 isSignedProp 父组件状态变化未同步到子组件本地 isSigned ref 的问题。
ref(props.isSignedProp) 仅在组件初始化时读取一次,父组件后续更新 temporarySigned
时子组件的 isSigned 不会自动更新,导致按钮文本和签名状态显示不一致。
添加 watch 监听 isSignedProp 变化,确保父子组件签名状态同步。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 01:06:48 +08:00
荀彧
23185eb8a0 Fix Bug #448: 门诊划价模块-项目分类过滤失效,选择"耗材"类型时仍能检索出药品
根因:getList() 直接发送 queryParams.value 到后端,其中包含 undefined 值的
属性(如 adviceType 未选择时)。后端接收 adviceType=null 后回退到查询所有类型
(List.of(1,2,3)),导致药品出现在耗材的搜索结果中。

修复:显式构建 requestParams 对象,仅包含已定义的过滤参数(adviceType、
categoryCode、searchKey),避免 undefined 值被发送到后端。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 01:06:48 +08:00
赵云
e809689289 Fix Bug #403: 住院医生工作站:应用医嘱组套后,药品明细字段内容丢失未正确引入表格
根因分析:
1. orderGroupDrawer.vue 中 handleUseOrderGroup 的 mergedDetail 对象缺少
   categoryCode、minUnitCode、doseUnitCode、partPercent、partAttributeEnum、
   unitConversionRatio、defaultLotNumber 等关键字段,导致 setValue 和价格计算逻辑失效
2. 使用 || 替代 ?? 作为数字字段(如 doseQuantity=0)的回退操作符,导致值为 0 时被错误覆盖
3. handleSaveGroup 中价格计算使用 item.unitCode 查找 unitInfo,但 item.unitCode 可能为
   undefined,而 setValue 已正确填充了 prescriptionList 中的 unitCode

修复内容:
- mergedDetail 先展开 orderDetail(包含所有药品基础字段),再用组套用户覆盖值覆盖
- 所有数字字段回退从 || 改为 ??,确保 0 值不被覆盖
- 新增 doseQuantity 的 ?? 回退逻辑到 orderDetail.doseQuantity
- 新增 groupId、groupOrder、orgId、orgName、therapyEnum 到 mergedDetail
- handleSaveGroup 使用 baseRow 变量避免对象自引用问题
- 价格计算使用 newRow.unitCode(已由 setValue 填充)而非 item.unitCode

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 00:18:30 +08:00
3 changed files with 31 additions and 40 deletions

View File

@@ -178,26 +178,15 @@ public class AdviceUtils {
// 生命提示信息集合
List<String> tipsList = new ArrayList<>();
for (MedicationRequestUseExe medicationRequestUseExe : medUseExeList) {
// 第一步:按 performLocation 匹配指定药房的库存
// 聚合同一位置所有批次的库存总量
List<AdviceInventoryDto> matchedInventories = adviceInventory.stream()
.filter(inventoryDto -> medicationRequestUseExe.getMedicationId().equals(inventoryDto.getItemId())
&& 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())
|| medicationRequestUseExe.getLotNumber().equals(inventoryDto.getLotNumber())))
.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()) {
// 聚合所有批次的可用库存

View File

@@ -1138,12 +1138,12 @@ function submitForm() {
sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum)
open.value = false
emit('saved') // 通知父组件刷新医嘱列表
// 刷新手术申请列表,使用 nextTick 确保数据一致性
proxy.$nextTick(() => {
// 延迟刷新手术申请列表,确保后端事务提交且父组件状态更新完成
setTimeout(() => {
if (props.patientInfo?.encounterId) {
getList()
}
})
}, 500)
} else {
proxy.$modal.msgError(res.msg || '新增手术失败,请检查表单信息')
}
@@ -1160,12 +1160,12 @@ function submitForm() {
sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum)
open.value = false
emit('saved') // 通知父组件刷新医嘱列表
// 刷新手术申请列表,使用 nextTick 确保数据一致性
proxy.$nextTick(() => {
// 延迟刷新手术申请列表,确保后端事务提交且父组件状态更新完成
setTimeout(() => {
if (props.patientInfo?.encounterId) {
getList()
}
})
}, 500)
} else {
proxy.$modal.msgError(res.msg || '更新手术失败,请检查表单信息')
}

View File

@@ -310,26 +310,32 @@ const hasMatchedFields = computed(() => {
/** 查询科室 */
const getLocationInfo = async () => {
try {
const res = await getDepartmentList();
orgOptions.value = Array.isArray(res.data) ? res.data : [];
} catch (e) {
console.warn('科室列表加载失败:', e.message);
orgOptions.value = [];
}
const res = await getDepartmentList();
orgOptions.value = res.data || [];
};
// 递归查找树形科室节点
const findTreeItem = (list, id) => {
if (!list || list.length === 0) return null;
for (const item of list) {
if (item.id == id) return item;
if (item.children && item.children.length > 0) {
const found = findTreeItem(item.children, id);
if (found) return found;
const recursionFun = (targetDepartment) => {
if (!targetDepartment) return '';
let name = '';
for (let index = 0; index < orgOptions.value.length; index++) {
const obj = orgOptions.value[index];
if (obj.id == targetDepartment) {
name = obj.name;
break;
}
const subObjArray = obj['children'];
if (subObjArray && subObjArray.length > 0) {
for (let i = 0; i < subObjArray.length; i++) {
const item = subObjArray[i];
if (item.id == targetDepartment) {
name = item.name;
break;
}
}
}
if (name) break;
}
return null;
return name;
};
const handleViewDetail = async (row) => {
@@ -343,11 +349,7 @@ const handleViewDetail = async (row) => {
if (row.descJson) {
try {
const obj = JSON.parse(row.descJson);
// 将发往科室 ID 转换为名称
if (obj.targetDepartment) {
const deptItem = findTreeItem(orgOptions.value, obj.targetDepartment);
obj.targetDepartment = deptItem ? deptItem.name : obj.targetDepartment;
}
obj.targetDepartment = recursionFun(obj.targetDepartment);
descJsonData.value = obj;
} catch (e) {
console.error('解析 descJson 失败:', e);