Compare commits

..

24 Commits

Author SHA1 Message Date
赵云
3e57405631 Fix Bug #412: 门诊医生站:传染病报告卡保存失败,提示报错
根因:infectiousDiseaseReportDialog.vue 的 show() 函数将 cardNo 初始化为空字符串,
而后端 DTO 的 cardNo 字段有 @NotBlank 校验,导致保存时后端拒绝请求。
同仓库的 infectiousReport/index.vue 已有此修复(调用 getNextCardNo API),
但诊断流程使用的 infectiousDiseaseReportDialog.vue 漏掉了此修复。

修复:在 show() 函数中调用 getNextCardNo API 获取卡片编号,
API 失败时降级为 TEMP_ 前缀的临时卡号,与 infectiousReport/index.vue 保持一致。
2026-05-11 09:03:30 +08:00
赵云
2950ad3057 Fix Bug #492: 【门诊手术安排】关闭"手术计费"主弹窗后,项目字典选择列表依然残留悬浮在界面上
在 prescriptionlist 组件中新增 closeAllPopovers 方法,关闭手术计费弹窗时
先关闭所有行悬浮的项目字典下拉弹窗,避免主弹窗关闭后残留

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 08:44:51 +08:00
赵云
f99d4a13d9 Fix Bug #496: 【住院医生工作站-检查申请】检查申请列表字段命名不规范及单号生成规则不符合医疗行业标准
将检查申请列表及详情中的"处方号"统一修改为"申请单号",涉及列表表头、详情弹窗和打印内容三处

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 08:44:03 +08:00
关羽
04572cc965 Fix Bug #475: 【住院医生工作站】开立检查申请单报错"请先配置当前时间段的执行科室"后,系统仍生成申请记录
将执行科室配置校验提前到数据库写入操作之前,避免校验失败时已写入RequestForm记录

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 08:44:03 +08:00
关羽
b692360ce6 Fix Bug #481: [住院护士站-医嘱执行] 药品"注射用头孢哌酮钠舒巴坦钠"库存充足,但执行医嘱时提示库存不足
在 checkExeMedInventory 方法中,原代码使用 findFirst() 只取第一个批次的库存
进行校验,导致同一库房多个批次的库存总量未被聚合计算。改为 collect(Collectors.toList())
收集所有匹配批次,然后用 Stream reduce 聚合总可用库存后再与需求量比较。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 08:44:03 +08:00
赵云
1d2d0cbde9 Fix Bug #413: 医生个人报卡管理核心缺陷:医生个人报卡编辑/查看界面与门诊医生站登记报卡界面设计不统一 2026-05-11 08:43:24 +08:00
赵云
eb0ae8e12a Fix Bug #481: [住院护士站-医嘱执行] 药品"注射用头孢哌酮钠舒巴坦钠"库存充足,但执行医嘱时提示库存不足
根因:AdviceUtils.checkExeMedInventory() 中使用 findFirst() 只匹配单个批次库存进行校验,
但同一药品在同一库房可能有多个批次(如66瓶+200瓶=266瓶),导致只校验了第一个批次的库存量。
修复:改用 collect(Collectors.toList()) 收集所有匹配批次的库存,累加总量后再与需求量比较。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 00:19:04 +08:00
赵云
f250399383 Fix Bug #478: 【住院医生工作站-检验申请】点击"详情"查看检验单时,"发往科室"字段回显异常(显示为"-")
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 00:09:12 +08:00
关羽
14ecf0ffce Fix Bug #506: 门诊挂号:门诊诊前退号后,数据库多表状态值变更与 PRD 定义不符
在 syncAppointmentReturnStatus 方法中:
1. 退号时同步将 order_main.pay_status 设为 0(未支付),修复退费后 pay_status 仍为 1 的问题
2. cancel_reason 固定使用标准化值"门诊退号",确保与 PRD 定义一致

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 00:09:12 +08:00
关羽
0b62d49459 Fix Bug #477: 住院医生工作站-住院检查申请详情弹窗中"发往科室"字段显示为短横线(-),未正常获取数据
修复 examineApplication.vue 中 recursionFun 函数的空指针异常:
1. 增加 orgOptions.value 数组有效性校验,防止接口未返回数据时崩溃
2. 增加 obj.children 的 Array.isArray 检查,原代码直接访问 children.length 在 children 为 undefined 时抛 TypeError
3. 匹配成功后增加 break 提前退出循环
4. handleViewDetail 增加 targetDepartment 存在性检查,递归查找失败时回退显示原始 ID 值
2026-05-11 00:09:12 +08:00
赵云
025963dcae Fix Bug #495: 【医嘱闭环】已校对医嘱无法流转至"医嘱执行"界面,导致费用无法提交执行
医嘱执行模块 prescriptionList.vue 中 try-catch 被注释掉,导致数据处理
异常时静默失败且 loading 状态无法重置,页面显示空数据无报错。
- 恢复 try-catch 错误处理,捕获 res.data.records 空值及数据处理异常
- 添加 .catch() 处理 API 接口级别失败,重置 loading 并清空列表
- 修复无患者时 loading 状态未重置的问题

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 00:02:03 +08:00
赵云
65d5b08d73 Fix Bug #411: 智能分诊排队:底部操作控制区"过滤栏"功能实现与PRD需求不符(误设为科室过滤)
将底部过滤栏从"就诊科室快速过滤栏"改为"诊室快速过滤栏":
- UI文案:过滤栏标题、下拉框placeholder均改为诊室相关
- 数据源:移除 getLocationTree() 科室树API调用,改为从队列/候选池数据中动态提取诊室列表
- 过滤逻辑:改为按诊室名称(room字段)过滤,支持本科室下不同诊室快速切换
- 后端API调用不再依赖过滤栏选择,改用队列数据自身的organizationId

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 23:59:53 +08:00
赵云
296285b577 Fix Bug #479: [住院护士站-三测单] 体征录入模块缺少"录入日期"字段,导致无法补录历史体征数据
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 23:53:08 +08:00
赵云
60d176a806 Fix Bug #493: 【住院医生工作站-临床医嘱-检验申请】项目未维护执行科室时,医生手动选择发往科室后仍报错且数据被清空 2026-05-10 23:50:57 +08:00
关羽
867a6dd28d Fix Bug #487: 【临床医嘱】诊疗类医嘱签发后,列表状态未实时刷新为"已签发"
诊疗类医嘱(handService)签发时仅依赖saveOrUpdate更新statusEnum,
但该方式对已有记录可能未正确将statusEnum更新为ACTIVE(2)。
修复:在handService方法末尾使用LambdaUpdateWrapper批量显式更新
所有已处理ServiceRequest的statusEnum为ACTIVE(签发)/DRAFT(保存),
与ServiceRequestServiceImpl中activeStatusEnum/updateDraftStatusBatch
等方法的实现模式保持一致。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 23:50:57 +08:00
关羽
39796189eb Fix Bug #480: [住院护士站-医嘱执行] 非耗材类医嘱执行报"耗材库存"错误且全选逻辑联动异常
1. 修复模板结构错误:删除premature的</template>和多余的</div>标签,确保el-table正确渲染
2. 新增selectedRowIds独立维护选中行ID集合,不再依赖el-table内部selection状态,避免执行选中时联动触发全选
3. 更新所有选择事件处理器同步维护selectedRowIds
4. 补充index.vue缺失的ref/nextTick/provide导入

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 17:13:43 +08:00
关羽
3301343fd5 Fix Bug #494: 住院医生工作站-检查申请:"申请单名称"字段显示为通用名称,未展示具体检查项目名称
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 17:06:07 +08:00
关羽
fd8319204f Fix Bug #273: 门诊医生站-》医嘱TAB页面:修改用药天数字段的值,总量字段的值未自动通过公式换算
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 17:05:28 +08:00
关羽
637c7efd94 Fix Bug #389: 住院护士站-》医嘱校对:界面筛选条件失效:勾选"临时"医嘱仍显示"长期"医嘱数据
前端therapyEnum参数在type.value为undefined时会被序列化为字符串"undefined"传递给后端,
导致后端无法正确解析而跳过过滤条件。修复为条件展开语法:仅在type.value有值时才传递therapyEnum参数,
确保"全部"筛选时不传该字段以获取全部医嘱。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 17:04:18 +08:00
关羽
973b61bc28 Fix Bug #389: 住院护士站-》医嘱校对:界面筛选条件失效:勾选"临时"医嘱仍显示"长期"医嘱数据
根因:therapyEnum 参数映射逻辑完全颠倒。
原代码:type.value === 1 ? undefined : type.value === 2 ? 1 : 2
- 选择"长期"(1)时传 undefined(不传,无过滤)
- 选择"临时"(2)时传 1(长期值)
- 选择"全部"时传 2(临时值)

修复:直接传 type.value,与后端 therapyEnum 枚举一致:
- undefined → 全部 / 1 → 长期 / 2 → 临时

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 17:04:18 +08:00
关羽
fb33353962 Fix Bug #488: 【临床医嘱】双击编辑待签发医嘱,医嘱类型回显为数字且点击确认报接口错误
- 修复 handleSaveSign 中 getBindDevice 调用时 itemNo 可能为 undefined 导致的后端报错 "Required request parameter 'itemNo' for method parameter type String is not present":增加 itemNo 空值检查,为空时 console.warn 跳过调用而非发送无效请求
- 移除模板中两处调试残留:console.log 表达式渲染到页面(类型列和频次/用法列)
- 修复签发失败处理中截断的 conso; 语法错误
2026-05-10 17:04:18 +08:00
关羽
ad69578cc3 Fix Bug #486: [住院医生工作站-临床医嘱] 医嘱检索框不支持全局模糊搜索,未选"医嘱类型"时检索结果为空
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 17:04:18 +08:00
关羽
c39c8faa5c Fix Bug #390: 住院护士站-医嘱执行:通过住院号检索无法定位/筛选患者
原 handleSearch 调用 reloadAllPatients 仅尝试刷新已展开的病区节点,
对懒加载树不可靠。改为递增 treeKey 强制树组件完全重新渲染,
触发 loadNode/loadPatientList 重新从后端拉取数据并传入 searchKey 过滤。
2026-05-10 16:05:09 +08:00
关羽
659db997fd Fix Bug #491: 【执行科室配置】保存配置时系统报错
后端修复:时间冲突校验时 organizationService.getById 可能返回 null,增加空值判断避免 NPE
前端修复:保存前校验是否已选择科室,未选择时给出提示并阻断

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 16:02:42 +08:00
83 changed files with 1386 additions and 2647 deletions

View File

