Compare commits

..

18 Commits

Author SHA1 Message Date
赵云
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
67 changed files with 1291 additions and 2022 deletions

View File

@@ -25,7 +25,7 @@ public class SysTenantController extends BaseController {
private ISysTenantService sysTenantService; private ISysTenantService sysTenantService;
/** /**
* 查询租户分页列表(只读操作,不限制租户管理权限) * 查询租户分页列表
* *
* @param tenantId 租户ID查询 * @param tenantId 租户ID查询
* @param tenantCode 租户编码模糊查询 * @param tenantCode 租户编码模糊查询
@@ -35,7 +35,7 @@ public class SysTenantController extends BaseController {
* @param pageSize 每页多少条 * @param pageSize 每页多少条
* @return 租户分页列表 * @return 租户分页列表
*/ */
@PreAuthorize("@ss.hasPermi('system:tenant:list')") @PreAuthorize("@ss.hasPermi('system:tenant:operate')")
@GetMapping("/page") @GetMapping("/page")
public R<IPage<SysTenant>> getTenantPage(@RequestParam(required = false) Integer tenantId, public R<IPage<SysTenant>> getTenantPage(@RequestParam(required = false) Integer tenantId,
@RequestParam(required = false) String tenantCode, @RequestParam(required = false) String tenantName, @RequestParam(required = false) String tenantCode, @RequestParam(required = false) String tenantName,
@@ -45,19 +45,19 @@ public class SysTenantController extends BaseController {
} }
/** /**
* 查询租户详情(只读操作) * 查询租户详情
* *
* @param tenantId 租户ID * @param tenantId 租户ID
* @return 租户分页列表 * @return 租户分页列表
*/ */
@PreAuthorize("@ss.hasPermi('system:tenant:list')") @PreAuthorize("@ss.hasPermi('system:tenant:operate')")
@GetMapping("/{tenantId}") @GetMapping("/{tenantId}")
public R<SysTenant> getTenantDetail(@PathVariable Integer tenantId) { public R<SysTenant> getTenantDetail(@PathVariable Integer tenantId) {
return R.ok(sysTenantService.getById(tenantId)); return R.ok(sysTenantService.getById(tenantId));
} }
/** /**
* 查询租户所属用户分页列表(只读操作) * 查询租户所属用户分页列表
* *
* @param tenantId 租户ID查询 * @param tenantId 租户ID查询
* @param userName 用户昵称模糊查询 * @param userName 用户昵称模糊查询
@@ -67,7 +67,7 @@ public class SysTenantController extends BaseController {
* @param pageSize 每页多少条 * @param pageSize 每页多少条
* @return 租户所属用户分页列表 * @return 租户所属用户分页列表
*/ */
@PreAuthorize("@ss.hasPermi('system:tenant:list')") @PreAuthorize("@ss.hasPermi('system:tenant:operate')")
@GetMapping("/user/page") @GetMapping("/user/page")
public R<IPage<SysUser>> getTenantUserPage(@RequestParam(required = false) Integer tenantId, public R<IPage<SysUser>> getTenantUserPage(@RequestParam(required = false) Integer tenantId,
@RequestParam(required = false) String userName, @RequestParam(required = false) String nickName, @RequestParam(required = false) String userName, @RequestParam(required = false) String nickName,
@@ -141,14 +141,14 @@ public class SysTenantController extends BaseController {
} }
/** /**
* 查询租户未绑定的用户列表(只读操作) * 查询租户未绑定的用户列表
* *
* @param tenantId 租户ID * @param tenantId 租户ID
* @param pageNum 当前页 * @param pageNum 当前页
* @param pageSize 每页多少条 * @param pageSize 每页多少条
* @return 结果 * @return 结果
*/ */
@PreAuthorize("@ss.hasPermi('system:tenant:list')") @PreAuthorize("@ss.hasPermi('system:tenant:operate')")
@GetMapping("/{tenantId}/unbind-users") @GetMapping("/{tenantId}/unbind-users")
public R<IPage<SysUser>> getUnbindTenantUserList(@PathVariable Integer tenantId, public R<IPage<SysUser>> getUnbindTenantUserList(@PathVariable Integer tenantId,
@RequestParam(required = false) String userName, @RequestParam(required = false) String nickName, @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) { public R<List<SysTenant>> getUserBindTenantList(@PathVariable String username) {
return sysTenantService.getUserBindTenantList(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.appservice.ITicketAppService;
import com.openhis.web.appointmentmanage.dto.TicketDto; import com.openhis.web.appointmentmanage.dto.TicketDto;
import com.openhis.common.constant.CommonConstants.SlotStatus; 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 org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
@@ -198,11 +198,10 @@ public class TicketAppServiceImpl implements ITicketAppService {
if (SlotStatus.CHECKED_IN.equals(slotStatus)) { if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
dto.setStatus("已取号"); dto.setStatus("已取号");
} else if (SlotStatus.BOOKED.equals(slotStatus)) { } else if (SlotStatus.BOOKED.equals(slotStatus)) {
// order_main.status: 0=患者取消(已退号) 2=系统取消 其余=已预约 if (AppointmentOrderStatus.CHECKED_IN.equals(raw.getOrderStatus())) {
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) { dto.setStatus("已取号");
} else if (AppointmentOrderStatus.RETURNED.equals(raw.getOrderStatus())) {
dto.setStatus("已退号"); dto.setStatus("已退号");
} else if (OrderStatus.SYSTEM_CANCELLED.getValue().equals(raw.getOrderStatus())) {
dto.setStatus("系统取消");
} else { } else {
dto.setStatus("已预约"); dto.setStatus("已预约");
} }
@@ -373,11 +372,10 @@ public class TicketAppServiceImpl implements ITicketAppService {
if (SlotStatus.CHECKED_IN.equals(slotStatus)) { if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
dto.setStatus("已取号"); dto.setStatus("已取号");
} else if (SlotStatus.BOOKED.equals(slotStatus)) { } else if (SlotStatus.BOOKED.equals(slotStatus)) {
// order_main.status: 0=患者取消(已退号) 2=系统取消 其余=已预约 if (AppointmentOrderStatus.CHECKED_IN.equals(raw.getOrderStatus())) {
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) { dto.setStatus("已取号");
} else if (AppointmentOrderStatus.RETURNED.equals(raw.getOrderStatus())) {
dto.setStatus("已退号"); dto.setStatus("已退号");
} else if (OrderStatus.SYSTEM_CANCELLED.getValue().equals(raw.getOrderStatus())) {
dto.setStatus("系统取消");
} else { } else {
dto.setStatus("已预约"); 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.Location;
import com.openhis.administration.domain.Organization; import com.openhis.administration.domain.Organization;
import com.openhis.administration.domain.OrganizationLocation; import com.openhis.administration.domain.OrganizationLocation;
import com.openhis.workflow.domain.ActivityDefinition;
import com.openhis.administration.mapper.OrganizationLocationMapper; import com.openhis.administration.mapper.OrganizationLocationMapper;
import com.openhis.administration.service.ILocationService; import com.openhis.administration.service.ILocationService;
import com.openhis.administration.service.IOrganizationLocationService; 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<Organization> organizationList = organizationService.getList(OrganizationType.DEPARTMENT.getValue(), null);
List<OrgLocInitDto.departmentOption> organizationOptions = organizationList.stream() List<OrgLocInitDto.departmentOption> organizationOptions = organizationList.stream()
.filter(organization -> organization != null && organization.getName() != null)
.map(organization -> new OrgLocInitDto.departmentOption(organization.getId(), organization.getName())) .map(organization -> new OrgLocInitDto.departmentOption(organization.getId(), organization.getName()))
.collect(Collectors.toList()); .collect(Collectors.toList());
initDto.setLocationFormOptions(chargeItemStatusOptions).setDepartmentOptions(organizationOptions); initDto.setLocationFormOptions(chargeItemStatusOptions).setDepartmentOptions(organizationOptions);
@@ -133,18 +131,11 @@ public class OrganizationLocationAppServiceImpl implements IOrganizationLocation
@Override @Override
public R<?> addOrEditOrgLoc(OrgLocQueryDto orgLocQueryDto) { public R<?> addOrEditOrgLoc(OrgLocQueryDto orgLocQueryDto) {
// Validate required fields before processing
if (orgLocQueryDto.getOrganizationId() == null) {
return R.fail("请选择执行科室");
}
OrganizationLocation orgLoc = new OrganizationLocation(); OrganizationLocation orgLoc = new OrganizationLocation();
BeanUtils.copyProperties(orgLocQueryDto, orgLoc); BeanUtils.copyProperties(orgLocQueryDto, orgLoc);
Long activityDefinitionId = orgLoc.getActivityDefinitionId(); Long activityDefinitionId = orgLoc.getActivityDefinitionId();
ActivityDefinition activityDef = activityDefinitionId != null String activityName = activityDefinitionId != null ? activityDefinitionMapper.selectById(activityDefinitionId).getName() : "";
? activityDefinitionMapper.selectById(activityDefinitionId) : null;
String activityName = activityDef != null ? activityDef.getName() : "";
List<OrganizationLocation> organizationLocationList = List<OrganizationLocation> organizationLocationList =
organizationLocationService.getOrgLocListByOrgIdAndActivityDefinitionId(orgLoc.getActivityDefinitionId()); 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.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 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.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R; 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) { if (result != null && result.getCode() == 200) {
refundOrderMainId = syncAppointmentReturnStatus(byId, cancelRegPaymentDto.getReason()); syncAppointmentReturnStatus(byId, cancelRegPaymentDto.getReason());
// 同步移除分诊队列中的记录
removeTriageQueueItem(byId.getId()); removeTriageQueueItem(byId.getId());
} }
// 退号日志独立事务写入,无论退费成功与否均记录
recordRefundLog(cancelRegPaymentDto, byId, result, paymentRecon, refundOrderMainId); // 记录退号日志
recordRefundLog(cancelRegPaymentDto, byId, result, paymentRecon);
// 2025/05/05 该处保存费用项后,会通过统一收费处理进行收费 // 2025/05/05 该处保存费用项后,会通过统一收费处理进行收费
return R.ok(paymentRecon, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[] {"退号"})); 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>() LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<Order>()
.eq(Order::getPatientId, encounter.getPatientId()) .eq(Order::getPatientId, encounter.getPatientId())
.in(Order::getStatus, CommonConstants.AppointmentOrderStatus.BOOKED,
CommonConstants.AppointmentOrderStatus.CHECKED_IN)
.orderByDesc(Order::getUpdateTime) .orderByDesc(Order::getUpdateTime)
.orderByDesc(Order::getCreateTime) .orderByDesc(Order::getCreateTime)
.last("LIMIT 1"); .last("LIMIT 1");
@@ -587,25 +590,20 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
} }
/** /**
* 诊前退号:回滚预约订单、号源槽位、号源池统计 * 同步预约号源状态为已退号
* * 说明:
* <p>处理四件事: * 1) 门诊退号主流程不依赖该步骤成功与否,因此此方法内部异常仅记录日志,不向上抛出。
* <ol> * 2) 通过患者、科室、日期以及状态筛选最近一条预约订单,尽量避免误匹配。
* <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>异常仅记录日志不向上抛,不影响主流程返回成功。
*/ */
private Long syncAppointmentReturnStatus(Encounter encounter, String reason) { private void syncAppointmentReturnStatus(Encounter encounter, String reason) {
if (encounter == null || encounter.getPatientId() == null) { if (encounter == null || encounter.getPatientId() == null) {
return null; return;
} }
try { try {
LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<Order>() LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<Order>()
.eq(Order::getPatientId, encounter.getPatientId()) .eq(Order::getPatientId, encounter.getPatientId())
.in(Order::getStatus, CommonConstants.AppointmentOrderStatus.BOOKED,
CommonConstants.AppointmentOrderStatus.CHECKED_IN)
.orderByDesc(Order::getUpdateTime) .orderByDesc(Order::getUpdateTime)
.orderByDesc(Order::getCreateTime) .orderByDesc(Order::getCreateTime)
.last("LIMIT 1"); .last("LIMIT 1");
@@ -627,55 +625,35 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
Order appointmentOrder = orderService.getOne(queryWrapper, false); Order appointmentOrder = orderService.getOne(queryWrapper, false);
if (appointmentOrder == null) { if (appointmentOrder == null) {
return null; return;
} }
// 只有有效订单(1)才能退号 Date now = new Date();
if (!OrderStatus.ACTIVE.getValue().equals(appointmentOrder.getStatus())) { if (!CommonConstants.AppointmentOrderStatus.RETURNED.equals(appointmentOrder.getStatus())) {
log.warn("退号跳过:订单状态非有效, orderId={}, status={}", Order updateOrder = new Order();
appointmentOrder.getId(), appointmentOrder.getStatus()); updateOrder.setId(appointmentOrder.getId());
return null; updateOrder.setStatus(CommonConstants.AppointmentOrderStatus.RETURNED);
} updateOrder.setPayStatus(0);
updateOrder.setCancelTime(now);
// 乐观锁更新WHERE version = 旧值,防并发重复退号 updateOrder.setCancelReason("门诊退号");
boolean updated = orderService.update( updateOrder.setUpdateTime(now);
new LambdaUpdateWrapper<Order>() orderService.updateById(updateOrder);
.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;
} }
Long slotId = appointmentOrder.getSlotId(); Long slotId = appointmentOrder.getSlotId();
if (slotId == null) { 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) { if (slotRows > 0) {
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId); Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
if (poolId != null) { if (poolId != null) {
schedulePoolMapper.refreshPoolStats(poolId); 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) { } catch (Exception e) {
log.warn("同步预约号源已退号状态失败, encounterId={}", encounter.getId(), 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 cancelRegPaymentDto 退号请求对象
* @param encounter 就诊信息 * @param encounter 就诊信息
* @param result 退号结果 * @param result 退号结果
* @param paymentRecon 支付对账信息 * @param paymentRecon 支付对账信息
* @param orderMainId 预约订单主键order_main.id用于关联业务数据
*/ */
@Transactional(propagation = Propagation.REQUIRES_NEW) @Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordRefundLog(CancelRegPaymentDto cancelRegPaymentDto, public void recordRefundLog(CancelRegPaymentDto cancelRegPaymentDto,
Encounter encounter, Encounter encounter,
R<?> result, R<?> result,
PaymentReconciliation paymentRecon, PaymentReconciliation paymentRecon) {
Long orderMainId) {
RefundLog refundLog = new RefundLog(); RefundLog refundLog = new RefundLog();
try { try {
// 1. 订单ID关联 order_main.id // 1. 订单ID唯一
String orderId = orderMainId != null String orderId = String.valueOf(cancelRegPaymentDto.getEncounterId());
? String.valueOf(orderMainId)
: String.valueOf(cancelRegPaymentDto.getEncounterId());
refundLog.setOrderId(orderId); refundLog.setOrderId(orderId);
// 已存在则不重复插入(防止唯一约束异常) // 已存在则不重复插入(防止唯一约束异常)

View File

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

View File

@@ -246,16 +246,6 @@ public class OpCreateScheduleDto {
*/ */
private String communicationInfo; 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 createByName;
/**
* 费用类别
*/
private String feeType;
} }

View File

@@ -231,49 +231,29 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
// 费用定价主表ID集合 // 费用定价主表ID集合
List<Long> chargeItemDefinitionIdList = adviceBaseDtoList.stream().map(AdviceBaseDto::getChargeItemDefinitionId) List<Long> chargeItemDefinitionIdList = adviceBaseDtoList.stream().map(AdviceBaseDto::getChargeItemDefinitionId)
.collect(Collectors.toList()); .collect(Collectors.toList());
// 医嘱库存集合
// 判断是否包含药品或耗材类型(只有这些类型才需要库存相关查询) List<AdviceInventoryDto> adviceInventoryList = doctorStationAdviceAppMapper.getAdviceInventory(locationId,
boolean hasMedOrDevice = adviceTypes != null adviceDefinitionIdList,
&& (adviceTypes.contains(1) || adviceTypes.contains(2)); CommonConstants.SqlCondition.ABOUT_INVENTORY_TABLE_STR, PublicationStatus.ACTIVE.getValue());
// 待发放个数信息
// 医嘱库存集合 — 仅药品/耗材需要库存查询,手术/诊疗(3,6)无库存概念,跳过以减少数据库开销 List<AdviceInventoryDto> adviceDraftInventoryList = doctorStationAdviceAppMapper.getAdviceDraftInventory(
List<AdviceInventoryDto> adviceInventoryList; CommonConstants.TableName.MED_MEDICATION_DEFINITION, CommonConstants.TableName.ADM_DEVICE_DEFINITION,
List<AdviceInventoryDto> adviceDraftInventoryList; DispenseStatus.DRAFT.getValue(), DispenseStatus.PREPARATION.getValue());
List<AdviceInventoryDto> adviceInventory; // 预减库存
if (hasMedOrDevice) { List<AdviceInventoryDto> adviceInventory = adviceUtils.subtractInventory(adviceInventoryList,
adviceInventoryList = doctorStationAdviceAppMapper.getAdviceInventory(locationId, adviceDraftInventoryList);
adviceDefinitionIdList, // 查询取药科室配置
CommonConstants.SqlCondition.ABOUT_INVENTORY_TABLE_STR, PublicationStatus.ACTIVE.getValue()); List<AdviceInventoryDto> medLocationConfig = doctorStationAdviceAppMapper.getMedLocationConfig(organizationId);
// 待发放个数信息 // 将配置转为 {categoryCode -> 允许的locationId集合}
adviceDraftInventoryList = doctorStationAdviceAppMapper.getAdviceDraftInventory( Map<String, Set<Long>> allowedLocByCategory = new HashMap<>();
CommonConstants.TableName.MED_MEDICATION_DEFINITION, CommonConstants.TableName.ADM_DEVICE_DEFINITION, if (medLocationConfig != null && !medLocationConfig.isEmpty()) {
DispenseStatus.DRAFT.getValue(), DispenseStatus.PREPARATION.getValue()); for (AdviceInventoryDto cfg : medLocationConfig) {
// 预减库存 if (cfg.getCategoryCode() == null || cfg.getLocationId() == null) {
adviceInventory = adviceUtils.subtractInventory(adviceInventoryList, adviceDraftInventoryList); continue;
} 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());
} }
allowedLocByCategory.computeIfAbsent(String.valueOf(cfg.getCategoryCode()), k -> new HashSet<>())
.add(cfg.getLocationId());
} }
} else {
medLocationConfig = Collections.emptyList();
allowedLocByCategory = Collections.emptyMap();
} }
// 费用定价子表信息 - 使用分批处理避免大量参数问题 // 费用定价子表信息 - 使用分批处理避免大量参数问题
List<AdvicePriceDto> childCharge = new ArrayList<>(); List<AdvicePriceDto> childCharge = new ArrayList<>();

View File

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

View File

@@ -175,10 +175,10 @@ public class AdviceUtils {
// 预减库存 // 预减库存
List<AdviceInventoryDto> adviceInventory List<AdviceInventoryDto> adviceInventory
= this.subtractInventory(adviceInventoryList, adviceDraftInventoryList); = this.subtractInventory(adviceInventoryList, adviceDraftInventoryList);
// 生命提示信息集合 // 提示信息集合
List<String> tipsList = new ArrayList<>(); List<String> tipsList = new ArrayList<>();
for (MedicationRequestUseExe medicationRequestUseExe : medUseExeList) { for (MedicationRequestUseExe medicationRequestUseExe : medUseExeList) {
// 聚合同一位置所有批次库存总量 // 汇总同一地点该药品的所有批次库存总量
List<AdviceInventoryDto> matchedInventories = adviceInventory.stream() List<AdviceInventoryDto> matchedInventories = adviceInventory.stream()
.filter(inventoryDto -> medicationRequestUseExe.getMedicationId().equals(inventoryDto.getItemId()) .filter(inventoryDto -> medicationRequestUseExe.getMedicationId().equals(inventoryDto.getItemId())
&& CommonConstants.TableName.MED_MEDICATION_DEFINITION.equals(inventoryDto.getItemTable()) && CommonConstants.TableName.MED_MEDICATION_DEFINITION.equals(inventoryDto.getItemTable())
@@ -189,15 +189,17 @@ public class AdviceUtils {
.collect(Collectors.toList()); .collect(Collectors.toList());
// 匹配到库存信息 // 匹配到库存信息
if (!matchedInventories.isEmpty()) { if (!matchedInventories.isEmpty()) {
// 聚合所有批次的可用库存 // 计算总可用库存
BigDecimal totalQuantity = matchedInventories.stream() BigDecimal totalAvailableQuantity = matchedInventories.stream()
.map(dto -> dto.getQuantity() != null ? dto.getQuantity() : BigDecimal.ZERO) .map(AdviceInventoryDto::getQuantity)
.reduce(BigDecimal.ZERO, BigDecimal::add); .reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal requestQuantity = medicationRequestUseExe.getExecuteTimesNum() BigDecimal totalRequiredQuantity = medicationRequestUseExe.getExecuteTimesNum()
.multiply(medicationRequestUseExe.getMinUnitQuantity()); .multiply(medicationRequestUseExe.getMinUnitQuantity());
if (requestQuantity.compareTo(totalQuantity) > 0) { if (totalRequiredQuantity.compareTo(totalAvailableQuantity) > 0) {
// 取第一个匹配的位置名称用于提示
String locationName = matchedInventories.get(0).getLocationName();
tipsList tipsList
.add("" + medicationRequestUseExe.getBusNo() + "】在" + matchedInventories.get(0).getLocationName() + "库存不足"); .add("" + medicationRequestUseExe.getBusNo() + "】在" + locationName + "库存不足");
} }
} else { } else {
tipsList.add("" + medicationRequestUseExe.getBusNo() + "】未匹配到库存信息"); 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.enums.*;
import com.openhis.common.utils.EnumUtils; import com.openhis.common.utils.EnumUtils;
import com.openhis.common.utils.HisQueryUtils; 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.basedatamanage.dto.LocationDto;
import com.openhis.web.common.dto.UnitDto; import com.openhis.web.common.dto.UnitDto;
import com.openhis.web.inventorymanage.appservice.IProductTransferAppService; import com.openhis.web.inventorymanage.appservice.IProductTransferAppService;
@@ -60,9 +57,6 @@ public class ProductTransferAppServiceImpl implements IProductTransferAppService
@Autowired @Autowired
private IPractitionerService practitionerService; private IPractitionerService practitionerService;
@Autowired
private IInventoryItemService inventoryItemService;
/** /**
* 商品调拨页面初始化 * 商品调拨页面初始化
* *
@@ -202,23 +196,6 @@ public class ProductTransferAppServiceImpl implements IProductTransferAppService
@Override @Override
public R<?> addOrEditBatchTransferReceipt(List<ProductTransferDto> productTransferDtoList, Boolean flag) { 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<>(); List<String> idList = new ArrayList<>();
if (flag) { if (flag) {
// 批量保存按钮 // 批量保存按钮
@@ -332,22 +309,6 @@ public class ProductTransferAppServiceImpl implements IProductTransferAppService
@Override @Override
public R<?> addOrEditTransferReceipt(List<ProductTransferDto> productTransferDtoList) { 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<>(); List<String> idList = new ArrayList<>();
// 单据号取得 // 单据号取得
@@ -419,25 +380,6 @@ public class ProductTransferAppServiceImpl implements IProductTransferAppService
*/ */
@Override @Override
public R<?> submitApproval(String busNo) { 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); boolean result = supplyRequestService.submitApproval(busNo);
return result ? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, null)) 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( Order appointmentOrder = iOrderService.getOne(
new LambdaQueryWrapper<Order>() new LambdaQueryWrapper<Order>()
.eq(Order::getPatientId, encounterFormData.getPatientId()) .eq(Order::getPatientId, encounterFormData.getPatientId())
.eq(Order::getStatus, OrderStatus.ACTIVE.getValue()) // 有效订单(1) .eq(Order::getStatus, CommonConstants.AppointmentOrderStatus.CHECKED_IN)
.eq(Order::getDeleteFlag, "0") .eq(Order::getDeleteFlag, "0")
.orderByDesc(Order::getCreateTime) .orderByDesc(Order::getCreateTime)
.last("LIMIT 1") .last("LIMIT 1")
@@ -2114,11 +2114,11 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
Long queuePoolId = null; Long queuePoolId = null;
Long queueSlotId = null; Long queueSlotId = null;
try { try {
// 查询患者当天有效订单(1);已取消(0/2)和已完成(3)的不参与排队 // 查询患者当天的待签到预约订单status = 1 或 2 表示已预约或已取号)
Order order = iOrderService.getOne( Order order = iOrderService.getOne(
new LambdaQueryWrapper<Order>() new LambdaQueryWrapper<Order>()
.eq(Order::getPatientId, encounter.getPatientId()) .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") .eq(Order::getDeleteFlag, "0")
.orderByDesc(Order::getCreateTime) .orderByDesc(Order::getCreateTime)
.last("LIMIT 1") .last("LIMIT 1")

View File

@@ -39,22 +39,10 @@ public interface IRequestFormManageAppService {
* @param typeCode 申请单类型 * @param typeCode 申请单类型
* @param startDate 开始日期可选格式yyyy-MM-dd * @param startDate 开始日期可选格式yyyy-MM-dd
* @param endDate 结束日期可选格式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 status 单据状态(可选)
* @param keyword 关键字(可选,申请单号/项目名称模糊匹配)
* @return 申请单列表 * @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()))) && (DbOpType.INSERT.getCode().equals(e.getDbOpType()) || DbOpType.UPDATE.getCode().equals(e.getDbOpType())))
.collect(Collectors.toList()); .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) { for (RegAdviceSaveDto regAdviceSaveDto : longInsertOrUpdateList) {
boolean firstTimeSave = false;// 第一次保存 boolean firstTimeSave = false;// 第一次保存
longMedicationRequest = new MedicationRequest(); longMedicationRequest = new MedicationRequest();
@@ -428,29 +406,6 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
.getValue().equals(e.getTherapyEnum()) .getValue().equals(e.getTherapyEnum())
&& (DbOpType.INSERT.getCode().equals(e.getDbOpType()) || DbOpType.UPDATE.getCode().equals(e.getDbOpType()))) && (DbOpType.INSERT.getCode().equals(e.getDbOpType()) || DbOpType.UPDATE.getCode().equals(e.getDbOpType())))
.collect(Collectors.toList()); .collect(Collectors.toList());
// 防重复保存:对新增医嘱进行去重
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) { for (RegAdviceSaveDto regAdviceSaveDto : tempInsertOrUpdateList) {
boolean firstTimeSave = false;// 第一次保存 boolean firstTimeSave = false;// 第一次保存
tempMedicationRequest = new MedicationRequest(); tempMedicationRequest = new MedicationRequest();

View File

@@ -414,7 +414,7 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
@Override @Override
public List<RequestFormQueryDto> getRequestForm(Long encounterId, String typeCode) { public List<RequestFormQueryDto> getRequestForm(Long encounterId, String typeCode) {
// 调用重载方法,不传筛选参数 // 调用重载方法,不传筛选参数
return getRequestForm(encounterId, typeCode, null, null); return getRequestForm(encounterId, typeCode, null, null, null);
} }
/** /**
@@ -424,32 +424,17 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
* @param typeCode 申请单类型 * @param typeCode 申请单类型
* @param startDate 开始日期可选格式yyyy-MM-dd * @param startDate 开始日期可选格式yyyy-MM-dd
* @param endDate 结束日期可选格式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 status 单据状态(可选)
* @param keyword 关键字(可选,申请单号/项目名称模糊匹配)
* @return 申请单列表 * @return 申请单列表
*/ */
@Override @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) { 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) { for (RequestFormQueryDto requestFormQueryDto : requestFormList) {
// 查询处方详情 // 查询处方详情
List<RequestFormDetailQueryDto> requestFormDetail = List<RequestFormDetailQueryDto> requestFormDetail =

View File

@@ -98,7 +98,6 @@ public class RequestFormManageController {
* @param startDate 开始日期可选格式yyyy-MM-dd * @param startDate 开始日期可选格式yyyy-MM-dd
* @param endDate 结束日期可选格式yyyy-MM-dd * @param endDate 结束日期可选格式yyyy-MM-dd
* @param status 单据状态(可选) * @param status 单据状态(可选)
* @param keyword 关键字(可选,申请单号/检验项目模糊匹配)
* @return 检验申请单 * @return 检验申请单
*/ */
@GetMapping(value = "/get-inspection") @GetMapping(value = "/get-inspection")
@@ -106,12 +105,11 @@ public class RequestFormManageController {
@RequestParam(required = false) Long encounterId, @RequestParam(required = false) Long encounterId,
@RequestParam(required = false) String startDate, @RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate, @RequestParam(required = false) String endDate,
@RequestParam(required = false) String status, @RequestParam(required = false) String status) {
@RequestParam(required = false) String keyword) {
if (encounterId == null) { if (encounterId == null) {
return R.fail("就诊ID不能为空"); return R.fail("就诊ID不能为空");
} }
return R.ok(iRequestFormManageAppService.getRequestForm(encounterId, ActivityDefCategory.PROOF.getCode(), startDate, endDate, status, keyword)); return R.ok(iRequestFormManageAppService.getRequestForm(encounterId, ActivityDefCategory.PROOF.getCode(), startDate, endDate, status));
} }
/** /**

View File

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

View File

@@ -6,7 +6,7 @@ spring:
druid: druid:
# 主库数据源 # 主库数据源
master: 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 username: postgresql
password: Jchl1528 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 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 = 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.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 = #{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 = #{medication} THEN T3."name"
WHEN T1.context_enum = #{device} THEN T4."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 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 = 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.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 = #{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 = #{medication} THEN T3."name"
WHEN T1.context_enum = #{device} THEN T4."name" WHEN T1.context_enum = #{device} THEN T4."name"

View File

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

View File

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

View File

@@ -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) 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' WHERE t1.delete_flag = '0'
AND T2.status_enum = #{statusEnum} AND T2.status_enum = #{statusEnum}
<if test="pricingFlag == 1">
AND 1 = 2
</if>
<if test="categoryCode != null and categoryCode != ''"> <if test="categoryCode != null and categoryCode != ''">
<!-- 🔧 BugFix: 支持两种匹配方式 --> <!-- 🔧 BugFix: 支持两种匹配方式 -->
<!-- 1. 直接匹配distribution_category_code = category_code都是数字代码 --> <!-- 1. 直接匹配distribution_category_code = category_code都是数字代码 -->
@@ -541,9 +544,9 @@
CI.service_id AS request_id, CI.service_id AS request_id,
CI.service_id || '-ci-dev' AS unique_key, CI.service_id || '-ci-dev' AS unique_key,
'' AS prescription_no, '' 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, 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, DR.content_json AS content_json,
NULL AS skin_test_flag, NULL AS skin_test_flag,
NULL AS inject_flag, NULL AS inject_flag,

View File

@@ -231,7 +231,7 @@
ae.priority_enum, ae.priority_enum,
ae.organization_id, ae.organization_id,
ae.start_time AS in_hos_time, 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_id AS bed_id,
bed.location_name AS bed_name, bed.location_name AS bed_name,
house.location_id AS house_id, house.location_id AS house_id,

View File

@@ -12,24 +12,13 @@
drf.desc_json, drf.desc_json,
drf.requester_id, drf.requester_id,
drf.create_time, drf.create_time,
ap.NAME AS patient_name, drf.status,
CASE MIN(wsr.status_enum) ap.NAME AS patient_name
WHEN 1 THEN 0
WHEN 2 THEN 1
WHEN 3 THEN 4
WHEN 4 THEN 4
WHEN 5 THEN 5
WHEN 6 THEN 5
WHEN 7 THEN 5
ELSE NULL
END AS status
FROM doc_request_form AS drf FROM doc_request_form AS drf
LEFT JOIN adm_encounter AS ae ON ae.ID = drf.encounter_id LEFT JOIN adm_encounter AS ae ON ae.ID = drf.encounter_id
AND ae.delete_flag = '0' AND ae.delete_flag = '0'
LEFT JOIN adm_patient AS ap ON ap.ID = ae.patient_id LEFT JOIN adm_patient AS ap ON ap.ID = ae.patient_id
AND ap.delete_flag = '0' 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' WHERE drf.delete_flag = '0'
AND drf.encounter_id = #{encounterId} AND drf.encounter_id = #{encounterId}
AND drf.type_code = #{typeCode} AND drf.type_code = #{typeCode}
@@ -40,32 +29,8 @@
AND drf.create_time &lt;= (#{endDate}::date + INTERVAL '1 day' - INTERVAL '1 second') AND drf.create_time &lt;= (#{endDate}::date + INTERVAL '1 day' - INTERVAL '1 second')
</if> </if>
<if test="status != null and status != ''"> <if test="status != null and status != ''">
AND CASE MIN(wsr.status_enum) AND drf.status = #{status}::integer
WHEN 1 THEN 0
WHEN 2 THEN 1
WHEN 3 THEN 4
WHEN 4 THEN 4
WHEN 5 THEN 5
WHEN 6 THEN 5
WHEN 7 THEN 5
ELSE NULL
END = #{status}::integer
</if> </if>
<if test="keyword != null and keyword != ''">
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>
<select id="getRequestFormDetail" resultType="com.openhis.web.regdoctorstation.dto.RequestFormDetailQueryDto"> <select id="getRequestFormDetail" resultType="com.openhis.web.regdoctorstation.dto.RequestFormDetailQueryDto">

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -475,13 +475,8 @@ function calculateTotalPrice() {
}); });
totalPrice.value = sum.toFixed(2); totalPrice.value = sum.toFixed(2);
// Bug #464: 零售价与诊疗子项合计总价实时同步 // Bug #464: 零售价与诊疗子项合计总价实时同步
const hasValidItem = treatmentItems.value.some( if (treatmentItems.value.length > 0 && treatmentItems.value[0].adviceDefinitionId !== '') {
(item) => item.adviceDefinitionId && item.adviceDefinitionId !== ''
);
if (hasValidItem) {
form.value.retailPrice = parseFloat(totalPrice.value); form.value.retailPrice = parseFloat(totalPrice.value);
} else {
form.value.retailPrice = undefined;
} }
} catch (error) { } catch (error) {
totalPrice.value = '0.00'; totalPrice.value = '0.00';
@@ -491,7 +486,7 @@ function calculateTotalPrice() {
// 添加表单项 // 添加表单项
function addItem() { function addItem() {
treatmentItems.value.push({ adviceDefinitionId: '', childrenRequestNum: 1, name: '', retailPrice: 0 }); treatmentItems.value.push({ adviceDefinitionId: '', childrenRequestNum: 1, retailPrice: 0 });
// 使用nextTick确保DOM更新后再计算 // 使用nextTick确保DOM更新后再计算
nextTick(() => { nextTick(() => {
calculateTotalPrice(); calculateTotalPrice();
@@ -652,15 +647,12 @@ async function submitForm() {
form.value.ybMatchFlag ? (form.value.ybMatchFlag = 1) : (form.value.ybMatchFlag = 0); form.value.ybMatchFlag ? (form.value.ybMatchFlag = 1) : (form.value.ybMatchFlag = 0);
form.value.ruleId ? (form.value.ruleId = 1) : (form.value.ruleId = 0); form.value.ruleId ? (form.value.ruleId = 1) : (form.value.ruleId = 0);
form.value.childrenJson = form.value.childrenJson =
treatmentItems.value.some((item) => item.adviceDefinitionId != '' && item.adviceDefinitionId) treatmentItems.value.length > 0 && treatmentItems.value[0].adviceDefinitionId != ''
? JSON.stringify(treatmentItems.value) ? JSON.stringify(treatmentItems.value)
: undefined; : undefined;
// Bug #464 修复:零售价自动与诊疗子项合计总价同步 // Bug #464 修复:零售价自动与诊疗子项合计总价同步
// 当有子项时,零售价自动设置为子项合计总价 // 当有子项时,零售价自动设置为子项合计总价
const hasValidItem = treatmentItems.value.some( if (treatmentItems.value.length > 0 && treatmentItems.value[0].adviceDefinitionId != '') {
(item) => item.adviceDefinitionId && item.adviceDefinitionId !== ''
);
if (hasValidItem) {
form.value.retailPrice = parseFloat(totalPrice.value) || 0; form.value.retailPrice = parseFloat(totalPrice.value) || 0;
} }
proxy.$refs['diagnosisTreatmentRef'].validate(async (valid) => { proxy.$refs['diagnosisTreatmentRef'].validate(async (valid) => {

View File

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

View File

@@ -1146,13 +1146,7 @@ function calculateTotalPrice(row, index) {
}); });
} }
function closeAllPopovers() { defineExpose({ getListInfo });
prescriptionList.value.forEach(row => {
row.showPopover = false;
});
}
defineExpose({ getListInfo, closeAllPopovers });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

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

View File

@@ -127,7 +127,6 @@ const queryParams = ref({
pageSize: 100, pageSize: 100,
pageNo: 1, pageNo: 1,
adviceTypes: '1,2,3', adviceTypes: '1,2,3',
categoryCode: '',
}); });
// 节流函数 - 与V1.3一致300ms首次立即响应 // 节流函数 - 与V1.3一致300ms首次立即响应
@@ -149,7 +148,6 @@ watch(
} else { } else {
queryParams.value.adviceTypes = '1,2,3'; queryParams.value.adviceTypes = '1,2,3';
} }
queryParams.value.categoryCode = newValue.categoryCode || '';
throttledGetList(); throttledGetList();
}, },
{ deep: true } { deep: true }
@@ -177,12 +175,6 @@ function getList() {
const types = adviceTypes.split(',').map(t => parseInt(t)); const types = adviceTypes.split(',').map(t => parseInt(t));
filteredData = filteredData.filter(item => types.includes(item.adviceType)); 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) { if (searchKey && searchKey.length >= 1) {

View File

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

View File

@@ -349,17 +349,11 @@ async function getList() {
if (res.code == 200) { if (res.code == 200) {
// 过滤掉中医诊断,只保留西医诊断 // 过滤掉中医诊断,只保留西医诊断
form.value.diagnosisList = res.data.filter(item => item.typeName !== '中医诊断'); form.value.diagnosisList = res.data.filter(item => item.typeName !== '中医诊断');
// 为旧数据添加默认分类和selectedDiseases // 为旧数据添加默认分类
form.value.diagnosisList.forEach(item => { form.value.diagnosisList.forEach(item => {
if (!item.classification) { if (!item.classification) {
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); emits('diagnosisSave', false);
} }
@@ -691,78 +685,23 @@ async function handleFoodDiseasesCheck() {
/** /**
* 传染病报告卡处理 * 传染病报告卡处理
* 通过诊断名称自动识别并勾选传染病报告卡中的疾病 * 通过诊断目录维护的'报卡类型'字段自动识别是否有需要填写的传染病报告卡
* 如果有则弹出诊断对应需登记的报告卡界面
*/ */
function handleInfectiousDiseaseReport() { function handleInfectiousDiseaseReport() {
// 疾病名称到报卡编码的映射(根据传染病报告卡弹窗中的疾病列表 // 查找所有有报卡类型的诊断reportTypeCode不为空
const diseaseNameToCode = { const diagnosesWithReportType = form.value.diagnosisList.filter(d => d.reportTypeCode);
// 甲类
'鼠疫': '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',
};
// 获取所有诊断名称对应的报卡编码 if (diagnosesWithReportType.length === 0) {
const allSelectedDiseases = form.value.diagnosisList
.map(d => diseaseNameToCode[d.name] || null)
.filter(code => code);
if (allSelectedDiseases.length === 0) {
return; return;
} }
// 优先使用主诊断 // 优先使用主诊断,如果没有主诊断有报卡类型则使用第一个有报卡类型的诊断
const mainDiagnosis = form.value.diagnosisList.find(d => d.maindiseFlag === 1); const mainDiagnosisWithReport = diagnosesWithReportType.find(d => d.maindiseFlag === 1);
const firstDiagnosis = form.value.diagnosisList[0]; const targetDiagnosis = mainDiagnosisWithReport || diagnosesWithReportType[0];
const diagnosisToShow = {
...(mainDiagnosis || firstDiagnosis),
selectedDiseases: allSelectedDiseases
};
// 弹出传染病报告卡弹窗 // 弹出传染病报告卡弹窗
proxy.$refs.infectiousDiseaseReportRef?.show(diagnosisToShow); proxy.$refs.infectiousDiseaseReportRef?.show(targetDiagnosis);
} }
/** /**
@@ -875,8 +814,7 @@ form.value.diagnosisList.push({
classification: '西医', // 默认为西医 classification: '西医', // 默认为西医
onsetDate: getCurrentDate(), onsetDate: getCurrentDate(),
diagnosisDoctor: props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name, diagnosisDoctor: props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name,
diagnosisTime: getCurrentDate(), diagnosisTime: getCurrentDate()
selectedDiseases: data.ybNo ? [data.ybNo] : [], // 用于传染病报告卡自动勾选
}); });
// 添加后按排序号排序 // 添加后按排序号排序

View File

@@ -16,15 +16,15 @@
v-model="form.cardNo" v-model="form.cardNo"
class="card-number-input" class="card-number-input"
placeholder="单位自编,与网络直报一致" placeholder="单位自编,与网络直报一致"
maxlength="20" maxlength="12"
:disabled="readOnly || dialogReadOnly" :disabled="readOnly"
/> />
</el-space> </el-space>
</el-card> </el-card>
</template> </template>
<el-card class="report-form" shadow="never"> <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-row :gutter="16" class="form-row">
<el-col :span="8" class="form-item"> <el-col :span="8" class="form-item">
@@ -41,9 +41,43 @@
</el-col> </el-col>
</el-row> </el-row>
<!-- 工作单位学校 --> <!-- 性别出生日期实足年龄 -->
<el-row :gutter="16" class="form-row align-bottom">
<el-col :span="6" class="form-item">
<span class="form-label required">性别</span>
<el-radio-group v-model="form.sex">
<el-radio label="男"></el-radio>
<el-radio label="女"></el-radio>
<el-radio label="未知">未知</el-radio>
</el-radio-group>
</el-col>
<el-col :span="10" class="form-item birth-date">
<span class="form-label required">出生日期</span>
<div class="date-inputs">
<el-input v-model="form.birthYear" class="underline-input small" maxlength="4" @change="calculateAge" />
<span></span>
<el-input v-model="form.birthMonth" class="underline-input small" maxlength="2" @change="calculateAge" />
<span></span>
<el-input v-model="form.birthDay" class="underline-input small" maxlength="2" @change="calculateAge" />
<span></span>
</div>
</el-col>
<el-col :span="8" class="form-item age-item">
<span class="form-label"> 实足年龄</span>
<div class="age-inputs">
<el-input v-model="form.age" class="underline-input small"/>
<el-select v-model="form.ageUnit" class="age-unit-select">
<el-option label="岁" value="岁" />
<el-option label="月" value="月" />
<el-option label="天" value="天" />
</el-select>
</div>
</el-col>
</el-row>
<!-- 工作单位 -->
<el-row :gutter="16" class="form-row"> <el-row :gutter="16" class="form-row">
<el-col :span="12" class="form-item"> <el-col :span="24" class="form-item full-width">
<span class="form-label">工作单位学校</span> <span class="form-label">工作单位学校</span>
<el-input v-model="form.workplace" class="underline-input" /> <el-input v-model="form.workplace" class="underline-input" />
</el-col> </el-col>
@@ -476,9 +510,9 @@
<template #footer> <template #footer>
<slot name="footer" :close="handleClose" :submit-loading="submitLoading"> <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-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 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> </el-space>
</slot> </slot>
</template> </template>
@@ -488,7 +522,7 @@
<script setup> <script setup>
import { ref, computed, getCurrentInstance, watch } from 'vue'; import { ref, computed, getCurrentInstance, watch } from 'vue';
import pcas from 'china-division/dist/pcas-code.json'; import pcas from 'china-division/dist/pcas-code.json';
import { saveInfectiousDiseaseReport, getNextCardNo } from '../api'; import { saveInfectiousDiseaseReport } from '../api';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
import { useDict } from '@/utils/dict'; import { useDict } from '@/utils/dict';
@@ -510,7 +544,6 @@ const DISEASE_NAMES = {
}; };
const dialogVisible = ref(false); const dialogVisible = ref(false);
const dialogReadOnly = ref(false);
const formRef = ref(null); const formRef = ref(null);
// 保存按钮加载状态,防止重复提交 // 保存按钮加载状态,防止重复提交
const submitLoading = ref(false); const submitLoading = ref(false);
@@ -1038,9 +1071,8 @@ function resetAddressSelector() {
* 以只读详情方式打开报卡弹窗,供报卡管理等页面复用医生站报卡样式。 * 以只读详情方式打开报卡弹窗,供报卡管理等页面复用医生站报卡样式。
* @param {Object} reportData - 报卡详情数据 * @param {Object} reportData - 报卡详情数据
*/ */
function showReport(reportData = {}, readOnly = true) { function showReport(reportData = {}) {
dialogVisible.value = true; dialogVisible.value = true;
dialogReadOnly.value = readOnly;
resetAddressSelector(); resetAddressSelector();
initProvinceOptions(); initProvinceOptions();
@@ -1071,7 +1103,7 @@ function showReport(reportData = {}, readOnly = true) {
addressHouse: reportData.addressHouse || '', addressHouse: reportData.addressHouse || '',
patientBelong: reportData.patientBelong || 1, patientBelong: reportData.patientBelong || 1,
occupation: reportData.occupation || '', occupation: reportData.occupation || '',
caseClass: reportData.diseaseType != null ? String(reportData.diseaseType) : '', caseClass: reportData.caseClass != null ? String(reportData.caseClass) : '',
onsetDate: normalizeDate(reportData.onsetDate), onsetDate: normalizeDate(reportData.onsetDate),
diagDate: normalizeDate(reportData.diagDate), diagDate: normalizeDate(reportData.diagDate),
deathDate: normalizeDate(reportData.deathDate), deathDate: normalizeDate(reportData.deathDate),
@@ -1080,13 +1112,13 @@ function showReport(reportData = {}, readOnly = true) {
selectedClassB: diseaseSelection.selectedClassB, selectedClassB: diseaseSelection.selectedClassB,
selectedClassC: diseaseSelection.selectedClassC, selectedClassC: diseaseSelection.selectedClassC,
otherDisease: reportData.otherDisease || (diseaseCode === 'OTHER' ? reportData.diseaseName || '' : ''), otherDisease: reportData.otherDisease || (diseaseCode === 'OTHER' ? reportData.diseaseName || '' : ''),
diseaseType: reportData.diseaseSubtype || '', diseaseType: reportData.diseaseType || '',
reportOrg: reportData.reportOrg || '', reportOrg: reportData.reportOrg || '',
reportOrgPhone: reportData.reportOrgPhone || '', reportOrgPhone: reportData.reportOrgPhone || '',
reportDoc: reportData.reportDoc || '', reportDoc: reportData.reportDoc || '',
reportDate: normalizeDate(reportData.reportDate || reportData.createdAt), reportDate: normalizeDate(reportData.reportDate || reportData.createdAt),
correctName: reportData.revisedDiseaseName || '', correctName: reportData.correctName || '',
withdrawReason: reportData.returnReason || '', withdrawReason: reportData.withdrawReason || '',
remark: reportData.remark || '', remark: reportData.remark || '',
encounterId: reportData.encounterId || reportData.visitId || '', encounterId: reportData.encounterId || reportData.visitId || '',
patientId: reportData.patientId || reportData.patId || '', patientId: reportData.patientId || reportData.patId || '',
@@ -1205,9 +1237,8 @@ function calculateAge() {
* 显示对话框并初始化表单数据 * 显示对话框并初始化表单数据
* @param {Object} diagnosisData - 诊断数据,包含已选疾病和地址信息 * @param {Object} diagnosisData - 诊断数据,包含已选疾病和地址信息
*/ */
async function show(diagnosisData) { function show(diagnosisData) {
dialogVisible.value = true; dialogVisible.value = true;
dialogReadOnly.value = false;
// 重置地址选择器状态 // 重置地址选择器状态
resetAddressSelector(); resetAddressSelector();
@@ -1236,23 +1267,12 @@ async function show(diagnosisData) {
}); });
} }
// 生成默认卡片编号:调用后端接口获取 // 生成默认卡片编号:医疗机构编码+当前年月日+4位流水
const orgCode = userStore.fixmedinsCode || '0000'; const orgCode = userStore.fixmedinsCode || '0000';
let cardNo = '';
try {
const res = await getNextCardNo(orgCode);
if (res.code === 200 && res.data && res.data.length >= 12) {
cardNo = res.data;
}
// API失败或返回不合规时保持为空字符串由用户手动填写或后端自动生成
} catch (err) {
console.error('获取卡片编号失败:', err);
// 保持为空,不使用不合规的临时值
}
form.value = { form.value = {
// 卡片编号:单位自编,与网络直报一致 // 卡片编号:单位自编,与网络直报一致
cardNo: cardNo, cardNo: '',
// 患者基本信息 // 患者基本信息
patName: patientInfo.patientName || patientInfo.name || '', // 患者姓名 patName: patientInfo.patientName || patientInfo.name || '', // 患者姓名
@@ -1318,11 +1338,7 @@ async function show(diagnosisData) {
// 系统关联信息 // 系统关联信息
encounterId: patientInfo.encounterId || '', // 就诊ID encounterId: patientInfo.encounterId || '', // 就诊ID
patientId: patientInfo.patientId || '', // 患者ID patientId: patientInfo.patientId || '', // 患者ID
diagnosisId: (diagnosisData?.conditionId != null && diagnosisData?.conditionId !== '') diagnosisId: diagnosisData?.conditionId || diagnosisData?.definitionId || '', // 诊断ID
? diagnosisData.conditionId
: (diagnosisData?.definitionId != null && diagnosisData?.definitionId !== '')
? diagnosisData.definitionId
: '', // 诊断ID
}; };
// 更新selectedDiseases数组 // 更新selectedDiseases数组
@@ -1377,7 +1393,7 @@ async function buildSubmitData() {
const submitData = { const submitData = {
cardNo: formData.cardNo, cardNo: formData.cardNo,
visitId: props.patientInfo?.encounterId || formData.encounterId || null, visitId: props.patientInfo?.encounterId || formData.encounterId || null,
diagId: formData.diagnosisId ? Number(formData.diagnosisId) : null, diagId: formData.diagnosisId || null,
patId: formData.patientId || null, patId: formData.patientId || null,
idType: 1, // 默认身份证 idType: 1, // 默认身份证
idNo: formData.idNo, idNo: formData.idNo,
@@ -1430,9 +1446,9 @@ async function buildSubmitData() {
function validateFormManually() { function validateFormManually() {
const errors = []; const errors = [];
// 卡片编号验证(至少12位后端自动生成16位编号 // 卡片编号验证(可选但如果填写了必须是12位
if (form.value.cardNo && form.value.cardNo.length < 12) { if (form.value.cardNo && form.value.cardNo.length !== 12) {
errors.push('卡片编号至少12位'); errors.push('卡片编号必须为12位');
} }
// 身份证号验证 // 身份证号验证
@@ -1543,12 +1559,6 @@ async function handleSubmit() {
return; return;
} }
// 检查诊断ID是否有效后端 @NotNull 校验要求)
if (!form.value.diagnosisId) {
proxy.$modal.msgError('诊断信息不完整,请重新选择诊断后重试');
return;
}
// 开始加载状态,防止重复提交 // 开始加载状态,防止重复提交
submitLoading.value = true; submitLoading.value = true;

View File

@@ -316,7 +316,6 @@
<span class="cat-title">{{ cat.categoryName }}</span> <span class="cat-title">{{ cat.categoryName }}</span>
<span v-if="categoryLoadingSet.has(cat.typeId)" class="loading-dot"></span> <span v-if="categoryLoadingSet.has(cat.typeId)" class="loading-dot"></span>
</template> </template>
<!-- 检查项目部位/项目列表 -->
<div <div
v-for="item in cat.items" v-for="item in cat.items"
:key="item.id" :key="item.id"
@@ -334,27 +333,6 @@
<div v-if="categoryLoadingSet.has(cat.typeId)" class="category-loading-hint"> <div v-if="categoryLoadingSet.has(cat.typeId)" class="category-loading-hint">
加载中... 加载中...
</div> </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-item>
</el-collapse> </el-collapse>
</div> </div>
@@ -544,7 +522,6 @@ const categoryList = ref([]); // 原始分类+项目数据
const dictSearchKey = ref(''); const dictSearchKey = ref('');
const activeNames = ref(''); // 当前展开的折叠项 const activeNames = ref(''); // 当前展开的折叠项
const categoryLoadingSet = ref(new Set()); // Bug #500: 正在加载方法的分类集合 const categoryLoadingSet = ref(new Set()); // Bug #500: 正在加载方法的分类集合
const isAnimating = ref(false); // Bug #500: 防止快速切换时折叠动画重叠导致抖动
const allMethods = ref([]); const allMethods = ref([]);
@@ -692,13 +669,8 @@ async function handleCategoryExpand(cat) {
categoryLoadingSet.value.delete(cat.typeId); categoryLoadingSet.value.delete(cat.typeId);
} }
} }
// Bug #500: 添加防抖逻辑,快速切换时跳过中间状态的动画,避免高度跳变和白屏闪烁 // Bug #500: 改为非 async 函数,避免 el-collapse 的 @change 等待异步完成导致抖动
function handleCollapseChange(activeName) { function handleCollapseChange(activeName) {
if (isAnimating.value) return; // 动画进行中,忽略后续点击
isAnimating.value = true;
setTimeout(() => { isAnimating.value = false; }, 300); // 与 CSS 过渡时长一致
if (activeName) { if (activeName) {
const cat = filteredCategoryList.value.find(c => c.typeId == activeName); const cat = filteredCategoryList.value.find(c => c.typeId == activeName);
if (cat && (!cat.methods || cat.methods.length === 0)) { if (cat && (!cat.methods || cat.methods.length === 0)) {
@@ -940,13 +912,6 @@ function handleSave() {
ElMessage.warning('请至少选择一个检查明细项目'); ElMessage.warning('请至少选择一个检查明细项目');
return; 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 // 从已选项目推导检查类型编码(取第一个项目的 checkType如 CT / ECG / GI
const firstCheckType = selectedItems.value[0]?.checkType || 'unknown'; const firstCheckType = selectedItems.value[0]?.checkType || 'unknown';
form.examTypeCode = firstCheckType; form.examTypeCode = firstCheckType;
@@ -1069,78 +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;
updateMethodDisplay();
return;
}
// 如果该项目不存在,创建一个并关联方法
if (selectedItems.value.length > 0) {
const currentCategory = selectedItems.value[0].checkType;
const newCategory = cat.typeCode || '';
if (currentCategory !== newCategory) {
ElMessage.warning('一个检查单不能同时选择多个项目类型的检查项目');
return;
}
}
selectedItems.value.push({
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,
isPackage: !!targetItem.packageName,
packageId: targetItem.packageId || null
});
// 自动回填执行科室
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) { async function handleItemSelect(checked, item, cat) {
if (checked) { if (checked) {
@@ -1206,6 +1099,12 @@ async function handleItemSelect(checked, item, cat) {
form.performDeptCode = cat.performDeptName; form.performDeptCode = cat.performDeptName;
} }
// 如果有且仅有一个检查方法,自动选中并更新显示
if (methods.length === 1) {
const lastIdx = selectedItems.value.length - 1;
selectedItems.value[lastIdx].selectedMethod = methods[0];
updateMethodDisplay(); // Bug #384修复: 联动更新显示
}
} else { } else {
const idx = selectedItems.value.findIndex(s => s.id === item.id); const idx = selectedItems.value.findIndex(s => s.id === item.id);
if (idx > -1) selectedItems.value.splice(idx, 1); if (idx > -1) selectedItems.value.splice(idx, 1);
@@ -1408,7 +1307,6 @@ defineExpose({ getList });
.collapse-scroll { .collapse-scroll {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; /* Bug #500: 防止切换时水平方向溢出导致抖动 */
} }
.empty-hint { .empty-hint {
color: #909399; color: #909399;
@@ -1461,53 +1359,6 @@ defineExpose({ getList });
margin-left: 6px; 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 */ /* 已选择 tags */
.selected-panel { .selected-panel {
width: 140px; /* Bug #384修复: 加宽以适应展开内容 */ width: 140px; /* Bug #384修复: 加宽以适应展开内容 */
@@ -1579,41 +1430,6 @@ defineExpose({ getList });
transform: rotate(180deg); 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修复: 检查方法勾选框列表 */ /* Bug #384修复: 检查方法勾选框列表 */
.method-list { .method-list {
padding: 6px 10px; padding: 6px 10px;
@@ -1663,10 +1479,10 @@ defineExpose({ getList });
padding-bottom: 4px; padding-bottom: 4px;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
/* Bug #500: 折叠面板动画容器,添加 overflow:hidden 防止展开时内容溢出导致闪烁 */ /* Bug #500: 折叠面板展开/收起动画使用 will-change 优化性能 */
:deep(.el-collapse-item__wrap) { :deep(.el-collapse-item__wrap) {
border: none; border: none;
overflow: hidden; will-change: height;
} }
:deep(.el-collapse-item) { :deep(.el-collapse-item) {
transition: margin 0.2s ease; transition: margin 0.2s ease;

View File

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

View File

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

View File

@@ -24,7 +24,7 @@
style="width: 100%" style="width: 100%"
> >
<el-table-column type="index" label="序号" width="60" align="center" /> <el-table-column type="index" label="序号" width="60" align="center" />
<!-- 手术单号 --> <!-- 手术单号 -->
<el-table-column label="手术单号" align="center" width="150"> <el-table-column label="手术单号" align="center" width="150">
<template #default="scope"> <template #default="scope">
@@ -33,31 +33,160 @@
</el-link> </el-link>
</template> </template>
</el-table-column> </el-table-column>
<!-- 申请日期 --> <!-- 申请日期 -->
<el-table-column label="申请日期" align="center" prop="createTime" width="180"> <el-table-column label="申请日期" align="center" prop="createTime" width="180">
<template #default="scope"> <template #default="scope">
{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }} {{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
</template> </template>
</el-table-column> </el-table-column>
<!-- 患者姓名 --> <!-- 患者姓名 -->
<el-table-column label="患者姓名" align="center" prop="patientName" width="100" /> <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="applyDoctorName" width="100" />
<!-- 申请科室 --> <!-- 申请科室 -->
<el-table-column label="申请科室" align="center" prop="applyDeptName" width="120" show-overflow-tooltip /> <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="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="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"> <el-table-column label="手术室确认时间" align="center" prop="operatingRoomConfirmTime" width="180">
<template #default="scope"> <template #default="scope">
{{ scope.row.operatingRoomConfirmTime ? parseTime(scope.row.operatingRoomConfirmTime, '{y}-{m}-{d} {h}:{i}:{s}') : '-' }} {{ scope.row.operatingRoomConfirmTime ? parseTime(scope.row.operatingRoomConfirmTime, '{y}-{m}-{d} {h}:{i}:{s}') : '-' }}
@@ -75,16 +204,16 @@
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<!-- 操作 --> <!-- 操作 -->
<el-table-column label="操作" align="center" width="200" fixed="right"> <el-table-column label="操作" align="center" width="200" fixed="right">
<template #default="scope"> <template #default="scope">
<!-- 查看显示手术申请详情只读模式 --> <!-- 查看显示手术申请详情只读模式 -->
<el-button link type="primary" icon="View" @click="handleView(scope.row)">查看</el-button> <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="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> <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-if="scope.row.statusEnum === 0 || scope.row.statusEnum === 1">删除</el-button>
</template> </template>
@@ -511,10 +640,6 @@ const props = defineProps({
}) })
const loading = ref(true) const loading = ref(true)
const surgeryLoading = ref(false)
const anesthesiaLoading = ref(false)
let surgerySearchTimer = null
let anesthesiaSearchTimer = null
const surgeryList = ref([]) const surgeryList = ref([])
const open = ref(false) const open = ref(false)
const viewOpen = ref(false) const viewOpen = ref(false)
@@ -1133,13 +1258,12 @@ function submitForm() {
// 新增手术 // 新增手术
addSurgery(form.value).then((res) => { addSurgery(form.value).then((res) => {
if (res.code === 200) { if (res.code === 200) {
proxy.$modal.msgSuccess(res.msg || '手术申请提交成功') proxy.$modal.msgSuccess(res.msg || '新增成功')
// 保存麻醉方式 // 保存麻醉方式
sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum) sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum)
open.value = false open.value = false
emit('saved') // 通知父组件刷新医嘱列表
// 刷新手术申请列表
getList() getList()
emit('saved') // 🔧 触发保存事件,通知父组件刷新医嘱列表
} else { } else {
proxy.$modal.msgError(res.msg || '新增手术失败,请检查表单信息') proxy.$modal.msgError(res.msg || '新增手术失败,请检查表单信息')
} }
@@ -1151,13 +1275,12 @@ function submitForm() {
// 修改手术 // 修改手术
updateSurgery(form.value).then((res) => { updateSurgery(form.value).then((res) => {
if (res.code === 200) { if (res.code === 200) {
proxy.$modal.msgSuccess(res.msg || '手术申请修改成功') proxy.$modal.msgSuccess(res.msg || '修改成功')
// 保存麻醉方式 // 保存麻醉方式
sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum) sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum)
open.value = false open.value = false
emit('saved') // 通知父组件刷新医嘱列表
// 刷新手术申请列表
getList() getList()
emit('saved') // 🔧 触发保存事件,通知父组件刷新医嘱列表
} else { } else {
proxy.$modal.msgError(res.msg || '更新手术失败,请检查表单信息') proxy.$modal.msgError(res.msg || '更新手术失败,请检查表单信息')
} }

View File

@@ -151,12 +151,23 @@
</div> </div>
</div> </div>
<InfectiousDiseaseReportDialog <el-dialog
ref="infectiousDiseaseReportRef" v-model="detailVisible"
:read-only="detailMode === 'view'" :title="detailMode === 'view' ? '报卡详情 - 中华人民共和国传染病报告卡' : '编辑报卡 - 中华人民共和国传染病报告卡'"
@success="detailVisible = false" width="1100px"
@close="detailVisible = false" 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> </div>
</template> </template>
@@ -164,7 +175,7 @@
import { ref, reactive, onMounted, onActivated, onBeforeUnmount, nextTick, computed } from 'vue'; import { ref, reactive, onMounted, onActivated, onBeforeUnmount, nextTick, computed } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { DataAnalysis, Warning, CircleCheck, Check, RefreshRight } from '@element-plus/icons-vue'; 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 { import {
getDoctorCardStatistics, getDoctorCardStatistics,
getDoctorCardList, getDoctorCardList,
@@ -174,6 +185,7 @@ import {
batchDeleteCards, batchDeleteCards,
exportCardToWord, exportCardToWord,
getCardDetail, getCardDetail,
updateDoctorCard,
} from './api'; } from './api';
const loading = ref(false); const loading = ref(false);
@@ -199,7 +211,7 @@ const queryParams = reactive({
const detailVisible = ref(false); const detailVisible = ref(false);
const detailMode = ref('view'); const detailMode = ref('view');
const infectiousDiseaseReportRef = ref(null); const currentCard = ref({});
// 计算表格高度:根据视口高度动态调整 // 计算表格高度:根据视口高度动态调整
const tableHeight = computed(() => { const tableHeight = computed(() => {
@@ -315,11 +327,9 @@ async function handleView(row) {
try { try {
const res = await getCardDetail(row.cardNo); const res = await getCardDetail(row.cardNo);
if (res.code === 200) { if (res.code === 200) {
currentCard.value = res.data || {};
detailMode.value = 'view'; detailMode.value = 'view';
detailVisible.value = true; detailVisible.value = true;
nextTick(() => {
infectiousDiseaseReportRef.value?.showReport(res.data || {});
});
} }
} catch (error) { } catch (error) {
ElMessage.error('获取详情失败'); ElMessage.error('获取详情失败');
@@ -330,17 +340,57 @@ async function handleEdit(row) {
try { try {
const res = await getCardDetail(row.cardNo); const res = await getCardDetail(row.cardNo);
if (res.code === 200) { if (res.code === 200) {
currentCard.value = res.data || {};
detailMode.value = 'edit'; detailMode.value = 'edit';
detailVisible.value = true; detailVisible.value = true;
nextTick(() => {
infectiousDiseaseReportRef.value?.showReport(res.data || {}, false);
});
} }
} catch (error) { } catch (error) {
ElMessage.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) { async function handleSubmit(row) {
try { try {
await ElMessageBox.confirm('确认提交该报卡?', '提示', { await ElMessageBox.confirm('确认提交该报卡?', '提示', {
@@ -750,4 +800,17 @@ function handleResize() {
margin-left: 0; 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> </style>

View File

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

View File

@@ -49,6 +49,15 @@
<el-option label="已作废" value="7" /> <el-option label="已作废" value="7" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="关键字">
<el-input
v-model="filterForm.keyword"
placeholder="申请单号/检查项目"
clearable
style="width: 200px"
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="handleSearch" :loading="loading"> <el-button type="primary" @click="handleSearch" :loading="loading">
<el-icon><Search /></el-icon> <el-icon><Search /></el-icon>
@@ -77,7 +86,11 @@
</template> </template>
<el-table-column type="index" label="序号" width="60" align="center" /> <el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="patientName" label="患者姓名" width="120" /> <el-table-column prop="patientName" label="患者姓名" width="120" />
<el-table-column 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="createTime" label="创建时间" width="160" />
<el-table-column prop="prescriptionNo" label="处方号" width="140" /> <el-table-column prop="prescriptionNo" label="处方号" width="140" />
<el-table-column prop="requesterId_dictText" label="申请者" width="120" /> <el-table-column prop="requesterId_dictText" label="申请者" width="120" />
@@ -86,9 +99,49 @@
<span>{{ parseStatus(scope.row.status) }}</span> <span>{{ parseStatus(scope.row.status) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" fixed="right"> <el-table-column label="操作" align="center" fixed="right" width="240">
<template #default="scope"> <template #default="scope">
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button> <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> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -110,7 +163,7 @@
currentDetail.patientName || '-' currentDetail.patientName || '-'
}}</el-descriptions-item> }}</el-descriptions-item>
<el-descriptions-item label="申请单名称">{{ <el-descriptions-item label="申请单名称">{{
currentDetail.name || '-' getItemName(currentDetail)
}}</el-descriptions-item> }}</el-descriptions-item>
<el-descriptions-item label="申请单状态">{{ <el-descriptions-item label="申请单状态">{{
parseStatus(currentDetail.status) parseStatus(currentDetail.status)
@@ -160,6 +213,60 @@
<el-button @click="detailDialogVisible = false">关闭</el-button> <el-button @click="detailDialogVisible = false">关闭</el-button>
</template> </template>
</el-dialog> </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> </div>
</template> </template>
@@ -167,8 +274,8 @@
import {computed, getCurrentInstance, ref, watch} from 'vue'; import {computed, getCurrentInstance, ref, watch} from 'vue';
import {Refresh, Search} from '@element-plus/icons-vue'; import {Refresh, Search} from '@element-plus/icons-vue';
import {patientInfo} from '../../store/patient.js'; import {patientInfo} from '../../store/patient.js';
import {getCheck} from './api'; import {getCheck, deleteRequestForm, withdrawRequestForm, getTestResult} from './api';
import {getDepartmentList} from '@/api/public.js'; import {getOrgList} from '@/views/doctorstation/components/api.js';
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
@@ -179,10 +286,33 @@ const currentDetail = ref(null);
const descJsonData = ref(null); const descJsonData = ref(null);
const orgOptions = ref([]); 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({ const filterForm = ref({
dateRange: [], // [startDate, endDate] dateRange: getDefaultDateRange(), // 默认近一周
status: '', // 申请单状态 status: '', // 申请单状态
keyword: '', // 关键字搜索
}); });
const fetchData = async () => { const fetchData = async () => {
@@ -207,6 +337,11 @@ const fetchData = async () => {
params.status = filterForm.value.status; params.status = filterForm.value.status;
} }
// 添加关键字搜索
if (filterForm.value.keyword && filterForm.value.keyword.trim()) {
params.keyword = filterForm.value.keyword.trim();
}
const res = await getCheck(params); const res = await getCheck(params);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const raw = res.data?.records || res.data; const raw = res.data?.records || res.data;
@@ -243,8 +378,9 @@ const handleSearch = async () => {
* 重置按钮处理 * 重置按钮处理
*/ */
const handleReset = () => { const handleReset = () => {
filterForm.value.dateRange = []; filterForm.value.dateRange = getDefaultDateRange();
filterForm.value.status = ''; filterForm.value.status = '';
filterForm.value.keyword = '';
fetchData(); fetchData();
}; };
@@ -267,6 +403,28 @@ const parseStatus = (status) => {
return statusMap[String(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 = { const labelMap = {
categoryType: '项目类别', categoryType: '项目类别',
targetDepartment: '发往科室', targetDepartment: '发往科室',
@@ -293,32 +451,37 @@ const hasMatchedFields = computed(() => {
/** 查询科室 */ /** 查询科室 */
const getLocationInfo = () => { const getLocationInfo = () => {
getDepartmentList().then((res) => { getOrgList().then((res) => {
orgOptions.value = res.data || []; orgOptions.value = res.data.records;
}); });
}; };
const recursionFun = (targetDepartment) => { const recursionFun = (targetDepartment) => {
if (!targetDepartment) return '';
if (!Array.isArray(orgOptions.value) || orgOptions.value.length === 0) return '';
let name = ''; let name = '';
for (let index = 0; index < orgOptions.value.length; index++) { for (let index = 0; index < orgOptions.value.length; index++) {
const obj = orgOptions.value[index]; const obj = orgOptions.value[index];
if (obj.id == targetDepartment) { if (obj.id == targetDepartment) {
name = obj.name; name = obj.name;
break;
} }
const subObjArray = obj['children']; const subObjArray = obj['children'];
if (subObjArray && subObjArray.length > 0) { if (Array.isArray(subObjArray)) {
for (let index = 0; index < subObjArray.length; index++) { for (let i = 0; i < subObjArray.length; i++) {
const item = subObjArray[index]; const item = subObjArray[i];
if (item.id == targetDepartment) { if (item.id == targetDepartment) {
name = item.name; name = item.name;
break;
} }
} }
} }
if (name) break;
} }
return name; return name;
}; };
const handleViewDetail = async (row) => { const handleViewDetail = (row) => {
console.log('targetDepartment========>', JSON.stringify(row)); console.log('targetDepartment========>', JSON.stringify(row));
currentDetail.value = row; currentDetail.value = row;
@@ -326,16 +489,10 @@ const handleViewDetail = async (row) => {
if (row.descJson) { if (row.descJson) {
try { try {
const obj = JSON.parse(row.descJson); const obj = JSON.parse(row.descJson);
// 确保科室数据已加载 if (obj.targetDepartment) {
if (!orgOptions.value || orgOptions.value.length === 0) { const deptName = recursionFun(obj.targetDepartment);
await new Promise((resolve) => { obj.targetDepartment = deptName || obj.targetDepartment;
getDepartmentList().then((res) => {
orgOptions.value = res.data || [];
resolve();
});
});
} }
obj.targetDepartment = recursionFun(obj.targetDepartment);
descJsonData.value = obj; descJsonData.value = obj;
} catch (e) { } catch (e) {
console.error('解析 descJson 失败:', e); console.error('解析 descJson 失败:', e);
@@ -347,6 +504,217 @@ const handleViewDetail = async (row) => {
detailDialogVisible.value = true; 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( watch(
() => patientInfo.value?.encounterId, () => patientInfo.value?.encounterId,
(val) => { (val) => {
@@ -355,8 +723,9 @@ watch(
getLocationInfo(); getLocationInfo();
} else { } else {
tableData.value = []; tableData.value = [];
filterForm.value.dateRange = []; filterForm.value.dateRange = getDefaultDateRange();
filterForm.value.status = ''; filterForm.value.status = '';
filterForm.value.keyword = '';
} }
}, },
{ immediate: true } { 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 getLocationInfo = () => {
const res = await getOrgList(); getOrgList().then((res) => {
orgOptions.value = res.data.records; orgOptions.value = res.data.records;
});
}; };
const recursionFun = (targetDepartment) => { const recursionFun = (targetDepartment) => {
@@ -204,12 +205,7 @@ const recursionFun = (targetDepartment) => {
return name; return name;
}; };
const handleViewDetail = async (row) => { const handleViewDetail = (row) => {
// 确保科室数据已加载,以便将 ID 解析为名称
if (!orgOptions.value || orgOptions.value.length === 0) {
await getLocationInfo();
}
currentDetail.value = row; currentDetail.value = row;
// 解析 descJson // 解析 descJson
if (row.descJson) { if (row.descJson) {
@@ -230,9 +226,10 @@ const handleViewDetail = async (row) => {
watch( watch(
() => patientInfo.value?.encounterId, () => patientInfo.value?.encounterId,
async (val) => { (val) => {
if (val) { if (val) {
await Promise.all([fetchData(), getLocationInfo()]); fetchData();
getLocationInfo();
} else { } else {
tableData.value = []; tableData.value = [];
} }

View File

@@ -41,19 +41,12 @@
<el-option label="全部" value="" /> <el-option label="全部" value="" />
<el-option label="待签发" value="0" /> <el-option label="待签发" value="0" />
<el-option label="已签发" value="1" /> <el-option label="已签发" value="1" />
<el-option label="已采集" value="2" />
<el-option label="已收样" value="3" />
<el-option label="报告已出" value="4" /> <el-option label="报告已出" value="4" />
<el-option label="已作废" value="5" /> <el-option label="已作废" value="5" />
</el-select> </el-select>
</el-form-item> </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-form-item>
<el-button type="primary" @click="handleSearch" :loading="loading"> <el-button type="primary" @click="handleSearch" :loading="loading">
<el-icon><Search /></el-icon> <el-icon><Search /></el-icon>
@@ -101,15 +94,8 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="requesterId_dictText" label="申请者" width="120" /> <el-table-column prop="requesterId_dictText" label="申请者" width="120" />
<el-table-column label="操作" align="center" fixed="right" width="160"> <el-table-column label="操作" align="center" fixed="right">
<template #default="scope"> <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> <el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
</template> </template>
</el-table-column> </el-table-column>
@@ -186,8 +172,8 @@
import {computed, getCurrentInstance, ref, watch} from 'vue'; import {computed, getCurrentInstance, ref, watch} from 'vue';
import {Refresh, Search} from '@element-plus/icons-vue'; import {Refresh, Search} from '@element-plus/icons-vue';
import {patientInfo} from '../../store/patient.js'; import {patientInfo} from '../../store/patient.js';
import {getInspection, deleteRequestForm, withdrawRequestForm} from './api'; import {getInspection} from './api';
import {getOrgList} from '@/views/doctorstation/components/api.js'; import {getDepartmentList} from '@/api/public.js';
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
@@ -202,7 +188,6 @@ const orgOptions = ref([]);
const filterForm = ref({ const filterForm = ref({
dateRange: [], // [startDate, endDate] dateRange: [], // [startDate, endDate]
status: '', // 单据状态 status: '', // 单据状态
keyword: '', // 关键字搜索
}); });
const fetchData = async () => { const fetchData = async () => {
@@ -226,12 +211,7 @@ const fetchData = async () => {
if (filterForm.value.status !== '' && filterForm.value.status !== undefined) { if (filterForm.value.status !== '' && filterForm.value.status !== undefined) {
params.status = filterForm.value.status; params.status = filterForm.value.status;
} }
// 添加关键字搜索
if (filterForm.value.keyword && filterForm.value.keyword.trim()) {
params.keyword = filterForm.value.keyword.trim();
}
const res = await getInspection(params); const res = await getInspection(params);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const raw = res.data?.records || res.data; const raw = res.data?.records || res.data;
@@ -271,7 +251,6 @@ const handleReset = () => {
// 重置筛选条件为默认值 // 重置筛选条件为默认值
filterForm.value.dateRange = []; filterForm.value.dateRange = [];
filterForm.value.status = ''; filterForm.value.status = '';
filterForm.value.keyword = '';
// 重新加载数据 // 重新加载数据
fetchData(); fetchData();
}; };
@@ -296,6 +275,8 @@ const parseBillStatus = (status) => {
const statusMap = { const statusMap = {
'0': '待签发', '0': '待签发',
'1': '已签发', '1': '已签发',
'2': '已采集',
'3': '已收样',
'4': '报告已出', '4': '报告已出',
'5': '已作废', '5': '已作废',
}; };
@@ -350,19 +331,18 @@ const hasMatchedFields = computed(() => {
}); });
/** 查询科室 */ /** 查询科室 */
const getLocationInfo = async () => { const getLocationInfo = () => {
const res = await getDepartmentList(); getDepartmentList().then((res) => {
orgOptions.value = res.data || []; orgOptions.value = res.data || [];
});
}; };
const recursionFun = (targetDepartment) => { const recursionFun = (targetDepartment) => {
if (!targetDepartment) return '';
let name = ''; let name = '';
for (let index = 0; index < orgOptions.value.length; index++) { for (let index = 0; index < orgOptions.value.length; index++) {
const obj = orgOptions.value[index]; const obj = orgOptions.value[index];
if (obj.id == targetDepartment) { if (obj.id == targetDepartment) {
name = obj.name; name = obj.name;
break;
} }
const subObjArray = obj['children']; const subObjArray = obj['children'];
if (subObjArray && subObjArray.length > 0) { if (subObjArray && subObjArray.length > 0) {
@@ -370,21 +350,14 @@ const recursionFun = (targetDepartment) => {
const item = subObjArray[i]; const item = subObjArray[i];
if (item.id == targetDepartment) { if (item.id == targetDepartment) {
name = item.name; name = item.name;
break;
} }
} }
} }
if (name) break;
} }
return name; return name;
}; };
const handleViewDetail = async (row) => { const handleViewDetail = (row) => {
// 确保科室数据已加载,以便将 ID 解析为名称
if (!orgOptions.value || orgOptions.value.length === 0) {
await getLocationInfo();
}
currentDetail.value = row; currentDetail.value = row;
// 解析 descJson // 解析 descJson
if (row.descJson) { if (row.descJson) {
@@ -392,6 +365,7 @@ const handleViewDetail = async (row) => {
const obj = JSON.parse(row.descJson); const obj = JSON.parse(row.descJson);
obj.targetDepartment = recursionFun(obj.targetDepartment); obj.targetDepartment = recursionFun(obj.targetDepartment);
descJsonData.value = obj; descJsonData.value = obj;
// descJsonData.value = JSON.parse(row.descJson);
} catch (e) { } catch (e) {
console.error('解析 descJson 失败:', e); console.error('解析 descJson 失败:', e);
descJsonData.value = null; descJsonData.value = null;
@@ -402,70 +376,15 @@ const handleViewDetail = async (row) => {
detailDialogVisible.value = true; 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( watch(
() => patientInfo.value?.encounterId, () => patientInfo.value?.encounterId,
async (val) => { (val) => {
if (val) { if (val) {
// 设置默认日期范围为近7天 // 设置默认日期范围为近7天
const today = new Date(); const today = new Date();
const sevenDaysAgo = new Date(today); const sevenDaysAgo = new Date(today);
sevenDaysAgo.setDate(today.getDate() - 6); // 包含今天共7天 sevenDaysAgo.setDate(today.getDate() - 6); // 包含今天共7天
// 格式化为 YYYY-MM-DD // 格式化为 YYYY-MM-DD
const formatDate = (date) => { const formatDate = (date) => {
const year = date.getFullYear(); const year = date.getFullYear();
@@ -473,19 +392,19 @@ watch(
const day = String(date.getDate()).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`; return `${year}-${month}-${day}`;
}; };
filterForm.value.dateRange = [ filterForm.value.dateRange = [
formatDate(sevenDaysAgo), formatDate(sevenDaysAgo),
formatDate(today) formatDate(today)
]; ];
await Promise.all([fetchData(), getLocationInfo()]); fetchData();
getLocationInfo();
} else { } else {
tableData.value = []; tableData.value = [];
// 重置筛选条件 // 重置筛选条件
filterForm.value.dateRange = []; filterForm.value.dateRange = [];
filterForm.value.status = ''; filterForm.value.status = '';
filterForm.value.keyword = '';
} }
}, },
{ immediate: true } { immediate: true }

View File

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

View File

@@ -81,7 +81,7 @@
import {getCurrentInstance, onBeforeMount, onMounted, reactive, watch} from 'vue'; import {getCurrentInstance, onBeforeMount, onMounted, reactive, watch} from 'vue';
import {patientInfo} from '../../../store/patient.js'; import {patientInfo} from '../../../store/patient.js';
import {getApplicationList, saveInspection} from './api'; 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 {getEncounterDiagnosis} from '../../api.js';
import {ElMessage} from 'element-plus'; import {ElMessage} from 'element-plus';
@@ -176,8 +176,6 @@ const projectWithDepartment = (selectProjectIds, type) => {
}); });
arr.push(searchData); arr.push(searchData);
}); });
// 保存用户手动选择的发往科室(提交时需要保留)
const manualDept = type === 2 ? form.targetDepartment : '';
// 清空科室 // 清空科室
form.targetDepartment = ''; form.targetDepartment = '';
if (arr.length > 0) { if (arr.length > 0) {
@@ -196,20 +194,16 @@ const projectWithDepartment = (selectProjectIds, type) => {
// 选中项目中的执行科室id与全部科室数据做匹配 // 选中项目中的执行科室id与全部科室数据做匹配
const findItem = findTreeItem(orgOptions.value, obj.orgId); const findItem = findTreeItem(orgOptions.value, obj.orgId);
if (!findItem) { if (!findItem) {
// type=2(提交)时,若用户已手动选择发往科室,则允许提交 isRelease = false;
if (type === 2 && manualDept) { ElMessage({
form.targetDepartment = manualDept; type: 'error',
isRelease = true; message: '未找到项目执行的科室',
} else { });
isRelease = false;
ElMessage({
type: 'error',
message: '未找到项目执行的科室',
});
}
} }
if (findItem && isRelease) { if (type == 1) {
form.targetDepartment = findItem.id; if (isRelease) {
form.targetDepartment = findItem.id;
}
} }
} }
return isRelease; return isRelease;
@@ -238,7 +232,7 @@ const submit = () => {
unitCode: item.priceList[0].unitCode /** 请求单位编码 */, unitCode: item.priceList[0].unitCode /** 请求单位编码 */,
unitPrice: item.priceList[0].price /** 单价 */, unitPrice: item.priceList[0].price /** 单价 */,
totalPrice: item.priceList[0].price /** 总价 */, totalPrice: item.priceList[0].price /** 总价 */,
positionId: item.positionId || form.targetDepartment, //执行科室id未配置时使用用户手动选择的科室 positionId: item.positionId, //执行科室id
ybClassEnum: item.ybClassEnum, //类别医保编码 ybClassEnum: item.ybClassEnum, //类别医保编码
conditionId: item.conditionId, //诊断ID conditionId: item.conditionId, //诊断ID
encounterDiagnosisId: item.encounterDiagnosisId, //就诊诊断id encounterDiagnosisId: item.encounterDiagnosisId, //就诊诊断id
@@ -270,8 +264,8 @@ const submit = () => {
}; };
/** 查询科室 */ /** 查询科室 */
const getLocationInfo = () => { const getLocationInfo = () => {
getOrgList().then((res) => { getDepartmentList().then((res) => {
orgOptions.value = res.data.records; orgOptions.value = res.data || [];
console.log('科室========>', JSON.stringify(orgOptions.value)); console.log('科室========>', JSON.stringify(orgOptions.value));
}); });
}; };
@@ -308,7 +302,7 @@ function getDiagnosisList() {
} }
}); });
} }
defineExpose({ state, submit, getLocationInfo, getDiagnosisList, getList }); defineExpose({ state, submit, getLocationInfo, getDiagnosisList });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.LaboratoryTests-container { .LaboratoryTests-container {

View File

@@ -1,242 +1,91 @@
<!-- <!--
* @Author: sjjh * @Author: sjjh
* @Date: 2025-09-05 22:35:29 * @Date: 2025-09-05 22:35:29
* @Description: 检查申请单 - 现代化医疗风格布局 * @Description: 检查
--> -->
<template> <template>
<div class="medicalExaminations-container"> <div class="medicalExaminations-container">
<!-- 顶部标题栏 --> <div v-loading="loading" class="transfer-wrapper">
<div class="form-header"> <el-transfer
<div class="header-left"> v-model="transferValue"
<el-icon class="header-icon"><Files /></el-icon> :data="applicationList"
<span class="header-title">检查申请单</span> filter-placeholder="项目代码/名称"
</div> filterable
<div class="header-right"> :titles="['未选择', '已选择']"
<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> </div>
<div class="bloodTransfusion-form">
<!-- 主体内容区 --> <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="demo-ruleForm">
<div class="form-body"> <el-row :gutter="20">
<!-- 选择检查项目 --> <!-- <el-col :span="12">
<div class="section-card"> <el-form-item label="项目类别" prop="categoryType" style="width: 100%">
<div class="section-header"> <el-input v-model="form.categoryType" autocomplete="off" />
<el-icon><Document /></el-icon> </el-form-item>
<span>选择检查项目</span> </el-col> -->
</div> <el-col :span="12">
<div v-loading="loading" class="transfer-wrapper"> <el-form-item label="发往科室" prop="targetDepartment" style="width: 100%">
<el-transfer <!-- <el-input v-model="form.targetDepartment" autocomplete="off" /> -->
v-model="transferValue" <el-tree-select
:data="applicationList" clearable
filter-placeholder="项目代码/名称" style="width: 100%"
filterable v-model="form.targetDepartment"
:titles="['可选项目', '已选项目']" filterable
/> :data="orgOptions"
</div> :props="{
</div> value: 'id',
label: 'name',
<!-- 申请信息 --> children: 'children',
<div class="section-card"> }"
<div class="section-header"> value-key="id"
<el-icon><EditPen /></el-icon> check-strictly
<span>申请信息</span> placeholder="请选择科室"
</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="如:造影剂过敏史等(系统将自动从患者档案带入)"
/> />
<span v-if="isSevereAllergy" class="allergy-severe-tag"> </el-form-item>
<el-icon><WarningFilled /></el-icon> </el-col>
严重过敏 <el-col :span="12">
</span> <el-form-item label="症状" prop="symptom" style="width: 100%">
</div> <el-input v-model="form.symptom" autocomplete="off" type="textarea" />
<div class="allergy-confirm"> </el-form-item>
<el-checkbox v-model="form.allergyConfirmed" size="small"> </el-col>
已通过口头询问确认无过敏史 <el-col :span="12">
</el-checkbox> <el-form-item label="体征" prop="sign" style="width: 100%">
</div> <el-input v-model="form.sign" autocomplete="off" type="textarea" />
</div> </el-form-item>
</div> </el-col>
<el-col :span="12">
<!-- 检查目的卡片 --> <el-form-item label="临床诊断" prop="clinicalDiagnosis" style="width: 100%">
<div class="section-card purpose-card"> <el-input disabled v-model="form.clinicalDiagnosis" autocomplete="off" />
<div class="section-header"> </el-form-item>
<el-icon><Aim /></el-icon> </el-col>
<span>检查目的</span> <el-col :span="12">
<span class="required-mark">必填</span> <el-form-item label="其他诊断" prop="otherDiagnosis" style="width: 100%">
</div> <el-input disabled v-model="form.otherDiagnosis" autocomplete="off" />
<el-input </el-form-item>
v-model="form.examinationPurpose" </el-col>
autocomplete="off" <el-col :span="12">
type="textarea" <el-form-item label="相关结果" prop="relatedResult" style="width: 100%">
:rows="2" <el-input v-model="form.relatedResult" autocomplete="off" type="textarea" />
maxlength="200" </el-form-item>
show-word-limit </el-col>
placeholder="请输入检查目的,如:明确诊断、术后复查、疗效评估等" <el-col :span="12">
/> <el-form-item label="注意事项" prop="attention" style="width: 100%">
</div> <el-input v-model="form.attention" autocomplete="off" type="textarea" />
</el-form-item>
<!-- 病史摘要卡片 --> </el-col>
<div class="section-card history-card"> </el-row>
<div class="section-header"> </el-form>
<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>
</div> </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> </div>
</template> </template>
<script setup name="MedicalExaminations"> <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 {patientInfo} from '../../../store/patient.js';
import {getDepartmentList} from '@/api/public.js'; import {getDepartmentList} from '@/api/public.js';
import {getEncounterDiagnosis} from '../../api.js'; import {getEncounterDiagnosis} from '../../api.js';
import {getApplicationList, saveCheckd} from './api'; import {getApplicationList, saveCheckd} from './api';
import {ElMessage, ElMessageBox} from 'element-plus'; import {ElMessage} from 'element-plus';
import {WarningFilled, Warning, Refresh, Files, Document, EditPen, Aim, DocumentCopy} from '@element-plus/icons-vue';
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
// 递归查找树形科室节点 // 递归查找树形科室节点
const findTreeItem = (list, id) => { const findTreeItem = (list, id) => {
if (!list || list.length === 0) return null; if (!list || list.length === 0) return null;
@@ -249,28 +98,13 @@ const findTreeItem = (list, id) => {
} }
return null; return null;
}; };
const emits = defineEmits(['submitOk']); const emits = defineEmits(['submitOk']);
const props = defineProps({}); const props = defineProps({});
const orgOptions = ref([]); const orgOptions = ref([]); // 科室选项
const state = reactive({}); const state = reactive({});
const applicationListAll = ref(); const applicationListAll = ref();
const applicationList = ref(); const applicationList = ref();
const loading = ref(false); 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 = () => { const getList = () => {
if (!patientInfo.value?.inHospitalOrgId) { if (!patientInfo.value?.inHospitalOrgId) {
applicationList.value = []; applicationList.value = [];
@@ -282,7 +116,7 @@ const getList = () => {
pageNum: 1, pageNum: 1,
categoryCode: '23', categoryCode: '23',
organizationId: patientInfo.value.inHospitalOrgId, organizationId: patientInfo.value.inHospitalOrgId,
adviceTypes: [3], adviceTypes: [3], //1 药品 2耗材 3诊疗
}) })
.then((res) => { .then((res) => {
if (res.code === 200) { if (res.code === 200) {
@@ -307,532 +141,187 @@ const getList = () => {
loading.value = false; loading.value = false;
}); });
}; };
const transferValue = ref([]); const transferValue = ref([]);
const form = reactive({ const form = reactive({
targetDepartment: '', // categoryType: '', // 项目类别
urgencyLevel: 'routine', targetDepartment: '', // 发往科室
allergyHistory: '', symptom: '', // 症状
examinationPurpose: '', sign: '', // 体征
medicalHistorySummary: '', clinicalDiagnosis: '', // 临床诊断
expectedExaminationTime: '', otherDiagnosis: '', // 其他诊断
symptom: '', relatedResult: '', // 相关结果
sign: '', attention: '', // 注意事项
clinicalDiagnosis: '', primaryDiagnosisList: [], //主诊断目录
otherDiagnosis: '', otherDiagnosisList: [], //其他断目录
relatedResult: '',
attention: '',
primaryDiagnosisList: [],
otherDiagnosisList: [],
allergyConfirmed: false,
}); });
const rules = reactive({});
// 表单校验规则 onBeforeMount(() => {});
const rules = reactive({ onMounted(() => {
examinationPurpose: [ getList();
{ required: true, message: '请输入检查目的', trigger: 'blur' },
{ max: 200, message: '检查目的不能超过200个字符', trigger: 'blur' },
],
medicalHistorySummary: [
{ required: true, message: '请输入病史摘要', trigger: 'blur' },
],
}); });
/**
// 禁用过去的时间 * type(1watch监听类型 2:点击保存类型)
const disabledFutureDate = (time) => { * selectProjectIds(选中项目的id数组)
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;
};
const projectWithDepartment = (selectProjectIds, type) => { const projectWithDepartment = (selectProjectIds, type) => {
//1.获取选中的项目 2.判断项目的执行科室是否相同 3.判断执行科室是否配置 4.将项目的执行科室复值到执行科室下拉选位置
let isRelease = true; let isRelease = true;
// 选中项目的数组
const arr = []; const arr = [];
// 根据选中的项目id查找对应的项目
selectProjectIds.forEach((element) => { selectProjectIds.forEach((element) => {
const searchData = applicationList.value.find((item) => { const searchData = applicationList.value.find((item) => {
return element == item.adviceDefinitionId; return element == item.adviceDefinitionId;
}); });
arr.push(searchData); arr.push(searchData);
}); });
// 只有当选择了项目arr 非空)时才设置 targetDepartment // 清空科室
form.targetDepartment = '';
if (arr.length > 0) { if (arr.length > 0) {
const obj = arr[0]; const obj = arr[0];
// 检查是否有未定义的项目applicationList 中找不到) // 判断科室是否相同
if (!obj) { const isCompare = arr.every((item) => {
console.warn('未找到项目定义,无法设置执行科室'); return item.orgId == obj.orgId;
return false; });
}
const isCompare = arr.every((item) => item && item.orgId == obj.orgId);
if (!isCompare) { if (!isCompare) {
ElMessage({ type: 'error', message: '执行科室不同' }); ElMessage({
type: 'error',
message: '执行科室不同',
});
isRelease = false; isRelease = false;
} }
// 选中项目中的执行科室id与全部科室数据做匹配
const findItem = findTreeItem(orgOptions.value, obj.orgId); const findItem = findTreeItem(orgOptions.value, obj.orgId);
if (!findItem) { if (!findItem) {
isRelease = false; isRelease = false;
ElMessage({ type: 'error', message: '未找到项目执行的科室' }); ElMessage({
type: 'error',
message: '未找到项目执行的科室',
});
} }
if (type == 1 && isRelease) { if (type == 1) {
form.targetDepartment = findItem.id; if (isRelease) {
console.log('targetDepartment 设置为:', form.targetDepartment, '科室名称:', findItem.name); form.targetDepartment = findItem.id;
}
} }
} else {
// 清空选择时,也要清空 targetDepartment
form.targetDepartment = '';
} }
return isRelease; return isRelease;
}; };
// 监听选择项目变化
watch(() => transferValue.value, (newValue) => { watch(
projectWithDepartment(newValue, 1); () => transferValue.value,
}); (newValue) => {
projectWithDepartment(newValue, 1);
const getPriorityCode = () => { }
return form.urgencyLevel === 'emergency' ? 1 : 0; );
};
const submit = () => { const submit = () => {
if (transferValue.value.length == 0) { if (transferValue.value.length == 0) {
return proxy.$message.error('请选择检查项目'); return proxy.$message.error('请选择申请单');
} }
if (!projectWithDepartment(transferValue.value, 2)) { if (!projectWithDepartment(transferValue.value, 2)) {
return; 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) => { let applicationListAllFilter = applicationListAll.value.filter((item) => {
return transferValue.value.includes(item.adviceDefinitionId); return transferValue.value.includes(item.adviceDefinitionId);
}); });
applicationListAllFilter = applicationListAllFilter.map((item) => { applicationListAllFilter = applicationListAllFilter.map((item) => {
return { return {
adviceDefinitionId: item.adviceDefinitionId, adviceDefinitionId: item.adviceDefinitionId /** 诊疗定义id */,
quantity: 1, quantity: 1, // /** 请求数量 */
unitCode: item.priceList[0].unitCode, unitCode: item.priceList[0].unitCode /** 请求单位编码 */,
unitPrice: item.priceList[0].price, unitPrice: item.priceList[0].price /** 单价 */,
totalPrice: item.priceList[0].price, totalPrice: item.priceList[0].price /** 总价 */,
positionId: item.positionId, positionId: item.positionId, //执行科室id
ybClassEnum: item.ybClassEnum, ybClassEnum: item.ybClassEnum, //类别医保编码
conditionId: item.conditionId, conditionId: item.conditionId, //诊断ID
encounterDiagnosisId: item.encounterDiagnosisId, encounterDiagnosisId: item.encounterDiagnosisId, //就诊诊断id
adviceType: item.adviceType, adviceType: item.adviceType, ///** 医嘱类型 */
definitionId: item.priceList[0].definitionId, definitionId: item.priceList[0].definitionId, //费用定价主表ID */
definitionDetailId: item.priceList[0].definitionDetailId, definitionDetailId: item.definitionDetailId, //费用定价子表ID */
accountId: patientInfo.value.accountId, accountId: patientInfo.value.accountId, // // 账户id
}; };
}); });
const submitForm = { ...form, priorityCode: getPriorityCode() };
console.log('提交 descJson:', JSON.stringify(submitForm));
saveCheckd({ saveCheckd({
activityList: applicationListAllFilter, activityList: applicationListAllFilter,
patientId: patientInfo.value.patientId, patientId: patientInfo.value.patientId, //患者ID
encounterId: patientInfo.value.encounterId, encounterId: patientInfo.value.encounterId, // 就诊ID
organizationId: patientInfo.value.inHospitalOrgId, organizationId: patientInfo.value.inHospitalOrgId, // 医疗机构ID
requestFormId: '', requestFormId: '', // 申请单ID
name: applicationListAllFilter.map(item => item.adviceName).join('、'), name: '检查申请单',
descJson: JSON.stringify(submitForm), descJson: JSON.stringify(form),
categoryEnum: '2', categoryEnum: '2', // 1 检验 2 检查 3 输血 4 手术
}).then((res) => { }).then((res) => {
if (res.code === 200) { if (res.code === 200) {
proxy.$message.success(res.msg); proxy.$message.success(res.msg);
applicationList.value = []; applicationList.value = [];
resetForm();
emits('submitOk'); emits('submitOk');
} else { } else {
proxy.$message.error(res.message); 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 = () => { const getLocationInfo = () => {
getDepartmentList().then((res) => { getDepartmentList().then((res) => {
orgOptions.value = res.data || []; orgOptions.value = res.data || [];
}); });
}; };
// 获取诊断目录
function getDiagnosisList() { function getDiagnosisList() {
getEncounterDiagnosis(patientInfo.value.encounterId).then((res) => { getEncounterDiagnosis(patientInfo.value.encounterId).then((res) => {
console.log('诊断目录========>', JSON.stringify(res.data));
if (res.code == 200) { if (res.code == 200) {
const datas = (res.data || []).map((item) => { const datas = (res.data || []).map((item) => {
let obj = { ...item }; let obj = {
if (obj.diagSrtNo == null) obj.diagSrtNo = '1'; ...item,
};
if (obj.diagSrtNo == null) {
obj.diagSrtNo = '1';
}
return obj; 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) { 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(',');
} }
}); });
} }
defineExpose({ state, submit, getLocationInfo, getDiagnosisList });
onMounted(() => {
getList();
getLocationInfo();
loadPatientAllergyHistory();
});
defineExpose({ state, submit, getLocationInfo, getDiagnosisList, resetForm });
</script> </script>
<style lang="scss" scoped> <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 { .medicalExaminations-container {
height: 100%; height: 100%;
width: 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 { .transfer-wrapper {
position: relative; position: relative;
min-height: 300px; min-height: 300px;
width: 100%;
} }
:deep(.el-transfer) { .el-transfer {
--el-transfer-panel-width: 480px !important; --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;
}
}
} }
// 过敏史卡片 .bloodTransfusion-form {
.allergy-card { padding: 8px 8px 0 8px;
.allergy-content { height: 100%;
.allergy-input-row { width: 100%;
position: relative; display: flex;
:deep(.el-textarea) {
.el-textarea__inner.allergy-danger {
border-color: $danger-color !important;
background-color: #fef0f0;
}
}
}
.allergy-severe-tag {
position: absolute;
right: 12px;
top: 8px;
display: flex;
align-items: center;
gap: 4px;
color: $danger-color;
font-size: 13px;
font-weight: 600;
background: #fef0f0;
padding: 3px 10px;
border-radius: 12px;
border: 1px solid #fde2e2;
}
.allergy-confirm {
margin-top: 10px;
padding-left: 4px;
}
}
} }
// 病史摘要卡片
.history-card {
.section-header {
.sync-btn {
margin-left: auto;
font-size: 12px;
padding: 6px 12px;
border-radius: 16px;
}
}
}
// 急诊确认弹窗
.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> </style>

View File

@@ -6,15 +6,13 @@
<template> <template>
<div class="surgery-container"> <div class="surgery-container">
<div class="transfer-wrapper"> <div class="transfer-wrapper">
<div v-loading="loading" style="min-height: 300px;"> <el-transfer
<el-transfer v-model="transferValue"
v-model="transferValue" :data="applicationList"
:data="applicationList" filter-placeholder="项目代码/名称"
filter-placeholder="项目代码/名称" filterable
filterable :titles="['未选择', '已选择']"
:titles="['未选择', '已选择']" />
/>
</div>
</div> </div>
<div class="bloodTransfusion-form"> <div class="bloodTransfusion-form">
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="demo-ruleForm"> <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="demo-ruleForm">
@@ -106,13 +104,11 @@ const state = reactive({});
const applicationListAll = ref([]); const applicationListAll = ref([]);
const applicationList = ref([]); const applicationList = ref([]);
const orgOptions = ref([]); // 科室选项 const orgOptions = ref([]); // 科室选项
const loading = ref(false); // 加载状态
const getList = () => { const getList = () => {
if (!patientInfo.value?.inHospitalOrgId) { if (!patientInfo.value?.inHospitalOrgId) {
applicationList.value = []; applicationList.value = [];
return; return;
} }
loading.value = true;
getApplicationList({ getApplicationList({
pageSize: 500, pageSize: 500,
pageNum: 1, pageNum: 1,
@@ -142,9 +138,6 @@ const getList = () => {
.catch((e) => { .catch((e) => {
console.warn('手术项目列表加载失败(可能无权限):', e?.message || e); console.warn('手术项目列表加载失败(可能无权限):', e?.message || e);
applicationList.value = []; applicationList.value = [];
})
.finally(() => {
loading.value = false;
}); });
}; };
const transferValue = ref([]); const transferValue = ref([]);
@@ -308,7 +301,7 @@ function getDiagnosisList() {
} }
}); });
} }
defineExpose({ state, submit, getLocationInfo, getDiagnosisList, getList }); defineExpose({ state, submit, getLocationInfo, getDiagnosisList });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.surgery-container { .surgery-container {

View File

@@ -197,6 +197,7 @@
style="width: 62%" style="width: 62%"
v-model="scope.row.adviceName" v-model="scope.row.adviceName"
placeholder="请选择项目" placeholder="请选择项目"
@input="handleChange"
@click="handleFocus(scope.row, scope.$index)" @click="handleFocus(scope.row, scope.$index)"
@keyup.enter.stop="handleFocus(scope.row, scope.$index)" @keyup.enter.stop="handleFocus(scope.row, scope.$index)"
@keydown=" @keydown="
@@ -379,7 +380,7 @@ const form = ref({
prescriptionList: prescriptionList.value, prescriptionList: prescriptionList.value,
}); });
const adviceQueryParams = ref({ const adviceQueryParams = ref({
adviceType: '', adviceType: 1,
categoryCode: '', // 初始为空,等待加载配置后动态设置 categoryCode: '', // 初始为空,等待加载配置后动态设置
searchKey: '', searchKey: '',
}); });
@@ -685,7 +686,7 @@ function loadConfiguredCategories() {
nextTick(() => { nextTick(() => {
// 创建新对象触发响应式更新 // 创建新对象触发响应式更新
adviceQueryParams.value = { adviceQueryParams.value = {
adviceType: '', adviceType: 1,
categoryCode: defaultCategoryCode, categoryCode: defaultCategoryCode,
searchKey: '', searchKey: '',
}; };
@@ -801,8 +802,8 @@ function clickRowDb(row, column, event) {
return; return;
} }
row.showPopover = false; row.showPopover = false;
// 仅”待签发(statusEnum==1)”允许编辑;”已签发(statusEnum==2)”及之后状态不允许编辑 // 待签发(已保存 requestId存在)”允许编辑;仅“待保存(无requestId)”允许编辑
if (row.statusEnum == 1) { if (row.statusEnum == 1 && !row.requestId) {
// 确保治疗类型为字符串,方便与单选框 label 对齐,默认为长期医嘱('1') // 确保治疗类型为字符串,方便与单选框 label 对齐,默认为长期医嘱('1')
row.therapyEnum = String(row.therapyEnum ?? '1'); row.therapyEnum = String(row.therapyEnum ?? '1');
row.isEdit = true; row.isEdit = true;
@@ -880,9 +881,8 @@ function handleFocus(row, index) {
// 用 adviceType + categoryCode 组合查找匹配的选项 // 用 adviceType + categoryCode 组合查找匹配的选项
const selectValue = (adviceType == 1 && row.categoryCode) ? '1-' + row.categoryCode : adviceType; 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); const selectedItem = adviceTypeList.value.find(item => item.value === selectValue) || adviceTypeList.value.find(item => item.adviceType === adviceType);
// 修复Bug #486当行没有显式选择医嘱类型时row.adviceType为undefined // 修复Bug #486当行没有显式选择医嘱类型时不传categoryCode让搜索在全药库中进行
// 不传categoryCode让搜索在全药库中进行只有行已选择类型时才用对应categoryCode过滤 const categoryCode = selectedItem ? selectedItem.categoryCode : (row.adviceType !== undefined ? (adviceQueryParams.value.categoryCode || '') : '');
const categoryCode = row.adviceType !== undefined ? (selectedItem ? selectedItem.categoryCode : '') : '';
const searchKey = row.adviceName || ''; const searchKey = row.adviceName || '';
nextTick(() => { nextTick(() => {
@@ -919,9 +919,8 @@ function handleChange(value) {
// 用 adviceType + categoryCode 组合查找匹配的选项 // 用 adviceType + categoryCode 组合查找匹配的选项
const selectValue = (adviceType == 1 && row?.categoryCode) ? '1-' + row.categoryCode : adviceType; 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); const selectedItem = adviceTypeList.value.find(item => item.value === selectValue) || adviceTypeList.value.find(item => item.adviceType === adviceType);
// 修复Bug #486当行没有显式选择医嘱类型时row?.adviceType为undefined // 修复Bug #486当行没有显式选择医嘱类型时不传categoryCode让搜索在全药库中进行
// 不传categoryCode让搜索在全药库中进行只有行已选择类型时才用对应categoryCode过滤 const categoryCode = selectedItem ? selectedItem.categoryCode : (row?.adviceType !== undefined ? (adviceQueryParams.value.categoryCode || '') : '');
const categoryCode = row?.adviceType !== undefined ? (selectedItem ? selectedItem.categoryCode : '') : '';
tableRef.refresh(adviceType, categoryCode, value); tableRef.refresh(adviceType, categoryCode, value);
} }
} }

View File

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

View File

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

View File

@@ -174,6 +174,7 @@ const activeNames = ref([]);
const userStore = useUserStore(); const userStore = useUserStore();
const prescriptionList = ref([]); const prescriptionList = ref([]);
const deadline = ref(formatDateStr(new Date(), 'YYYY-MM-DD') + ' 23:59:59'); const deadline = ref(formatDateStr(new Date(), 'YYYY-MM-DD') + ' 23:59:59');
const therapyEnum = ref(undefined);
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
const loading = ref(false); const loading = ref(false);
const chooseAll = ref(false); const chooseAll = ref(false);
@@ -189,10 +190,6 @@ const props = defineProps({
deadline: { deadline: {
type: String, type: String,
}, },
therapyEnum: {
type: Number,
default: undefined,
},
}); });
function handleGetPrescription() { function handleGetPrescription() {
@@ -203,7 +200,7 @@ function handleGetPrescription() {
encounterIds: encounterIds, encounterIds: encounterIds,
pageSize: 10000, pageSize: 10000,
pageNo: 1, pageNo: 1,
therapyEnum: props.therapyEnum, therapyEnum: therapyEnum.value,
exeStatus: props.exeStatus, exeStatus: props.exeStatus,
requestStatus: props.requestStatus, requestStatus: props.requestStatus,
}) })

View File

@@ -58,7 +58,7 @@
:clearable="false" :clearable="false"
@change="handleGetPrescription" @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="undefined">全部</el-radio>
<el-radio :value="1">长期</el-radio> <el-radio :value="1">长期</el-radio>
<el-radio :value="2">临时</el-radio> <el-radio :value="2">临时</el-radio>
@@ -79,7 +79,6 @@
:exeStatus="exeStatus" :exeStatus="exeStatus"
:requestStatus="requestStatus" :requestStatus="requestStatus"
:deadline="deadline" :deadline="deadline"
:therapyEnum="therapyEnum"
/> />
<SummaryMedicineList v-else /> <SummaryMedicineList v-else />
<!-- <el-tabs v-model="activeName" class="demo-tabs centered-tabs" @tab-change="handleClick"> <!-- <el-tabs v-model="activeName" class="demo-tabs centered-tabs" @tab-change="handleClick">
@@ -121,7 +120,6 @@ const requestStatus = ref(RequestStatus.COMPLETED);
const chooseAll = ref(false); const chooseAll = ref(false);
const drugType = ref('1'); const drugType = ref('1');
const isDetails = ref('1'); const isDetails = ref('1');
const therapyEnum = ref(undefined);
// 存储子组件引用的对象 // 存储子组件引用的对象
const prescriptionRefs = ref(); const prescriptionRefs = ref();
@@ -177,10 +175,6 @@ function handleRadioChange(value) {
} }
} }
function handleTherapyChange() {
handleGetPrescription();
}
function handleExecute() { function handleExecute() {
proxy.$refs['prescriptionRefs'].handleMedicineSummary(); proxy.$refs['prescriptionRefs'].handleMedicineSummary();
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -829,7 +829,7 @@
</el-descriptions> </el-descriptions>
</div> </div>
<div style="padding: 10px"> <div style="padding: 10px">
<prescriptionlist v-if="showChargeDialog" :patientInfo="chargePatientInfo" ref="prescriptionRef" /> <prescriptionlist :patientInfo="chargePatientInfo" ref="prescriptionRef" />
<div class="overlay" v-if="disabled"></div> <div class="overlay" v-if="disabled"></div>
</div> </div>
</div> </div>
@@ -1136,14 +1136,12 @@ function loadDeptList() {
const tree = res.data?.records || res.data || [] const tree = res.data?.records || res.data || []
deptList.value = flattenOrgTree(tree) deptList.value = flattenOrgTree(tree)
} else { } else {
// 权限不足时静默降级不弹窗阻断Bug #441 proxy.$modal.msgError('获取科室列表失败')
console.warn('科室列表加载失败(可能无权限):', res.message || res.code)
deptList.value = [] deptList.value = []
} }
}) })
.catch(error => { .catch(error => {
// 网络错误或权限拒绝:静默降级 proxy.$modal.msgError('获取科室列表失败')
console.warn('科室列表加载失败:', error?.message || error)
deptList.value = [] deptList.value = []
}) })
} }
@@ -1156,14 +1154,12 @@ function loadDoctorList() {
const records = res.data?.records || [] const records = res.data?.records || []
doctorList.value = records.map(mapPractitionerToOption).filter(it => it.code && it.name) doctorList.value = records.map(mapPractitionerToOption).filter(it => it.code && it.name)
} else { } else {
// 权限不足时静默降级不弹窗阻断Bug #441 proxy.$modal.msgError('获取医生列表失败')
console.warn('医生列表加载失败(可能无权限):', res.message || res.code)
doctorList.value = [] doctorList.value = []
} }
}) })
.catch(error => { .catch(error => {
// 网络错误或权限拒绝:静默降级 proxy.$modal.msgError('获取医生列表失败')
console.warn('医生列表加载失败:', error?.message || error)
doctorList.value = [] doctorList.value = []
}) })
} }
@@ -1176,14 +1172,12 @@ function loadNurseList() {
const records = res.data?.records || [] const records = res.data?.records || []
nurseList.value = records.map(mapPractitionerToOption).filter(it => it.code && it.name) nurseList.value = records.map(mapPractitionerToOption).filter(it => it.code && it.name)
} else { } else {
// 权限不足时静默降级不弹窗阻断Bug #441 proxy.$modal.msgError('获取护士列表失败')
console.warn('护士列表加载失败(可能无权限):', res.message || res.code)
nurseList.value = [] nurseList.value = []
} }
}) })
.catch(error => { .catch(error => {
// 网络错误或权限拒绝:静默降级 proxy.$modal.msgError('获取护士列表失败')
console.warn('护士列表加载失败:', error?.message || error)
nurseList.value = [] nurseList.value = []
}) })
} }
@@ -1196,14 +1190,12 @@ function loadOperatingRoomList() {
const records = res.data?.records || [] const records = res.data?.records || []
operatingRoomList.value = records.map(mapOperatingRoomToOption).filter(it => it.roomCode) operatingRoomList.value = records.map(mapOperatingRoomToOption).filter(it => it.roomCode)
} else { } else {
// 权限不足时静默降级不弹窗阻断Bug #441 proxy.$modal.msgError('获取手术室列表失败')
console.warn('手术室列表加载失败(可能无权限):', res.message || res.code)
operatingRoomList.value = [] operatingRoomList.value = []
} }
}) })
.catch(error => { .catch(error => {
// 网络错误或权限拒绝:静默降级 proxy.$modal.msgError('获取手术室列表失败')
console.warn('手术室列表加载失败:', error?.message || error)
operatingRoomList.value = [] operatingRoomList.value = []
}) })
} }
@@ -1412,16 +1404,9 @@ async function handleChargeCharge(row) {
// 关闭计费弹窗 // 关闭计费弹窗
function closeChargeDialog() { function closeChargeDialog() {
// 先关闭 prescriptionlist 内所有已打开的项目字典 popover showChargeDialog.value = false
if (prescriptionRef.value && prescriptionRef.value.closeAllPopovers) { chargePatientInfo.value = {}
prescriptionRef.value.closeAllPopovers() chargeSurgeryInfo.value = {}
}
// 等 Vue 完成 DOM 更新后再关闭弹窗,确保 popover 先消失
nextTick(() => {
showChargeDialog.value = false
chargePatientInfo.value = {}
chargeSurgeryInfo.value = {}
})
} }
// 🔧 新增:标志位,用于区分是"打开"还是"刷新" // 🔧 新增:标志位,用于区分是"打开"还是"刷新"
@@ -1925,7 +1910,6 @@ function resetForm() {
allergyRemark: undefined, allergyRemark: undefined,
surgeryNature: undefined, surgeryNature: undefined,
surgerySite: undefined, surgerySite: undefined,
incisionType: undefined,
admissionTime: undefined, admissionTime: undefined,
entryTime: undefined, entryTime: undefined,
roomCode: undefined, roomCode: undefined,

View File

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

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(), '其他标本');