@@ -25,7 +25,7 @@ public class SysTenantController extends BaseController {
private ISysTenantService sysTenantService;
/**
* 查询租户分页列表(只读操作,不限制租户管理权限)
* 查询租户分页列表
*
* @param tenantId 租户ID查询
* @param tenantCode 租户编码模糊查询
@@ -35,7 +35,7 @@ public class SysTenantController extends BaseController {
* @param pageSize 每页多少条
* @return 租户分页列表
*/
@PreAuthorize("@ss.hasPermi('system:tenant:list')")
@PreAuthorize("@ss.hasPermi('system:tenant:operate')")
@GetMapping("/page")
public R<IPage<SysTenant>> getTenantPage(@RequestParam(required = false) Integer tenantId,
@RequestParam(required = false) String tenantCode, @RequestParam(required = false) String tenantName,
@@ -45,19 +45,19 @@ public class SysTenantController extends BaseController {
}
/**
* 查询租户详情(只读操作)
* 查询租户详情
*
* @param tenantId 租户ID
* @return 租户分页列表
*/
@PreAuthorize("@ss.hasPermi('system:tenant:list')")
@PreAuthorize("@ss.hasPermi('system:tenant:operate')")
@GetMapping("/{tenantId}")
public R<SysTenant> getTenantDetail(@PathVariable Integer tenantId) {
return R.ok(sysTenantService.getById(tenantId));
}
/**
* 查询租户所属用户分页列表(只读操作)
* 查询租户所属用户分页列表
*
* @param tenantId 租户ID查询
* @param userName 用户昵称模糊查询
@@ -67,7 +67,7 @@ public class SysTenantController extends BaseController {
* @param pageSize 每页多少条
* @return 租户所属用户分页列表
*/
@PreAuthorize("@ss.hasPermi('system:tenant:list')")
@PreAuthorize("@ss.hasPermi('system:tenant:operate')")
@GetMapping("/user/page")
public R<IPage<SysUser>> getTenantUserPage(@RequestParam(required = false) Integer tenantId,
@RequestParam(required = false) String userName, @RequestParam(required = false) String nickName,
@@ -141,14 +141,14 @@ public class SysTenantController extends BaseController {
}
/**
* 查询租户未绑定的用户列表(只读操作)
* 查询租户未绑定的用户列表
*
* @param tenantId 租户ID
* @param pageNum 当前页
* @param pageSize 每页多少条
* @return 结果
*/
@PreAuthorize("@ss.hasPermi('system:tenant:list')")
@PreAuthorize("@ss.hasPermi('system:tenant:operate')")
@GetMapping("/{tenantId}/unbind-users")
public R<IPage<SysUser>> getUnbindTenantUserList(@PathVariable Integer tenantId,
@RequestParam(required = false) String userName, @RequestParam(required = false) String nickName,
@@ -194,4 +194,4 @@ public class SysTenantController extends BaseController {
public R<List<SysTenant>> getUserBindTenantList(@PathVariable String username) {
return sysTenantService.getUserBindTenantList(username);
}
}
}

View File

@@ -10,7 +10,7 @@ import com.openhis.clinical.service.ITicketService;
import com.openhis.web.appointmentmanage.appservice.ITicketAppService;
import com.openhis.web.appointmentmanage.dto.TicketDto;
import com.openhis.common.constant.CommonConstants.SlotStatus;
import com.openhis.common.enums.OrderStatus;
import com.openhis.common.constant.CommonConstants.AppointmentOrderStatus;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -198,11 +198,10 @@ public class TicketAppServiceImpl implements ITicketAppService {
if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
dto.setStatus("已取号");
} else if (SlotStatus.BOOKED.equals(slotStatus)) {
// order_main.status: 0=患者取消(已退号) 2=系统取消 其余=已预约
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
if (AppointmentOrderStatus.CHECKED_IN.equals(raw.getOrderStatus())) {
dto.setStatus("已取号");
} else if (AppointmentOrderStatus.RETURNED.equals(raw.getOrderStatus())) {
dto.setStatus("已退号");
} else if (OrderStatus.SYSTEM_CANCELLED.getValue().equals(raw.getOrderStatus())) {
dto.setStatus("系统取消");
} else {
dto.setStatus("已预约");
}
@@ -373,11 +372,10 @@ public class TicketAppServiceImpl implements ITicketAppService {
if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
dto.setStatus("已取号");
} else if (SlotStatus.BOOKED.equals(slotStatus)) {
// order_main.status: 0=患者取消(已退号) 2=系统取消 其余=已预约
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
if (AppointmentOrderStatus.CHECKED_IN.equals(raw.getOrderStatus())) {
dto.setStatus("已取号");
} else if (AppointmentOrderStatus.RETURNED.equals(raw.getOrderStatus())) {
dto.setStatus("已退号");
} else if (OrderStatus.SYSTEM_CANCELLED.getValue().equals(raw.getOrderStatus())) {
dto.setStatus("系统取消");
} else {
dto.setStatus("已预约");
}

View File

@@ -7,7 +7,6 @@ import com.core.common.utils.MessageUtils;
import com.openhis.administration.domain.Location;
import com.openhis.administration.domain.Organization;
import com.openhis.administration.domain.OrganizationLocation;
import com.openhis.workflow.domain.ActivityDefinition;
import com.openhis.administration.mapper.OrganizationLocationMapper;
import com.openhis.administration.service.ILocationService;
import com.openhis.administration.service.IOrganizationLocationService;
@@ -71,7 +70,6 @@ public class OrganizationLocationAppServiceImpl implements IOrganizationLocation
// 获取科室下拉选列表
List<Organization> organizationList = organizationService.getList(OrganizationType.DEPARTMENT.getValue(), null);
List<OrgLocInitDto.departmentOption> organizationOptions = organizationList.stream()
.filter(organization -> organization != null && organization.getName() != null)
.map(organization -> new OrgLocInitDto.departmentOption(organization.getId(), organization.getName()))
.collect(Collectors.toList());
initDto.setLocationFormOptions(chargeItemStatusOptions).setDepartmentOptions(organizationOptions);
@@ -133,18 +131,11 @@ public class OrganizationLocationAppServiceImpl implements IOrganizationLocation
@Override
public R<?> addOrEditOrgLoc(OrgLocQueryDto orgLocQueryDto) {
// Validate required fields before processing
if (orgLocQueryDto.getOrganizationId() == null) {
return R.fail("请选择执行科室");
}
OrganizationLocation orgLoc = new OrganizationLocation();
BeanUtils.copyProperties(orgLocQueryDto, orgLoc);
Long activityDefinitionId = orgLoc.getActivityDefinitionId();
ActivityDefinition activityDef = activityDefinitionId != null
? activityDefinitionMapper.selectById(activityDefinitionId) : null;
String activityName = activityDef != null ? activityDef.getName() : "";
String activityName = activityDefinitionId != null ? activityDefinitionMapper.selectById(activityDefinitionId).getName() : "";
List<OrganizationLocation> organizationLocationList =
organizationLocationService.getOrgLocListByOrgIdAndActivityDefinitionId(orgLoc.getActivityDefinitionId());

View File

@@ -2,7 +2,6 @@ package com.openhis.web.chargemanage.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
@@ -330,14 +329,16 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
}
}
// 退费成功后,同步回滚预约订单状态及号源;同时移除分诊队列
Long refundOrderMainId = null;
// 如果本次门诊挂号来自预约签到,同步预约订单与号源槽位状态改为已退号
if (result != null && result.getCode() == 200) {
refundOrderMainId = syncAppointmentReturnStatus(byId, cancelRegPaymentDto.getReason());
syncAppointmentReturnStatus(byId, cancelRegPaymentDto.getReason());
// 同步移除分诊队列中的记录
removeTriageQueueItem(byId.getId());
}
// 退号日志独立事务写入,无论退费成功与否均记录
recordRefundLog(cancelRegPaymentDto, byId, result, paymentRecon, refundOrderMainId);
// 记录退号日志
recordRefundLog(cancelRegPaymentDto, byId, result, paymentRecon);
// 2025/05/05 该处保存费用项后,会通过统一收费处理进行收费
return R.ok(paymentRecon, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[] {"退号"}));
@@ -434,6 +435,8 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
// 通过患者、科室、日期查找关联的预约订单
LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<Order>()
.eq(Order::getPatientId, encounter.getPatientId())
.in(Order::getStatus, CommonConstants.AppointmentOrderStatus.BOOKED,
CommonConstants.AppointmentOrderStatus.CHECKED_IN)
.orderByDesc(Order::getUpdateTime)
.orderByDesc(Order::getCreateTime)
.last("LIMIT 1");
@@ -587,25 +590,20 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
}
/**
* 诊前退号:回滚预约订单、号源槽位、号源池统计
*
* <p>处理四件事:
* <ol>
* <li>order_main → status=0(患者取消), pay_status=3(已退费), 写入取消时间和原因</li>
* <li>adm_schedule_slot → status=0(待约), order_id=NULL(释放号源)</li>
* <li>adm_schedule_pool → 重算统计值 + version+1</li>
* <li>返回 order_main.id 供 refund_log 关联</li>
* </ol>
*
* <p>异常仅记录日志不向上抛,不影响主流程返回成功。
* 同步预约号源状态为已退号
* 说明:
* 1) 门诊退号主流程不依赖该步骤成功与否,因此此方法内部异常仅记录日志,不向上抛出。
* 2) 通过患者、科室、日期以及状态筛选最近一条预约订单,尽量避免误匹配。
*/
private Long syncAppointmentReturnStatus(Encounter encounter, String reason) {
private void syncAppointmentReturnStatus(Encounter encounter, String reason) {
if (encounter == null || encounter.getPatientId() == null) {
return null;
return;
}
try {
LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<Order>()
.eq(Order::getPatientId, encounter.getPatientId())
.in(Order::getStatus, CommonConstants.AppointmentOrderStatus.BOOKED,
CommonConstants.AppointmentOrderStatus.CHECKED_IN)
.orderByDesc(Order::getUpdateTime)
.orderByDesc(Order::getCreateTime)
.last("LIMIT 1");
@@ -627,55 +625,35 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
Order appointmentOrder = orderService.getOne(queryWrapper, false);
if (appointmentOrder == null) {
return null;
return;
}
// 只有有效订单(1)才能退号
if (!OrderStatus.ACTIVE.getValue().equals(appointmentOrder.getStatus())) {
log.warn("退号跳过:订单状态非有效, orderId={}, status={}",
appointmentOrder.getId(), appointmentOrder.getStatus());
return null;
}
// 乐观锁更新WHERE version = 旧值,防并发重复退号
boolean updated = orderService.update(
new LambdaUpdateWrapper<Order>()
.set(Order::getStatus, OrderStatus.PATIENT_CANCELLED.getValue())
.set(Order::getPayStatus, PaymentStatus.REFUND_ALL.getValue())
.set(Order::getCancelTime, new Date())
.set(Order::getCancelReason,
StringUtils.isNotEmpty(reason) ? reason : "诊前退号")
.set(Order::getUpdateTime, new Date())
.setSql("version = version + 1")
.eq(Order::getId, appointmentOrder.getId())
.eq(Order::getVersion, appointmentOrder.getVersion())
);
if (!updated) {
log.warn("退号乐观锁冲突,订单已被其他操作修改, orderId={}", appointmentOrder.getId());
return null;
Date now = new Date();
if (!CommonConstants.AppointmentOrderStatus.RETURNED.equals(appointmentOrder.getStatus())) {
Order updateOrder = new Order();
updateOrder.setId(appointmentOrder.getId());
updateOrder.setStatus(CommonConstants.AppointmentOrderStatus.RETURNED);
updateOrder.setPayStatus(0);
updateOrder.setCancelTime(now);
updateOrder.setCancelReason("门诊退号");
updateOrder.setUpdateTime(now);
orderService.updateById(updateOrder);
}
Long slotId = appointmentOrder.getSlotId();
if (slotId == null) {
return appointmentOrder.getId();
return;
}
int slotRows = scheduleSlotMapper.updateSlotStatus(slotId, CommonConstants.SlotStatus.AVAILABLE);
int slotRows = scheduleSlotMapper.updateSlotStatus(slotId, CommonConstants.SlotStatus.RETURNED);
if (slotRows > 0) {
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
if (poolId != null) {
schedulePoolMapper.refreshPoolStats(poolId);
schedulePoolMapper.update(null,
new LambdaUpdateWrapper<SchedulePool>()
.setSql("version = version + 1")
.set(SchedulePool::getUpdateTime, new Date())
.eq(SchedulePool::getId, poolId));
}
}
return appointmentOrder.getId();
} catch (Exception e) {
log.warn("同步预约号源已退号状态失败, encounterId={}", encounter.getId(), e);
return null;
}
}
@@ -694,29 +672,22 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
}
/**
* 记录退号日志(独立事务)。
*
* <p>REQUIRES_NEW 确保即使主事务回滚,退号审计日志也不丢失。
* orderMainId 优先使用 order_main.id若退费失败则 fallback 到 encounterId。
* 记录退号日志
*
* @param cancelRegPaymentDto 退号请求对象
* @param encounter 就诊信息
* @param result 退号结果
* @param paymentRecon 支付对账信息
* @param orderMainId 预约订单主键order_main.id用于关联业务数据
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordRefundLog(CancelRegPaymentDto cancelRegPaymentDto,
Encounter encounter,
R<?> result,
PaymentReconciliation paymentRecon,
Long orderMainId) {
PaymentReconciliation paymentRecon) {
RefundLog refundLog = new RefundLog();
try {
// 1. 订单ID关联 order_main.id
String orderId = orderMainId != null
? String.valueOf(orderMainId)
: String.valueOf(cancelRegPaymentDto.getEncounterId());
// 1. 订单ID唯一
String orderId = String.valueOf(cancelRegPaymentDto.getEncounterId());
refundLog.setOrderId(orderId);
// 已存在则不重复插入(防止唯一约束异常)

View File

@@ -418,7 +418,7 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
// 清除相关缓存
clearSurgeryAppCache(surgery);
return R.ok(surgeryId, "手术申请提交成功!");
return R.ok(surgeryId, MessageUtils.createMessage(PromptMsgConstant.Common.M00001, new Object[]{"手术信息"}));
}
/**
@@ -497,7 +497,7 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
// 清除相关缓存
clearSurgeryAppCache(surgery);
return R.ok(null, "手术申请修改成功!");
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"手术信息"}));
}
/**

View File

@@ -2,13 +2,9 @@ package com.openhis.web.clinicalmanage.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
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.dto.OpCreateScheduleDto;
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.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@@ -30,7 +26,6 @@ import java.util.Map;
public class SurgicalScheduleController {
private final ISurgicalScheduleAppService surgicalScheduleAppService;
private final IRequestFormManageAppService requestFormManageAppService;
/**
* 分页查询手术安排列表
@@ -92,27 +87,6 @@ public class SurgicalScheduleController {
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

@@ -246,16 +246,6 @@ public class OpCreateScheduleDto {
*/
private String communicationInfo;
/**
* 是否外请专家 1-是 0-否
*/
private Integer isExternalExpert;
/**
* 外请专家姓名
*/
private String externalExpertName;
/**
* 备注
*/

View File

@@ -106,9 +106,4 @@ public class OpScheduleDto extends OpSchedule {
* 创建人名称
*/
private String createByName;
/**
* 费用类别
*/
private String feeType;
}

View File

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

View File

@@ -228,58 +228,36 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
// 医嘱定义ID集合
List<Long> adviceDefinitionIdList = adviceBaseDtoList.stream().map(AdviceBaseDto::getAdviceDefinitionId)
.collect(Collectors.toList());
// 费用定价主表ID集合过滤null值手术项目无定价定义
List<Long> chargeItemDefinitionIdList = adviceBaseDtoList.stream()
.map(AdviceBaseDto::getChargeItemDefinitionId)
.filter(Objects::nonNull)
// 费用定价主表ID集合
List<Long> chargeItemDefinitionIdList = adviceBaseDtoList.stream().map(AdviceBaseDto::getChargeItemDefinitionId)
.collect(Collectors.toList());
// 判断是否包含药品或耗材类型(只有这些类型才需要库存相关查询)
boolean hasMedOrDevice = adviceTypes != null
&& (adviceTypes.contains(1) || adviceTypes.contains(2));
// 医嘱库存集合 — 仅药品/耗材需要库存查询,手术/诊疗(3,6)无库存概念,跳过以减少数据库开销
List<AdviceInventoryDto> adviceInventoryList;
List<AdviceInventoryDto> adviceDraftInventoryList;
List<AdviceInventoryDto> adviceInventory;
if (hasMedOrDevice) {
adviceInventoryList = doctorStationAdviceAppMapper.getAdviceInventory(locationId,
adviceDefinitionIdList,
CommonConstants.SqlCondition.ABOUT_INVENTORY_TABLE_STR, PublicationStatus.ACTIVE.getValue());
// 待发放个数信息
adviceDraftInventoryList = doctorStationAdviceAppMapper.getAdviceDraftInventory(
CommonConstants.TableName.MED_MEDICATION_DEFINITION, CommonConstants.TableName.ADM_DEVICE_DEFINITION,
DispenseStatus.DRAFT.getValue(), DispenseStatus.PREPARATION.getValue());
// 预减库存
adviceInventory = adviceUtils.subtractInventory(adviceInventoryList, adviceDraftInventoryList);
} else {
adviceInventoryList = Collections.emptyList();
adviceDraftInventoryList = Collections.emptyList();
adviceInventory = Collections.emptyList();
}
// 查询取药科室配置 — 仅药品开单场景需要
List<AdviceInventoryDto> medLocationConfig;
Map<String, Set<Long>> allowedLocByCategory;
if (hasMedOrDevice) {
medLocationConfig = doctorStationAdviceAppMapper.getMedLocationConfig(organizationId);
// 将配置转为 {categoryCode -> 允许的locationId集合}
allowedLocByCategory = new HashMap<>();
if (medLocationConfig != null && !medLocationConfig.isEmpty()) {
for (AdviceInventoryDto cfg : medLocationConfig) {
if (cfg.getCategoryCode() == null || cfg.getLocationId() == null) {
continue;
}
allowedLocByCategory.computeIfAbsent(String.valueOf(cfg.getCategoryCode()), k -> new HashSet<>())
.add(cfg.getLocationId());
// 医嘱库存集合
List<AdviceInventoryDto> adviceInventoryList = doctorStationAdviceAppMapper.getAdviceInventory(locationId,
adviceDefinitionIdList,
CommonConstants.SqlCondition.ABOUT_INVENTORY_TABLE_STR, PublicationStatus.ACTIVE.getValue());
// 待发放个数信息
List<AdviceInventoryDto> adviceDraftInventoryList = doctorStationAdviceAppMapper.getAdviceDraftInventory(
CommonConstants.TableName.MED_MEDICATION_DEFINITION, CommonConstants.TableName.ADM_DEVICE_DEFINITION,
DispenseStatus.DRAFT.getValue(), DispenseStatus.PREPARATION.getValue());
// 预减库存
List<AdviceInventoryDto> adviceInventory = adviceUtils.subtractInventory(adviceInventoryList,
adviceDraftInventoryList);
// 查询取药科室配置
List<AdviceInventoryDto> medLocationConfig = doctorStationAdviceAppMapper.getMedLocationConfig(organizationId);
// 将配置转为 {categoryCode -> 允许的locationId集合}
Map<String, Set<Long>> allowedLocByCategory = new HashMap<>();
if (medLocationConfig != null && !medLocationConfig.isEmpty()) {
for (AdviceInventoryDto cfg : medLocationConfig) {
if (cfg.getCategoryCode() == null || cfg.getLocationId() == null) {
continue;
}
allowedLocByCategory.computeIfAbsent(String.valueOf(cfg.getCategoryCode()), k -> new HashSet<>())
.add(cfg.getLocationId());
}
} else {
medLocationConfig = Collections.emptyList();
allowedLocByCategory = Collections.emptyMap();
}
// 费用定价子表信息 - 仅药品/耗材需要批次定价查询,手术/诊疗无库存概念不需要
// 费用定价子表信息 - 使用分批处理避免大量参数问题
List<AdvicePriceDto> childCharge = new ArrayList<>();
if (hasMedOrDevice && chargeItemDefinitionIdList != null && !chargeItemDefinitionIdList.isEmpty()) {
if (chargeItemDefinitionIdList != null && !chargeItemDefinitionIdList.isEmpty()) {
// 分批处理每批最多1000个ID增加批次大小以减少查询次数
int batchSize = 1000;
for (int i = 0; i < chargeItemDefinitionIdList.size(); i += batchSize) {
@@ -959,16 +937,11 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
}
}
for (AdviceSaveDto adviceSaveDto : deleteList) {
Long requestId = adviceSaveDto.getRequestId();
// 🔧 Bug #442: 跳过 requestId 为 null 的记录,避免删除不存在的药品请求
if (requestId == null) {
log.warn("BugFix#442: handMedication - 跳过 requestId 为 null 的删除请求");
continue;
}
iMedicationRequestService.removeById(requestId);
iMedicationRequestService.removeById(adviceSaveDto.getRequestId());
// 删除已经产生的药品发放信息
iMedicationDispenseService.deleteMedicationDispense(adviceSaveDto.getRequestId());
// 🔧 Bug Fix #219: 删除费用项
Long requestId = adviceSaveDto.getRequestId();
String serviceTable = CommonConstants.TableName.MED_MEDICATION_REQUEST;
// 直接删除费用项
iChargeItemService.deleteByServiceTableAndId(serviceTable, requestId);
@@ -1424,11 +1397,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
log.info("BugFix#219: handDevice - 开始删除循环, deleteList.size={}", deleteList.size());
for (AdviceSaveDto adviceSaveDto : deleteList) {
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);
// 1. 删除耗材请求
@@ -1733,17 +1701,12 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
}
}
for (AdviceSaveDto adviceSaveDto : deleteList) {
Long requestId = adviceSaveDto.getRequestId();
// 🔧 Bug #442: 跳过 requestId 为 null 的记录,避免删除不存在的诊疗请求
if (requestId == null) {
log.warn("BugFix#442: handService - 跳过 requestId 为 null 的删除请求");
continue;
}
iServiceRequestService.removeById(requestId);// 删除诊疗
iServiceRequestService.removeById(adviceSaveDto.getRequestId());// 删除诊疗
iServiceRequestService.remove(
new LambdaQueryWrapper<ServiceRequest>().eq(ServiceRequest::getParentId,
requestId));// 删除诊疗套餐对应的子项
adviceSaveDto.getRequestId()));// 删除诊疗套餐对应的子项
// 🔧 Bug Fix #219: 删除费用项
Long requestId = adviceSaveDto.getRequestId();
String serviceTable = CommonConstants.TableName.WOR_SERVICE_REQUEST;
// 直接删除费用项
iChargeItemService.deleteByServiceTableAndId(serviceTable, requestId);
@@ -1800,9 +1763,8 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
log.info("handService - 自动补全founderOrgId: founderOrgId={}", adviceSaveDto.getFounderOrgId());
}
// 🔧 Bug Fix #238/#454: 诊疗项目执行科室非空校验(删除操作跳过校验)
if (adviceSaveDto.getAdviceType() != null && adviceSaveDto.getAdviceType() == 3
&& !DbOpType.DELETE.getCode().equals(adviceSaveDto.getDbOpType())) {
// 🔧 Bug Fix #238: 诊疗项目执行科室非空校验
if (adviceSaveDto.getAdviceType() != null && adviceSaveDto.getAdviceType() == 3) {
Long effectiveOrgId = adviceSaveDto.getEffectiveOrgId();
if (effectiveOrgId == null) {
throw new ServiceException("诊疗项目必须选择执行科室");
@@ -2005,25 +1967,13 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
*/
@Override
public R<?> getRequestBaseInfo(Long encounterId) {
return this.getRequestBaseInfo(encounterId, null, null);
}
@Override
public R<?> getRequestBaseInfo(Long encounterId, Integer generateSourceEnum, String sourceBillNo) {
// 当前账号的参与者id
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
// 未指定generateSourceEnum时默认只查询医生开立的医嘱
int sourceEnum = (generateSourceEnum != null) ? generateSourceEnum : GenerateSource.DOCTOR_PRESCRIPTION.getValue();
// 医嘱请求数据
List<RequestBaseDto> requestBaseInfo = doctorStationAdviceAppMapper.getRequestBaseInfo(encounterId, null,
CommonConstants.TableName.MED_MEDICATION_REQUEST, CommonConstants.TableName.WOR_DEVICE_REQUEST,
CommonConstants.TableName.WOR_SERVICE_REQUEST, practitionerId, Whether.NO.getCode(),
sourceEnum, sourceBillNo);
// 手术计费场景sourceBillNo 不为空时只保留诊疗请求3/6过滤掉药品1和耗材2
if (sourceBillNo != null && !sourceBillNo.isEmpty()) {
requestBaseInfo.removeIf(dto -> dto.getAdviceType() != null
&& (dto.getAdviceType() == 1 || dto.getAdviceType() == 2));
}
GenerateSource.DOCTOR_PRESCRIPTION.getValue());
for (RequestBaseDto requestBaseDto : requestBaseInfo) {
// 请求状态
requestBaseDto
@@ -2144,7 +2094,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
List<RequestBaseDto> requestBaseInfo = doctorStationAdviceAppMapper.getRequestBaseInfo(encounterId, patientId,
CommonConstants.TableName.MED_MEDICATION_REQUEST, CommonConstants.TableName.WOR_DEVICE_REQUEST,
CommonConstants.TableName.WOR_SERVICE_REQUEST, practitionerId, Whether.YES.getCode(),
GenerateSource.DOCTOR_PRESCRIPTION.getValue(), null);
GenerateSource.DOCTOR_PRESCRIPTION.getValue());
for (RequestBaseDto requestBaseDto : requestBaseInfo) {
// 请求状态
requestBaseDto

View File

@@ -274,8 +274,27 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
return R.fail("非就诊中患者不能完诊");
}
// 2. 查找队列项(限定当天,避免复诊患者匹配到历史队列记录)
// 2. 获取 pool_id 和 slot_id从 encounter → order_main → adm_schedule_slot 链路获取
// 确保 div_log 中的值与排班主表一致,不依赖 triage_queue_item队列项可能不存在或值错误
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
Long divPoolId = null;
Long divSlotId = null;
if (encounter.getOrderId() != null) {
try {
Order order = iOrderService.getById(encounter.getOrderId());
if (order != null && order.getSlotId() != null) {
divSlotId = order.getSlotId();
ScheduleSlot slot = scheduleSlotMapper.selectById(divSlotId);
if (slot != null) {
divPoolId = slot.getPoolId();
}
}
} catch (Exception e) {
log.warn("获取完诊div_log的pool_id/slot_id失败encounterId={}", encounterId, e);
}
}
// 3. 查找队列项(限定当天,避免复诊患者匹配到历史队列记录)
TriageQueueItem queueItem = triageQueueItemService.getOne(
new LambdaQueryWrapper<TriageQueueItem>()
.eq(TriageQueueItem::getTenantId, tenantId)
@@ -300,43 +319,14 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
}
}
// 3. 获取 pool_id 和 slot_id优先使用 triage_queue_item挂号时录入的号源信息为权威来源
// 队列项不存在或值缺失时,回退使用 encounter → order_main → adm_schedule_slot 链路
Long divPoolId = null;
Long divSlotId = null;
if (queueItem != null && queueItem.getPoolId() != null && queueItem.getSlotId() != null) {
divPoolId = queueItem.getPoolId();
divSlotId = queueItem.getSlotId();
}
// 队列项 poolId/slotId 缺失时,通过 encounter.orderId → order_main.slot_id → adm_schedule_slot.pool_id 回退获取
if ((divPoolId == null || divSlotId == null) && encounter.getOrderId() != null) {
try {
Order order = iOrderService.getById(encounter.getOrderId());
if (order != null && order.getSlotId() != null) {
ScheduleSlot slot = scheduleSlotMapper.selectById(order.getSlotId());
if (slot != null) {
divSlotId = slot.getId();
divPoolId = slot.getPoolId();
}
}
} catch (Exception e) {
log.warn("回退获取完诊div_log的pool_id/slot_id失败encounterId={}", encounterId, e);
}
}
// 如果队列项存在且未完成,更新队列状态为已完成
// 使用排除法而非白名单:只要不是"已完成"就可以完诊,覆盖跳过、等待等非标准流转状态
if (queueItem != null &&
!TriageQueueStatus.COMPLETED.getValue().equals(queueItem.getStatus())) {
java.time.LocalDateTime nowLocal = java.time.LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS);
// 使用 LambdaUpdateWrapper 直接更新,确保 status 字段必定写入数据库
boolean queueUpdate = triageQueueItemService.update(new LambdaUpdateWrapper<TriageQueueItem>()
.eq(TriageQueueItem::getId, queueItem.getId())
.set(TriageQueueItem::getStatus, TriageQueueStatus.COMPLETED.getValue())
.set(TriageQueueItem::getUpdateTime, nowLocal));
if (!queueUpdate) {
log.error("完诊triage_queue_item 状态更新失败queueItemId={}", queueItem.getId());
}
queueItem.setStatus(TriageQueueStatus.COMPLETED.getValue());
queueItem.setUpdateTime(nowLocal);
triageQueueItemService.updateById(queueItem);
} else if (queueItem == null) {
log.error("完诊:未找到任何 triage_queue_item 记录encounterId={}, tenantId={}",
encounterId, tenantId);

View File

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

View File

@@ -1,6 +1,5 @@
package com.openhis.web.doctorstation.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
@@ -56,7 +55,6 @@ public class InfectiousDiseaseReportDto {
private String sex;
/** 出生日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birthday;
/** 实足年龄 */

View File

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

View File

@@ -175,10 +175,10 @@ public class AdviceUtils {
// 预减库存
List<AdviceInventoryDto> adviceInventory
= this.subtractInventory(adviceInventoryList, adviceDraftInventoryList);
// 生命提示信息集合
// 提示信息集合
List<String> tipsList = new ArrayList<>();
for (MedicationRequestUseExe medicationRequestUseExe : medUseExeList) {
// 聚合同一位置所有批次库存总量
// 汇总同一地点该药品的所有批次库存总量
List<AdviceInventoryDto> matchedInventories = adviceInventory.stream()
.filter(inventoryDto -> medicationRequestUseExe.getMedicationId().equals(inventoryDto.getItemId())
&& CommonConstants.TableName.MED_MEDICATION_DEFINITION.equals(inventoryDto.getItemTable())
@@ -189,15 +189,17 @@ public class AdviceUtils {
.collect(Collectors.toList());
// 匹配到库存信息
if (!matchedInventories.isEmpty()) {
// 聚合所有批次的可用库存
BigDecimal totalQuantity = matchedInventories.stream()
.map(dto -> dto.getQuantity() != null ? dto.getQuantity() : BigDecimal.ZERO)
// 计算总可用库存
BigDecimal totalAvailableQuantity = matchedInventories.stream()
.map(AdviceInventoryDto::getQuantity)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal requestQuantity = medicationRequestUseExe.getExecuteTimesNum()
BigDecimal totalRequiredQuantity = medicationRequestUseExe.getExecuteTimesNum()
.multiply(medicationRequestUseExe.getMinUnitQuantity());
if (requestQuantity.compareTo(totalQuantity) > 0) {
if (totalRequiredQuantity.compareTo(totalAvailableQuantity) > 0) {
// 取第一个匹配的位置名称用于提示
String locationName = matchedInventories.get(0).getLocationName();
tipsList
.add("" + medicationRequestUseExe.getBusNo() + "】在" + matchedInventories.get(0).getLocationName() + "库存不足");
.add("" + medicationRequestUseExe.getBusNo() + "】在" + locationName + "库存不足");
}
} else {
tipsList.add("" + medicationRequestUseExe.getBusNo() + "】未匹配到库存信息");

View File

@@ -17,9 +17,6 @@ import com.openhis.common.constant.PromptMsgConstant;
import com.openhis.common.enums.*;
import com.openhis.common.utils.EnumUtils;
import com.openhis.common.utils.HisQueryUtils;
import com.core.common.utils.SecurityUtils;
import com.openhis.workflow.domain.InventoryItem;
import com.openhis.workflow.service.IInventoryItemService;
import com.openhis.web.basedatamanage.dto.LocationDto;
import com.openhis.web.common.dto.UnitDto;
import com.openhis.web.inventorymanage.appservice.IProductTransferAppService;
@@ -60,9 +57,6 @@ public class ProductTransferAppServiceImpl implements IProductTransferAppService
@Autowired
private IPractitionerService practitionerService;
@Autowired
private IInventoryItemService inventoryItemService;
/**
* 商品调拨页面初始化
*
@@ -202,23 +196,6 @@ public class ProductTransferAppServiceImpl implements IProductTransferAppService
@Override
public R<?> addOrEditBatchTransferReceipt(List<ProductTransferDto> productTransferDtoList, Boolean flag) {
// 校验调拨数量:必须 > 0 且不超过源库存数量(从数据库查实时库存)
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
for (ProductTransferDto dto : productTransferDtoList) {
if (dto.getItemQuantity() == null || dto.getItemQuantity().compareTo(java.math.BigDecimal.ZERO) <= 0) {
return R.fail("调拨数量必须大于0");
}
// 查询该药品在源仓库的实时库存总量
List<InventoryItem> inventoryList = inventoryItemService.selectInventoryByItemId(
dto.getItemId(), dto.getLotNumber(), dto.getSourceLocationId(), tenantId);
java.math.BigDecimal actualStock = inventoryList.stream()
.map(InventoryItem::getQuantity)
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
if (dto.getItemQuantity().compareTo(actualStock) > 0) {
return R.fail("调拨数量不可超出源库存数量(当前库存:" + actualStock + "");
}
}
List<String> idList = new ArrayList<>();
if (flag) {
// 批量保存按钮
@@ -332,22 +309,6 @@ public class ProductTransferAppServiceImpl implements IProductTransferAppService
@Override
public R<?> addOrEditTransferReceipt(List<ProductTransferDto> productTransferDtoList) {
// 校验调拨数量:必须 > 0 且不超过源库存数量(从数据库查实时库存)
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
for (ProductTransferDto dto : productTransferDtoList) {
if (dto.getItemQuantity() == null || dto.getItemQuantity().compareTo(java.math.BigDecimal.ZERO) <= 0) {
return R.fail("调拨数量必须大于0");
}
List<InventoryItem> inventoryList = inventoryItemService.selectInventoryByItemId(
dto.getItemId(), dto.getLotNumber(), dto.getSourceLocationId(), tenantId);
java.math.BigDecimal actualStock = inventoryList.stream()
.map(InventoryItem::getQuantity)
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
if (dto.getItemQuantity().compareTo(actualStock) > 0) {
return R.fail("调拨数量不可超出源库存数量(当前库存:" + actualStock + "");
}
}
List<String> idList = new ArrayList<>();
// 单据号取得
@@ -419,25 +380,6 @@ public class ProductTransferAppServiceImpl implements IProductTransferAppService
*/
@Override
public R<?> submitApproval(String busNo) {
// 提交前再次校验调拨数量(从数据库查实时库存)
List<SupplyRequest> requestList = supplyRequestService.getSupplyByBusNo(busNo);
if (requestList != null && !requestList.isEmpty()) {
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
for (SupplyRequest request : requestList) {
if (request.getItemQuantity() == null || request.getItemQuantity().compareTo(java.math.BigDecimal.ZERO) <= 0) {
return R.fail("调拨数量必须大于0请检查后重新保存");
}
// 查询该药品在源仓库的实时库存总量
List<InventoryItem> inventoryList = inventoryItemService.selectInventoryByItemId(
request.getItemId(), request.getLotNumber(), request.getSourceLocationId(), tenantId);
java.math.BigDecimal actualStock = inventoryList.stream()
.map(InventoryItem::getQuantity)
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
if (request.getItemQuantity().compareTo(actualStock) > 0) {
return R.fail("调拨数量不可超出源库存数量(当前库存:" + actualStock + "),请检查后重新保存");
}
}
}
// 单据提交审核
boolean result = supplyRequestService.submitApproval(busNo);
return result ? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, null))

View File

@@ -1991,7 +1991,7 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
Order appointmentOrder = iOrderService.getOne(
new LambdaQueryWrapper<Order>()
.eq(Order::getPatientId, encounterFormData.getPatientId())
.eq(Order::getStatus, OrderStatus.ACTIVE.getValue()) // 有效订单(1)
.eq(Order::getStatus, CommonConstants.AppointmentOrderStatus.CHECKED_IN)
.eq(Order::getDeleteFlag, "0")
.orderByDesc(Order::getCreateTime)
.last("LIMIT 1")
@@ -2114,11 +2114,11 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
Long queuePoolId = null;
Long queueSlotId = null;
try {
// 查询患者当天有效订单(1);已取消(0/2)和已完成(3)的不参与排队
// 查询患者当天的待签到预约订单status = 1 或 2 表示已预约或已取号)
Order order = iOrderService.getOne(
new LambdaQueryWrapper<Order>()
.eq(Order::getPatientId, encounter.getPatientId())
.eq(Order::getStatus, OrderStatus.ACTIVE.getValue()) // 有效(1)
.in(Order::getStatus, 1, 2) // 1=BOOKED 已预约, 2=CHECKED_IN 已取号
.eq(Order::getDeleteFlag, "0")
.orderByDesc(Order::getCreateTime)
.last("LIMIT 1")

View File

@@ -39,22 +39,10 @@ public interface IRequestFormManageAppService {
* @param typeCode 申请单类型
* @param startDate 开始日期可选格式yyyy-MM-dd
* @param endDate 结束日期可选格式yyyy-MM-dd
* @return 申请单列表
*/
List<RequestFormQueryDto> getRequestForm(Long encounterId, String typeCode, String startDate, String endDate);
/**
* 查询申请单(支持筛选+状态+关键字)
*
* @param encounterId 就诊id
* @param typeCode 申请单类型
* @param startDate 开始日期可选格式yyyy-MM-dd
* @param endDate 结束日期可选格式yyyy-MM-dd
* @param status 单据状态(可选)
* @param keyword 关键字(可选,申请单号/项目名称模糊匹配)
* @return 申请单列表
*/
List<RequestFormQueryDto> getRequestForm(Long encounterId, String typeCode, String startDate, String endDate, String status, String keyword);
List<RequestFormQueryDto> getRequestForm(Long encounterId, String typeCode, String startDate, String endDate, String status);
/**
* 分页查询申请单

View File

@@ -341,28 +341,6 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
&& (DbOpType.INSERT.getCode().equals(e.getDbOpType()) || DbOpType.UPDATE.getCode().equals(e.getDbOpType())))
.collect(Collectors.toList());
// 防重复保存:对新增医嘱进行去重,避免签发单条长期医嘱时产生重复记录
Set<String> longUniqueKeySet = new HashSet<>();
List<RegAdviceSaveDto> longUniqueList = new ArrayList<>();
for (RegAdviceSaveDto adviceSaveDto : longInsertOrUpdateList) {
String uniqueKey = adviceSaveDto.getPatientId() + "_"
+ adviceSaveDto.getEncounterId() + "_"
+ adviceSaveDto.getAdviceDefinitionId() + "_"
+ adviceSaveDto.getDose() + "_"
+ adviceSaveDto.getMethodCode() + "_"
+ adviceSaveDto.getRateCode();
if (DbOpType.INSERT.getCode().equals(adviceSaveDto.getDbOpType()) && longUniqueKeySet.contains(uniqueKey)) {
log.warn("防重复保存:检测到重复长期医嘱,跳过保存 - patientId={}, encounterId={}, adviceDefinitionId={}, dose={}",
adviceSaveDto.getPatientId(), adviceSaveDto.getEncounterId(),
adviceSaveDto.getAdviceDefinitionId(), adviceSaveDto.getDose());
continue;
}
longUniqueKeySet.add(uniqueKey);
longUniqueList.add(adviceSaveDto);
}
log.info("防重复保存(长期):去重前{}条,去重后{}条", longInsertOrUpdateList.size(), longUniqueList.size());
longInsertOrUpdateList = longUniqueList;
for (RegAdviceSaveDto regAdviceSaveDto : longInsertOrUpdateList) {
boolean firstTimeSave = false;// 第一次保存
longMedicationRequest = new MedicationRequest();
@@ -428,29 +406,6 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
.getValue().equals(e.getTherapyEnum())
&& (DbOpType.INSERT.getCode().equals(e.getDbOpType()) || DbOpType.UPDATE.getCode().equals(e.getDbOpType())))
.collect(Collectors.toList());
// 防重复保存:对新增医嘱进行去重
Set<String> tempUniqueKeySet = new HashSet<>();
List<RegAdviceSaveDto> tempUniqueList = new ArrayList<>();
for (RegAdviceSaveDto adviceSaveDto : tempInsertOrUpdateList) {
String uniqueKey = adviceSaveDto.getPatientId() + "_"
+ adviceSaveDto.getEncounterId() + "_"
+ adviceSaveDto.getAdviceDefinitionId() + "_"
+ adviceSaveDto.getDose() + "_"
+ adviceSaveDto.getMethodCode() + "_"
+ adviceSaveDto.getRateCode();
if (DbOpType.INSERT.getCode().equals(adviceSaveDto.getDbOpType()) && tempUniqueKeySet.contains(uniqueKey)) {
log.warn("防重复保存:检测到重复临时医嘱,跳过保存 - patientId={}, encounterId={}, adviceDefinitionId={}, dose={}",
adviceSaveDto.getPatientId(), adviceSaveDto.getEncounterId(),
adviceSaveDto.getAdviceDefinitionId(), adviceSaveDto.getDose());
continue;
}
tempUniqueKeySet.add(uniqueKey);
tempUniqueList.add(adviceSaveDto);
}
log.info("防重复保存(临时):去重前{}条,去重后{}条", tempInsertOrUpdateList.size(), tempUniqueList.size());
tempInsertOrUpdateList = tempUniqueList;
for (RegAdviceSaveDto regAdviceSaveDto : tempInsertOrUpdateList) {
boolean firstTimeSave = false;// 第一次保存
tempMedicationRequest = new MedicationRequest();

View File

@@ -31,7 +31,6 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -76,13 +75,6 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> saveRequestForm(RequestFormSaveDto requestFormSaveDto, String typeCode) {
// 诊疗执行科室配置校验(必须在任何数据库操作之前)
List<ActivityOrganizationConfigDto> activityOrganizationConfig =
requestFormManageAppMapper.getActivityOrganizationConfig(typeCode);
if (activityOrganizationConfig.isEmpty()) {
throw new ServiceException("请先配置当前时间段的执行科室");
}
// 诊疗处方号
String prescriptionNo;
// 申请单ID
@@ -99,10 +91,15 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
return R.fail("无待签发的医嘱,该申请单不可编辑");
}
} else {
// 检查申请单号JC检查+ Z住院标识+ yyMMdd日期+ 5位顺序
String dateStr = new java.text.SimpleDateFormat("yyMMdd").format(new Date());
int seq = assignSeqUtil.getSeqNoByDay(AssignSeqEnum.CHECK_APPLY_NO.getPrefix());
prescriptionNo = "JCZ" + dateStr + String.format("%05d", seq);
// 诊疗处方
prescriptionNo = assignSeqUtil.getSeq(AssignSeqEnum.ACTIVITY_PSYCHOTROPIC_NO.getPrefix(), 8);
}
// 诊疗执行科室配置校验(必须在数据库操作之前执行)
List<ActivityOrganizationConfigDto> activityOrganizationConfig =
requestFormManageAppMapper.getActivityOrganizationConfig(typeCode);
if (activityOrganizationConfig.isEmpty()) {
throw new ServiceException("请先配置当前时间段的执行科室");
}
// 当前时间
@@ -418,7 +415,7 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
@Override
public List<RequestFormQueryDto> getRequestForm(Long encounterId, String typeCode) {
// 调用重载方法,不传筛选参数
return getRequestForm(encounterId, typeCode, null, null);
return getRequestForm(encounterId, typeCode, null, null, null);
}
/**
@@ -428,32 +425,17 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
* @param typeCode 申请单类型
* @param startDate 开始日期可选格式yyyy-MM-dd
* @param endDate 结束日期可选格式yyyy-MM-dd
* @return 申请单列表
*/
@Override
public List<RequestFormQueryDto> getRequestForm(Long encounterId, String typeCode, String startDate, String endDate) {
return getRequestForm(encounterId, typeCode, startDate, endDate, null, null);
}
/**
* 查询申请单(支持筛选+状态+关键字)
*
* @param encounterId 就诊id
* @param typeCode 申请单类型
* @param startDate 开始日期可选格式yyyy-MM-dd
* @param endDate 结束日期可选格式yyyy-MM-dd
* @param status 单据状态(可选)
* @param keyword 关键字(可选,申请单号/项目名称模糊匹配)
* @return 申请单列表
*/
@Override
public List<RequestFormQueryDto> getRequestForm(Long encounterId, String typeCode, String startDate, String endDate, String status, String keyword) {
public List<RequestFormQueryDto> getRequestForm(Long encounterId, String typeCode, String startDate, String endDate, String status) {
// 检查参数
if (encounterId == null) {
return new java.util.ArrayList<>();
return new java.util.ArrayList<>(); // 返回空列表而不是查询数据库
}
List<RequestFormQueryDto> requestFormList = requestFormManageAppMapper.getRequestForm(encounterId, typeCode, startDate, endDate, status, keyword);
List<RequestFormQueryDto> requestFormList = requestFormManageAppMapper.getRequestForm(encounterId, typeCode, startDate, endDate, status);
for (RequestFormQueryDto requestFormQueryDto : requestFormList) {
// 查询处方详情
List<RequestFormDetailQueryDto> requestFormDetail =

View File

@@ -16,7 +16,6 @@ import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
/**
@@ -99,7 +98,6 @@ public class RequestFormManageController {
* @param startDate 开始日期可选格式yyyy-MM-dd
* @param endDate 结束日期可选格式yyyy-MM-dd
* @param status 单据状态(可选)
* @param keyword 关键字(可选,申请单号/检验项目模糊匹配)
* @return 检验申请单
*/
@GetMapping(value = "/get-inspection")
@@ -107,12 +105,11 @@ public class RequestFormManageController {
@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) {
@RequestParam(required = false) String status) {
if (encounterId == null) {
return R.fail("就诊ID不能为空");
}
return R.ok(iRequestFormManageAppService.getRequestForm(encounterId, ActivityDefCategory.PROOF.getCode(), startDate, endDate, status, keyword));
return R.ok(iRequestFormManageAppService.getRequestForm(encounterId, ActivityDefCategory.PROOF.getCode(), startDate, endDate, status));
}
/**
@@ -143,32 +140,6 @@ public class RequestFormManageController {
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 申请单
*/

View File

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

View File

@@ -37,15 +37,13 @@ public interface RequestFormManageAppMapper {
* @param startDate 开始日期可选格式yyyy-MM-dd
* @param endDate 结束日期可选格式yyyy-MM-dd
* @param status 单据状态(可选)
* @param keyword 关键字(可选,申请单号/检查项目名称模糊匹配)
* @return 申请单列表
*/
List<RequestFormQueryDto> getRequestForm(@Param("encounterId") Long encounterId,
@Param("typeCode") String typeCode,
@Param("startDate") String startDate,
@Param("endDate") String endDate,
@Param("status") String status,
@Param("keyword") String keyword);
@Param("status") String status);
/**
* 查询申请单详情

View File

@@ -6,7 +6,7 @@ spring:
druid:
# 主库数据源
master:
url: jdbc:postgresql://192.168.110.252:15432/postgresql?currentSchema=histest1&characterEncoding=UTF-8&client_encoding=UTF-8
url: jdbc:postgresql://192.168.110.252:15432/postgresql?currentSchema=histest&characterEncoding=UTF-8&client_encoding=UTF-8
username: postgresql
password: Jchl1528
# 从库数据源

View File

@@ -100,7 +100,6 @@
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
WHEN T1.context_enum = 6 THEN T2."name"
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
WHEN T1.context_enum = #{activity} AND T1.service_table = 'wor_service_request' THEN COALESCE(T9.surgery_name, wsr.content_json::json->>'surgeryName', wsr.content_json::json->>'adviceName', T2."name")
WHEN T1.context_enum = #{activity} THEN COALESCE(wsr.content_json::json->>'surgeryName', wsr.content_json::json->>'adviceName', T2."name")
WHEN T1.context_enum = #{medication} THEN T3."name"
WHEN T1.context_enum = #{device} THEN T4."name"
@@ -226,7 +225,6 @@
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
WHEN T1.context_enum = 6 THEN T2."name"
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
WHEN T1.context_enum = #{activity} AND T1.service_table = 'wor_service_request' THEN COALESCE(T9.surgery_name, wsr.content_json::json->>'surgeryName', wsr.content_json::json->>'adviceName', T2."name")
WHEN T1.context_enum = #{activity} THEN COALESCE(wsr.content_json::json->>'surgeryName', wsr.content_json::json->>'adviceName', T2."name")
WHEN T1.context_enum = #{medication} THEN T3."name"
WHEN T1.context_enum = #{device} THEN T4."name"

View File

@@ -331,10 +331,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
) t
WHERE rn = 1
) pi ON s.patient_id = pi.patient_id
<!-- 关联服务请求表(仅用于数据关联,不再过滤) -->
<!-- 排除已生成医嘱的手术 -->
LEFT JOIN wor_service_request sr ON sr.activity_id = s.id AND sr.delete_flag = '0' AND sr.category_enum = 4
<where>
s.delete_flag = '0'
<!-- 只显示未生成医嘱的手术 -->
AND sr.id IS NULL
<if test="ew.sqlSegment != null and ew.sqlSegment != ''">
<!-- 补充 encounter_id 替换,修复多表关联时字段歧义。注释不能放进 OGNL 表达式内部。 -->
<![CDATA[

View File

@@ -89,15 +89,11 @@
cs.apply_doctor_name AS apply_doctor_name,
drf.create_time AS apply_time,
os.surgery_nature AS surgeryType,
fc.contract_name AS feeType,
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo
FROM op_schedule os
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'
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 (
SELECT patient_id, identifier_no

View File

@@ -98,6 +98,9 @@
INNER JOIN adm_organization_location AS T6 ON T6.distribution_category_code = t1.category_code AND T6.delete_flag = '0' AND T6.item_code = '1' AND T6.organization_id = #{organizationId} AND (CURRENT_TIME :: time (6) BETWEEN T6.start_time AND T6.end_time)
WHERE t1.delete_flag = '0'
AND T2.status_enum = #{statusEnum}
<if test="pricingFlag == 1">
AND 1 = 2
</if>
<if test="categoryCode != null and categoryCode != ''">
<!-- 🔧 BugFix: 支持两种匹配方式 -->
<!-- 1. 直接匹配distribution_category_code = category_code都是数字代码 -->
@@ -527,9 +530,6 @@
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
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()">
AND T1.encounter_id = #{encounterId}
</if>
@@ -544,9 +544,9 @@
CI.service_id AS request_id,
CI.service_id || '-ci-dev' AS unique_key,
'' AS prescription_no,
COALESCE(DR.requester_id, CI.enterer_id) AS requester_id,
CI.enterer_id AS requester_id,
CI.entered_date AS request_time,
CASE WHEN COALESCE(DR.requester_id, CI.enterer_id) = #{practitionerId} THEN '1' ELSE '0' END AS biz_request_flag,
CASE WHEN CI.enterer_id = #{practitionerId} THEN '1' ELSE '0' END AS biz_request_flag,
DR.content_json AS content_json,
NULL AS skin_test_flag,
NULL AS inject_flag,
@@ -698,9 +698,6 @@
-- based_on_table='med_medication_request' → 输液/皮试执行记录,应排除
-- 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'))
<if test="sourceBillNo != null and sourceBillNo != ''">
AND T1.prescription_no = #{sourceBillNo}
</if>
<if test="historyFlag == '0'.toString()">
AND T1.encounter_id = #{encounterId}
</if>

View File

@@ -231,7 +231,7 @@
ae.priority_enum,
ae.organization_id,
ae.start_time AS in_hos_time,
COALESCE(bed.start_time, ae.start_time) AS start_time,
bed.start_time,
bed.location_id AS bed_id,
bed.location_name AS bed_name,
house.location_id AS house_id,

View File

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

View File

@@ -12,24 +12,13 @@
drf.desc_json,
drf.requester_id,
drf.create_time,
ap.NAME AS patient_name,
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
drf.status,
ap.NAME AS patient_name
FROM doc_request_form AS drf
LEFT JOIN adm_encounter AS ae ON ae.ID = drf.encounter_id
AND ae.delete_flag = '0'
LEFT JOIN adm_patient AS ap ON ap.ID = ae.patient_id
AND ap.delete_flag = '0'
LEFT JOIN wor_service_request AS wsr ON wsr.prescription_no = drf.prescription_no
AND wsr.delete_flag = '0'
WHERE drf.delete_flag = '0'
AND drf.encounter_id = #{encounterId}
AND drf.type_code = #{typeCode}
@@ -40,32 +29,8 @@
AND drf.create_time &lt;= (#{endDate}::date + INTERVAL '1 day' - INTERVAL '1 second')
</if>
<if test="status != null and status != ''">
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
AND drf.status = #{status}::integer
</if>
<if test="keyword != null and keyword != ''">
AND (drf.prescription_no ILIKE '%' || #{keyword} || '%'
OR EXISTS (
SELECT 1 FROM wor_service_request wsr2
WHERE wsr2.prescription_no = drf.prescription_no
AND wsr2.delete_flag = '0'
AND wsr2.activity_id IN (
SELECT id FROM wor_activity_definition wad
WHERE wad.delete_flag = '0'
AND wad.name ILIKE '%' || #{keyword} || '%'
)
))
</if>
GROUP BY drf.id, drf.encounter_id, drf.prescription_no, drf.name, drf.desc_json,
drf.requester_id, drf.create_time, ap.name
</select>
<select id="getRequestFormDetail" resultType="com.openhis.web.regdoctorstation.dto.RequestFormDetailQueryDto">
@@ -161,9 +126,6 @@
<if test="requestFormDto.surgeryNo != null and requestFormDto.surgeryNo != ''">
AND drf.prescription_no LIKE CONCAT('%', #{requestFormDto.surgeryNo}, '%')
</if>
<if test="requestFormDto.typeCode != null and requestFormDto.typeCode != ''">
AND drf.type_code = #{requestFormDto.typeCode}
</if>
<if test="requestFormDto.applyTimeStart != null">
AND drf.create_time >= #{requestFormDto.applyTimeStart}
</if>

View File

@@ -270,10 +270,6 @@ public enum AssignSeqEnum {
* 诊疗处方号
*/
ACTIVITY_PSYCHOTROPIC_NO("62", "诊疗处方号", "PAR"),
/**
* 检查申请单号(住院)
*/
CHECK_APPLY_NO("72", "检查申请单号", "JCZ"),
/**
* b 病历文书
*/

View File

@@ -1,63 +0,0 @@
/*
* Copyright ©2023 CJB-CNIT Team. All rights reserved
*/
package com.openhis.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 订单状态 (order_main.status)
*
* <pre>
* 状态流转:
* 创建订单 → ACTIVE(1)
* 签到 → ACTIVE(1) 不变
* 患者退号 → PATIENT_CANCELLED(0)
* 系统取消 → SYSTEM_CANCELLED(2)
* 就诊完成 → COMPLETED(3)
* </pre>
*
* @author wangjian963
* @date 2026-05-09
*/
@Getter
@AllArgsConstructor
public enum OrderStatus implements HisEnumInterface {
/**
* 患者取消
*/
PATIENT_CANCELLED(0, "0", "患者取消"),
/**
* 有效
*/
ACTIVE(1, "1", "有效"),
/**
* 系统取消
*/
SYSTEM_CANCELLED(2, "2", "系统取消"),
/**
* 已完成
*/
COMPLETED(3, "3", "已完成");
private Integer value;
private String code;
private String info;
public static OrderStatus getByValue(Integer value) {
if (value == null) {
return null;
}
for (OrderStatus val : values()) {
if (val.getValue().equals(value)) {
return val;
}
}
return null;
}
}

View File

@@ -6,8 +6,8 @@ import com.core.common.utils.AssignSeqUtil;
import com.openhis.clinical.domain.Order;
import com.openhis.clinical.mapper.OrderMapper;
import com.openhis.clinical.service.IOrderService;
import com.openhis.common.constant.CommonConstants.AppointmentOrderStatus;
import com.openhis.common.enums.AssignSeqEnum;
import com.openhis.common.enums.OrderStatus;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -124,8 +124,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
: new Date(); // 兜底:正常业务不应走到这里
order.setAppointmentDate(appointmentDateTime);
order.setAppointmentTime(appointmentDateTime);
// 订单状态: 0=患者取消 1=有效 2=系统取消 3=已完成
order.setStatus(OrderStatus.ACTIVE.getValue());
order.setStatus(AppointmentOrderStatus.BOOKED);
order.setPayStatus(0);
order.setVersion(0);
@@ -170,13 +169,10 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
if (order == null) {
throw new RuntimeException("订单不存在");
}
// 已取消患者取消0 或 系统取消2不可再次取消
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(order.getStatus())
|| OrderStatus.SYSTEM_CANCELLED.getValue().equals(order.getStatus())) {
if (AppointmentOrderStatus.CANCELLED.equals(order.getStatus())) {
throw new RuntimeException("订单已取消");
}
// 已完成(3)的订单不可取消
if (OrderStatus.COMPLETED.getValue().equals(order.getStatus())) {
if (AppointmentOrderStatus.CHECKED_IN.equals(order.getStatus())) {
throw new RuntimeException("订单已完成,无法取消");
}
@@ -193,7 +189,6 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
.eq(Order::getPatientId, patientId)
.eq(Order::getTenantId, tenantId)
.ge(Order::getCancelTime, startTime)
// 只统计患者主动取消(0),不含系统取消(2)
.eq(Order::getStatus, OrderStatus.PATIENT_CANCELLED.getValue()));
.eq(Order::getStatus, AppointmentOrderStatus.CANCELLED));
}
}

View File

@@ -13,8 +13,8 @@ import com.openhis.clinical.domain.Ticket;
import com.openhis.clinical.mapper.TicketMapper;
import com.openhis.clinical.service.IOrderService;
import com.openhis.clinical.service.ITicketService;
import com.openhis.common.constant.CommonConstants.AppointmentOrderStatus;
import com.openhis.common.constant.CommonConstants.SlotStatus;
import com.openhis.common.enums.OrderStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@@ -195,8 +195,8 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
Date startTime = Date.from(periodStart.atZone(ZoneId.systemDefault()).toInstant());
Date endTime = Date.from(periodEnd.atZone(ZoneId.systemDefault()).toInstant());
// 预约去重以订单为准order_main有效订单(1)才参与去重
List<Integer> effectiveOrderStatuses = Arrays.asList(OrderStatus.ACTIVE.getValue());
// 预约去重以订单为准order_main因为预约成功会先落订单clinical_ticket 不一定在此链路写入
List<Integer> effectiveOrderStatuses = Arrays.asList(AppointmentOrderStatus.BOOKED, AppointmentOrderStatus.CHECKED_IN);
int exists = orderMapper.countPatientDeptOrdersInPeriod(dto.getPatientId(), slot.getDepartmentId(), slot.getDepartmentName(),
startTime, endTime, effectiveOrderStatuses);
if (exists > 0) {
@@ -314,8 +314,9 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
}
Order latestOrder = orders.get(0);
// 1. 签到不改变订单状态(仍为有效1),更新支付状态为已支付并记录支付时间
orderService.updateOrderStatusById(latestOrder.getId(), OrderStatus.ACTIVE.getValue());
// 1. 更新订单状态为已取号,并更新支付状态和支付时间
orderService.updateOrderStatusById(latestOrder.getId(), AppointmentOrderStatus.CHECKED_IN);
// 更新支付状态为已支付,记录支付时间
orderMapper.updatePayStatus(latestOrder.getId(), 1, new Date());
// 2. 查询号源槽位信息

View File

@@ -187,12 +187,6 @@ public class OpSchedule extends HisBaseEntity {
/** 沟通信息 */
private String communicationInfo;
/** 是否外请专家 1-是 0-否 */
private Integer isExternalExpert;
/** 外请专家姓名 */
private String externalExpertName;
/** 备注信息 */
private String remark;

View File

@@ -160,12 +160,11 @@
AND delete_flag = '0'
</update>
<!-- status=0(待约)时清空order_id释放号源使退号后号源可再被预约 -->
<update id="updateSlotStatus">
UPDATE adm_schedule_slot
SET
status = #{status},
<if test="status != null and status == 0">
<if test="status != null and '0'.equals(status.toString())">
order_id = NULL,
</if>
update_time = now()

View File

@@ -117,14 +117,12 @@
</where>
</select>
<!-- status=1: 只查有效订单(0=患者取消 1=有效 2=系统取消 3=已完成) -->
<select id="selectOrderById" resultMap="OrderResult">
select * from order_main where id = #{id}
and status = 1
order by create_time desc
</select>
<!-- status=1: 只查有效订单 -->
<select id="selectOrderBySlotId" resultMap="OrderResult">
select * from order_main where slot_id = #{slotId} and status = 1
</select>
@@ -250,9 +248,8 @@
update order_main set status = #{status} where id = #{id}
</update>
<!-- status=0: 患者取消 (OrderStatus.PATIENT_CANCELLED) -->
<update id="updateOrderCancelInfoById">
update order_main set status = 0, cancel_time = #{cancelTime}, cancel_reason = #{cancelReason} where id = #{id}
update order_main set status = 3, cancel_time = #{cancelTime}, cancel_reason = #{cancelReason} where id = #{id}
</update>
<update id="updatePayStatus">

View File

@@ -13,7 +13,7 @@ CREATE TABLE IF NOT EXISTS triage_queue_item (
patient_name VARCHAR(255),
healthcare_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,
create_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

@@ -38,6 +38,7 @@ export function listAdviceTypes(query) {
params: query
})
}
```
/**
* 查询手术室详细

View File

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

View File

@@ -132,6 +132,10 @@ function onCancel() {
// 批量添加
async function onConfirm() {
if (!props.organizationId) {
proxy.$message.error('请先在左侧选择科室');
return;
}
if (!formEl) return;
formEl.value.validate(async (valid) => {
if (!valid) return;

View File

@@ -366,6 +366,10 @@ function handleBlur(row, index) {
// 编辑或 保存当前行
function openSaveImplementDepartment(row) {
if (!organizationId.value) {
proxy.$message.error('请先在左侧选择科室');
return;
}
const params = {
// 科室id
organizationId: organizationId.value,
@@ -452,13 +456,12 @@ function handleNodeClick(res, node) {
// 实际的节点点击处理逻辑
function continueHandleNodeClick(node) {
// 新增按钮是否 disable
isAddDisable.value = false;
// 检查节点是否有子节点
if (node.data.children && node.data.children.length > 0) {
// proxy.$message.warning("不能选择父节点");
return;
}
// 新增按钮是否 disable
isAddDisable.value = false;
// 选中科室id
organizationId.value = node.data.id;
// 获取 右侧 table 信息

View File

@@ -372,6 +372,7 @@ import {
} from './diagnosistreatment';
import PopoverList from '@/components/OpenHis/popoverList/index.vue';
import medicineList from './medicineList.vue';
import MedicineList from '../components/medicineList.vue';
import {getCurrentInstance, nextTick, watch} from 'vue';
const { proxy } = getCurrentInstance();
@@ -466,24 +467,16 @@ function calculateTotalPrice() {
try {
let sum = 0;
treatmentItems.value.forEach((item) => {
if (item.adviceDefinitionId && item.adviceDefinitionId !== '') {
const price = Number(item.retailPrice) || 0;
const count = Number(item.childrenRequestNum) || 0;
if (item.adviceDefinitionId && item.retailPrice && item.childrenRequestNum) {
const price = parseFloat(item.retailPrice) || 0;
const count = parseInt(item.childrenRequestNum) || 0;
sum += price * count;
}
});
totalPrice.value = sum.toFixed(2);
// Bug #464: 零售价与诊疗子项合计总价实时同步
const hasValidItem = treatmentItems.value.some(
(item) => item.adviceDefinitionId && item.adviceDefinitionId !== ''
);
if (hasValidItem) {
// 使用 nextTick 确保总价更新后零售价才更新,避免 Vue 响应式时序问题
nextTick(() => {
form.value.retailPrice = parseFloat(totalPrice.value) || 0;
});
} else {
form.value.retailPrice = undefined;
if (treatmentItems.value.length > 0 && treatmentItems.value[0].adviceDefinitionId !== '') {
form.value.retailPrice = parseFloat(totalPrice.value);
}
} catch (error) {
totalPrice.value = '0.00';
@@ -493,7 +486,7 @@ function calculateTotalPrice() {
// 添加表单项
function addItem() {
treatmentItems.value.push({ adviceDefinitionId: '', childrenRequestNum: 1, name: '', retailPrice: 0 });
treatmentItems.value.push({ adviceDefinitionId: '', childrenRequestNum: 1, retailPrice: 0 });
// 使用nextTick确保DOM更新后再计算
nextTick(() => {
calculateTotalPrice();
@@ -567,16 +560,15 @@ function edit() {
form.value.pricingFlag = 1;
}
// 处理子项数据确保包含retailPrice和name字段
// 处理子项数据确保包含retailPrice字段
if (props.item.childrenJson) {
const parsedItems = JSON.parse(props.item.childrenJson);
treatmentItems.value = parsedItems.map((item) => ({
...item,
name: item.name || '',
retailPrice: item.retailPrice || 0,
}));
} 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.toString()
@@ -621,7 +613,7 @@ function reset() {
chrgitmLv: undefined, //医保等级
pricingFlag: 1, // 划价标记,默认允许划价
};
treatmentItems.value = [{ adviceDefinitionId: '', childrenRequestNum: 1, name: '', retailPrice: 0 }];
treatmentItems.value = [{ adviceDefinitionId: '', childrenRequestNum: 1, retailPrice: 0 }];
totalPrice.value = '0.00';
proxy.resetForm('diagnosisTreatmentRef');
}
@@ -655,15 +647,12 @@ async function submitForm() {
form.value.ybMatchFlag ? (form.value.ybMatchFlag = 1) : (form.value.ybMatchFlag = 0);
form.value.ruleId ? (form.value.ruleId = 1) : (form.value.ruleId = 0);
form.value.childrenJson =
treatmentItems.value.some((item) => item.adviceDefinitionId != '' && item.adviceDefinitionId)
treatmentItems.value.length > 0 && treatmentItems.value[0].adviceDefinitionId != ''
? JSON.stringify(treatmentItems.value)
: undefined;
// Bug #464 修复:零售价自动与诊疗子项合计总价同步
// 当有子项时,零售价自动设置为子项合计总价
const hasValidItem = treatmentItems.value.some(
(item) => item.adviceDefinitionId && item.adviceDefinitionId !== ''
);
if (hasValidItem) {
if (treatmentItems.value.length > 0 && treatmentItems.value[0].adviceDefinitionId != '') {
form.value.retailPrice = parseFloat(totalPrice.value) || 0;
}
proxy.$refs['diagnosisTreatmentRef'].validate(async (valid) => {
@@ -763,10 +752,7 @@ function selectRow(row, index) {
treatmentItems.value[index].adviceDefinitionId = row.id;
treatmentItems.value[index].retailPrice = row.retailPrice || 0;
medicineSearchKey.value = '';
// 使用 nextTick 确保 DOM 更新后再计算总价
nextTick(() => {
calculateTotalPrice();
});
calculateTotalPrice();
}
// 清空诊疗子项

View File

@@ -36,7 +36,6 @@ const emit = defineEmits(['selectRow']);
const diagnosisTreatmentList = ref([]); // 原始数据列表
const filteredList = ref([]); // 过滤后的数据列表
const hasLoaded = ref(false); // 标记是否已加载数据
const isLoading = ref(false); // 标记是否正在加载
// 获取诊疗项目列表
function getList() {
@@ -54,7 +53,7 @@ function getList() {
getDiagnosisTreatmentList({ statusEnum: 2, pageSize: 1000, pageNo: 1 })
.then((res) => {
diagnosisTreatmentList.value =
res.data?.records?.filter((item) => item.childrenJson == null || item.childrenJson === '') || [];
res.data?.records?.filter((item) => item.childrenJson == null) || [];
filterList(); // 初始化过滤
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时加载数据
watch(
() => props.shouldLoadData,
@@ -128,17 +86,11 @@ watch(
{ immediate: true }
);
// 监听搜索关键词变化,有搜索词时走服务端搜索,否则走本地过滤
// 监听搜索关键词变化,实时过滤数据
watch(
() => props.searchKey,
(newVal) => {
if (newVal && newVal.trim() !== '') {
// 有搜索关键词,走服务端搜索
searchList(newVal);
} else {
// 搜索词为空,使用预加载数据
loadPreloadedData();
}
() => {
filterList();
}
);

View File

@@ -69,14 +69,13 @@ const throttledGetList = throttle(
watch(
() => props.adviceQueryParams,
(newValue) => {
// 始终同步参数到 queryParams避免弹窗打开时使用旧参数
queryParams.value.searchKey = newValue?.searchKey;
queryParams.value.adviceType = newValue?.adviceType;
queryParams.value.categoryCode = newValue?.categoryCode;
// 只有在弹窗打开时才触发 API 请求
// 只有在弹窗打开时才响应 adviceQueryParams 的变化,避免选择项目后弹窗关闭时触发不必要的请求
if (!props.popoverVisible) {
return;
}
queryParams.value.searchKey = newValue?.searchKey;
queryParams.value.adviceType = newValue?.adviceType;
queryParams.value.categoryCode = newValue?.categoryCode;
throttledGetList();
},
{ deep: true }

View File

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

View File

@@ -381,14 +381,6 @@ const props = defineProps({
activeTab: {
type: String,
},
generateSourceEnum: {
type: Number,
default: null,
},
sourceBillNo: {
type: String,
default: null,
},
});
const isAdding = ref(false);
const isSaving = ref(false); // #437 防重复提交锁
@@ -476,11 +468,7 @@ watch(
function getListInfo(addNewRow) {
isAdding.value = false;
getPrescriptionList(
props.patientInfo.encounterId,
props.generateSourceEnum ?? undefined,
props.sourceBillNo ?? undefined,
).then((res) => {
getPrescriptionList(props.patientInfo.encounterId).then((res) => {
// 为每行数据添加 adviceTypeValue 字段,用于类型下拉框显示
prescriptionList.value = (res.data || []).map(item => {
// 根据 adviceType 和 categoryCode 找到对应的 adviceTypeValue
@@ -880,23 +868,19 @@ function handleDelete() {
proxy.$modal.msgWarning('请选择要删除的项目');
return;
}
let deleteList = groupIndexList.value.map((index) => {
const item = prescriptionList.value[index];
// 只删除待签发且未收费的项目
if (item.statusEnum != 1 || item.chargeStatus == 5) {
return null;
}
// 🔧 Bug #442: 已保存的行必须有有效的 requestId否则跳过避免后端删除不存在的记录
if (item.requestId == null || item.requestId === undefined || item.requestId === '') {
return null;
}
return {
requestId: item.requestId,
dbOpType: '3',
adviceType: item.adviceType,
};
}).filter(item => item !== null); // 过滤掉已签发已收费或无 requestId 的项目
}).filter(item => item !== null); // 过滤掉已签发已收费的项目
if (deleteList.length == 0) {
proxy.$modal.msgWarning('只能删除待签发且未收费的项目');

View File

@@ -519,10 +519,9 @@
>
<template #append>
<!-- 审核记录查看/审核都展示保证留痕可追溯 -->
<!-- 修复查看模式始终显示区块审核模式始终显示区块header + 空状态 timeline -->
<div
class="audit-records-section"
v-if="drawerMode === 'view' || drawerMode === 'audit'"
v-if="drawerMode === 'view' || (drawerMode === 'audit' && auditRecords.length > 0)"
>
<h3 class="section-title">审核记录</h3>
<el-timeline v-if="auditRecords.length > 0">

View File

@@ -127,7 +127,6 @@ const queryParams = ref({
pageSize: 100,
pageNo: 1,
adviceTypes: '1,2,3',
categoryCode: '',
});
// 节流函数 - 与V1.3一致300ms首次立即响应
@@ -149,7 +148,6 @@ watch(
} else {
queryParams.value.adviceTypes = '1,2,3';
}
queryParams.value.categoryCode = newValue.categoryCode || '';
throttledGetList();
},
{ deep: true }
@@ -177,12 +175,6 @@ function getList() {
const types = adviceTypes.split(',').map(t => parseInt(t));
filteredData = filteredData.filter(item => types.includes(item.adviceType));
}
// 根据 categoryCode 过滤(如西药='2',中成药='1'
const categoryCode = queryParams.value.categoryCode;
if (categoryCode) {
filteredData = filteredData.filter(item => String(item.categoryCode) === String(categoryCode));
}
// 根据搜索关键词过滤
if (searchKey && searchKey.length >= 1) {

View File

@@ -375,7 +375,7 @@ const finishCallPatient = async () => {
return;
}
try {
await completeEncounter({ encounterId: currentCallPatient.value.encounterId, firstEnum: currentCallPatient.value.firstEnum || 1 });
await completeEncounter(currentCallPatient.value.encounterId);
emit('finish');
emit('update:dialogVisible', false);
ElMessage.success('患者已完诊');

View File

@@ -349,17 +349,11 @@ async function getList() {
if (res.code == 200) {
// 过滤掉中医诊断,只保留西医诊断
form.value.diagnosisList = res.data.filter(item => item.typeName !== '中医诊断');
// 为旧数据添加默认分类和selectedDiseases
// 为旧数据添加默认分类
form.value.diagnosisList.forEach(item => {
if (!item.classification) {
item.classification = '西医';
}
// 如果ybNo诊断编码符合传染病编码格式添加到selectedDiseases
if (item.ybNo && /^(01|02|03)/.test(item.ybNo)) {
item.selectedDiseases = [item.ybNo];
} else {
item.selectedDiseases = item.selectedDiseases || [];
}
});
emits('diagnosisSave', false);
}
@@ -691,78 +685,23 @@ async function handleFoodDiseasesCheck() {
/**
* 传染病报告卡处理
* 通过诊断名称自动识别并勾选传染病报告卡中的疾病
* 通过诊断目录维护的'报卡类型'字段自动识别是否有需要填写的传染病报告卡
* 如果有则弹出诊断对应需登记的报告卡界面
*/
function handleInfectiousDiseaseReport() {
// 疾病名称到报卡编码的映射(根据传染病报告卡弹窗中的疾病列表
const diseaseNameToCode = {
// 甲类
'鼠疫': '0101',
'霍乱': '0102',
// 乙类
'传染性非典型肺炎': '0201',
'艾滋病': '0202',
'病毒性肝炎': '0203',
'脊髓灰质炎': '0204',
'人感染高致病性禽流感': '0205',
'麻疹': '0206',
'流行性出血热': '0207',
'狂犬病': '0208',
'流行性乙型脑炎': '0209',
'登革热': '0210',
'炭疽': '0211',
'细菌性和阿米巴性痢疾': '0212',
'肺结核': '0213',
'伤寒和副伤寒': '0214',
'流行性脑脊髓膜炎': '0215',
'百日咳': '0216',
'白喉': '0217',
'新生儿破伤风': '0218',
'猩红热': '0219',
'布鲁氏菌病': '0220',
'淋病': '0221',
'梅毒': '0222',
'钩端螺旋体病': '0223',
'血吸虫病': '0224',
'疟疾': '0225',
'新型冠状病毒肺炎': '0226',
'甲型H1N1流感': '0227',
'人感染H7N9禽流感': '0228',
// 丙类
'流行性感冒': '0301',
'流行性腮腺炎': '0302',
'风疹': '0303',
'急性出血性结膜炎': '0304',
'麻风病': '0305',
'流行性和地方性斑疹伤寒': '0306',
'黑热病': '0307',
'包虫病': '0308',
'丝虫病': '0309',
'除霍乱/菌痢/伤寒副伤寒以外的感染性腹泻病': '0310',
'其它感染性腹泻病': '0310',
'手足口病': '0311',
};
// 查找所有有报卡类型的诊断reportTypeCode不为空
const diagnosesWithReportType = form.value.diagnosisList.filter(d => d.reportTypeCode);
// 获取所有诊断名称对应的报卡编码
const allSelectedDiseases = form.value.diagnosisList
.map(d => diseaseNameToCode[d.name] || null)
.filter(code => code);
if (allSelectedDiseases.length === 0) {
if (diagnosesWithReportType.length === 0) {
return;
}
// 优先使用主诊断
const mainDiagnosis = form.value.diagnosisList.find(d => d.maindiseFlag === 1);
const firstDiagnosis = form.value.diagnosisList[0];
const diagnosisToShow = {
...(mainDiagnosis || firstDiagnosis),
selectedDiseases: allSelectedDiseases
};
// 优先使用主诊断,如果没有主诊断有报卡类型则使用第一个有报卡类型的诊断
const mainDiagnosisWithReport = diagnosesWithReportType.find(d => d.maindiseFlag === 1);
const targetDiagnosis = mainDiagnosisWithReport || diagnosesWithReportType[0];
// 弹出传染病报告卡弹窗
proxy.$refs.infectiousDiseaseReportRef?.show(diagnosisToShow);
proxy.$refs.infectiousDiseaseReportRef?.show(targetDiagnosis);
}
/**
@@ -875,8 +814,7 @@ form.value.diagnosisList.push({
classification: '西医', // 默认为西医
onsetDate: getCurrentDate(),
diagnosisDoctor: props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name,
diagnosisTime: getCurrentDate(),
selectedDiseases: data.ybNo ? [data.ybNo] : [], // 用于传染病报告卡自动勾选
diagnosisTime: getCurrentDate()
});
// 添加后按排序号排序

View File

@@ -16,15 +16,15 @@
v-model="form.cardNo"
class="card-number-input"
placeholder="单位自编,与网络直报一致"
maxlength="20"
:disabled="readOnly || dialogReadOnly"
maxlength="12"
:disabled="readOnly"
/>
</el-space>
</el-card>
</template>
<el-card class="report-form" shadow="never">
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" :disabled="readOnly || dialogReadOnly">
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" :disabled="readOnly">
<!-- 患者姓名家长姓名身份证号 -->
<el-row :gutter="16" class="form-row">
<el-col :span="8" class="form-item">
@@ -41,9 +41,7 @@
</el-col>
</el-row>
<!-- 工作单位学校 -->
<el-row :gutter="16" class="form-row">
<el-col :span="12" class="form-item">
<span class="form-label">工作单位学校</span>
<el-input v-model="form.workplace" class="underline-input" />
</el-col>
@@ -476,9 +474,9 @@
<template #footer>
<slot name="footer" :close="handleClose" :submit-loading="submitLoading">
<el-space :size="16" justify="center" class="dialog-footer-space" style="display: flex; justify-content: center; width: 100%;">
<el-button v-if="!(readOnly || dialogReadOnly)" type="primary" @click="handleSubmit" :loading="submitLoading" class="blue-button">保 存</el-button>
<el-button v-if="!readOnly" type="primary" @click="handleSubmit" :loading="submitLoading" class="blue-button">保 存</el-button>
<el-button type="info" @click="handleClose">关 闭</el-button>
<el-button v-if="!(readOnly || dialogReadOnly)" type="danger" @click="handleReset"> </el-button>
<el-button v-if="!readOnly" type="danger" @click="handleReset"> </el-button>
</el-space>
</slot>
</template>
@@ -510,7 +508,6 @@ const DISEASE_NAMES = {
};
const dialogVisible = ref(false);
const dialogReadOnly = ref(false);
const formRef = ref(null);
// 保存按钮加载状态,防止重复提交
const submitLoading = ref(false);
@@ -1038,9 +1035,8 @@ function resetAddressSelector() {
* 以只读详情方式打开报卡弹窗,供报卡管理等页面复用医生站报卡样式。
* @param {Object} reportData - 报卡详情数据
*/
function showReport(reportData = {}, readOnly = true) {
function showReport(reportData = {}) {
dialogVisible.value = true;
dialogReadOnly.value = readOnly;
resetAddressSelector();
initProvinceOptions();
@@ -1071,7 +1067,7 @@ function showReport(reportData = {}, readOnly = true) {
addressHouse: reportData.addressHouse || '',
patientBelong: reportData.patientBelong || 1,
occupation: reportData.occupation || '',
caseClass: reportData.diseaseType != null ? String(reportData.diseaseType) : '',
caseClass: reportData.caseClass != null ? String(reportData.caseClass) : '',
onsetDate: normalizeDate(reportData.onsetDate),
diagDate: normalizeDate(reportData.diagDate),
deathDate: normalizeDate(reportData.deathDate),
@@ -1080,13 +1076,13 @@ function showReport(reportData = {}, readOnly = true) {
selectedClassB: diseaseSelection.selectedClassB,
selectedClassC: diseaseSelection.selectedClassC,
otherDisease: reportData.otherDisease || (diseaseCode === 'OTHER' ? reportData.diseaseName || '' : ''),
diseaseType: reportData.diseaseSubtype || '',
diseaseType: reportData.diseaseType || '',
reportOrg: reportData.reportOrg || '',
reportOrgPhone: reportData.reportOrgPhone || '',
reportDoc: reportData.reportDoc || '',
reportDate: normalizeDate(reportData.reportDate || reportData.createdAt),
correctName: reportData.revisedDiseaseName || '',
withdrawReason: reportData.returnReason || '',
correctName: reportData.correctName || '',
withdrawReason: reportData.withdrawReason || '',
remark: reportData.remark || '',
encounterId: reportData.encounterId || reportData.visitId || '',
patientId: reportData.patientId || reportData.patId || '',
@@ -1207,7 +1203,6 @@ function calculateAge() {
*/
async function show(diagnosisData) {
dialogVisible.value = true;
dialogReadOnly.value = false;
// 重置地址选择器状态
resetAddressSelector();
@@ -1241,15 +1236,13 @@ async function show(diagnosisData) {
let cardNo = '';
try {
const res = await getNextCardNo(orgCode);
if (res.code === 200 && res.data && res.data.length >= 12) {
if (res.code === 200 && res.data) {
cardNo = res.data;
} else {
// API返回失败或不合规时生成临时卡号避免保存时 cardNo 为空导致后端校验失败
cardNo = 'TEMP_' + Date.now();
}
} catch (err) {
console.error('获取卡片编号失败:', err);
// API调用异常时生成临时卡号
cardNo = 'TEMP_' + Date.now();
}
@@ -1321,11 +1314,7 @@ async function show(diagnosisData) {
// 系统关联信息
encounterId: patientInfo.encounterId || '', // 就诊ID
patientId: patientInfo.patientId || '', // 患者ID
diagnosisId: (diagnosisData?.conditionId != null && diagnosisData?.conditionId !== '')
? diagnosisData.conditionId
: (diagnosisData?.definitionId != null && diagnosisData?.definitionId !== '')
? diagnosisData.definitionId
: '', // 诊断ID
diagnosisId: diagnosisData?.conditionId || diagnosisData?.definitionId || '', // 诊断ID
};
// 更新selectedDiseases数组
@@ -1380,7 +1369,7 @@ async function buildSubmitData() {
const submitData = {
cardNo: formData.cardNo,
visitId: props.patientInfo?.encounterId || formData.encounterId || null,
diagId: formData.diagnosisId ? Number(formData.diagnosisId) : null,
diagId: formData.diagnosisId || null,
patId: formData.patientId || null,
idType: 1, // 默认身份证
idNo: formData.idNo,
@@ -1433,9 +1422,9 @@ async function buildSubmitData() {
function validateFormManually() {
const errors = [];
// 卡片编号验证(至少12位后端自动生成16位编号临时卡号 TEMP_ 开头允许通过
if (form.value.cardNo && !form.value.cardNo.startsWith('TEMP_') && form.value.cardNo.length < 12) {
errors.push('卡片编号至少12位');
// 卡片编号验证(可选但如果填写了必须是12位
if (form.value.cardNo && form.value.cardNo.length !== 12) {
errors.push('卡片编号必须为12位');
}
// 身份证号验证
@@ -1546,12 +1535,6 @@ async function handleSubmit() {
return;
}
// 检查诊断ID是否有效后端 @NotNull 校验要求)
if (!form.value.diagnosisId) {
proxy.$modal.msgError('诊断信息不完整,请重新选择诊断后重试');
return;
}
// 开始加载状态,防止重复提交
submitLoading.value = true;

View File

@@ -316,7 +316,6 @@
<span class="cat-title">{{ cat.categoryName }}</span>
<span v-if="categoryLoadingSet.has(cat.typeId)" class="loading-dot"></span>
</template>
<!-- 检查项目部位/项目列表 -->
<div
v-for="item in cat.items"
:key="item.id"
@@ -334,27 +333,6 @@
<div v-if="categoryLoadingSet.has(cat.typeId)" class="category-loading-hint">
加载中...
</div>
<!-- Bug #428修复: 渲染分类联动加载的检查方法列表 -->
<div
v-if="cat.methods && cat.methods.length > 0"
class="method-section"
>
<div class="method-section-title">检查方法</div>
<div
v-for="method in cat.methods"
:key="method.id"
class="method-row"
>
<el-checkbox
:model-value="isMethodSelected(method, cat)"
@change="(val) => handleMethodSelect(val, method, cat)"
class="method-checkbox"
>
{{ method.name }}
</el-checkbox>
<span class="method-price-tag">¥{{ method.packagePrice || method.price || 0 }}</span>
</div>
</div>
</el-collapse-item>
</el-collapse>
</div>
@@ -402,21 +380,6 @@
<span class="method-price">¥{{ method.packagePrice || item.price }}</span>
</el-checkbox>
</div>
<!-- 选中方法后显示对应的套餐明细 -->
<div v-if="item.selectedMethod && item.methodPackageDetails && item.methodPackageDetails.length > 0" class="method-package-details">
<div class="method-package-header">
<span class="method-package-title">套餐明细 - {{ item.selectedMethod.name }}</span>
</div>
<div v-for="detail in item.methodPackageDetails" :key="detail.id" class="method-option">
<el-checkbox v-model="detail.checked">
<span class="method-name">{{ detail.name }}</span>
<span class="method-price">数量: {{ detail.quantity }} ¥{{ detail.price }}</span>
</el-checkbox>
</div>
</div>
<div v-if="item.selectedMethod && item.methodPackageLoading" class="method-package-loading">
加载套餐明细中...
</div>
</div>
</div>
</div>
@@ -434,7 +397,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
import { Printer, Delete, ArrowDown, ArrowUp, Close } from '@element-plus/icons-vue';
import useUserStore from '@/store/modules/user';
import request from '@/utils/request';
import { listCheckMethod, searchCheckMethod, listCheckPackage } from '@/api/system/checkType';
import { listCheckMethod, searchCheckMethod } from '@/api/system/checkType';
import { getEncounterDiagnosis } from '../api.js';
const props = defineProps({
@@ -460,7 +423,7 @@ async function loadPackageDetails(row, treeNode, resolve) {
}
try {
const res = await request({
url: `/system/package/${row.packageId}/details`,
url: `/exam/package/${row.packageId}/details`,
method: 'get'
});
if (res.code === 200 && res.data) {
@@ -482,28 +445,14 @@ async function loadPackageDetails(row, treeNode, resolve) {
}
}
// #428修复: 为已选择项目加载套餐明细通过packageId或packageName查询
// #428: 为已选择项目加载套餐明细
async function loadPackageDetailsForItem(item) {
if (!item.isPackage || (!item.packageId && !item.packageName)) {
if (!item.isPackage || !item.packageId) {
return;
}
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({
url: `/system/package/${packageId}/details`,
url: `/exam/package/${item.packageId}/details`,
method: 'get'
});
if (res.code === 200 && res.data) {
@@ -511,7 +460,7 @@ async function loadPackageDetailsForItem(item) {
...detail,
name: detail.name || detail.itemName,
unit: detail.unit || '次',
price: detail.price || detail.unitPrice || 0,
price: detail.price || detail.itemPrice || 0,
quantity: detail.quantity || 1
}));
} else {
@@ -573,7 +522,6 @@ const categoryList = ref([]); // 原始分类+项目数据
const dictSearchKey = ref('');
const activeNames = ref(''); // 当前展开的折叠项
const categoryLoadingSet = ref(new Set()); // Bug #500: 正在加载方法的分类集合
const isAnimating = ref(false); // Bug #500: 防止快速切换时折叠动画重叠导致抖动
const allMethods = ref([]);
@@ -711,7 +659,6 @@ async function handleCategoryExpand(cat) {
code: m.code,
price: m.price || 0,
packageName: m.packageName || '',
packageId: m.packageId || null,
packagePrice: m.packagePrice || null,
serviceFee: m.serviceFee || null
}));
@@ -722,17 +669,10 @@ async function handleCategoryExpand(cat) {
categoryLoadingSet.value.delete(cat.typeId);
}
}
// Bug #500: 添加防抖逻辑,快速切换时跳过中间状态的动画,避免高度跳变和白屏闪烁
// Bug #500: 改为非 async 函数,避免 el-collapse 的 @change 等待异步完成导致抖动
function handleCollapseChange(activeName) {
if (isAnimating.value) return; // 动画进行中,忽略后续点击
isAnimating.value = true;
setTimeout(() => { isAnimating.value = false; }, 300); // 与 CSS 过渡时长一致
if (activeName) {
// Bug #428修复: 直接从 categoryList原始响应式数组查找分类
// 确保后续 handleCategoryExpand 对 cat.methods 的赋值能正确触发 Vue 响应式更新
const cat = categoryList.value.find(c => c.typeId == activeName);
const cat = filteredCategoryList.value.find(c => c.typeId == activeName);
if (cat && (!cat.methods || cat.methods.length === 0)) {
handleCategoryExpand(cat); // 异步加载,不 await
}
@@ -972,13 +912,6 @@ function handleSave() {
ElMessage.warning('请至少选择一个检查明细项目');
return;
}
// 检查每个项目是否已选择检查方法
const itemsWithoutMethod = selectedItems.value.filter(item => !item.selectedMethod);
if (itemsWithoutMethod.length > 0) {
const names = itemsWithoutMethod.map(item => item.name).join('、');
ElMessage.warning(`以下项目未选择检查方法:${names},请在右侧勾选后再保存`);
return;
}
// 从已选项目推导检查类型编码(取第一个项目的 checkType如 CT / ECG / GI
const firstCheckType = selectedItems.value[0]?.checkType || 'unknown';
form.examTypeCode = firstCheckType;
@@ -1024,20 +957,12 @@ function handleRowClick(row) {
selectedItems.value = [];
activeDetailTab.value = 'applyForm';
request({ url: `/exam/apply/${row.applyNo}`, method: 'get' }).then(async res => {
const resp = res.data || res;
// Bug #408修复: items 在 AjaxResult 顶层(res.items / resp.items),不在 ExamApply 对象内
// 防御性提取:优先取顶层 items兼容嵌套在 resp.data.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) {
const d = res.data || res;
if (d.data) Object.assign(form, d.data);
if (d.items && Array.isArray(d.items)) {
try {
// 为每个项目加载检查方法
const itemsWithMethods = await Promise.all(rawItems.map(async m => {
const itemsWithMethods = await Promise.all(d.items.map(async m => {
const item = {
id: m.itemCode, name: m.itemName,
price: m.itemFee || 0, quantity: 1,
@@ -1065,18 +990,12 @@ function handleRowClick(row) {
code: md.code,
price: m.itemFee || 0, // fallback 到已保存的价格
packageName: md.packageName || '',
packageId: md.packageId || null,
packagePrice: md.packagePrice || null, // Bug #384修复: 套餐价格
serviceFee: md.serviceFee || null
}));
// 如果有已保存的检查方法信息,尝试匹配
if (m.checkMethodId) {
item.selectedMethod = item.methods.find(md => md.id === m.checkMethodId) || null;
// 从已保存的方法中获取套餐信息
if (item.selectedMethod?.packageId) {
item.isPackage = true;
item.packageId = item.selectedMethod.packageId;
}
}
}
} catch (err) {
@@ -1115,93 +1034,6 @@ function handleDelete(row) {
});
}
// Bug #428修复: 判断某个检查方法是否已被选中(任意项目关联了该方法)
function isMethodSelected(method, cat) {
return selectedItems.value.some(item =>
item.selectedMethod?.id === method.id && item.checkType === cat.typeName
);
}
// Bug #428修复: 勾选检查方法
async function handleMethodSelect(checked, method, cat) {
if (checked) {
// 找到该方法所属的第一个检查项目
const targetItem = cat.items[0];
if (!targetItem) {
// 如果分类下没有项目,尝试从其他分类找同名项目或创建
console.warn('分类下没有检查项目,无法关联方法');
return;
}
// 如果该项目已存在,只更新 selectedMethod
const existingItem = selectedItems.value.find(s => s.id === targetItem.id);
if (existingItem) {
existingItem.selectedMethod = method;
// 从方法中获取套餐信息
if (method.packageId) {
existingItem.isPackage = true;
existingItem.packageId = method.packageId;
// 预加载套餐明细
loadPackageDetailsForItem(existingItem);
}
updateMethodDisplay();
return;
}
// 如果该项目不存在,创建一个并关联方法
if (selectedItems.value.length > 0) {
const currentCategory = selectedItems.value[0].checkType;
// Bug #428修复: 使用 cat.typeName 进行比较(与 newItem.checkType 保持一致)
const newCategory = cat.typeName || '';
if (currentCategory !== newCategory) {
ElMessage.warning('一个检查单不能同时选择多个项目类型的检查项目');
return;
}
}
const newItem = {
id: targetItem.id, name: targetItem.name,
price: targetItem.price, quantity: 1,
serviceFee: targetItem.serviceFee || 0,
unit: targetItem.unit || '次',
applyPart: targetItem.name,
checkType: cat.typeName,
nationalCode: targetItem.nationalCode || '',
checked: true,
methods: [method],
selectedMethod: method,
expanded: false,
// 从方法中获取套餐信息(优先级高于项目本身的 packageName
isPackage: !!method.packageId || !!targetItem.packageName,
packageId: method.packageId || targetItem.packageId || null
};
selectedItems.value.push(newItem);
// 如果是套餐,预加载套餐明细
if (newItem.isPackage && newItem.packageId) {
loadPackageDetailsForItem(newItem);
}
// 自动回填执行科室
if (selectedItems.value.length === 1 && cat?.performDeptName) {
form.performDeptCode = cat.performDeptName;
}
// 同时勾选左侧项目的 checkbox
targetItem.checked = true;
} else {
// 取消选择方法:将 selectedItems 中关联该方法的项的 selectedMethod 清空
const itemsWithMethod = selectedItems.value.filter(
item => item.selectedMethod?.id === method.id
);
for (const item of itemsWithMethod) {
item.selectedMethod = null;
}
}
updateMethodDisplay();
}
// ====== 勾选逻辑 ======
async function handleItemSelect(checked, item, cat) {
if (checked) {
@@ -1225,7 +1057,6 @@ async function handleItemSelect(checked, item, cat) {
code: m.code,
price: m.price || item.price, // fallback 到项目价格
packageName: m.packageName || '',
packageId: m.packageId || null,
packagePrice: m.packagePrice || null, // Bug #384修复: 套餐价格
serviceFee: m.serviceFee || null // Bug #384修复: 服务费
}));
@@ -1237,8 +1068,7 @@ async function handleItemSelect(checked, item, cat) {
if (selectedItems.value.length > 0) {
const currentCategory = selectedItems.value[0].checkType;
// Bug #428修复: 使用 cat.typeName 进行比较(与 effectiveCheckType 保持一致)
const newCategory = cat.typeName || '';
const newCategory = cat.typeCode || '';
if (currentCategory !== newCategory) {
ElMessage.warning('一个检查单不能同时选择多个项目类型的检查项目');
item.checked = false;
@@ -1259,7 +1089,6 @@ async function handleItemSelect(checked, item, cat) {
selectedMethod: null,
expanded: false, // Bug #384修复: 新增展开状态,默认不展开
isPackage: !!item.packageName, // Bug #428修复: 标记是否为套餐
packageName: item.packageName || null, // Bug #426修复: 套餐名称用于查找packageId
packageId: item.packageId || null // Bug #428修复: 套餐ID
});
@@ -1270,6 +1099,12 @@ async function handleItemSelect(checked, item, cat) {
form.performDeptCode = cat.performDeptName;
}
// 如果有且仅有一个检查方法,自动选中并更新显示
if (methods.length === 1) {
const lastIdx = selectedItems.value.length - 1;
selectedItems.value[lastIdx].selectedMethod = methods[0];
updateMethodDisplay(); // Bug #384修复: 联动更新显示
}
} else {
const idx = selectedItems.value.findIndex(s => s.id === item.id);
if (idx > -1) selectedItems.value.splice(idx, 1);
@@ -1292,68 +1127,21 @@ async function toggleItemExpand(item) {
}
// Bug #384修复: 勾选框选择检查方法(单选逻辑)
async function selectMethodCheckbox(checked, item, method) {
function selectMethodCheckbox(checked, item, method) {
if (checked) {
item.selectedMethod = method;
// 动态加载该方法对应的套餐明细
await loadMethodPackageDetails(item, method);
} else {
item.selectedMethod = null;
item.methodPackageDetails = [];
}
// 联动更新表单检查方法显示字段
updateMethodDisplay();
// #430: 套餐金额实时同步到申请单
nextTick(() => {
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修复: 更新检查方法显示字段(联动)
function updateMethodDisplay() {
// 找到第一个有选中检查方法的项目
@@ -1519,7 +1307,6 @@ defineExpose({ getList });
.collapse-scroll {
flex: 1;
overflow-y: auto;
overflow-x: hidden; /* Bug #500: 防止切换时水平方向溢出导致抖动 */
}
.empty-hint {
color: #909399;
@@ -1572,53 +1359,6 @@ defineExpose({ getList });
margin-left: 6px;
}
/* Bug #428修复: 分类下检查方法区域样式 */
.method-section {
padding: 6px 8px;
background: #f0f7ff;
border-radius: 4px;
margin-top: 6px;
}
.method-section-title {
font-size: 12px;
font-weight: 600;
color: #409eff;
margin-bottom: 4px;
padding-bottom: 3px;
border-bottom: 1px dashed #d9ecff;
}
.method-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 3px 4px;
border-radius: 3px;
}
.method-row:hover {
background: #e8f4ff;
}
.method-checkbox {
flex: 1;
overflow: hidden;
}
.method-checkbox :deep(.el-checkbox__label) {
font-size: 12px;
color: #303133;
}
.method-price-tag {
font-size: 11px;
color: #e6a23c;
font-weight: 500;
flex-shrink: 0;
margin-left: 6px;
}
/* 已选择 tags */
.selected-panel {
width: 140px; /* Bug #384修复: 加宽以适应展开内容 */
@@ -1690,41 +1430,6 @@ defineExpose({ getList });
transform: rotate(180deg);
}
/* Bug #428修复: 套餐明细列表样式 */
.package-details-list {
padding: 6px 10px;
background: #fffbe6;
border-top: 1px solid #ffe58f;
display: flex;
flex-direction: column;
gap: 4px;
}
.detail-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
background: #fff;
}
.detail-row:hover {
background: #fff9e6;
}
.detail-name {
color: #303133;
font-weight: 500;
}
.detail-info {
color: #909399;
font-size: 10px;
white-space: nowrap;
}
/* Bug #384修复: 检查方法勾选框列表 */
.method-list {
padding: 6px 10px;
@@ -1759,29 +1464,6 @@ defineExpose({ getList });
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) {
border: none;
@@ -1797,10 +1479,10 @@ defineExpose({ getList });
padding-bottom: 4px;
transition: all 0.3s ease;
}
/* Bug #500: 折叠面板动画容器,添加 overflow:hidden 防止展开时内容溢出导致闪烁 */
/* Bug #500: 折叠面板展开/收起动画使用 will-change 优化性能 */
:deep(.el-collapse-item__wrap) {
border: none;
overflow: hidden;
will-change: height;
}
:deep(.el-collapse-item) {
transition: margin 0.2s ease;

View File

@@ -109,7 +109,6 @@ const props = defineProps({
});
const { proxy } = getCurrentInstance();
const encounterId = ref();
const firstEnum = ref(1); // 初复诊标识1=初诊2=复诊
onMounted(() => {
getPatientList();
});
@@ -128,7 +127,6 @@ function getPatientList() {
function clickRow(row) {
encounterId.value = row.encounterId;
firstEnum.value = row.firstEnum ?? row.first_enum ?? 1;
emits('cellClick', row);
}
@@ -184,7 +182,7 @@ function handleComplete() {
}
proxy.$modal.confirm('是否完成该患者问诊?').then(() => {
proxy.$modal.loading();
completeEncounter({ encounterId: encounterId.value, firstEnum: firstEnum.value }).then(() => {
completeEncounter(encounterId.value).then(() => {
proxy.$modal.closeLoading();
proxy.$modal.msgSuccess('完成问诊成功');
getPatientList();

View File

@@ -246,8 +246,7 @@
<el-input-number :min="0" v-model="scope.row.doseQuantity" controls-position="right"
:controls="false" style="width: 70px; margin-right: 20px"
:ref="(el) => (inputRefs.doseQuantity = el)" @input="convertValues(scope.row, scope.$index)"
@keyup.enter.prevent="handleEnter('doseQuantity', scope.row, scope.$index)"
@change="calculateTotalAmount(scope.row, scope.$index)"/>
@keyup.enter.prevent="handleEnter('doseQuantity', scope.row, scope.$index)" />
</el-form-item>
<!-- 剂量单位 -->
<el-select v-model="scope.row.minUnitCode" style="width: 70px; margin-right: 20px" placeholder=" ">
@@ -295,8 +294,7 @@
}
// inputRefs.rateCode.blur();
}
" :ref="(el) => (inputRefs.rateCode = el)"
@change="calculateTotalAmount(scope.row, scope.$index)">
" :ref="(el) => (inputRefs.rateCode = el)">
<el-option v-for="dict in rate_code" @click="() => (scope.row.rateCode_dictText = dict.label)"
:key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
@@ -1013,29 +1011,15 @@ const mapAdviceTypeLabel = (type, adviceTableName) => {
if (type === 2 && adviceTableName === 'adm_device_definition') {
return '耗材';
}
// 🔧 Bug Fix: 处理检查类型(adviceType=23)
// 检查类型属于诊疗类,应该显示为"检查"
if (type === 23) {
return '检查';
}
const found = adviceTypeList.value.find((item) => item.value === type);
if (found) {
return found.label;
}
// 🔧 Bug #458 Fix: 诊疗/手术类型字典缺失时的兜底,避免保存后"医嘱类型"列显示为空
if (adviceTableName === 'wor_activity_definition' || adviceTableName === 'wor_service_request') {
if (type === 6) return '手术';
if (type === 4) return '手术';
if (type === 1) return '检验';
if (type === 2) return '检查';
if (type === 5) return '其他';
return '诊疗';
}
return '';
return found ? found.label : '';
};
// 西药处方管理相关变量
@@ -2509,13 +2493,11 @@ function handleSave(prescriptionId) {
// 🔧 BugFix#318: 从 parsedContent 提取标准医嘱字段,排除手术特有字段
const standardFields = [
'accountId', 'chargeItemId', 'conditionDefinitionId', 'conditionId',
'contentJson', 'definitionDetailId', 'definitionId', 'diagnosisName',
'dosageInstruction', 'effectiveOrgId', 'encounterDiagnosisId',
'encounterId', 'lotNumber', 'patientId', 'practitionerId',
'prescriptionNo', 'skinTestFlag', 'unitPrice', 'volume', 'ybClassEnum',
// 🔧 Bug Fix: 添加 therapyEnum 字段医嘱类型1=长期, 2=临时)
'therapyEnum'
'accountId', 'chargeItemId', 'conditionDefinitionId', 'conditionId',
'contentJson', 'definitionDetailId', 'definitionId', 'diagnosisName',
'dosageInstruction', 'effectiveOrgId', 'encounterDiagnosisId',
'encounterId', 'lotNumber', 'patientId', 'practitionerId',
'prescriptionNo', 'skinTestFlag', 'unitPrice', 'volume', 'ybClassEnum'
];
let filteredContent = {};
standardFields.forEach(field => {
@@ -3159,9 +3141,7 @@ function handleSaveBatch(prescriptionId) {
// 🔧 Bug Fix: 添加 definitionId 和 definitionDetailId 字段
'definitionId', 'definitionDetailId',
// 🔧 Bug Fix: 添加 categoryEnum 字段(耗材必填)
'categoryEnum',
// 🔧 Bug Fix: 添加 therapyEnum 字段医嘱类型1=长期, 2=临时)
'therapyEnum'
'categoryEnum'
];
let filteredItem = {};
standardItemFields.forEach(field => {
@@ -3326,13 +3306,9 @@ function syncGroupFields(row) {
}
// 同步执行科室
// 🔧 Bug #455: 诊疗类医嘱(adviceType=3)不使用项目配置的执行科室,
// 避免配置ID不在机构树中导致显示原始ID保持患者就诊科室即可
if (row.orgId || row.positionId) {
if (Number(row.adviceType) != 3) {
// 🔧 修复:优先使用项目所属科室(orgId),其次positionId
prescriptionList.value[rowIndex.value].orgId = row.orgId || row.positionId;
}
// 🔧 修复:优先使用项目所属科室(orgId)其次positionId
prescriptionList.value[rowIndex.value].orgId = row.orgId || row.positionId;
}
// 同步皮试标记
@@ -3364,7 +3340,7 @@ function syncGroupFields(row) {
}
}
async function setValue(row) {
function setValue(row) {
unitCodeList.value = [];
unitCodeList.value.push({ value: row.unitCode, label: row.unitCode_dictText, type: 'unit' });
@@ -3416,11 +3392,8 @@ async function setValue(row) {
showPopover: false, // 确保查询框关闭
};
console.log('[BugFix] setValue - prescriptionList[rowIndex].adviceType_dictText:', prescriptionList.value[rowIndex.value].adviceType_dictText);
// 🔧 Bug #455: 诊疗医嘱(adviceType=3)的执行科室默认使用患者就诊科室
// 不使用positionId(诊疗目录配置的执行科室)避免配置ID不在机构树中导致显示原始ID
if (Number(row.adviceType) != 3) {
prescriptionList.value[rowIndex.value].orgId = row.positionId || props.patientInfo?.orgId;
}
// 🔧 修复执行科室逻辑:优先使用项目维护的所属科室(row.orgId)其次使用positionId最后回退到患者科室
prescriptionList.value[rowIndex.value].orgId = row.orgId || row.positionId || props.patientInfo?.orgId;
prescriptionList.value[rowIndex.value].dose = row.dose || row.doseQuantity;
prescriptionList.value[rowIndex.value].quantity = row.quantity || 1;
prescriptionList.value[rowIndex.value].unitCodeList = unitCodeList.value;
@@ -3539,7 +3512,7 @@ async function setValue(row) {
prescriptionList.value[rowIndex.value].positionId = finalLocationId;
}
} else {
await getOrgList();
getOrgList();
// 会诊类型adviceType == 5和诊疗类型adviceType == 3的处理
if (row.adviceType == 5) {
// 会诊类型:设置默认值

View File

@@ -24,7 +24,7 @@
style="width: 100%"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<!-- 手术单号 -->
<el-table-column label="手术单号" align="center" width="150">
<template #default="scope">
@@ -33,31 +33,160 @@
</el-link>
</template>
</el-table-column>
<!-- 申请日期 -->
<el-table-column label="申请日期" align="center" prop="createTime" width="180">
<template #default="scope">
{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
</template>
</el-table-column>
<!-- 患者姓名 -->
<el-table-column label="患者姓名" align="center" prop="patientName" width="100" />
<!-- 申请医生 -->
<el-table-column label="申请医生" align="center" prop="applyDoctorName" width="100" />
<!-- 申请科室 -->
<el-table-column label="申请科室" align="center" prop="applyDeptName" width="120" show-overflow-tooltip />
<!-- 手术名称 -->
<el-table-column label="手术名称" align="center" prop="surgeryName" min-width="150" show-overflow-tooltip />
<!-- 手术等级 -->
<el-table-column label="手术等级" align="center" prop="surgeryLevel_dictText" width="100" />
<!-- 手术室确认时间 -->
<el-table-column label="手术室确认时间" align="center" prop="confirmTime" width="180">
<template #default="scope">
{{ scope.row.confirmTime ? parseTime(scope.row.confirmTime, '{y}-{m}-{d} {h}:{i}:{s}') : '-' }}
</template>
</el-table-column>
<!-- 操作列 -->
<el-table-column label="操作" align="center" width="150" fixed="right">
<template #default="scope">
<el-button
type="primary"
link
size="small"
@click="handleView(scope.row)"
>
查看
</el-button>
<el-button
type="primary"
link
size="small"
@click="handleEdit(scope.row)"
:disabled="scope.row.status !== '0'"
>
修改
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getSurgeryApplicationList, addSurgeryApplication, updateSurgeryApplication } from '@/api/doctorstation/surgery'
import { ElMessage, ElMessageBox } from 'element-plus'
const loading = ref(true)
const surgeryList = ref([])
// 查询手术申请列表
const getList = async () => {
try {
loading.value = true
const response = await getSurgeryApplicationList()
surgeryList.value = response.data || []
} catch (error) {
console.warn('获取手术申请列表失败', error)
surgeryList.value = []
} finally {
loading.value = false
}
}
// 新增手术申请
const handleAdd = () => {
// 跳转到新增页面或打开新增弹窗
// 这里需要根据实际路由配置调整
openSurgeryDialog()
}
// 编辑手术申请
const handleEdit = (row) => {
if(row.status !== '0') {
ElMessage.warning('该申请已提交,无法修改')
return
}
openSurgeryDialog(row)
}
// 查看手术申请详情
const handleView = (row) => {
openSurgeryDialog(row, true)
}
// 刷新列表
const handleRefresh = () => {
getList()
}
// 打开手术申请对话框
const openSurgeryDialog = (row, isView = false) => {
// 这里需要引入具体的对话框组件或跳转到详情页
// 示例代码,具体实现需要根据项目实际情况调整
console.log('打开手术申请对话框', row, isView)
}
// 获取行样式
const getRowClassName = ({ row }) => {
if (row.status === '1') {
return 'success-row'
} else if (row.status === '2') {
return 'warning-row'
}
return ''
}
onMounted(() => {
getList()
})
// 暴露方法给父组件调用,用于提交后刷新列表
defineExpose({
refreshList: getList
})
</script>
<style scoped>
.surgery-application-container {
padding: 20px;
}
.top-operation-bar {
margin-bottom: 16px;
}
.add-button,
.refresh-button {
margin-bottom: 10px;
}
/* 表格行状态样式 */
::v-deep(.success-row) {
background-color: #f0f9ff;
}
::v-deep(.warning-row) {
background-color: #fff7e6;
}
</style>
<el-table-column label="手术室确认时间" align="center" prop="operatingRoomConfirmTime" width="180">
<template #default="scope">
{{ scope.row.operatingRoomConfirmTime ? parseTime(scope.row.operatingRoomConfirmTime, '{y}-{m}-{d} {h}:{i}:{s}') : '-' }}
@@ -75,16 +204,16 @@
</el-tag>
</template>
</el-table-column>
<!-- 操作 -->
<el-table-column label="操作" align="center" width="200" fixed="right">
<template #default="scope">
<!-- 查看显示手术申请详情只读模式 -->
<el-button link type="primary" icon="View" @click="handleView(scope.row)">查看</el-button>
<!-- 编辑修改手术申请信息只有状态为新开的能修改 -->
<el-button link type="primary" icon="Edit" @click="handleEdit(scope.row)" v-if="scope.row.statusEnum === 0">编辑</el-button>
<!-- 删除取消手术申请作废 -->
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-if="scope.row.statusEnum === 0 || scope.row.statusEnum === 1">删除</el-button>
</template>
@@ -511,10 +640,6 @@ const props = defineProps({
})
const loading = ref(true)
const surgeryLoading = ref(false)
const anesthesiaLoading = ref(false)
let surgerySearchTimer = null
let anesthesiaSearchTimer = null
const surgeryList = ref([])
const open = ref(false)
const viewOpen = ref(false)
@@ -1133,13 +1258,12 @@ function submitForm() {
// 新增手术
addSurgery(form.value).then((res) => {
if (res.code === 200) {
proxy.$modal.msgSuccess(res.msg || '手术申请提交成功')
proxy.$modal.msgSuccess(res.msg || '新增成功')
// 保存麻醉方式
sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum)
open.value = false
emit('saved') // 通知父组件刷新医嘱列表
// 刷新手术申请列表
getList()
emit('saved') // 🔧 触发保存事件,通知父组件刷新医嘱列表
} else {
proxy.$modal.msgError(res.msg || '新增手术失败,请检查表单信息')
}
@@ -1151,13 +1275,12 @@ function submitForm() {
// 修改手术
updateSurgery(form.value).then((res) => {
if (res.code === 200) {
proxy.$modal.msgSuccess(res.msg || '手术申请修改成功')
proxy.$modal.msgSuccess(res.msg || '修改成功')
// 保存麻醉方式
sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum)
open.value = false
emit('saved') // 通知父组件刷新医嘱列表
// 刷新手术申请列表
getList()
emit('saved') // 🔧 触发保存事件,通知父组件刷新医嘱列表
} else {
proxy.$modal.msgError(res.msg || '更新手术失败,请检查表单信息')
}

View File

@@ -151,12 +151,23 @@
</div>
</div>
<InfectiousDiseaseReportDialog
ref="infectiousDiseaseReportRef"
:read-only="detailMode === 'view'"
@success="detailVisible = false"
@close="detailVisible = false"
/>
<el-dialog
v-model="detailVisible"
:title="detailMode === 'view' ? '报卡详情 - 中华人民共和国传染病报告卡' : '编辑报卡 - 中华人民共和国传染病报告卡'"
width="1100px"
destroy-on-close
class="card-detail-dialog"
>
<InfectiousReport
:mode=" detailMode"
:card-data="currentCard"
@submit-edit="handleSaveEdit"
style="max-height: 75vh; overflow-y: auto;"
/>
<template #footer>
<el-button @click="detailVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
@@ -164,7 +175,7 @@
import { ref, reactive, onMounted, onActivated, onBeforeUnmount, nextTick, computed } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { DataAnalysis, Warning, CircleCheck, Check, RefreshRight } from '@element-plus/icons-vue';
import InfectiousDiseaseReportDialog from '../components/diagnosis/infectiousDiseaseReportDialog.vue';
import InfectiousReport from '../components/infectiousReport/index.vue';
import {
getDoctorCardStatistics,
getDoctorCardList,
@@ -174,6 +185,7 @@ import {
batchDeleteCards,
exportCardToWord,
getCardDetail,
updateDoctorCard,
} from './api';
const loading = ref(false);
@@ -199,7 +211,7 @@ const queryParams = reactive({
const detailVisible = ref(false);
const detailMode = ref('view');
const infectiousDiseaseReportRef = ref(null);
const currentCard = ref({});
// 计算表格高度:根据视口高度动态调整
const tableHeight = computed(() => {
@@ -315,11 +327,9 @@ async function handleView(row) {
try {
const res = await getCardDetail(row.cardNo);
if (res.code === 200) {
currentCard.value = res.data || {};
detailMode.value = 'view';
detailVisible.value = true;
nextTick(() => {
infectiousDiseaseReportRef.value?.showReport(res.data || {});
});
}
} catch (error) {
ElMessage.error('获取详情失败');
@@ -330,17 +340,57 @@ async function handleEdit(row) {
try {
const res = await getCardDetail(row.cardNo);
if (res.code === 200) {
currentCard.value = res.data || {};
detailMode.value = 'edit';
detailVisible.value = true;
nextTick(() => {
infectiousDiseaseReportRef.value?.showReport(res.data || {}, false);
});
}
} catch (error) {
ElMessage.error('获取详情失败');
}
}
async function handleSaveEdit(submitData) {
// submitData 来自 InfectiousReport 组件的 submit-edit 事件
try {
const updateData = {
cardNo: submitData.cardNo,
phone: submitData.phone,
contactPhone: submitData.contactPhone,
onsetDate: submitData.onsetDate,
diagDate: submitData.diagDate,
diseaseCode: submitData.diseaseCode,
diseaseType: submitData.diseaseType,
otherDisease: submitData.otherDisease,
caseClass: submitData.caseClass,
occupation: submitData.occupation,
patientBelong: submitData.patientBelong,
addressProv: submitData.addressProv,
addressCity: submitData.addressCity,
addressCounty: submitData.addressCounty,
addressTown: submitData.addressTown,
addressVillage: submitData.addressVillage,
addressHouse: submitData.addressHouse,
workplace: submitData.workplace,
parentName: submitData.parentName,
deathDate: submitData.deathDate,
correctName: submitData.correctName,
withdrawReason: submitData.withdrawReason,
remark: submitData.remark,
};
const res = await updateDoctorCard(updateData);
if (res.code === 200) {
ElMessage.success('保存成功');
detailVisible.value = false;
getList();
} else {
ElMessage.error(res.msg || '保存失败');
}
} catch (error) {
ElMessage.error('保存失败:' + (error.message || '网络错误'));
}
}
async function handleSubmit(row) {
try {
await ElMessageBox.confirm('确认提交该报卡?', '提示', {
@@ -750,4 +800,17 @@ function handleResize() {
margin-left: 0;
}
}
/* 报卡详情弹窗 */
:deep(.card-detail-dialog .el-dialog__body) {
padding: 0;
overflow: hidden;
}
:deep(.card-detail-dialog .infectious-report-container) {
padding: 16px;
height: auto;
max-height: 70vh;
overflow-y: auto;
}
</style>

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

@@ -175,9 +175,10 @@ const hasMatchedFields = computed(() => {
});
/** 查询科室 */
const getLocationInfo = async () => {
const res = await getOrgList();
orgOptions.value = res.data.records;
const getLocationInfo = () => {
getOrgList().then((res) => {
orgOptions.value = res.data.records;
});
};
const recursionFun = (targetDepartment) => {
@@ -198,12 +199,7 @@ const recursionFun = (targetDepartment) => {
return name;
};
const handleViewDetail = async (row) => {
// 确保科室数据已加载,以便将 ID 解析为名称
if (!orgOptions.value || orgOptions.value.length === 0) {
await getLocationInfo();
}
const handleViewDetail = (row) => {
currentDetail.value = row;
// 解析 descJson
if (row.descJson) {
@@ -224,9 +220,10 @@ const handleViewDetail = async (row) => {
watch(
() => patientInfo.value?.encounterId,
async (val) => {
(val) => {
if (val) {
await Promise.all([fetchData(), getLocationInfo()]);
fetchData();
getLocationInfo();
} else {
tableData.value = [];
}

View File

@@ -49,6 +49,15 @@
<el-option label="已作废" value="7" />
</el-select>
</el-form-item>
<el-form-item label="关键字">
<el-input
v-model="filterForm.keyword"
placeholder="申请单号/检查项目"
clearable
style="width: 200px"
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch" :loading="loading">
<el-icon><Search /></el-icon>
@@ -77,7 +86,11 @@
</template>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="patientName" label="患者姓名" width="120" />
<el-table-column prop="name" label="申请单名称" width="140" />
<el-table-column label="申请单名称" width="140">
<template #default="scope">
<span>{{ getItemName(scope.row) }}</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="160" />
<el-table-column prop="prescriptionNo" label="申请单号" width="140" />
<el-table-column prop="requesterId_dictText" label="申请者" width="120" />
@@ -86,9 +99,49 @@
<span>{{ parseStatus(scope.row.status) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right">
<el-table-column label="操作" align="center" fixed="right" width="240">
<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="handleEdit(scope.row)">修改</el-button>
<el-popconfirm
title="确认删除该申请单?"
@confirm="handleDelete(scope.row)"
width="200"
>
<template #reference>
<el-button link type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
<!-- 已签发护士未校对撤回 -->
<template v-else-if="scope.row.status === '1' || scope.row.status === 1">
<el-popconfirm
title="确认撤回该申请单?撤回后将回滚至待签发状态"
@confirm="handleWithdraw(scope.row)"
width="260"
>
<template #reference>
<el-button link type="warning">撤回</el-button>
</template>
</el-popconfirm>
</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="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="handlePrint(scope.row)">打印</el-button>
<el-button link type="primary" @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="handlePrint(scope.row)">打印</el-button>
<el-button link type="success" @click="handleViewReport(scope.row)">看报告</el-button>
</template>
<!-- 已作废仅详情 -->
</template>
</el-table-column>
</el-table>
@@ -110,7 +163,7 @@
currentDetail.patientName || '-'
}}</el-descriptions-item>
<el-descriptions-item label="申请单名称">{{
currentDetail.name || '-'
getItemName(currentDetail)
}}</el-descriptions-item>
<el-descriptions-item label="申请单状态">{{
parseStatus(currentDetail.status)
@@ -160,6 +213,60 @@
<el-button @click="detailDialogVisible = false">关闭</el-button>
</template>
</el-dialog>
<!-- 修改申请单弹窗 -->
<el-dialog
v-model="editDialogVisible"
title="修改检查申请"
width="800px"
destroy-on-close
top="5vh"
:close-on-click-modal="false"
>
<div v-if="editDetail" class="applicationShow-container">
<el-form :model="editForm" label-width="120px">
<el-form-item label="申请单名称">
<el-input v-model="editForm.name" />
</el-form-item>
<el-form-item label="临床诊断">
<el-input v-model="editForm.clinicalDiagnosis" type="textarea" :rows="2" />
</el-form-item>
<el-form-item label="注意事项">
<el-input v-model="editForm.attention" type="textarea" :rows="2" />
</el-form-item>
<div v-if="editDetail.requestFormDetailList && editDetail.requestFormDetailList.length">
<el-divider>申请项目</el-divider>
<el-table :data="editDetail.requestFormDetailList" border size="small">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="adviceName" label="医嘱名称" />
<el-table-column prop="quantity" label="数量" width="80" align="center" />
<el-table-column prop="unitCode_dictText" label="单位" width="100" />
<el-table-column prop="totalPrice" label="总价" width="100" align="right" />
</el-table>
</div>
</el-form>
</div>
<template #footer>
<el-button @click="editDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSaveEdit" :loading="saveLoading">保存</el-button>
</template>
</el-dialog>
<!-- 报告查看弹窗 -->
<el-dialog
v-model="reportDialogVisible"
title="检查报告"
width="900px"
destroy-on-close
top="5vh"
:close-on-click-modal="false"
>
<div v-if="reportUrl" style="min-height: 500px;">
<iframe :src="reportUrl" style="width: 100%; height: 500px; border: none;" />
</div>
<el-empty v-else description="暂无报告数据" />
<template #footer>
<el-button @click="reportDialogVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
@@ -167,8 +274,8 @@
import {computed, getCurrentInstance, ref, watch} from 'vue';
import {Refresh, Search} from '@element-plus/icons-vue';
import {patientInfo} from '../../store/patient.js';
import {getCheck} from './api';
import {getDepartmentList} from '@/api/public.js';
import {getCheck, deleteRequestForm, withdrawRequestForm, getTestResult} from './api';
import {getOrgList} from '@/views/doctorstation/components/api.js';
const { proxy } = getCurrentInstance();
@@ -179,10 +286,33 @@ const currentDetail = ref(null);
const descJsonData = ref(null);
const orgOptions = ref([]);
// 修改申请单相关
const editDialogVisible = ref(false);
const editDetail = ref(null);
const editForm = ref({});
const saveLoading = ref(false);
// 报告查看相关
const reportDialogVisible = ref(false);
const reportUrl = ref('');
// 筛选表单数据
const getDefaultDateRange = () => {
const now = new Date();
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
const formatDate = (d) => {
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
return [formatDate(weekAgo), formatDate(now)];
};
const filterForm = ref({
dateRange: [], // [startDate, endDate]
dateRange: getDefaultDateRange(), // 默认近一周
status: '', // 申请单状态
keyword: '', // 关键字搜索
});
const fetchData = async () => {
@@ -207,6 +337,11 @@ const fetchData = async () => {
params.status = filterForm.value.status;
}
// 添加关键字搜索
if (filterForm.value.keyword && filterForm.value.keyword.trim()) {
params.keyword = filterForm.value.keyword.trim();
}
const res = await getCheck(params);
if (res.code === 200 && res.data) {
const raw = res.data?.records || res.data;
@@ -243,8 +378,9 @@ const handleSearch = async () => {
* 重置按钮处理
*/
const handleReset = () => {
filterForm.value.dateRange = [];
filterForm.value.dateRange = getDefaultDateRange();
filterForm.value.status = '';
filterForm.value.keyword = '';
fetchData();
};
@@ -267,6 +403,28 @@ const parseStatus = (status) => {
return statusMap[String(status)] || '-';
};
/**
* 获取申请单展示名称:优先使用具体医嘱名称列表
*/
const getItemName = (row) => {
const items = row?.requestFormDetailList;
if (items && items.length > 0) {
return items.map(item => item.adviceName).filter(Boolean).join('、') || row.name || '-';
}
return row.name || '-';
};
/**
* 构建打印用项目名称字符串(同步函数,用于模板字符串)
*/
const buildItemNames = (row) => {
const items = row?.requestFormDetailList;
if (items && items.length > 0) {
return items.map(item => item.adviceName).filter(Boolean).join('、') || row.name || '';
}
return row.name || '';
};
const labelMap = {
categoryType: '项目类别',
targetDepartment: '发往科室',
@@ -293,32 +451,37 @@ const hasMatchedFields = computed(() => {
/** 查询科室 */
const getLocationInfo = () => {
getDepartmentList().then((res) => {
orgOptions.value = res.data || [];
getOrgList().then((res) => {
orgOptions.value = res.data.records;
});
};
const recursionFun = (targetDepartment) => {
if (!targetDepartment) return '';
if (!Array.isArray(orgOptions.value) || orgOptions.value.length === 0) 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 index = 0; index < subObjArray.length; index++) {
const item = subObjArray[index];
if (Array.isArray(subObjArray)) {
for (let i = 0; i < subObjArray.length; i++) {
const item = subObjArray[i];
if (item.id == targetDepartment) {
name = item.name;
break;
}
}
}
if (name) break;
}
return name;
};
const handleViewDetail = async (row) => {
const handleViewDetail = (row) => {
console.log('targetDepartment========>', JSON.stringify(row));
currentDetail.value = row;
@@ -326,16 +489,10 @@ const handleViewDetail = async (row) => {
if (row.descJson) {
try {
const obj = JSON.parse(row.descJson);
// 确保科室数据已加载
if (!orgOptions.value || orgOptions.value.length === 0) {
await new Promise((resolve) => {
getDepartmentList().then((res) => {
orgOptions.value = res.data || [];
resolve();
});
});
if (obj.targetDepartment) {
const deptName = recursionFun(obj.targetDepartment);
obj.targetDepartment = deptName || obj.targetDepartment;
}
obj.targetDepartment = recursionFun(obj.targetDepartment);
descJsonData.value = obj;
} catch (e) {
console.error('解析 descJson 失败:', e);
@@ -347,6 +504,217 @@ const handleViewDetail = async (row) => {
detailDialogVisible.value = true;
};
/**
* 修改申请单
*/
const handleEdit = (row) => {
editDetail.value = row;
// 解析 descJson 为表单数据
const form = { name: row.name || '' };
if (row.descJson) {
try {
const desc = JSON.parse(row.descJson);
form.clinicalDiagnosis = desc.clinicalDiagnosis || '';
form.attention = desc.attention || '';
form.symptom = desc.symptom || '';
form.sign = desc.sign || '';
} catch (e) {
console.error('解析 descJson 失败:', e);
}
}
editForm.value = form;
editDialogVisible.value = true;
};
/**
* 保存修改
*/
const handleSaveEdit = async () => {
if (!editDetail.value?.requestFormId) {
proxy.$modal?.msgWarning?.('申请单ID不存在');
return;
}
saveLoading.value = true;
try {
// 复用 save-check 接口进行更新(传入 requestFormId 即为编辑场景)
const res = await proxy.$http?.post?.('/reg-doctorstation/request-form/save-check', {
requestFormId: editDetail.value.requestFormId,
encounterId: editDetail.value.encounterId,
patientId: editDetail.value.patientId,
name: editForm.value.name,
organizationId: editDetail.value.inHospitalOrgId || patientInfo.value?.inHospitalOrgId,
descJson: JSON.stringify({
clinicalDiagnosis: editForm.value.clinicalDiagnosis,
attention: editForm.value.attention,
symptom: editForm.value.symptom,
sign: editForm.value.sign,
}),
activityList: [], // 修改场景仅更新描述信息,不修改项目
});
if (res?.code === 200) {
proxy.$modal?.msgSuccess?.('修改成功');
editDialogVisible.value = false;
await fetchData();
} else {
proxy.$modal?.msgError?.(res?.msg || '修改失败');
}
} catch (e) {
console.warn('修改申请单失败:', e.message);
proxy.$modal?.msgError?.('修改失败: ' + (e.message || '网络异常'));
} finally {
saveLoading.value = false;
}
};
/**
* 删除申请单(仅待签发状态可删除)
*/
const handleDelete = async (row) => {
if (!row.requestFormId) {
proxy.$modal?.msgWarning?.('申请单ID不存在');
return;
}
try {
const res = await deleteRequestForm({ requestFormId: row.requestFormId });
if (res?.code === 200) {
proxy.$modal?.msgSuccess?.('删除成功');
await fetchData();
} else {
proxy.$modal?.msgError?.(res?.msg || '删除失败');
}
} catch (e) {
console.warn('删除申请单失败:', e.message);
proxy.$modal?.msgError?.('删除失败: ' + (e.message || '网络异常'));
}
};
/**
* 撤回申请单(已签发状态撤回至待签发)
*/
const handleWithdraw = async (row) => {
if (!row.requestFormId) {
proxy.$modal?.msgWarning?.('申请单ID不存在');
return;
}
try {
const res = await withdrawRequestForm({ requestFormId: row.requestFormId });
if (res?.code === 200) {
proxy.$modal?.msgSuccess?.('撤回成功');
await fetchData();
} else {
proxy.$modal?.msgError?.(res?.msg || '撤回失败');
}
} catch (e) {
console.warn('撤回申请单失败:', e.message);
proxy.$modal?.msgError?.('撤回失败: ' + (e.message || '网络异常'));
}
};
/**
* 打印申请单
*/
const handlePrint = (row) => {
// 打开新窗口进行打印
const printWindow = window.open('', '_blank');
if (!printWindow) {
proxy.$modal?.msgWarning?.('请允许浏览器弹出窗口以进行打印');
return;
}
const content = buildPrintContent(row);
printWindow.document.write(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>检查申请单打印</title>
<style>
body { font-family: "Microsoft YaHei", sans-serif; padding: 20px; font-size: 14px; }
h2 { text-align: center; margin-bottom: 20px; }
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background: #f5f5f5; }
.info-row { margin: 8px 0; }
.label { font-weight: bold; display: inline-block; width: 100px; }
@media print { .no-print { display: none; } }
</style>
</head>
<body>
${content}
<div class="no-print" style="text-align: center; margin-top: 20px;">
<button onclick="window.print()">打印</button>
<button onclick="window.close()">关闭</button>
</div>
</body>
</html>
`);
printWindow.document.close();
};
/**
* 构建打印内容
*/
const buildPrintContent = (row) => {
let descHtml = '';
if (row.descJson) {
try {
const desc = JSON.parse(row.descJson);
descHtml = Object.entries(labelMap)
.filter(([key]) => desc[key])
.map(([key, label]) => `<div class="info-row"><span class="label">${label}:</span>${desc[key]}</div>`)
.join('');
} catch (e) {
console.error('解析 descJson 失败:', e);
}
}
let itemsHtml = '';
if (row.requestFormDetailList && row.requestFormDetailList.length) {
itemsHtml = `
<h3>申请项目</h3>
<table>
<tr><th>序号</th><th>医嘱名称</th><th>数量</th><th>单位</th><th>总价</th></tr>
${row.requestFormDetailList.map((item, i) => `
<tr>
<td>${i + 1}</td>
<td>${item.adviceName || '-'}</td>
<td>${item.quantity || '-'}</td>
<td>${item.unitCode_dictText || '-'}</td>
<td>${item.totalPrice || '-'}</td>
</tr>
`).join('')}
</table>
`;
}
return `
<h2>检查申请单</h2>
<div class="info-row"><span class="label">患者姓名:</span>${row.patientName || '-'}</div>
<div class="info-row"><span class="label">申请单名称:</span>${buildItemNames(row) || '-'}</div>
<div class="info-row"><span class="label">创建时间:</span>${row.createTime || '-'}</div>
<div class="info-row"><span class="label">申请单号:</span>${row.prescriptionNo || '-'}</div>
<div class="info-row"><span class="label">申请者:</span>${row.requesterId_dictText || '-'}</div>
<div class="info-row"><span class="label">申请单状态:</span>${parseStatus(row.status)}</div>
${descHtml ? `<h3>申请单描述</h3>${descHtml}` : ''}
${itemsHtml}
`;
};
/**
* 查看报告
*/
const handleViewReport = async (row) => {
reportUrl.value = '';
reportDialogVisible.value = true;
try {
const res = await getTestResult({ encounterId: row.encounterId, requestFormId: row.requestFormId });
if (res?.code === 200 && res?.data) {
reportUrl.value = res.data;
} else {
console.warn('获取检查报告失败:', res?.msg || '无数据');
}
} catch (e) {
console.warn('获取检查报告异常:', e.message);
}
};
watch(
() => patientInfo.value?.encounterId,
(val) => {
@@ -355,8 +723,9 @@ watch(
getLocationInfo();
} else {
tableData.value = [];
filterForm.value.dateRange = [];
filterForm.value.dateRange = getDefaultDateRange();
filterForm.value.status = '';
filterForm.value.keyword = '';
}
},
{ immediate: true }

View File

@@ -1,20 +0,0 @@
<!--
* 住院医生站 汇总发药申请复用药房发药汇总单能力
-->
<template>
<div class="summary-drug-application">
<MedicationSummary />
</div>
</template>
<script setup>
import MedicationSummary from '@/views/drug/inpatientMedicationDispensing/components/MedicationSummary.vue';
</script>
<style scoped>
.summary-drug-application {
height: 100%;
min-height: 400px;
overflow: auto;
}
</style>

View File

@@ -181,9 +181,10 @@ const hasMatchedFields = computed(() => {
});
/** 查询科室 */
const getLocationInfo = async () => {
const res = await getOrgList();
orgOptions.value = res.data.records;
const getLocationInfo = () => {
getOrgList().then((res) => {
orgOptions.value = res.data.records;
});
};
const recursionFun = (targetDepartment) => {
@@ -204,12 +205,7 @@ const recursionFun = (targetDepartment) => {
return name;
};
const handleViewDetail = async (row) => {
// 确保科室数据已加载,以便将 ID 解析为名称
if (!orgOptions.value || orgOptions.value.length === 0) {
await getLocationInfo();
}
const handleViewDetail = (row) => {
currentDetail.value = row;
// 解析 descJson
if (row.descJson) {
@@ -230,9 +226,10 @@ const handleViewDetail = async (row) => {
watch(
() => patientInfo.value?.encounterId,
async (val) => {
(val) => {
if (val) {
await Promise.all([fetchData(), getLocationInfo()]);
fetchData();
getLocationInfo();
} else {
tableData.value = [];
}

View File

@@ -41,19 +41,12 @@
<el-option label="全部" value="" />
<el-option label="待签发" value="0" />
<el-option label="已签发" value="1" />
<el-option label="已采集" value="2" />
<el-option label="已收样" value="3" />
<el-option label="报告已出" value="4" />
<el-option label="已作废" value="5" />
</el-select>
</el-form-item>
<el-form-item label="关键字">
<el-input
v-model="filterForm.keyword"
placeholder="申请单号/检验项目"
clearable
style="width: 200px"
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch" :loading="loading">
<el-icon><Search /></el-icon>
@@ -101,15 +94,8 @@
</template>
</el-table-column>
<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">
<template #default="scope">
<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="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
<template v-else-if="scope.row.billStatus == 1 || scope.row.status == 1">
<el-button link type="warning" @click="handleWithdraw(scope.row)">撤回</el-button>
</template>
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
</template>
</el-table-column>
@@ -186,8 +172,8 @@
import {computed, getCurrentInstance, ref, watch} from 'vue';
import {Refresh, Search} from '@element-plus/icons-vue';
import {patientInfo} from '../../store/patient.js';
import {getInspection, deleteRequestForm, withdrawRequestForm} from './api';
import {getOrgList} from '@/views/doctorstation/components/api.js';
import {getInspection} from './api';
import {getDepartmentList} from '@/api/public.js';
const { proxy } = getCurrentInstance();
@@ -202,7 +188,6 @@ const orgOptions = ref([]);
const filterForm = ref({
dateRange: [], // [startDate, endDate]
status: '', // 单据状态
keyword: '', // 关键字搜索
});
const fetchData = async () => {
@@ -226,12 +211,7 @@ const fetchData = async () => {
if (filterForm.value.status !== '' && filterForm.value.status !== undefined) {
params.status = filterForm.value.status;
}
// 添加关键字搜索
if (filterForm.value.keyword && filterForm.value.keyword.trim()) {
params.keyword = filterForm.value.keyword.trim();
}
const res = await getInspection(params);
if (res.code === 200 && res.data) {
const raw = res.data?.records || res.data;
@@ -271,7 +251,6 @@ const handleReset = () => {
// 重置筛选条件为默认值
filterForm.value.dateRange = [];
filterForm.value.status = '';
filterForm.value.keyword = '';
// 重新加载数据
fetchData();
};
@@ -296,6 +275,8 @@ const parseBillStatus = (status) => {
const statusMap = {
'0': '待签发',
'1': '已签发',
'2': '已采集',
'3': '已收样',
'4': '报告已出',
'5': '已作废',
};
@@ -350,19 +331,18 @@ const hasMatchedFields = computed(() => {
});
/** 查询科室 */
const getLocationInfo = async () => {
const res = await getDepartmentList();
orgOptions.value = res.data || [];
const getLocationInfo = () => {
getDepartmentList().then((res) => {
orgOptions.value = res.data || [];
});
};
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) {
@@ -370,21 +350,14 @@ const recursionFun = (targetDepartment) => {
const item = subObjArray[i];
if (item.id == targetDepartment) {
name = item.name;
break;
}
}
}
if (name) break;
}
return name;
};
const handleViewDetail = async (row) => {
// 确保科室数据已加载,以便将 ID 解析为名称
if (!orgOptions.value || orgOptions.value.length === 0) {
await getLocationInfo();
}
const handleViewDetail = (row) => {
currentDetail.value = row;
// 解析 descJson
if (row.descJson) {
@@ -392,6 +365,7 @@ const handleViewDetail = async (row) => {
const obj = JSON.parse(row.descJson);
obj.targetDepartment = recursionFun(obj.targetDepartment);
descJsonData.value = obj;
// descJsonData.value = JSON.parse(row.descJson);
} catch (e) {
console.error('解析 descJson 失败:', e);
descJsonData.value = null;
@@ -402,70 +376,15 @@ const handleViewDetail = async (row) => {
detailDialogVisible.value = true;
};
/**
* 修改检验申请单(待签发状态)
*/
const handleEdit = (row) => {
// 复用详情查看逻辑,后续可扩展为打开编辑弹窗
handleViewDetail(row);
proxy.$modal?.msgInfo?.('修改功能待接入,请通过详情弹窗查看后重新开立');
};
/**
* 删除检验申请单(仅待签发状态可删除)
*/
const handleDelete = async (row) => {
try {
await proxy.$modal?.confirm?.(`确定要删除申请单 "${row.prescriptionNo}" 吗?此操作不可恢复。`);
} catch {
return; // 用户取消
}
try {
const res = await deleteRequestForm({ prescriptionNo: row.prescriptionNo });
if (res?.code === 200) {
proxy.$modal?.msgSuccess?.('删除成功');
await fetchData();
} else {
proxy.$modal?.msgError?.(res?.msg || '删除失败');
}
} catch (e) {
proxy.$modal?.msgError?.(e.message || '删除异常');
}
};
/**
* 撤回检验申请单(已签发状态撤回至待签发)
*/
const handleWithdraw = async (row) => {
try {
await proxy.$modal?.confirm?.(`确定要撤回申请单 "${row.prescriptionNo}" 吗?撤回后将恢复为待签发状态。`);
} catch {
return; // 用户取消
}
try {
const res = await withdrawRequestForm({ prescriptionNo: row.prescriptionNo });
if (res?.code === 200) {
proxy.$modal?.msgSuccess?.('撤回成功');
await fetchData();
} else {
proxy.$modal?.msgError?.(res?.msg || '撤回失败');
}
} catch (e) {
proxy.$modal?.msgError?.(e.message || '撤回异常');
}
};
watch(
() => patientInfo.value?.encounterId,
async (val) => {
(val) => {
if (val) {
// 设置默认日期范围为近7天
const today = new Date();
const sevenDaysAgo = new Date(today);
sevenDaysAgo.setDate(today.getDate() - 6); // 包含今天共7天
// 格式化为 YYYY-MM-DD
const formatDate = (date) => {
const year = date.getFullYear();
@@ -473,19 +392,19 @@ watch(
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
filterForm.value.dateRange = [
formatDate(sevenDaysAgo),
formatDate(today)
];
await Promise.all([fetchData(), getLocationInfo()]);
fetchData();
getLocationInfo();
} else {
tableData.value = [];
// 重置筛选条件
filterForm.value.dateRange = [];
filterForm.value.status = '';
filterForm.value.keyword = '';
}
},
{ immediate: true }

View File

@@ -112,8 +112,6 @@ const showApplicationFormDialog = (name) => {
setTimeout(() => {
applicationFormName.value = components.value[name];
applicationFormDialogVisible.value = true;
// 列表(项目列表)
applicationFormNameRef?.value.getList?.();
// 科室列表
applicationFormNameRef?.value.getLocationInfo();
// 诊断目录列表
@@ -123,8 +121,6 @@ const showApplicationFormDialog = (name) => {
applicationFormName.value = components.value[name];
applicationFormDialogVisible.value = true;
nextTick(() => {
// 列表(项目列表)
applicationFormNameRef?.value.getList?.();
// 科室列表
applicationFormNameRef?.value.getLocationInfo();
// 诊断目录列表

View File

@@ -72,45 +72,6 @@
<el-input v-model="form.attention" autocomplete="off" type="textarea" />
</el-form-item>
</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-form>
</div>
@@ -120,7 +81,7 @@
import {getCurrentInstance, onBeforeMount, onMounted, reactive, watch} from 'vue';
import {patientInfo} from '../../../store/patient.js';
import {getApplicationList, saveInspection} from './api';
import {getOrgList} from '@/views/doctorstation/components/api.js';
import {getDepartmentList} from '@/api/public.js';
import {getEncounterDiagnosis} from '../../api.js';
import {ElMessage} from 'element-plus';
@@ -191,9 +152,6 @@ const form = reactive({
otherDiagnosis: '', // 其他诊断
relatedResult: '', // 相关结果
attention: '', // 注意事项
applicationType: 0, // 申请类型 0-普通 1-急诊
specimenName: '血液', // 标本类型
executeTime: null, // 执行时间
primaryDiagnosisList: [], //主诊断目录
otherDiagnosisList: [], //其他断目录
});
@@ -218,8 +176,6 @@ const projectWithDepartment = (selectProjectIds, type) => {
});
arr.push(searchData);
});
// 保存用户手动选择的发往科室(提交时需要保留)
const manualDept = type === 2 ? form.targetDepartment : '';
// 清空科室
form.targetDepartment = '';
if (arr.length > 0) {
@@ -238,20 +194,16 @@ const projectWithDepartment = (selectProjectIds, type) => {
// 选中项目中的执行科室id与全部科室数据做匹配
const findItem = findTreeItem(orgOptions.value, obj.orgId);
if (!findItem) {
// type=2(提交)时,若用户已手动选择发往科室,则允许提交
if (type === 2 && manualDept) {
form.targetDepartment = manualDept;
isRelease = true;
} else {
isRelease = false;
ElMessage({
type: 'error',
message: '未找到项目执行的科室',
});
}
isRelease = false;
ElMessage({
type: 'error',
message: '未找到项目执行的科室',
});
}
if (findItem && isRelease) {
form.targetDepartment = findItem.id;
if (type == 1) {
if (isRelease) {
form.targetDepartment = findItem.id;
}
}
}
return isRelease;
@@ -280,7 +232,7 @@ const submit = () => {
unitCode: item.priceList[0].unitCode /** 请求单位编码 */,
unitPrice: item.priceList[0].price /** 单价 */,
totalPrice: item.priceList[0].price /** 总价 */,
positionId: item.positionId || form.targetDepartment, //执行科室id未配置时使用用户手动选择的科室
positionId: item.positionId, //执行科室id
ybClassEnum: item.ybClassEnum, //类别医保编码
conditionId: item.conditionId, //诊断ID
encounterDiagnosisId: item.encounterDiagnosisId, //就诊诊断id
@@ -312,8 +264,8 @@ const submit = () => {
};
/** 查询科室 */
const getLocationInfo = () => {
getOrgList().then((res) => {
orgOptions.value = res.data.records;
getDepartmentList().then((res) => {
orgOptions.value = res.data || [];
console.log('科室========>', JSON.stringify(orgOptions.value));
});
};
@@ -350,7 +302,7 @@ function getDiagnosisList() {
}
});
}
defineExpose({ state, submit, getLocationInfo, getDiagnosisList, getList });
defineExpose({ state, submit, getLocationInfo, getDiagnosisList });
</script>
<style lang="scss" scoped>
.LaboratoryTests-container {

View File

@@ -1,242 +1,91 @@
<!--
* @Author: sjjh
* @Date: 2025-09-05 22:35:29
* @Description: 检查申请单 - 现代化医疗风格布局
* @Description: 检查
-->
<template>
<div class="medicalExaminations-container">
<!-- 顶部标题栏 -->
<div class="form-header">
<div class="header-left">
<el-icon class="header-icon"><Files /></el-icon>
<span class="header-title">检查申请单</span>
</div>
<div class="header-right">
<span class="urgency-label">紧急程度</span>
<el-radio-group v-model="form.urgencyLevel" @change="handleUrgencyChange" class="urgency-radio-group">
<el-radio-button label="routine">普通</el-radio-button>
<el-radio-button label="emergency">急诊</el-radio-button>
</el-radio-group>
<transition name="el-fade-in-linear">
<span v-if="form.urgencyLevel === 'emergency'" class="emergency-tip">
<el-icon><WarningFilled /></el-icon>
急诊单将进入绿色通道
</span>
</transition>
</div>
<div v-loading="loading" class="transfer-wrapper">
<el-transfer
v-model="transferValue"
:data="applicationList"
filter-placeholder="项目代码/名称"
filterable
:titles="['未选择', '已选择']"
/>
</div>
<!-- 主体内容区 -->
<div class="form-body">
<!-- 选择检查项目 -->
<div class="section-card">
<div class="section-header">
<el-icon><Document /></el-icon>
<span>选择检查项目</span>
</div>
<div v-loading="loading" class="transfer-wrapper">
<el-transfer
v-model="transferValue"
:data="applicationList"
filter-placeholder="项目代码/名称"
filterable
:titles="['可选项目', '已选项目']"
/>
</div>
</div>
<!-- 申请信息 -->
<div class="section-card">
<div class="section-header">
<el-icon><EditPen /></el-icon>
<span>申请信息</span>
</div>
<el-form :model="form" :rules="rules" ref="formRef" label-position="top" class="info-form">
<!-- 第一行发往科室 + 期望检查时间 -->
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="发往科室" prop="targetDepartment">
<el-tree-select
clearable
style="width: 100%"
v-model="form.targetDepartment"
filterable
:data="orgOptions"
:props="{ value: 'id', label: 'name', children: 'children' }"
value-key="id"
check-strictly
placeholder="请选择执行科室"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="期望检查时间">
<el-date-picker
v-model="form.expectedExaminationTime"
type="datetime"
placeholder="默认当前时间"
style="width: 100%"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm"
:disabled-date="disabledFutureDate"
:default-value="new Date()"
/>
</el-form-item>
</el-col>
</el-row>
<!-- 第二行症状 + 体征 -->
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="症状">
<el-input v-model="form.symptom" autocomplete="off" type="textarea" :rows="2" placeholder="请输入患者症状" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="体征">
<el-input v-model="form.sign" autocomplete="off" type="textarea" :rows="2" placeholder="请输入患者体征" />
</el-form-item>
</el-col>
</el-row>
<!-- 第三行临床诊断 + 其他诊断 -->
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="临床诊断">
<el-input disabled v-model="form.clinicalDiagnosis" placeholder="自动带入主诊断" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="其他诊断">
<el-input disabled v-model="form.otherDiagnosis" placeholder="自动带入其他诊断" />
</el-form-item>
</el-col>
</el-row>
<!-- 第四行相关结果 + 注意事项 -->
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="相关结果">
<el-input v-model="form.relatedResult" autocomplete="off" type="textarea" :rows="2" placeholder="请输入相关检验结果" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="注意事项">
<el-input v-model="form.attention" autocomplete="off" type="textarea" :rows="2" placeholder="请输入检查注意事项" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- 过敏史卡片 -->
<div class="section-card allergy-card">
<div class="section-header">
<el-icon><Warning /></el-icon>
<span>过敏史</span>
<span v-if="form.allergyHistory" class="header-count">{{ form.allergyHistory.length }}</span>
</div>
<div class="allergy-content">
<div class="allergy-input-row">
<el-input
v-model="form.allergyHistory"
autocomplete="off"
type="textarea"
:rows="2"
:class="{ 'allergy-danger': isSevereAllergy }"
placeholder="如:造影剂过敏史等(系统将自动从患者档案带入)"
<div class="bloodTransfusion-form">
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="demo-ruleForm">
<el-row :gutter="20">
<!-- <el-col :span="12">
<el-form-item label="项目类别" prop="categoryType" style="width: 100%">
<el-input v-model="form.categoryType" autocomplete="off" />
</el-form-item>
</el-col> -->
<el-col :span="12">
<el-form-item label="发往科室" prop="targetDepartment" style="width: 100%">
<!-- <el-input v-model="form.targetDepartment" autocomplete="off" /> -->
<el-tree-select
clearable
style="width: 100%"
v-model="form.targetDepartment"
filterable
:data="orgOptions"
:props="{
value: 'id',
label: 'name',
children: 'children',
}"
value-key="id"
check-strictly
placeholder="请选择科室"
/>
<span v-if="isSevereAllergy" class="allergy-severe-tag">
<el-icon><WarningFilled /></el-icon>
严重过敏
</span>
</div>
<div class="allergy-confirm">
<el-checkbox v-model="form.allergyConfirmed" size="small">
已通过口头询问确认无过敏史
</el-checkbox>
</div>
</div>
</div>
<!-- 检查目的卡片 -->
<div class="section-card purpose-card">
<div class="section-header">
<el-icon><Aim /></el-icon>
<span>检查目的</span>
<span class="required-mark">必填</span>
</div>
<el-input
v-model="form.examinationPurpose"
autocomplete="off"
type="textarea"
:rows="2"
maxlength="200"
show-word-limit
placeholder="请输入检查目的,如:明确诊断、术后复查、疗效评估等"
/>
</div>
<!-- 病史摘要卡片 -->
<div class="section-card history-card">
<div class="section-header">
<el-icon><DocumentCopy /></el-icon>
<span>病史摘要</span>
<span class="required-mark">必填</span>
<el-button
type="primary"
plain
size="small"
class="sync-btn"
@click="handleSyncHistory"
:loading="syncingHistory"
>
<el-icon><Refresh /></el-icon>
同步现病史/体征
</el-button>
</div>
<el-input
v-model="form.medicalHistorySummary"
autocomplete="off"
type="textarea"
:rows="3"
placeholder="请简要描述患者病史摘要"
/>
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="症状" prop="symptom" style="width: 100%">
<el-input v-model="form.symptom" autocomplete="off" type="textarea" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="体征" prop="sign" style="width: 100%">
<el-input v-model="form.sign" autocomplete="off" type="textarea" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="临床诊断" prop="clinicalDiagnosis" style="width: 100%">
<el-input disabled v-model="form.clinicalDiagnosis" autocomplete="off" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="其他诊断" prop="otherDiagnosis" style="width: 100%">
<el-input disabled v-model="form.otherDiagnosis" autocomplete="off" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="相关结果" prop="relatedResult" style="width: 100%">
<el-input v-model="form.relatedResult" autocomplete="off" type="textarea" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="注意事项" prop="attention" style="width: 100%">
<el-input v-model="form.attention" autocomplete="off" type="textarea" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<!-- 急诊确认弹窗 -->
<el-dialog
v-model="emergencyDialogVisible"
title="急诊确认"
width="400px"
:close-on-click-modal="false"
>
<div class="emergency-dialog-content">
<el-icon class="emergency-dialog-icon"><WarningFilled /></el-icon>
<p class="emergency-dialog-text">请确认患者符合急诊指征</p>
<p class="emergency-dialog-sub">急诊申请单将进入绿色通道优先处理</p>
</div>
<template #footer>
<el-button @click="cancelEmergency">取消</el-button>
<el-button type="danger" @click="confirmEmergency">确认急诊</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup name="MedicalExaminations">
import {getCurrentInstance, onMounted, reactive, ref, watch, computed} from 'vue';
import {getCurrentInstance, onBeforeMount, onMounted, reactive, watch} from 'vue';
import {patientInfo} from '../../../store/patient.js';
import {getDepartmentList} from '@/api/public.js';
import {getEncounterDiagnosis} from '../../api.js';
import {getApplicationList, saveCheckd} from './api';
import {ElMessage, ElMessageBox} from 'element-plus';
import {WarningFilled, Warning, Refresh, Files, Document, EditPen, Aim, DocumentCopy} from '@element-plus/icons-vue';
import {ElMessage} from 'element-plus';
const { proxy } = getCurrentInstance();
// 递归查找树形科室节点
const findTreeItem = (list, id) => {
if (!list || list.length === 0) return null;
@@ -249,28 +98,13 @@ const findTreeItem = (list, id) => {
}
return null;
};
const emits = defineEmits(['submitOk']);
const props = defineProps({});
const orgOptions = ref([]);
const orgOptions = ref([]); // 科室选项
const state = reactive({});
const applicationListAll = ref();
const applicationList = ref();
const loading = ref(false);
// 过敏史相关
const syncingHistory = ref(false);
const emergencyDialogVisible = ref(false);
const pendingUrgencyLevel = ref('');
// 严重过敏关键词
const severeAllergyKeywords = ['造影剂', '碘', '青霉素', '普鲁卡因', '头孢', '麻醉剂', '海鲜', '过敏性休克'];
const isSevereAllergy = computed(() => {
if (!form.allergyHistory) return false;
return severeAllergyKeywords.some(keyword => form.allergyHistory.includes(keyword));
});
const getList = () => {
if (!patientInfo.value?.inHospitalOrgId) {
applicationList.value = [];
@@ -282,7 +116,7 @@ const getList = () => {
pageNum: 1,
categoryCode: '23',
organizationId: patientInfo.value.inHospitalOrgId,
adviceTypes: [3],
adviceTypes: [3], //1 药品 2耗材 3诊疗
})
.then((res) => {
if (res.code === 200) {
@@ -307,532 +141,187 @@ const getList = () => {
loading.value = false;
});
};
const transferValue = ref([]);
const form = reactive({
targetDepartment: '',
urgencyLevel: 'routine',
allergyHistory: '',
examinationPurpose: '',
medicalHistorySummary: '',
expectedExaminationTime: '',
symptom: '',
sign: '',
clinicalDiagnosis: '',
otherDiagnosis: '',
relatedResult: '',
attention: '',
primaryDiagnosisList: [],
otherDiagnosisList: [],
allergyConfirmed: false,
// categoryType: '', // 项目类别
targetDepartment: '', // 发往科室
symptom: '', // 症状
sign: '', // 体征
clinicalDiagnosis: '', // 临床诊断
otherDiagnosis: '', // 其他诊断
relatedResult: '', // 相关结果
attention: '', // 注意事项
primaryDiagnosisList: [], //主诊断目录
otherDiagnosisList: [], //其他断目录
});
// 表单校验规则
const rules = reactive({
examinationPurpose: [
{ required: true, message: '请输入检查目的', trigger: 'blur' },
{ max: 200, message: '检查目的不能超过200个字符', trigger: 'blur' },
],
medicalHistorySummary: [
{ required: true, message: '请输入病史摘要', trigger: 'blur' },
],
const rules = reactive({});
onBeforeMount(() => {});
onMounted(() => {
getList();
});
// 禁用过去的时间
const disabledFutureDate = (time) => {
return time.getTime() < Date.now() - 8.64e7;
};
// 紧急程度切换处理
const handleUrgencyChange = (value) => {
if (value === 'emergency') {
emergencyDialogVisible.value = true;
}
};
// 确认急诊
const confirmEmergency = () => {
emergencyDialogVisible.value = false;
ElMessage({
type: 'success',
message: '已设置为急诊检查,将进入绿色通道',
duration: 3000,
});
};
// 取消急诊
const cancelEmergency = () => {
emergencyDialogVisible.value = false;
form.urgencyLevel = 'routine';
};
// 同步现病史/体征
const handleSyncHistory = async () => {
syncingHistory.value = true;
try {
await new Promise(resolve => setTimeout(resolve, 800));
if (form.symptom || form.sign) {
form.medicalHistorySummary = `${form.symptom ? '症状:' + form.symptom + '\n' : ''}${form.sign ? '体征:' + form.sign : ''}`;
} else {
form.medicalHistorySummary = '患者目前一般情况可,无特殊不适主诉。';
}
ElMessage({ type: 'success', message: '已同步现病史/体征' });
} catch (error) {
ElMessage.error('同步失败');
} finally {
syncingHistory.value = false;
}
};
// 自动带入患者过敏史
const loadPatientAllergyHistory = () => {
if (!patientInfo.value?.patientId) return;
};
/**
* type(1watch监听类型 2:点击保存类型)
* selectProjectIds(选中项目的id数组)
* */
const projectWithDepartment = (selectProjectIds, type) => {
//1.获取选中的项目 2.判断项目的执行科室是否相同 3.判断执行科室是否配置 4.将项目的执行科室复值到执行科室下拉选位置
let isRelease = true;
// 选中项目的数组
const arr = [];
// 根据选中的项目id查找对应的项目
selectProjectIds.forEach((element) => {
const searchData = applicationList.value.find((item) => {
return element == item.adviceDefinitionId;
});
arr.push(searchData);
});
// 只有当选择了项目arr 非空)时才设置 targetDepartment
// 清空科室
form.targetDepartment = '';
if (arr.length > 0) {
const obj = arr[0];
// 检查是否有未定义的项目applicationList 中找不到)
if (!obj) {
console.warn('未找到项目定义,无法设置执行科室');
return false;
}
const isCompare = arr.every((item) => item && item.orgId == obj.orgId);
// 判断科室是否相同
const isCompare = arr.every((item) => {
return item.orgId == obj.orgId;
});
if (!isCompare) {
ElMessage({ type: 'error', message: '执行科室不同' });
ElMessage({
type: 'error',
message: '执行科室不同',
});
isRelease = false;
}
// 选中项目中的执行科室id与全部科室数据做匹配
const findItem = findTreeItem(orgOptions.value, obj.orgId);
if (!findItem) {
isRelease = false;
ElMessage({ type: 'error', message: '未找到项目执行的科室' });
ElMessage({
type: 'error',
message: '未找到项目执行的科室',
});
}
if (type == 1 && isRelease) {
form.targetDepartment = findItem.id;
console.log('targetDepartment 设置为:', form.targetDepartment, '科室名称:', findItem.name);
if (type == 1) {
if (isRelease) {
form.targetDepartment = findItem.id;
}
}
} else {
// 清空选择时,也要清空 targetDepartment
form.targetDepartment = '';
}
return isRelease;
};
watch(() => transferValue.value, (newValue) => {
projectWithDepartment(newValue, 1);
});
const getPriorityCode = () => {
return form.urgencyLevel === 'emergency' ? 1 : 0;
};
// 监听选择项目变化
watch(
() => transferValue.value,
(newValue) => {
projectWithDepartment(newValue, 1);
}
);
const submit = () => {
if (transferValue.value.length == 0) {
return proxy.$message.error('请选择检查项目');
return proxy.$message.error('请选择申请单');
}
if (!projectWithDepartment(transferValue.value, 2)) {
return;
}
if (!form.examinationPurpose) {
return ElMessageBox.alert('请输入检查目的', '提示', { confirmButtonText: '确定', type: 'warning' });
}
if (!form.medicalHistorySummary) {
return ElMessageBox.alert('请输入病史摘要', '提示', { confirmButtonText: '确定', type: 'warning' });
}
if (!form.allergyHistory && !form.allergyConfirmed) {
return ElMessageBox.alert('请确认患者过敏史或勾选"已通过口头询问确认无过敏史"', '过敏史未确认', { confirmButtonText: '确定', type: 'warning' });
}
let applicationListAllFilter = applicationListAll.value.filter((item) => {
return transferValue.value.includes(item.adviceDefinitionId);
});
applicationListAllFilter = applicationListAllFilter.map((item) => {
return {
adviceDefinitionId: item.adviceDefinitionId,
quantity: 1,
unitCode: item.priceList[0].unitCode,
unitPrice: item.priceList[0].price,
totalPrice: item.priceList[0].price,
positionId: item.positionId,
ybClassEnum: item.ybClassEnum,
conditionId: item.conditionId,
encounterDiagnosisId: item.encounterDiagnosisId,
adviceType: item.adviceType,
definitionId: item.priceList[0].definitionId,
definitionDetailId: item.priceList[0].definitionDetailId,
accountId: patientInfo.value.accountId,
adviceDefinitionId: item.adviceDefinitionId /** 诊疗定义id */,
quantity: 1, // /** 请求数量 */
unitCode: item.priceList[0].unitCode /** 请求单位编码 */,
unitPrice: item.priceList[0].price /** 单价 */,
totalPrice: item.priceList[0].price /** 总价 */,
positionId: item.positionId, //执行科室id
ybClassEnum: item.ybClassEnum, //类别医保编码
conditionId: item.conditionId, //诊断ID
encounterDiagnosisId: item.encounterDiagnosisId, //就诊诊断id
adviceType: item.adviceType, ///** 医嘱类型 */
definitionId: item.priceList[0].definitionId, //费用定价主表ID */
definitionDetailId: item.definitionDetailId, //费用定价子表ID */
accountId: patientInfo.value.accountId, // // 账户id
};
});
const submitForm = { ...form, priorityCode: getPriorityCode() };
console.log('提交 descJson:', JSON.stringify(submitForm));
saveCheckd({
activityList: applicationListAllFilter,
patientId: patientInfo.value.patientId,
encounterId: patientInfo.value.encounterId,
organizationId: patientInfo.value.inHospitalOrgId,
requestFormId: '',
name: applicationListAllFilter.map(item => item.adviceName).join('、'),
descJson: JSON.stringify(submitForm),
categoryEnum: '2',
patientId: patientInfo.value.patientId, //患者ID
encounterId: patientInfo.value.encounterId, // 就诊ID
organizationId: patientInfo.value.inHospitalOrgId, // 医疗机构ID
requestFormId: '', // 申请单ID
name: '检查申请单',
descJson: JSON.stringify(form),
categoryEnum: '2', // 1 检验 2 检查 3 输血 4 手术
}).then((res) => {
if (res.code === 200) {
proxy.$message.success(res.msg);
applicationList.value = [];
resetForm();
emits('submitOk');
} else {
proxy.$message.error(res.message);
}
});
};
const resetForm = () => {
form.targetDepartment = '';
form.urgencyLevel = 'routine';
form.allergyHistory = '';
form.examinationPurpose = '';
form.medicalHistorySummary = '';
form.expectedExaminationTime = '';
form.symptom = '';
form.sign = '';
form.clinicalDiagnosis = '';
form.otherDiagnosis = '';
form.relatedResult = '';
form.attention = '';
form.primaryDiagnosisList = [];
form.otherDiagnosisList = [];
form.allergyConfirmed = false;
transferValue.value = [];
};
/** 查询科室 */
const getLocationInfo = () => {
getDepartmentList().then((res) => {
orgOptions.value = res.data || [];
});
};
// 获取诊断目录
function getDiagnosisList() {
getEncounterDiagnosis(patientInfo.value.encounterId).then((res) => {
console.log('诊断目录========>', JSON.stringify(res.data));
if (res.code == 200) {
const datas = (res.data || []).map((item) => {
let obj = { ...item };
if (obj.diagSrtNo == null) obj.diagSrtNo = '1';
let obj = {
...item,
};
if (obj.diagSrtNo == null) {
obj.diagSrtNo = '1';
}
return obj;
});
form.primaryDiagnosisList = datas.filter((item) => item?.maindiseFlag == 1);
// 主诊断
form.primaryDiagnosisList = datas.filter((item) => {
return item?.maindiseFlag == 1;
});
console.log('@@@@@@========>', form.primaryDiagnosisList);
if (form.primaryDiagnosisList.length == 1) {
form.clinicalDiagnosis = form.primaryDiagnosisList[0].name;
const obj = form.primaryDiagnosisList[0];
form.clinicalDiagnosis = obj.name;
}
form.otherDiagnosisList = datas.filter((item) => item?.maindiseFlag !== 1);
form.otherDiagnosis = form.otherDiagnosisList.map((item) => item.name).join(',');
//其他诊断
form.otherDiagnosisList = datas.filter((item) => {
return item?.maindiseFlag !== 1;
});
const otherDiagnosisNameList = form.otherDiagnosisList.map((item) => {
return item.name;
});
form.otherDiagnosis = otherDiagnosisNameList.join(',');
}
});
}
onMounted(() => {
getList();
getLocationInfo();
loadPatientAllergyHistory();
});
defineExpose({ state, submit, getLocationInfo, getDiagnosisList, resetForm });
defineExpose({ state, submit, getLocationInfo, getDiagnosisList });
</script>
<style lang="scss" scoped>
// 医疗主题色
$primary-color: #409eff;
$success-color: #67c23a;
$warning-color: #e6a23c;
$danger-color: #f56c6c;
$info-color: #909399;
$text-primary: #303133;
$text-regular: #606266;
$text-secondary: #909399;
$border-color: #dcdfe6;
$bg-color: #f5f7fa;
.medicalExaminations-container {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
background: $bg-color;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
// 顶部标题栏
.form-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 20px;
background: linear-gradient(135deg, #fff 0%, #f0f7ff 100%);
border-bottom: 1px solid $border-color;
.header-left {
display: flex;
align-items: center;
gap: 10px;
.header-icon {
font-size: 24px;
color: $primary-color;
}
.header-title {
font-size: 18px;
font-weight: 600;
color: $text-primary;
letter-spacing: 1px;
}
}
.header-right {
display: flex;
align-items: center;
gap: 12px;
.urgency-label {
font-size: 13px;
color: $text-secondary;
font-weight: 500;
}
.urgency-radio-group {
:deep(.el-radio-button__inner) {
border-radius: 4px;
margin: 0;
}
:deep(.el-radio-button:first-child .el-radio-button__inner) {
border-radius: 4px;
}
:deep(.el-radio-button:last-child .el-radio-button__inner) {
border-radius: 4px;
}
}
.emergency-tip {
display: flex;
align-items: center;
gap: 4px;
color: $danger-color;
font-size: 13px;
font-weight: 500;
background: #fef0f0;
padding: 4px 10px;
border-radius: 4px;
border: 1px solid #fde2e2;
}
}
}
// 主体内容区
.form-body {
flex: 1;
display: flex;
flex-direction: column;
gap: 12px;
padding: 16px;
overflow-y: auto;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #d0d3da;
border-radius: 3px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
// 卡片通用样式
.section-card {
background: #fff;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border: 1px solid rgba(0, 0, 0, 0.04);
.section-header {
display: flex;
align-items: center;
gap: 8px;
padding-bottom: 12px;
margin-bottom: 12px;
border-bottom: 1px dashed $border-color;
font-size: 14px;
font-weight: 600;
color: $text-primary;
> i {
font-size: 16px;
color: $primary-color;
}
.header-count {
margin-left: auto;
font-size: 12px;
font-weight: 400;
color: $text-secondary;
}
.required-mark {
font-size: 12px;
font-weight: 500;
color: #fff;
background: $danger-color;
padding: 2px 8px;
border-radius: 10px;
margin-left: 4px;
}
}
}
.transfer-wrapper {
position: relative;
min-height: 300px;
width: 100%;
}
:deep(.el-transfer) {
.el-transfer {
--el-transfer-panel-width: 480px !important;
display: flex !important;
flex-direction: row !important;
}
// 信息表单
.info-form {
:deep(.el-form-item) {
margin-bottom: 14px;
.el-form-item__label {
font-size: 13px;
font-weight: 500;
color: $text-regular;
padding-bottom: 6px;
}
.el-input__wrapper,
.el-textarea__inner {
border-radius: 6px;
transition: all 0.2s;
&:hover {
box-shadow: 0 0 0 1px $primary-color;
}
&:focus {
box-shadow: 0 0 0 2px rgba($primary-color, 0.2);
}
}
.el-input.is-disabled .el-input__wrapper {
background: #f5f7fa;
cursor: not-allowed;
}
}
}
// 过敏史卡片
.allergy-card {
.allergy-content {
.allergy-input-row {
position: relative;
:deep(.el-textarea) {
.el-textarea__inner.allergy-danger {
border-color: $danger-color !important;
background-color: #fef0f0;
}
}
}
.allergy-severe-tag {
position: absolute;
right: 12px;
top: 8px;
display: flex;
align-items: center;
gap: 4px;
color: $danger-color;
font-size: 13px;
font-weight: 600;
background: #fef0f0;
padding: 3px 10px;
border-radius: 12px;
border: 1px solid #fde2e2;
}
.allergy-confirm {
margin-top: 10px;
padding-left: 4px;
}
}
.bloodTransfusion-form {
padding: 8px 8px 0 8px;
height: 100%;
width: 100%;
display: flex;
}
// 病史摘要卡片
.history-card {
.section-header {
.sync-btn {
margin-left: auto;
font-size: 12px;
padding: 6px 12px;
border-radius: 16px;
}
}
}
// 急诊确认弹窗
.emergency-dialog-content {
text-align: center;
padding: 24px 0;
.emergency-dialog-icon {
font-size: 52px;
color: $danger-color;
margin-bottom: 18px;
}
.emergency-dialog-text {
font-size: 18px;
font-weight: 600;
color: $text-primary;
margin: 0 0 10px;
}
.emergency-dialog-sub {
font-size: 14px;
color: $text-secondary;
margin: 0;
}
}
}
// 过渡动画
.fade-in-linear-enter-active,
.fade-in-linear-leave-active {
transition: opacity 0.2s linear;
}
.fade-in-linear-enter-from,
.fade-in-linear-leave-to {
opacity: 0;
}
</style>

View File

@@ -5,7 +5,7 @@
-->
<template>
<div class="surgery-container">
<div v-loading="loading" class="transfer-wrapper" style="min-height: 300px;">
<div class="transfer-wrapper">
<el-transfer
v-model="transferValue"
:data="applicationList"
@@ -101,16 +101,14 @@ const findTreeItem = (list, id) => {
const emits = defineEmits(['submitOk']);
const props = defineProps({});
const state = reactive({});
const applicationListAll = ref();
const applicationList = ref();
const applicationListAll = ref([]);
const applicationList = ref([]);
const orgOptions = ref([]); // 科室选项
const loading = ref(false); // 加载状态
const getList = () => {
if (!patientInfo.value?.inHospitalOrgId) {
applicationList.value = [];
return;
}
loading.value = true;
getApplicationList({
pageSize: 500,
pageNum: 1,
@@ -140,9 +138,6 @@ const getList = () => {
.catch((e) => {
console.warn('手术项目列表加载失败(可能无权限):', e?.message || e);
applicationList.value = [];
})
.finally(() => {
loading.value = false;
});
};
const transferValue = ref([]);
@@ -306,7 +301,7 @@ function getDiagnosisList() {
}
});
}
defineExpose({ state, submit, getLocationInfo, getDiagnosisList, getList });
defineExpose({ state, submit, getLocationInfo, getDiagnosisList });
</script>
<style lang="scss" scoped>
.surgery-container {

View File

@@ -197,6 +197,7 @@
style="width: 62%"
v-model="scope.row.adviceName"
placeholder="请选择项目"
@input="handleChange"
@click="handleFocus(scope.row, scope.$index)"
@keyup.enter.stop="handleFocus(scope.row, scope.$index)"
@keydown="
@@ -379,7 +380,7 @@ const form = ref({
prescriptionList: prescriptionList.value,
});
const adviceQueryParams = ref({
adviceType: '',
adviceType: 1,
categoryCode: '', // 初始为空,等待加载配置后动态设置
searchKey: '',
});
@@ -685,7 +686,7 @@ function loadConfiguredCategories() {
nextTick(() => {
// 创建新对象触发响应式更新
adviceQueryParams.value = {
adviceType: '',
adviceType: 1,
categoryCode: defaultCategoryCode,
searchKey: '',
};
@@ -801,8 +802,8 @@ function clickRowDb(row, column, event) {
return;
}
row.showPopover = false;
// 仅”待签发(statusEnum==1)”允许编辑;”已签发(statusEnum==2)”及之后状态不允许编辑
if (row.statusEnum == 1) {
// 待签发(已保存 requestId存在)”允许编辑;仅“待保存(无requestId)”允许编辑
if (row.statusEnum == 1 && !row.requestId) {
// 确保治疗类型为字符串,方便与单选框 label 对齐,默认为长期医嘱('1')
row.therapyEnum = String(row.therapyEnum ?? '1');
row.isEdit = true;
@@ -880,9 +881,8 @@ function handleFocus(row, index) {
// 用 adviceType + categoryCode 组合查找匹配的选项
const selectValue = (adviceType == 1 && row.categoryCode) ? '1-' + row.categoryCode : adviceType;
const selectedItem = adviceTypeList.value.find(item => item.value === selectValue) || adviceTypeList.value.find(item => item.adviceType === adviceType);
// 修复Bug #486当行没有显式选择医嘱类型时row.adviceType为undefined
// 不传categoryCode让搜索在全药库中进行只有行已选择类型时才用对应categoryCode过滤
const categoryCode = row.adviceType !== undefined ? (selectedItem ? selectedItem.categoryCode : '') : '';
// 修复Bug #486当行没有显式选择医嘱类型时不传categoryCode让搜索在全药库中进行
const categoryCode = selectedItem ? selectedItem.categoryCode : (row.adviceType !== undefined ? (adviceQueryParams.value.categoryCode || '') : '');
const searchKey = row.adviceName || '';
nextTick(() => {
@@ -919,9 +919,8 @@ function handleChange(value) {
// 用 adviceType + categoryCode 组合查找匹配的选项
const selectValue = (adviceType == 1 && row?.categoryCode) ? '1-' + row.categoryCode : adviceType;
const selectedItem = adviceTypeList.value.find(item => item.value === selectValue) || adviceTypeList.value.find(item => item.adviceType === adviceType);
// 修复Bug #486当行没有显式选择医嘱类型时row?.adviceType为undefined
// 不传categoryCode让搜索在全药库中进行只有行已选择类型时才用对应categoryCode过滤
const categoryCode = row?.adviceType !== undefined ? (selectedItem ? selectedItem.categoryCode : '') : '';
// 修复Bug #486当行没有显式选择医嘱类型时不传categoryCode让搜索在全药库中进行
const categoryCode = selectedItem ? selectedItem.categoryCode : (row?.adviceType !== undefined ? (adviceQueryParams.value.categoryCode || '') : '');
tableRef.refresh(adviceType, categoryCode, value);
}
}
@@ -1181,27 +1180,19 @@ function handleSave() {
});
// 此处签发处方和单行保存处方传参相同后台已经将传参存为JSON字符串此处直接转换为JSON即可
loading.value = true;
let list = [];
try {
list = saveList.map((item) => {
const parsedContent = item.contentJson ? JSON.parse(item.contentJson) || {} : {};
return {
...parsedContent,
adviceType: item.adviceType,
requestId: item.requestId,
dbOpType: '1',
groupId: item.groupId,
uniqueKey: undefined,
// 确保 therapyEnum 被正确传递
therapyEnum: parsedContent.therapyEnum || item.therapyEnum || '1',
};
});
} catch (error) {
loading.value = false;
isSaving.value = false;
proxy.$modal.msgError('医嘱内容解析失败,请检查待签发医嘱');
return;
}
let list = saveList.map((item) => {
const parsedContent = JSON.parse(item.contentJson);
return {
...parsedContent,
adviceType: item.adviceType,
requestId: item.requestId,
dbOpType: '1',
groupId: item.groupId,
uniqueKey: undefined,
// 确保 therapyEnum 被正确传递
therapyEnum: parsedContent.therapyEnum || item.therapyEnum || '1',
};
});
// 保存签发按钮
isSaving.value = true;
console.log('签发处方参数:', {
@@ -1218,7 +1209,7 @@ function handleSave() {
isSaving.value = false;
getListInfo(false);
bindMethod.value = {};
nextId.value = 1;
nextId.value == 1;
} else {
proxy.$modal.msgError(res.message);
isSaving.value = false;
@@ -1384,21 +1375,13 @@ function handleSaveSign(row, index) {
savePrescription({ regAdviceSaveList: [row] }).then((res) => {
if (res.code === 200) {
proxy.$modal.msgSuccess('保存成功');
nextId.value = 1;
nextId.value == 1;
}
});
} else {
// 新增行:调用保存接口将数据持久化到后端
row.dbOpType = '1';
savePrescription({ regAdviceSaveList: [row] }).then((res) => {
if (res.code === 200) {
proxy.$modal.msgSuccess('保存成功');
nextId.value = 1;
// 保存成功后刷新列表,确保后端返回的数据带 requestId
getListInfo(false);
}
});
// 不需要再添加空行,保存成功后由 getListInfo 处理
if (prescriptionList.value[0].adviceName) {
handleAddPrescription();
}
}
adviceQueryParams.value.adviceType = undefined;
}
@@ -1445,13 +1428,12 @@ function handleSaveBatch() {
if (row) row.isEdit = false;
});
getListInfo(false);
nextId.value = 1;
nextId.value == 1;
isSaving.value = false;
}
})
.catch((error) => {
isSaving.value = false;
proxy.$modal.msgError(error?.msg || '保存失败,请重试');
});
}
@@ -1579,21 +1561,17 @@ function handleSaveGroup(orderGroupList) {
// 🔥 新版组件已经预处理了数据,优先使用 mergedDetail
const mergedDetail = item.mergedDetail || {
...(item.orderDetailInfos || {}),
adviceName: item.orderDefinitionName || item.orderDetailInfos?.adviceName || '未知项目',
adviceName: item.orderDetailInfos?.adviceName || item.orderDefinitionName || '未知项目',
adviceType: item.orderDetailInfos?.adviceType,
adviceDefinitionId: item.orderDefinitionId || item.orderDetailInfos?.adviceDefinitionId,
quantity: item.quantity,
unitCode: item.unitCode || item.orderDetailInfos?.unitCode,
unitCodeName: item.unitCodeName,
// 🔧 Bug #403 修复dose/doseQuantity/dispensePerDuration 需用 null 检查,
// 避免组套中值为 null 时回退到医嘱库的 orderDetailInfos
dose: item.dose !== undefined && item.dose !== null ? item.dose : item.orderDetailInfos?.dose,
dose: item.dose || item.orderDetailInfos?.dose,
rateCode: item.rateCode || item.orderDetailInfos?.rateCode,
methodCode: item.methodCode || item.orderDetailInfos?.methodCode,
dispensePerDuration: item.dispensePerDuration !== undefined && item.dispensePerDuration !== null
? item.dispensePerDuration : item.orderDetailInfos?.dispensePerDuration,
doseQuantity: item.doseQuantity !== undefined && item.doseQuantity !== null
? item.doseQuantity : item.orderDetailInfos?.doseQuantity,
dispensePerDuration: item.dispensePerDuration || item.orderDetailInfos?.dispensePerDuration,
doseQuantity: item.doseQuantity,
inventoryList: item.orderDetailInfos?.inventoryList || [],
priceList: item.orderDetailInfos?.priceList || [],
partPercent: item.orderDetailInfos?.partPercent || 1,
@@ -1612,21 +1590,20 @@ function handleSaveGroup(orderGroupList) {
setValue(mergedDetail);
// 创建新的处方项目
// 🔧 Bug #403 修复:关键字段使用 null-safe 回退到 mergedDetail已由 setValue 填充完整数据)
const newRow = {
...prescriptionList.value[rowIndex.value],
patientId: patientInfo.value.patientId,
encounterId: patientInfo.value.encounterId,
accountId: accountId.value,
quantity: item.quantity ?? mergedDetail.quantity,
methodCode: item.methodCode ?? mergedDetail.methodCode,
rateCode: item.rateCode ?? mergedDetail.rateCode,
dispensePerDuration: item.dispensePerDuration ?? mergedDetail.dispensePerDuration,
dose: item.dose ?? mergedDetail.dose,
doseQuantity: item.doseQuantity ?? mergedDetail.doseQuantity,
quantity: item.quantity,
methodCode: item.methodCode,
rateCode: item.rateCode,
dispensePerDuration: item.dispensePerDuration,
dose: item.dose,
doseQuantity: item.doseQuantity,
executeNum: 1,
unitCode: item.unitCode ?? mergedDetail.unitCode,
unitCode_dictText: item.unitCodeName || mergedDetail.unitCodeName || '',
unitCode: item.unitCode,
unitCode_dictText: item.unitCodeName || '',
statusEnum: 1,
orgId: resolveOrgId(item.orderDetailInfos?.orgId || mergedDetail.orgId || patientInfo.value?.inHospitalOrgId) || '',
// 🔧 修复:同时保存 orgName确保树匹配不到时仍有中文名称可显示

View File

@@ -16,14 +16,12 @@
<Advice ref="adviceRef" />
</el-tab-pane>
<!-- <el-tab-pane label="医技报告" name="fourth">Task</el-tab-pane> -->
<el-tab-pane label="检验申请" name="test">
<TestApplication ref="testApplicationRef" :show-status-column="true" />
<el-tab-pane label="检验申请" name="test">
<TestApplication ref="testApplicationRef" :show-status-column="false" />
</el-tab-pane>
```vue
<el-tab-pane label="检查申请" name="examine">
<ExamineApplication ref="examineApplicationRef" />
</el-tab-pane>
```
<el-tab-pane label="汇总发药申请" name="summaryDrug">
<SummaryDrugApplication ref="summaryDrugApplicationRef" />
</el-tab-pane>
@@ -48,6 +46,10 @@
<script setup>
import {computed, onBeforeMount, onMounted, provide, reactive, ref, watch,} from 'vue';
import Emr from './emr/index.vue';
import inPatientBarDoctorFold from '@/component
```
import Emr from './emr/index.vue';
import inPatientBarDoctorFold from '@/components/patientBar/inPatientBarDoctorFold.vue';
import PatientList from '@/components/PatientList/patient-list.vue';

View File

@@ -181,7 +181,7 @@
v-for="dept in getFilteredOptions(scope.row, 'departmentOptions')"
:key="dept.id"
:label="dept.name"
:value="String(dept.id)"
:value="dept.id"
/>
</el-select>
<el-select
@@ -197,7 +197,7 @@
v-for="dept in getFilteredOptions(scope.row, 'locationOptions')"
:key="dept.value"
:label="dept.label"
:value="String(dept.value)"
:value="dept.value"
/>
</el-select>
</template>
@@ -280,16 +280,8 @@
style="width: 100%"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="组套名称" min-width="180" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.name || scope.row.Name || '' }}
</template>
</el-table-column>
<el-table-column label="使用范围" width="100" align="center">
<template #default="scope">
{{ getRangeText(scope.row) }}
</template>
</el-table-column>
<el-table-column label="组套名称" prop="name" min-width="180" show-overflow-tooltip />
<el-table-column label="使用范围" prop="rangeCode_dictText" width="100" align="center" />
<el-table-column label="明细数量" width="100" align="center">
<template #default="scope">
{{ scope.row.detailList?.length || 0 }} 项
@@ -489,16 +481,11 @@ watch(
// 加载科室选项
function loadDepartmentOptions() {
getOrgList()
.then((res) => {
if (res.data && res.data.records && res.data.records.length > 0) {
departmentOptions.value = res.data.records[0].children || [];
}
})
.catch(() => {
console.warn('科室列表加载失败(可能无权限)');
departmentOptions.value = [];
});
getOrgList().then((res) => {
if (res.data && res.data.records && res.data.records.length > 0) {
departmentOptions.value = res.data.records[0].children || [];
}
});
}
// 加载收费组套数据
@@ -517,15 +504,10 @@ function getAdviceBaseInfos() {
});
}
function getDiseaseInitLoc() {
getDiseaseTreatmentInitLoc(16)
.then((response) => {
console.log('Disease Treatment Init Loc:', response);
locationOptions.value = response.data.locationOptions;
})
.catch(() => {
console.warn('位置列表加载失败(可能无权限)');
locationOptions.value = [];
});
getDiseaseTreatmentInitLoc(16).then((response) => {
console.log('Disease Treatment Init Loc:', response);
locationOptions.value = response.data.locationOptions;
});
}
// 下拉框模糊搜索过滤
function filterOptions(val, row, optionsKey) {
@@ -621,23 +603,15 @@ function selectChange(row) {
const price = row.priceList?.[0]?.price || 0;
//获取大小单位
const uniqueUnitCodes = getUnitCodeOptions(row);
// 设置默认单位:优先使用 minUnitCode小单位回退到 unitCode大单位
let defaultUnitCode = '';
if (row.minUnitCode) {
defaultUnitCode = String(row.minUnitCode);
} else if (row.unitCode) {
defaultUnitCode = String(row.unitCode);
}
// 设置默认执行科室/位置(统一转为字符串,避免 el-select 类型不匹配)
// 设置默认执行科室/位置
let defaultPositionId = undefined;
if (row.adviceType === 3 && departmentOptions.value.length > 0) {
// 诊疗:优先使用患者所在科室,否则取第一个科室
const patientOrgId = props.patientInfo.organizationId;
const matched = departmentOptions.value.find(d => String(d.id) === String(patientOrgId));
defaultPositionId = matched ? String(matched.id) : String(departmentOptions.value[0].id);
defaultPositionId = departmentOptions.value.find(d => d.id === props.patientInfo.organizationId)?.id
|| departmentOptions.value[0]?.id;
} else if (row.adviceType === 2 && locationOptions.value.length > 0) {
// 耗材:默认取第一个药房/耗材房
defaultPositionId = String(locationOptions.value[0].value);
defaultPositionId = locationOptions.value[0]?.value;
}
//插入费用列表
feeItemsList.value.push({
@@ -645,8 +619,8 @@ function selectChange(row) {
uniqueUnitCodes: uniqueUnitCodes,
unitPrice: (price / (row.partPercent || 1)).toFixed(6), // 根据拆零比计算单价
quantity: 1,
positionId: defaultPositionId, // 默认执行科室/位置(字符串类型)
selectUnitCode: defaultUnitCode, // 默认选择小单位
positionId: defaultPositionId, // 默认执行科室/位置
selectUnitCode: String(row.minUnitCode || ''), // 默认选择小单位,确保字符串类型
});
}
@@ -771,7 +745,7 @@ function openGroupSetDialog() {
function loadGroupSets() {
groupSetLoading.value = true;
getOrderGroup({ organizationId: orgId.value })
getOrderGroup({ organizationId: orgId.value, searchKey: groupSetSearchText.value })
.then((res) => {
const data = res?.data || {};
if (groupSetRange.value === 1) {
@@ -791,13 +765,6 @@ function loadGroupSets() {
});
}
function getRangeText(row) {
// 后端未返回rangeCode根据当前选中的范围标签显示
const rangeMap = { 1: '个人', 2: '科室', 3: '全院' };
if (row.rangeCode_dictText) return row.rangeCode_dictText;
return rangeMap[groupSetRange.value] || '';
}
function handleGroupSetSelect(row) {
selectedGroupSet.value = row;
}

View File

@@ -174,6 +174,7 @@ const activeNames = ref([]);
const userStore = useUserStore();
const prescriptionList = ref([]);
const deadline = ref(formatDateStr(new Date(), 'YYYY-MM-DD') + ' 23:59:59');
const therapyEnum = ref(undefined);
const { proxy } = getCurrentInstance();
const loading = ref(false);
const chooseAll = ref(false);
@@ -189,10 +190,6 @@ const props = defineProps({
deadline: {
type: String,
},
therapyEnum: {
type: Number,
default: undefined,
},
});
function handleGetPrescription() {
@@ -203,7 +200,7 @@ function handleGetPrescription() {
encounterIds: encounterIds,
pageSize: 10000,
pageNo: 1,
therapyEnum: props.therapyEnum,
therapyEnum: therapyEnum.value,
exeStatus: props.exeStatus,
requestStatus: props.requestStatus,
})
@@ -302,29 +299,6 @@ function getSelectRows() {
});
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) {
// 拼接当前选中时间
if (value) {
@@ -342,8 +316,6 @@ function handleRateChange(value, item, row) {
defineExpose({
handleGetPrescription,
handleMedicineSummary,
selectAllRows,
clearSelection,
});
</script>

View File

@@ -58,7 +58,7 @@
:clearable="false"
@change="handleGetPrescription"
/>
<el-radio-group v-model="therapyEnum" class="ml20" @change="handleTherapyChange">
<el-radio-group v-model="therapyEnum" class="ml20" @change="handleRadioChange">
<el-radio :value="undefined">全部</el-radio>
<el-radio :value="1">长期</el-radio>
<el-radio :value="2">临时</el-radio>
@@ -69,11 +69,7 @@
</div>
<div>
<span class="descriptions-item-label">全选</span>
<el-switch
v-model="chooseAll"
:disabled="isDetails != '1'"
@change="handelSwicthChange"
/>
<el-switch v-model="chooseAll" @change="handelSwicthChange" />
<el-button class="ml20 mr20" type="primary" @click="handleExecute"> 汇总领药 </el-button>
</div>
</div>
@@ -83,7 +79,6 @@
:exeStatus="exeStatus"
:requestStatus="requestStatus"
:deadline="deadline"
:therapyEnum="therapyEnum"
/>
<SummaryMedicineList v-else />
<!-- <el-tabs v-model="activeName" class="demo-tabs centered-tabs" @tab-change="handleClick">
@@ -125,7 +120,6 @@ const requestStatus = ref(RequestStatus.COMPLETED);
const chooseAll = ref(false);
const drugType = ref('1');
const isDetails = ref('1');
const therapyEnum = ref(undefined);
// 存储子组件引用的对象
const prescriptionRefs = ref();
@@ -164,34 +158,23 @@ function handleClick(tabName) {
}
function handleGetPrescription() {
chooseAll.value = false;
prescriptionRefs.value?.handleGetPrescription();
prescriptionRefs.value.handleGetPrescription();
}
function handelSwicthChange(value) {
if (!prescriptionRefs.value) {
chooseAll.value = false;
return;
}
if (value) {
prescriptionRefs.value.selectAllRows();
function handelSwicthChange() {
if (chooseAll.value) {
proxy.$refs['prescriptionRefs'].selectAllRows();
} else {
prescriptionRefs.value.clearSelection();
proxy.$refs['prescriptionRefs'].clearSelection();
}
}
function handleRadioChange(value) {
chooseAll.value = false;
if (value == '1') {
handleGetPrescription();
}
}
function handleTherapyChange() {
chooseAll.value = false;
handleGetPrescription();
}
function handleExecute() {
proxy.$refs['prescriptionRefs'].handleMedicineSummary();
}
@@ -227,4 +210,4 @@ provide('handleGetPrescription', (value) => {
:deep(.el-tabs__header) {
margin: 0;
}
</style>
</style>

View File

@@ -453,10 +453,6 @@ const loadPatientInfo = () => {
interventionForm.value.startTime = dayjs(res.data.startTime).format(
'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 {
interventionForm.value.startTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss');
}

View File

@@ -67,7 +67,8 @@
</el-button>
</div>
</div>
<div
</div>
<div
style="padding: 10px; background-color: #eef9fd; height: 100%; overflow-y: auto"
v-loading="loading"
>
@@ -270,7 +271,7 @@ function normalizeDayTimeHm(part) {
return `${h}:${m}`;
}
function handleGetPrescription(skipAutoSelectAll = false) {
function handleGetPrescription() {
if (patientInfoList.value.length > 0) {
loading.value = true;
let encounterIds = patientInfoList.value.map((i) => i.encounterId).join(',');
@@ -430,12 +431,10 @@ function handleGetPrescription(skipAutoSelectAll = false) {
// 将分组结果转换为数组形式
prescriptionList.value = Object.values(groupedPrescriptions);
// 默认选中全部行(执行后刷新时不自动全选,保持用户操作状态)
if (!skipAutoSelectAll) {
nextTick(() => {
defaultSelectAllRows();
});
}
// 默认选中全部行
nextTick(() => {
defaultSelectAllRows();
});
} catch (error) {
console.error('医嘱执行-获取处方列表数据处理失败:', error);
prescriptionList.value = [];
@@ -471,18 +470,11 @@ function handleExecute() {
console.log(list, 'list');
adviceExecute({ exeDate: exeDate.value, adviceExecuteDetailList: list }).then((res) => {
if (res.code == 200) {
// 仅当选中医嘱中包含耗材类医嘱时,才调用耗材批号匹配(排除纯药品医嘱场景)
const hasDevice = list.some((item) =>
String(item.adviceTable || '').includes('device'),
);
if (hasDevice) {
lotNumberMatch({ encounterIdList: encounterIds }, { skipErrorMsg: true }).catch((error) => {
console.warn('lotNumberMatch failed after adviceExecute:', error);
});
}
// 刷新列表(不自动全选,保持用户操作前的选择状态)
handleGetPrescription(true);
proxy.$modal.msgSuccess(res.msg || '医嘱执行成功');
handleGetPrescription();
lotNumberMatch({ encounterIdList: encounterIds }, { skipErrorMsg: true }).catch((error) => {
console.warn('lotNumberMatch failed after adviceExecute:', error);
});
} else {
proxy.$modal.msgError(res.msg || '医嘱执行失败');
}
@@ -505,7 +497,7 @@ function handleNoExecute() {
adviceNoExecute({ adviceExecuteDetailList: list }).then((res) => {
if (res.code == 200) {
proxy.$modal.msgSuccess(res.msg || '操作成功');
handleGetPrescription(true);
handleGetPrescription();
} else {
proxy.$modal.msgError(res.msg || '操作失败');
}
@@ -517,39 +509,32 @@ function handleCancel() {
let list = getSelectRows();
let producerIds = [];
list.forEach((item) => {
// 从 exePerformRecordList 直接提取 procedureId过滤空值避免后端SQL异常
const procedureIds = (item.exePerformRecordList || [])
.map((record) => record.procedureId)
.filter((id) => id != null && id !== '');
// 从 exePerformRecordList 直接提取 procedureId确保取消执行时数据完整
const procedureIds = (item.exePerformRecordList || []).map((record) => record.procedureId);
if (procedureIds.length === 0 && (!item.procedureIds || item.procedureIds.length === 0)) {
proxy.$modal.msgError('请选择已执行的医嘱记录');
return;
}
const ids = procedureIds.length > 0 ? procedureIds : item.procedureIds;
producerIds.push(
...ids
.filter((value) => value != null && value !== '')
.map((value) => {
return {
procedureId: value,
therapyEnum: Number(item.therapyEnum),
};
})
...ids.map((value) => {
return {
procedureId: value,
therapyEnum: item.therapyEnum,
};
})
);
});
if (producerIds.length === 0) {
proxy.$modal.msgError('未找到有效的执行记录,无法取消执行');
return;
}
adviceCancel({ adviceExecuteDetailList: producerIds }).then((res) => {
if (res.code == 200) {
proxy.$modal.msgSuccess(res.msg || '取消执行成功');
handleGetPrescription(true);
handleGetPrescription();
} else {
proxy.$modal.msgError(res.msg || '取消执行失败');
}
}).catch(() => {
proxy.$modal.msgError('取消执行失败,请稍后重试');
});
}

View File

@@ -11,9 +11,9 @@
>
<div>
<el-radio-group v-model="type" class="ml20" @change="handleRadioChange">
<el-radio :label="null">全部</el-radio>
<el-radio :label="1">长期</el-radio>
<el-radio :label="2">临时</el-radio>
<el-radio :label="1">全部</el-radio>
<el-radio :label="2">长期</el-radio>
<el-radio :label="3">临时</el-radio>
</el-radio-group>
<el-button class="ml20" type="primary" plain @click="handleGetPrescription">
查询
@@ -159,7 +159,7 @@ import {formatDateStr} from '@/utils/index';
const activeNames = ref([]);
const prescriptionList = ref([]);
const deadline = ref(formatDateStr(new Date(), 'YYYY-MM-DD') + ' 23:59:59');
const type = ref(null);
const type = ref(1);
const { proxy } = getCurrentInstance();
const loading = ref(false);
const chooseAll = ref(false);
@@ -181,7 +181,7 @@ function handleGetPrescription() {
getPrescriptionList({
encounterIds: encounterIds,
requestStatus: props.requestStatus,
...(type.value != null ? { therapyEnum: type.value } : {}),
...(type.value !== undefined ? { therapyEnum: type.value } : {}),
pageSize: 10000,
pageNo: 1,
}).then((res) => {

View File

@@ -744,7 +744,11 @@ function handleSubmitApproval() {
} else {
submitApproval(receiptHeaderForm.busNo).then((response) => {
proxy.$modal.msgSuccess('提交审批成功');
// proxy.$tab.closePage(route).then(({ visitedViews }) => { // 关闭当前页
// toLastView(visitedViews, route)
// })
tagsViewStore.delView(router.currentRoute.value);
// 跳转到调拨管理页面
router.replace({ path: 'transferManagentList' });
});
}
@@ -867,6 +871,7 @@ function handleUnitCodeChange(row, index, value) {
}
function handleItemQuantityChange(row, value) {
debugger;
let quantityTemp = ''; // 转换成小单位的临时变量 做校验
// 大单位情况
if (row.unitCodeMap[row.unitCode] == 'unit') {
@@ -877,6 +882,12 @@ function handleItemQuantityChange(row, value) {
row.itemQuantity = value;
quantityTemp = value;
}
if (row.totalSourceQuantity < quantityTemp) {
proxy.$modal.msgWarning('调拨数量不可超出源库存数量');
row.itemQuantity = 0;
row.itemQuantityDisplay = 0;
return;
}
row.totalPrice = ((row.price * quantityTemp) / row.partPercent).toFixed(2);
// 更新总数据中对应的记录
const totalIndex = totalIncentoryInfoList.value.findIndex((item) => item.id === row.id);
@@ -907,12 +918,11 @@ function remakeBlur(row, index) {
editBatchTransfer(index);
}
function handleSave() {
// 校验单价
const invalidPriceRow = totalIncentoryInfoList.value.find(
const invalidRow = totalIncentoryInfoList.value.find(
(row) => !row.price || row.price <= 0
);
if (invalidPriceRow) {
proxy.$modal.msgError('调拨单价不能为空或为0请检查');
if (invalidRow) {
proxy.$message.warning('调拨单价不能为空或为0请检查');
return;
}
addTransferProductBatch(totalIncentoryInfoList.value).then((res) => {

View File

@@ -970,6 +970,12 @@ function handleItemQuantityChange(row, value) {
row.itemQuantity = value;
quantityTemp = value;
}
if (row.totalSourceQuantity < quantityTemp) {
proxy.$modal.msgWarning('调拨数量不可超出源库存数量');
row.itemQuantity = 0;
row.itemQuantityDisplay = 0;
return;
}
row.totalPrice = ((row.price * quantityTemp) / row.partPercent).toFixed(2);
}
@@ -1016,7 +1022,11 @@ function handleSubmitApproval() {
} else {
submitApproval(receiptHeaderForm.busNo).then((response) => {
proxy.$modal.msgSuccess('提交审批成功');
// proxy.$tab.closePage(route).then(({ visitedViews }) => { // 关闭当前页
// toLastView(visitedViews, route)
// })
tagsViewStore.delView(router.currentRoute.value);
// 跳转到调拨管理页面
router.replace({ path: 'transferManagentList' });
store.clearCurrentDataDB();
});

View File

@@ -829,9 +829,7 @@
</el-descriptions>
</div>
<div style="padding: 10px">
<prescriptionlist v-if="showChargeDialog" :patientInfo="chargePatientInfo" ref="prescriptionRef"
:generateSourceEnum="1"
:sourceBillNo="chargePatientInfo.sourceBillNo" />
<prescriptionlist :patientInfo="chargePatientInfo" ref="prescriptionRef" />
<div class="overlay" v-if="disabled"></div>
</div>
</div>
@@ -874,32 +872,16 @@ import { Loading } from '@element-plus/icons-vue' // 🔧 新增:导入 Loadin
import { getPrescriptionList } from '@/views/clinicmanagement/bargain/component/api'
// API 导入
import {
getSurgerySchedulePage,
addSurgerySchedule,
updateSurgerySchedule,
deleteSurgerySchedule,
getSurgeryScheduleDetail
} from '@/api/surgicalschedule'
import { getSurgerySchedulePage, addSurgerySchedule, updateSurgerySchedule, deleteSurgerySchedule, getSurgeryScheduleDetail } from '@/api/surgicalschedule'
import { listUser } from '@/api/system/user'
import { deptTreeSelect } from '@/api/system/user'
import { listOperatingRoom } from '@/api/operatingroom'
import { getSurgeryPage} from '@/views/inpatientDoctor/home/components/applicationShow/api.js'
import { getSurgery} from '@/views/inpatientDoctor/home/components/applicationShow/api.js'
import { getTenantPage } from '@/api/system/tenant'
import { getContract } from '@/views/inpatientDoctor/home/components/api.js'
import request from '@/utils/request'
import SurgeryCharge from '../charge/surgerycharge/index.vue'
import TemporaryMedical from './temporaryMedical.vue'
// 静默获取卫生机构列表(跳过拦截器错误提示,手术室护士等角色可能无此权限)
function getTenantPageSilent(query) {
return request({
url: '/system/tenant/page',
method: 'get',
params: query,
skipErrorMsg: true
})
}
const { proxy } = getCurrentInstance()
const userStore = useUserStore()
const loading = ref(true)
@@ -1128,7 +1110,7 @@ onMounted(() => {
// 加载卫生机构列表
function loadOrgList() {
getTenantPageSilent({ pageNo: 1, pageSize: 1000 })
getTenantPage({ pageNo: 1, pageSize: 1000 })
.then(res => {
if (res.code === 200) {
const records = res.data?.records || res.data || []
@@ -1154,14 +1136,12 @@ function loadDeptList() {
const tree = res.data?.records || res.data || []
deptList.value = flattenOrgTree(tree)
} else {
// 权限不足时静默降级不弹窗阻断Bug #441
console.warn('科室列表加载失败(可能无权限):', res.message || res.code)
proxy.$modal.msgError('获取科室列表失败')
deptList.value = []
}
})
.catch(error => {
// 网络错误或权限拒绝:静默降级
console.warn('科室列表加载失败:', error?.message || error)
proxy.$modal.msgError('获取科室列表失败')
deptList.value = []
})
}
@@ -1174,14 +1154,12 @@ function loadDoctorList() {
const records = res.data?.records || []
doctorList.value = records.map(mapPractitionerToOption).filter(it => it.code && it.name)
} else {
// 权限不足时静默降级不弹窗阻断Bug #441
console.warn('医生列表加载失败(可能无权限):', res.message || res.code)
proxy.$modal.msgError('获取医生列表失败')
doctorList.value = []
}
})
.catch(error => {
// 网络错误或权限拒绝:静默降级
console.warn('医生列表加载失败:', error?.message || error)
proxy.$modal.msgError('获取医生列表失败')
doctorList.value = []
})
}
@@ -1194,14 +1172,12 @@ function loadNurseList() {
const records = res.data?.records || []
nurseList.value = records.map(mapPractitionerToOption).filter(it => it.code && it.name)
} else {
// 权限不足时静默降级不弹窗阻断Bug #441
console.warn('护士列表加载失败(可能无权限):', res.message || res.code)
proxy.$modal.msgError('获取护士列表失败')
nurseList.value = []
}
})
.catch(error => {
// 网络错误或权限拒绝:静默降级
console.warn('护士列表加载失败:', error?.message || error)
proxy.$modal.msgError('获取护士列表失败')
nurseList.value = []
})
}
@@ -1214,14 +1190,12 @@ function loadOperatingRoomList() {
const records = res.data?.records || []
operatingRoomList.value = records.map(mapOperatingRoomToOption).filter(it => it.roomCode)
} else {
// 权限不足时静默降级不弹窗阻断Bug #441
console.warn('手术室列表加载失败(可能无权限):', res.message || res.code)
proxy.$modal.msgError('获取手术室列表失败')
operatingRoomList.value = []
}
})
.catch(error => {
// 网络错误或权限拒绝:静默降级
console.warn('手术室列表加载失败:', error?.message || error)
proxy.$modal.msgError('获取手术室列表失败')
operatingRoomList.value = []
})
}
@@ -1406,8 +1380,8 @@ async function handleChargeCharge(row) {
orgId: userStore.organizationId || userStore.orgId || userStore.tenantId || 1,
// 添加账户ID
accountId: accountId,
// 添加手术单号用于关联对应的手术医嘱
sourceBillNo: row.operCode,
// 添加手术申请单号用于追溯
sourceBillNo: row.applyId,
//添加计费标志手术计费
generateSourceEnum: 6
}
@@ -1430,16 +1404,13 @@ async function handleChargeCharge(row) {
// 关闭计费弹窗
function closeChargeDialog() {
// 先关闭 prescriptionlist 内所有已打开的项目字典 popover
// 先关闭子组件中所有悬浮的字典弹窗,避免主弹窗关闭后残留
if (prescriptionRef.value && prescriptionRef.value.closeAllPopovers) {
prescriptionRef.value.closeAllPopovers()
}
// 等 Vue 完成 DOM 更新后再关闭弹窗,确保 popover 先消失
nextTick(() => {
showChargeDialog.value = false
chargePatientInfo.value = {}
chargeSurgeryInfo.value = {}
})
showChargeDialog.value = false
chargePatientInfo.value = {}
chargeSurgeryInfo.value = {}
}
// 🔧 新增:标志位,用于区分是"打开"还是"刷新"
@@ -1468,8 +1439,7 @@ function handleMedicalAdvice(row) {
role: userStore.roles[0],
effectiveOrgId : row.effectiveOrgId,
orgId: userStore.orgId,
positionId: userStore.orgId,
applyId: row.applyId // 手术申请单ID用于过滤关联医嘱
positionId: userStore.orgId
}
// 🔧 关键修复:如果已有提交的医嘱数据,并且是同一个患者的就诊,则使用保存的数据
@@ -1767,7 +1737,7 @@ function handleQuoteBilling() {
// 重新拉取计费药品数据
if (temporaryPatientInfo.value.visitId) {
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) {
// 🔧 修复:先清空旧数据,避免数据累积
temporaryBillingMedicines.value = []
@@ -1944,7 +1914,6 @@ function resetForm() {
allergyRemark: undefined,
surgeryNature: undefined,
surgerySite: undefined,
incisionType: undefined,
admissionTime: undefined,
entryTime: undefined,
roomCode: undefined,
@@ -2042,7 +2011,7 @@ function handleFindApply() {
getSurgicalScheduleList()
}
// 获取手术申请列表(用于查找”弹窗)
// 获取手术申请列表(用于查找”弹窗)
function getSurgicalScheduleList() {
applyLoading.value = true
const params = { ...applyQueryParams }
@@ -2051,7 +2020,8 @@ function getSurgicalScheduleList() {
params.applyTimeEnd = params.applyTimeRange[1]
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
applyList.value = responseData.records || []
applyTotal.value = responseData.total || 0
@@ -2300,4 +2270,4 @@ function getRowClassName({ row, rowIndex }) {
border-bottom: 1px solid #d9ecff !important;
}
</style>
</style>

View File

@@ -198,52 +198,53 @@
</el-button>
</div>
<div class="queue-actions-right">
<el-button
:type="showOnlyWaiting ? 'primary' : ''"
size="small"
@click="showOnlyWaiting = true"
>
只显示等待
</el-button>
<el-button
:type="!showOnlyWaiting ? 'primary' : ''"
size="small"
@click="showOnlyWaiting = false"
>
显示全部状态
</el-button>
</div>
</div>
</div>
</div>
<!-- 底部控制面板 -->
<div class="footer-section">
<!-- Bug #411诊室快速过滤栏筛选维度从科室改为诊室 -->
<!-- 诊室快速过滤栏 -->
<div class="filter-section">
<div class="filter-left">
<div class="filter-label">
诊室快速过滤栏
</div>
<div class="filter-button-wrapper">
<el-button
:type="selectedRoom === 'all' ? 'primary' : ''"
size="small"
@click="selectedRoom = 'all'"
>
全部
</el-button>
<el-button
v-for="room in uniqueRooms"
:key="room"
:type="selectedRoom === room ? 'primary' : ''"
size="small"
@click="selectedRoom = room"
>
{{ room }}
</el-button>
</div>
<div class="filter-label">
诊室快速过滤栏
</div>
<div class="filter-right">
<el-button
:type="showOnlyWaiting ? 'primary' : ''"
size="small"
@click="showOnlyWaiting = true"
<div class="filter-select-wrapper">
<el-select
v-model="selectedClinicRoom"
placeholder="请选择诊室"
clearable
filterable
style="width: 100%"
size="default"
>
只显示等待
</el-button>
<el-button
:type="!showOnlyWaiting ? 'primary' : ''"
size="small"
@click="showOnlyWaiting = false"
>
显示全部状态
</el-button>
<el-option
label="全部"
value="all"
/>
<el-option
v-for="room in clinicRoomList"
:key="room"
:label="room"
:value="room"
/>
</el-select>
</div>
</div>
@@ -679,8 +680,10 @@ const selectedCandidates = ref([])
// 显示选项
const showOnlyWaiting = ref(false)
// Bug #411诊室过滤替代原来的科室下拉框selectedDept/departmentList 已移除
const selectedRoom = ref('all')
// 诊室过滤(按诊室维度筛选
const selectedClinicRoom = ref('all')
// 诊室列表(从数据中动态提取)
const clinicRoomList = ref([])
// 修复【#397】动态获取当前科室名称
const currentDeptName = computed(() => {
@@ -903,12 +906,11 @@ const mapFrontendStatusToBackend = (status) => {
// 从数据库加载队列
const loadQueueFromDb = async () => {
try {
// Bug #411不再按科室选筛加载后端默认按当前登录人科室查询
const organizationId = undefined
// 只查询今天的患者
// 使用当前登录人科室
const today = new Date()
const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`
const res = await getTriageQueueList({ organizationId, date: todayStr }).catch((err) => {
console.log('【心内科】loadQueueFromDb 开始date=', todayStr)
const res = await getTriageQueueList({ date: todayStr }).catch((err) => {
console.error('【心内科】loadQueueFromDb 请求异常:', err)
return { code: 500, msg: err?.message || '请求失败', data: null }
})
@@ -1136,6 +1138,8 @@ const loadDataFromApi = async () => {
// 同步当前呼叫(队列从 DB 加载后已同步;这里再兜底一次)
syncCurrentCallFromQueue()
// 提取诊室列表供过滤栏使用
extractClinicRooms()
console.log('【心内科】数据加载完成:候选池', originalCandidatePoolList.value.length, '条,队列', originalQueueList.value.length, '条')
ElMessage.success('【心内科】已从门诊挂号接口加载数据')
} catch (e) {
@@ -1147,35 +1151,42 @@ const loadDataFromApi = async () => {
totalSignedIn.value = originalCandidatePoolList.value.length
totalInQueue.value = originalQueueList.value.length
syncCurrentCallFromQueue()
extractClinicRooms()
}
}
// 原始数据存储(用于过滤)
const originalCandidatePoolList = ref(getInitialCandidatePoolList())
// 过滤后的智能候选池数据(按诊室过滤
// 提取诊室列表(从队列和候选池数据中动态获取
const extractClinicRooms = () => {
const roomSet = new Set()
// 从队列中提取
originalQueueList.value.forEach(item => {
if (item.room && item.room !== '-') {
roomSet.add(item.room)
}
})
// 从候选池中提取
originalCandidatePoolList.value.forEach(item => {
if (item.room && item.room !== '-') {
roomSet.add(item.room)
}
})
clinicRoomList.value = Array.from(roomSet).sort()
}
// 过滤后的智能候选池数据
const filteredCandidatePoolList = computed(() => {
if (selectedRoom.value === 'all') {
if (selectedClinicRoom.value === 'all') {
return originalCandidatePoolList.value
}
return originalCandidatePoolList.value.filter(item => item.room === selectedRoom.value)
return originalCandidatePoolList.value.filter(item => item.room === selectedClinicRoom.value)
})
// 原始队列数据存储(用于过滤)
const originalQueueList = ref(getInitialQueueList())
// 动态计算已加载数据中的唯一诊室列表(依赖上方两个 ref确保声明顺序正确
const uniqueRooms = computed(() => {
const rooms = new Set()
originalCandidatePoolList.value.forEach(item => {
if (item.room && item.room !== '-') rooms.add(item.room)
})
originalQueueList.value.forEach(item => {
if (item.room && item.room !== '-') rooms.add(item.room)
})
return Array.from(rooms).sort()
})
const parseMmSsToSeconds = (mmss) => {
if (!mmss || typeof mmss !== 'string') return 0
const [mm, ss] = mmss.split(':')
@@ -1192,7 +1203,7 @@ const formatSecondsToMmSs = (totalSeconds) => {
return `${mm}:${ss}`
}
// 过滤后的智能队列数据(Bug #411诊室过滤 + 状态过滤)
// 过滤后的智能队列数据(同时考虑诊室过滤状态过滤)
const filteredQueueList = computed(() => {
let filtered = originalQueueList.value
@@ -1200,8 +1211,8 @@ const filteredQueueList = computed(() => {
filtered = filtered.filter(item => item.status !== '已完成')
// 再按诊室过滤
if (selectedRoom.value !== 'all') {
filtered = filtered.filter(item => item.room === selectedRoom.value)
if (selectedClinicRoom.value !== 'all') {
filtered = filtered.filter(item => item.room === selectedClinicRoom.value)
}
// 再按状态过滤(只显示等待)
@@ -1712,12 +1723,16 @@ const handleNextPatient = async () => {
reqData.id = selectedQueueRow.value.id
reqData.organizationId = selectedQueueRow.value.organizationId
} else {
// Bug #411已移除 selectedDept改为从队列数据中动态获取科室
const calling = originalQueueList.value.find((i) => i.status === '叫号中')
const waiting = originalQueueList.value.find((i) => i.status === '等待')
console.log('【心内科】handleNextPatient 查找:叫号中=', calling?.patientName, '等待=', waiting?.patientName)
const orgId = calling?.organizationId ?? waiting?.organizationId
console.log('【心内科】handleNextPatient 确定的 orgId=', orgId)
// 如果没有选中患者,使用查询条件(兼容旧逻辑)
// 全科模式:优先用"当前叫号中/第一个等待"所在科室
let orgId = null
{
const calling = originalQueueList.value.find((i) => i.status === '叫号中')
const waiting = originalQueueList.value.find((i) => i.status === '等待')
console.log('【心内科】handleNextPatient 查找:叫号中=', calling?.patientName, '等待=', waiting?.patientName)
orgId = calling?.organizationId ?? waiting?.organizationId
console.log('【心内科】handleNextPatient 确定的 orgId=', orgId)
}
if (orgId != null) {
reqData.organizationId = orgId
}
@@ -1746,9 +1761,13 @@ const handleSkip = async () => {
reqData.id = selectedQueueRow.value.id
reqData.organizationId = selectedQueueRow.value.organizationId
} else {
// 如果没有选中患者,使用当前叫号中的科室
const calling = originalQueueList.value.find((i) => i.status === '叫号中')
const orgId = calling?.organizationId
// 如果没有选中患者,使用查询条件(兼容旧逻辑)
// 全科模式:优先用”当前叫号中”所在科室
let orgId = null
{
const calling = originalQueueList.value.find((i) => i.status === '叫号中')
orgId = calling?.organizationId
}
if (orgId != null) {
reqData.organizationId = orgId
}
@@ -1776,9 +1795,13 @@ const handleComplete = async () => {
reqData.id = selectedQueueRow.value.id
reqData.organizationId = selectedQueueRow.value.organizationId
} else {
// 如果没有选中患者,使用当前叫号中的科室
const calling = originalQueueList.value.find((i) => i.status === '叫号中')
const orgId = calling?.organizationId
// 如果没有选中患者,使用查询条件(兼容旧逻辑)
// 全科模式:优先用”当前叫号中”所在科室
let orgId = null
{
const calling = originalQueueList.value.find((i) => i.status === '叫号中')
orgId = calling?.organizationId
}
if (orgId != null) {
reqData.organizationId = orgId
}
@@ -1806,9 +1829,13 @@ const handleRequeue = async () => {
reqData.id = selectedQueueRow.value.id
reqData.organizationId = selectedQueueRow.value.organizationId
} else {
// 如果没有选中患者,使用当前叫号中的科室
const calling = originalQueueList.value.find((i) => i.status === '叫号中')
const orgId = calling?.organizationId
// 如果没有选中患者,使用查询条件(兼容旧逻辑)
// 全科模式:优先用”当前叫号中”所在科室
let orgId = null
{
const calling = originalQueueList.value.find((i) => i.status === '叫号中')
orgId = calling?.organizationId
}
if (orgId != null) {
reqData.organizationId = orgId
}
@@ -2192,6 +2219,10 @@ onUnmounted(() => {
gap: 10px;
}
.queue-actions-right {
display: flex;
gap: 10px;
}
}
.candidate-actions {
@@ -2213,32 +2244,16 @@ onUnmounted(() => {
.filter-section {
margin-bottom: 20px;
display: flex;
align-items: flex-start;
justify-content: space-between;
.filter-left {
flex: 1;
.filter-label {
font-size: 14px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.filter-button-wrapper {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.filter-label {
font-size: 14px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.filter-right {
display: flex;
gap: 8px;
flex-shrink: 0;
align-self: flex-end;
.filter-select-wrapper {
width: 100%;
}
}

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级切口';

View File

@@ -1,18 +0,0 @@
-- Bug #462: 诊疗目录编辑弹窗中"所需标本"下拉框数据加载失败
-- 根因: sys_dict_type 表中缺少 specimen_code 字典类型sys_dict_data 表中缺少对应数据
-- 修复: 插入字典类型及7条标本数据
-- 插入字典类型
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
VALUES ('标本类型', 'specimen_code', '0', 'admin', NOW(), '诊疗项目所需标本类型字典');
-- 插入标本数据
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, status, create_by, create_time, remark)
VALUES
(1, '血液', '1', 'specimen_code', '0', 'admin', NOW(), '血液标本'),
(2, '尿液', '2', 'specimen_code', '0', 'admin', NOW(), '尿液标本'),
(3, '粪便', '3', 'specimen_code', '0', 'admin', NOW(), '粪便标本'),
(4, '呼吸道', '4', 'specimen_code', '0', 'admin', NOW(), '呼吸道标本'),
(5, '无菌体液', '5', 'specimen_code', '0', 'admin', NOW(), '无菌体液标本'),
(6, '生殖道', '6', 'specimen_code', '0', 'admin', NOW(), '生殖道标本'),
(7, '其他', '7', 'specimen_code', '0', 'admin', NOW(), '其他标本');