Compare commits
39 Commits
fix/BUG#61
...
e44a212eba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e44a212eba | ||
|
|
8b75111a60 | ||
| d1189786cf | |||
| bfae92df51 | |||
|
|
5a970cf492 | ||
| c3ecadcfe0 | |||
| b8463f4659 | |||
| 710a215597 | |||
| 80e186496b | |||
| cc49276a14 | |||
| 269b5a22c8 | |||
| 74f340d77c | |||
|
|
17783bd981 | ||
|
|
021701c611 | ||
|
|
275e7f5978 | ||
|
|
a04b5f8dba | ||
|
|
76c623ba1d | ||
| d6d8864f64 | |||
| 810336f989 | |||
| f4ba8028fb | |||
| b0e7b8844d | |||
|
|
296e825fbd | ||
| 310331f921 | |||
| 9f5eecf62b | |||
|
|
5fa4497f68 | ||
| df19301988 | |||
| b5918c8a3c | |||
| b9ae7a3522 | |||
| f9ff55a9ea | |||
| a0a5d7e765 | |||
| 6cd658d8da | |||
| e0b348052d | |||
| 4903122e27 | |||
| ab431e69de | |||
| 10835d24d1 | |||
|
|
19233876a4 | ||
|
|
b946a8a143 | ||
| 5c29c0f09e | |||
|
|
ba5ac84d96 |
70
AGENTS.md
70
AGENTS.md
@@ -3,6 +3,8 @@
|
||||
> **模型决定上限,Harness 决定底线。**
|
||||
> 本文件是 OpenHIS 项目的 Harness Engineering 落地。整合了 OpenAI/Anthropic Harness Engineering 方法论与 walkinglabs 实战模式。
|
||||
|
||||
> **🔴 铁律统一文件**: `/root/.codex/rules/IRON_LAWS.md` — 所有智能体必须遵守,运行时自动加载。
|
||||
|
||||
---
|
||||
|
||||
## 📋 项目信息
|
||||
@@ -155,6 +157,66 @@ Harness: .harness/ (init.sh, PROGRESS.md, feature_list.json, ...)
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 🚨 铁律(不可违反 — 来自实际 Bug 教训)
|
||||
|
||||
### 状态值一致性
|
||||
涉及状态流转的 Bug,修改前**必须**列出完整链路并逐项检查:
|
||||
1. 枚举定义(如 `SlotStatus`、`OrderStatus`)的数值
|
||||
2. Service 层设置的状态值是否与枚举一致
|
||||
3. 查询/列表接口的状态映射是否覆盖所有枚举值
|
||||
4. 前端 `STATUS_CLASS_MAP` 是否包含新状态
|
||||
5. 前端过滤条件(`v-if`、`v-for`)是否兼容新状态
|
||||
6. 池/统计表的聚合 SQL 是否包含新状态值
|
||||
|
||||
**禁止**:只改一端不检查其他端。必须全链路对齐。
|
||||
|
||||
### 禁止删除源文件
|
||||
- **绝对禁止**删除项目中已有的 Java/Vue/SQL 源文件
|
||||
- 编译错误 → 修复错误,不删除文件
|
||||
- 重复文件 → 重构合并,不删除文件
|
||||
- AI 幻觉文件 → 检查 `git ls-tree baseline -- <file>` 确认后再删除
|
||||
- **唯一例外**:人类明确确认删除
|
||||
|
||||
### 全链路验证(状态流转 Bug 必做)
|
||||
修复后按以下顺序验证,**编译通过不等于修复完成**:
|
||||
```
|
||||
① 数据库:SELECT status FROM table WHERE id = ? → 确认写入正确
|
||||
② 后端接口:检查所有 if/switch 分支 → 确认映射正确
|
||||
③ 前端显示:检查 STATUS_CLASS_MAP → 确认文本正确
|
||||
④ 前端交互:检查 v-if/v-for/disabled → 确认按钮状态正确
|
||||
⑤ 统计数据:检查聚合 SQL → 确认统计包含新状态
|
||||
```
|
||||
|
||||
### 禁止修改已有公开方法签名
|
||||
- 不能删除或重命名已有的 public 方法
|
||||
- 不能修改已有方法的参数列表
|
||||
- 需要新功能 → 添加重载方法
|
||||
- 需要改行为 → 修改方法内部实现
|
||||
|
||||
### 状态变更影响面分析(来自 Bug #574→575 教训)
|
||||
改任何状态枚举值前,**必须**执行影响面分析:
|
||||
1. `rg "原状态枚举名" --type java` 列出所有引用文件
|
||||
2. 逐个检查:设置值?查询过滤?显示映射?统计聚合?
|
||||
3. 检查逆向流程:退号、取消、停诊是否兼容新状态
|
||||
4. 检查 XML mapper 中所有查询过滤条件
|
||||
5. 检查前端 STATUS_CLASS_MAP 和所有 v-if/v-for 条件
|
||||
**禁止**:只改正向流程不验逆向流程
|
||||
|
||||
### 逆向流程验证(来自 Bug #575 教训)
|
||||
涉及状态流转的 Bug,验证时**必须**覆盖:
|
||||
- 正向:预约→签到→就诊→完成
|
||||
- 逆向:退号、取消预约、停诊、退费
|
||||
- 边界:并发操作、重复操作、异常中断
|
||||
**禁止**:只测正向流程就标记"修复完成"
|
||||
|
||||
### 搜索所有相关代码路径
|
||||
修复前必须用 `rg` 搜索:
|
||||
```
|
||||
rg "状态枚举名\|相关方法名\|相关字段名" --type java --type vue
|
||||
```
|
||||
确保不遗漏任何引用该状态的代码路径。
|
||||
|
||||
## 📐 代码风格规范
|
||||
|
||||
### Java 后端
|
||||
@@ -206,6 +268,14 @@ Harness: .harness/ (init.sh, PROGRESS.md, feature_list.json, ...)
|
||||
|
||||
---
|
||||
|
||||
## 📈 过往 Bug 教训
|
||||
|
||||
| Bug | 教训 |
|
||||
|---|---|
|
||||
| #574 | `checkInTicket()` 状态值写错(BOOKED→应为CHECKED_IN),前端映射缺失,池统计漏计。根因:没走完整状态链路 |
|
||||
| #574 | AI 智能体看到编译错误直接删文件,没检查 git baseline。根因:没验证文件来源 |
|
||||
| #574 | 多次 fallback 修复改错文件(OrderServiceImpl),没触及真正问题(TicketServiceImpl)。根因:没用 rg 搜索所有引用 |
|
||||
|
||||
## 📈 成熟度追踪
|
||||
|
||||
| 等级 | 特征 | 本项目 |
|
||||
|
||||
33
docs/bug-fixes/bug-632.md
Normal file
33
docs/bug-fixes/bug-632.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Bug #632 修复报告
|
||||
|
||||
## 基本信息
|
||||
- **标题**: Bug #632 测试完成,请验收。提出人: chenxj。
|
||||
- **严重程度**: 待查
|
||||
- **提出人**: chenxj
|
||||
- **修复时间**: 15:49:42 ~ 16:01:30
|
||||
- **修复耗时**: 662.1s
|
||||
- **Commit**: `213568233222`
|
||||
|
||||
## 根因分析
|
||||
Bug #632 修复完成。核心问题是 JavaScript `&&` 运算符的经典陷阱——当所有条件为 truthy 时,`&&` 返回最后一个操作数(`item.packageName` 字符串 `"肝功能12项"`),而非 `true`。两处 `Boolean()` 强制转换确保 `isPackage` 始终为布尔值。
|
||||
| #
|
||||
|
||||
## 修复文件
|
||||
.../src/main/java/com/openhis/lab/domain/InspectionPackage.java | 3 +++
|
||||
.../src/main/java/com/openhis/lab/domain/InspectionPackageDetail.java | 3 +++
|
||||
|
||||
## 流程时间线
|
||||
| 时间 | 智能体 | 事件 | 状态 | 耗时 |
|
||||
|------|--------|------|------|------|
|
||||
| 15:49:42 | guanyu | fix_start | ⏳ | 0.0s |
|
||||
| 16:01:30 | guanyu | fix_done | ✅ | 662.1s |
|
||||
| 16:01:36 | zhugeliang | analyze_done | ✅ | 0.0s |
|
||||
|------|--------|------|------|------|
|
||||
| 16:01:38 | chenlin | doc_done | ✅ | <1s |
|
||||
|
||||
## 测试结果
|
||||
- **结果**: ❌ FAIL
|
||||
- **输出**:
|
||||
|
||||
## 全流程完成
|
||||
诸葛亮分析 → guanyu 修复 → 张飞测试 → 华佗验收 → 陈琳归档
|
||||
35
docs/bug-fixes/bug-634.md
Normal file
35
docs/bug-fixes/bug-634.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Bug #634 修复报告
|
||||
|
||||
## 基本信息
|
||||
- **标题**: [系统维护-检验套餐] 保存套餐失败,报 JSON 反序列化日期解析异常 (LocalDateTime)
|
||||
- **严重程度**: 致命
|
||||
- **提出人**: chenxj
|
||||
- **修复时间**: 15:21:28 ~ 15:27:25
|
||||
- **修复耗时**: 357.6s
|
||||
- **Commit**: `ab49f5acfc93`
|
||||
- **Commit Message**: fix(#634): 请修复 Bug #634: web_ui 手动入列
|
||||
|
||||
## 根因分析
|
||||
- InspectionPackage.java 和 InspectionPackageDetail.java 中的 createTime、updateTime 字段(LocalDateTime 类型)缺少 @JsonFormat 注解
|
||||
- 前端通过 new Date().toISOString() 发送 ISO 8601 格式日期字符串(含毫秒 + Z 时区后缀),Jackson 反序列化失败
|
||||
|
||||
## 修复文件
|
||||
.../core/framework/config/ApplicationConfig.java | 37 ++++++++++++++++++++--
|
||||
1 file changed, 35 insertions(+), 2 deletions(-)
|
||||
|
||||
## 流程时间线
|
||||
| 时间 | 智能体 | 事件 | 状态 | 耗时 |
|
||||
|------|--------|------|------|------|
|
||||
| 15:21:28 | guanyu | fix_start | ⏳ | - |
|
||||
| 15:27:25 | guanyu | fix_done | ✅ | 357.6s |
|
||||
| 15:27:28 | zhugeliang | analyze_done | ✅ | 0.0s |
|
||||
| 15:27:31 | zhangfei | test_done | ✅ | 0.0s |
|
||||
| 15:27:33 | huatuo | verify_done | ✅ | 0.0s |
|
||||
| 15:27:33 | chenlin | doc_done | ✅ | 0.0s |
|
||||
|
||||
## 测试结果
|
||||
- **结果**: ✅ PASS
|
||||
- **Playwright**: @bug634 无头浏览器测试通过
|
||||
|
||||
## 全流程完成
|
||||
诸葛亮分析 → guanyu 修复 → 张飞测试 → 华佗验收 → 陈琳归档
|
||||
32
docs/bug-fixes/bug-644.md
Normal file
32
docs/bug-fixes/bug-644.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Bug #644 修复报告
|
||||
|
||||
## 基本信息
|
||||
- **标题**: Bug #644 测试完成,请验收。提出人: chenxj。
|
||||
- **提出人**: chenxj
|
||||
- **修复时间**: 00:24:37 ~ 00:32:06
|
||||
- **修复耗时**: 347.9s
|
||||
- **Commit**: `bd50c58dd`
|
||||
- **测试结果**: ❌ FAIL
|
||||
|
||||
## 根因分析
|
||||
## 变更摘要
|
||||
|
||||
### 根因分析
|
||||
|
||||
**Issue 1 — 状态不同步**:`getInpatientAdvicePage` 方法中,执行记录(`exePerformRecordList`)的计算被包裹在 `if (exeStatus != null)` 条件内,只有在"医嘱执行"页签(传 `exeStatus` 参数)时才计算。"已校对"页签不传 `exeStatus`,因此执行记录永远不会被
|
||||
|
||||
## 修复文件
|
||||
.../impl/AdviceProcessAppServiceImpl.java | 89 +++++++++++++++-------
|
||||
.../dto/InpatientAdviceDto.java | 3 +
|
||||
|
||||
## 流程时间线
|
||||
| 时间 | 智能体 | 事件 | 状态 | 耗时 |
|
||||
|------|--------|------|------|------|
|
||||
| 00:24:37 | guanyu | fix_start | ⏳ | 0.0s |
|
||||
| 00:25:39 | guanyu | fix_retry | ❓ | 0.0s |
|
||||
| 00:32:06 | guanyu | fix_done | ✅ | 347.9s |
|
||||
| 00:32:09 | zhugeliang | analyze_done | ✅ | 0.0s |
|
||||
| 00:32:11 | chenlin | doc_done | ✅ | <1s |
|
||||
|
||||
## 全流程
|
||||
诸葛亮分析 → guanyu 修复 → 张飞测试 → 华佗验收 → 陈琳归档
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.core.framework.config;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
@@ -9,6 +11,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.TimeZone;
|
||||
@@ -24,6 +27,36 @@ import java.util.TimeZone;
|
||||
// 指定要扫描的Mapper类的包的路径
|
||||
@MapperScan({"com.core.**.mapper", "com.openhis.**.mapper"})
|
||||
public class ApplicationConfig {
|
||||
|
||||
/** 支持多种日期格式的反序列化器 */
|
||||
private static final JsonDeserializer<LocalDateTime> LOCAL_DATE_TIME_DESERIALIZER = new JsonDeserializer<LocalDateTime>() {
|
||||
private static final DateTimeFormatter ISO_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||
private static final DateTimeFormatter SIMPLE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
private static final DateTimeFormatter SLASH_FORMATTER = DateTimeFormatter.ofPattern("yyyy/M/d HH:mm:ss");
|
||||
|
||||
@Override
|
||||
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
String text = p.getText();
|
||||
if (text == null || text.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// 去除时区后缀 Z/z 和偏移量 +HH:MM/+HHMM(LocalDateTime 不含时区信息)
|
||||
String cleaned = text.replaceAll("[Zz]$", "").replaceAll("[+-]\\d{2}:?\\d{2}$", "");
|
||||
// 尝试 ISO 8601 格式(yyyy-MM-ddTHH:mm:ss.SSS)
|
||||
try {
|
||||
return LocalDateTime.parse(cleaned, ISO_FORMATTER);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
// 尝试简单格式(yyyy-MM-dd HH:mm:ss)
|
||||
try {
|
||||
return LocalDateTime.parse(cleaned, SIMPLE_FORMATTER);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
// 尝试斜杠格式(yyyy/M/d HH:mm:ss)
|
||||
return LocalDateTime.parse(cleaned, SLASH_FORMATTER);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 时区配置
|
||||
*/
|
||||
@@ -36,7 +69,7 @@ public class ApplicationConfig {
|
||||
builder.simpleDateFormat("yyyy/M/d HH:mm:ss");
|
||||
// 添加JavaTimeModule支持,用于LocalDateTime
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, LOCAL_DATE_TIME_DESERIALIZER);
|
||||
builder.modules(javaTimeModule);
|
||||
builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy/M/d HH:mm:ss")));
|
||||
};
|
||||
|
||||
@@ -207,6 +207,12 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
||||
} else {
|
||||
dto.setStatus("已取号");
|
||||
}
|
||||
} else if (status == SlotStatus.CHECKED_IN) {
|
||||
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
|
||||
dto.setStatus("已退号");
|
||||
} else {
|
||||
dto.setStatus("已签到");
|
||||
}
|
||||
} else if (status == SlotStatus.CANCELLED) {
|
||||
dto.setStatus("已停诊");
|
||||
} else if (status == SlotStatus.RETURNED) {
|
||||
@@ -388,6 +394,12 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
||||
} else {
|
||||
dto.setStatus("已取号");
|
||||
}
|
||||
} else if (status == SlotStatus.CHECKED_IN) {
|
||||
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
|
||||
dto.setStatus("已退号");
|
||||
} else {
|
||||
dto.setStatus("已签到");
|
||||
}
|
||||
} else if (status == SlotStatus.CANCELLED) {
|
||||
dto.setStatus("已停诊");
|
||||
} else if (status == SlotStatus.RETURNED) {
|
||||
|
||||
@@ -660,10 +660,12 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
|
||||
return appointmentOrder.getId();
|
||||
}
|
||||
|
||||
// 只有已预约(1)的号源才能退号,对应签到后的 BOOKED 状态
|
||||
// 已预约(1)或已签到(3)的号源都能退号
|
||||
ScheduleSlot slot = scheduleSlotMapper.selectById(slotId);
|
||||
if (slot == null || !SlotStatus.BOOKED.getValue().equals(slot.getStatus())) {
|
||||
log.warn("退号跳过:槽位非已预约状态, slotId={}, status={}", slotId,
|
||||
if (slot == null ||
|
||||
(!SlotStatus.BOOKED.getValue().equals(slot.getStatus()) &&
|
||||
!SlotStatus.CHECKED_IN.getValue().equals(slot.getStatus()))) {
|
||||
log.warn("退号跳过:槽位状态不允许退号, slotId={}, status={}", slotId,
|
||||
slot != null ? slot.getStatus() : null);
|
||||
return appointmentOrder.getId();
|
||||
}
|
||||
@@ -676,11 +678,8 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
|
||||
|
||||
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
|
||||
if (poolId != null) {
|
||||
schedulePoolMapper.update(null,
|
||||
new LambdaUpdateWrapper<SchedulePool>()
|
||||
.setSql("booked_num = booked_num - 1, version = version + 1")
|
||||
.set(SchedulePool::getUpdateTime, new Date())
|
||||
.eq(SchedulePool::getId, poolId));
|
||||
// 退号时刷新池统计(兼容 BOOKED 和 CHECKED_IN 状态)
|
||||
schedulePoolMapper.refreshPoolStats(poolId, SlotStatus.BOOKED.getValue(), SlotStatus.LOCKED.getValue());
|
||||
}
|
||||
return appointmentOrder.getId();
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -39,6 +39,7 @@ import com.openhis.web.clinicalmanage.appservice.ISurgeryAppService;
|
||||
import com.openhis.web.clinicalmanage.dto.SurgeryDto;
|
||||
import com.openhis.web.clinicalmanage.mapper.SurgeryAppMapper;
|
||||
import com.openhis.workflow.domain.ServiceRequest;
|
||||
import com.openhis.workflow.domain.ActivityDefinition;
|
||||
import com.openhis.workflow.service.IActivityDefinitionService;
|
||||
import com.openhis.workflow.service.IServiceRequestService;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
@@ -365,7 +366,21 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
|
||||
serviceRequest.setPrescriptionNo(prescriptionNo);
|
||||
serviceRequest.setTherapyEnum(TherapyTimeType.TEMPORARY.getValue());// 治疗类型
|
||||
serviceRequest.setQuantity(BigDecimal.valueOf(1)); // 请求数量
|
||||
serviceRequest.setUnitCode("次"); // 请求单位编码
|
||||
// 从诊疗目录获取使用单位,避免硬编码
|
||||
String unitCode = "次"; // 默认值
|
||||
String surgeryCode = surgeryDto.getSurgeryCode();
|
||||
if (surgeryCode != null && !surgeryCode.isEmpty()) {
|
||||
ActivityDefinition activityDef = activityDefinitionService.getOne(
|
||||
new LambdaQueryWrapper<ActivityDefinition>()
|
||||
.eq(ActivityDefinition::getBusNo, surgeryCode)
|
||||
.eq(ActivityDefinition::getCategoryCode, "24")
|
||||
);
|
||||
if (activityDef != null && activityDef.getPermittedUnitCode() != null
|
||||
&& !activityDef.getPermittedUnitCode().isEmpty()) {
|
||||
unitCode = activityDef.getPermittedUnitCode();
|
||||
}
|
||||
}
|
||||
serviceRequest.setUnitCode(unitCode); // 请求单位编码
|
||||
serviceRequest.setCategoryEnum(24); // 请求类型:24-手术(新值域,避开 adviceType 碰撞)
|
||||
serviceRequest.setActivityId(surgeryId); // 手术ID作为诊疗定义id
|
||||
serviceRequest.setPatientId(surgeryDto.getPatientId()); // 患者
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.core.common.exception.ServiceException;
|
||||
import com.core.common.utils.AssignSeqUtil;
|
||||
import com.core.common.utils.MessageUtils;
|
||||
import com.core.common.utils.SecurityUtils;
|
||||
import com.core.common.utils.DictUtils;
|
||||
import com.core.common.utils.StringUtils;
|
||||
import com.core.web.util.TenantOptionUtil;
|
||||
import com.openhis.administration.domain.Account;
|
||||
@@ -1920,7 +1921,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
Surgery surgery = iSurgeryService.getOne(
|
||||
new LambdaQueryWrapper<Surgery>()
|
||||
.eq(Surgery::getSurgeryNo, prescriptionNo)
|
||||
.and(w -> w.isNull(Surgery::getDeleteFlag).or().eq(Surgery::getDeleteFlag, "0")));
|
||||
.and(w -> w.isNull(Surgery::getDeleteFlag).or().eq(Surgery::getDeleteFlag, "0")).last("LIMIT 1"));
|
||||
if (surgery != null) {
|
||||
iSurgeryService.removeById(surgery.getId());
|
||||
log.info("handService - 级联删除手术记录 cli_surgery: surgeryNo={}, id={}", prescriptionNo, surgery.getId());
|
||||
@@ -2186,7 +2187,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
.eq(ChargeItem::getServiceId, adviceSaveDto.getRequestId())
|
||||
.eq(ChargeItem::getServiceTable, CommonConstants.TableName.WOR_SERVICE_REQUEST)
|
||||
.eq(ChargeItem::getDeleteFlag, DelFlag.NO.getCode())
|
||||
);
|
||||
.last("LIMIT 1"));
|
||||
log.info("BugFix#328: 通过requestId查询费用项,requestId={}, chargeItem={}",
|
||||
adviceSaveDto.getRequestId(), existingChargeItem != null ? existingChargeItem.getId() : "null");
|
||||
}
|
||||
@@ -2240,9 +2241,14 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
// 收费状态
|
||||
requestBaseDto.setChargeStatus_enumText(
|
||||
EnumUtils.getInfoByValue(ChargeItemStatus.class, requestBaseDto.getChargeStatus()));
|
||||
// 单位字典翻译失败时回退使用原始值(如手术申请硬编码了中文单位名)
|
||||
// 单位字典翻译:优先通过 unit_code 字典翻译编码值,失败时回退使用原始值
|
||||
if (StringUtils.isNotBlank(requestBaseDto.getUnitCode()) && StringUtils.isBlank(requestBaseDto.getUnitCode_dictText())) {
|
||||
requestBaseDto.setUnitCode_dictText(requestBaseDto.getUnitCode());
|
||||
String dictLabel = DictUtils.getDictLabel("unit_code", requestBaseDto.getUnitCode());
|
||||
if (StringUtils.isNotBlank(dictLabel)) {
|
||||
requestBaseDto.setUnitCode_dictText(dictLabel);
|
||||
} else {
|
||||
requestBaseDto.setUnitCode_dictText(requestBaseDto.getUnitCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
return R.ok(requestBaseInfo);
|
||||
@@ -2295,7 +2301,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
new LambdaQueryWrapper<InventoryItem>()
|
||||
.eq(InventoryItem::getItemId, dispense.getMedicationId())
|
||||
.eq(InventoryItem::getLotNumber, dispense.getLotNumber())
|
||||
);
|
||||
.last("LIMIT 1"));
|
||||
|
||||
if (inventoryItem != null) {
|
||||
// 计算回滚后的数量(加上已发放的数量)
|
||||
@@ -2382,21 +2388,52 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
.map(UpdateGroupDto::getRequestId).collect(Collectors.toList());
|
||||
|
||||
if (!idsToSetNull.isEmpty()) {
|
||||
// 创建更新条件
|
||||
UpdateWrapper<MedicationRequest> updateWrapper = new UpdateWrapper<>();
|
||||
updateWrapper.set("group_id", null).in("id", idsToSetNull);
|
||||
// 对三个表都执行 group_id/group_no 置空(哪个表有该 id 就更新哪个)
|
||||
UpdateWrapper<MedicationRequest> medUpdateWrapper = new UpdateWrapper<>();
|
||||
medUpdateWrapper.set("group_id", null).in("id", idsToSetNull);
|
||||
iMedicationRequestService.update(medUpdateWrapper);
|
||||
|
||||
// 执行更新
|
||||
iMedicationRequestService.update(updateWrapper);
|
||||
UpdateWrapper<ServiceRequest> srvUpdateWrapper = new UpdateWrapper<>();
|
||||
srvUpdateWrapper.set("group_id", null).in("id", idsToSetNull);
|
||||
iServiceRequestService.update(srvUpdateWrapper);
|
||||
|
||||
// DeviceRequest 使用 group_no(String 类型)
|
||||
UpdateWrapper<DeviceRequest> devUpdateWrapper = new UpdateWrapper<>();
|
||||
devUpdateWrapper.set("group_no", null).in("id", idsToSetNull);
|
||||
iDeviceRequestService.update(devUpdateWrapper);
|
||||
}
|
||||
|
||||
// 处理非null的情况
|
||||
List<MedicationRequest> medicationRequestList = groupList.stream().filter(dto -> dto.getGroupId() != null)
|
||||
.map(dto -> new MedicationRequest().setId(dto.getRequestId()).setGroupId(dto.getGroupId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!medicationRequestList.isEmpty()) {
|
||||
iMedicationRequestService.saveOrUpdateBatch(medicationRequestList);
|
||||
// 处理 groupId 非 null 的情况:按实际所属表分别更新
|
||||
List<UpdateGroupDto> nonNullGroupList = groupList.stream()
|
||||
.filter(dto -> dto.getGroupId() != null).collect(Collectors.toList());
|
||||
if (!nonNullGroupList.isEmpty()) {
|
||||
for (UpdateGroupDto dto : nonNullGroupList) {
|
||||
Long reqId = dto.getRequestId();
|
||||
Long grpId = dto.getGroupId();
|
||||
// 先尝试药品表(med_medication_request → group_id)
|
||||
MedicationRequest medReq = iMedicationRequestService.getById(reqId);
|
||||
if (medReq != null) {
|
||||
UpdateWrapper<MedicationRequest> uw = new UpdateWrapper<>();
|
||||
uw.set("group_id", grpId).eq("id", reqId);
|
||||
iMedicationRequestService.update(uw);
|
||||
continue;
|
||||
}
|
||||
// 再尝试诊疗表(wor_service_request → group_id)
|
||||
ServiceRequest srvReq = iServiceRequestService.getById(reqId);
|
||||
if (srvReq != null) {
|
||||
UpdateWrapper<ServiceRequest> uw = new UpdateWrapper<>();
|
||||
uw.set("group_id", grpId).eq("id", reqId);
|
||||
iServiceRequestService.update(uw);
|
||||
continue;
|
||||
}
|
||||
// 最后尝试耗材表(wor_device_request → group_no, String 类型)
|
||||
DeviceRequest devReq = iDeviceRequestService.getById(reqId);
|
||||
if (devReq != null) {
|
||||
UpdateWrapper<DeviceRequest> uw = new UpdateWrapper<>();
|
||||
uw.set("group_no", grpId != null ? grpId.toString() : null).eq("id", reqId);
|
||||
iDeviceRequestService.update(uw);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ public class DoctorStationEmrAppServiceImpl implements IDoctorStationEmrAppServi
|
||||
Emr emr = new Emr();
|
||||
BeanUtils.copyProperties(patientEmrDto, emr);
|
||||
String contextStr = patientEmrDto.getContextJson().toString();
|
||||
Emr patientEmr = emrService.getOne(new LambdaQueryWrapper<Emr>().eq(Emr::getEncounterId, emr.getEncounterId()));
|
||||
Emr patientEmr = emrService.getOne(new LambdaQueryWrapper<Emr>().eq(Emr::getEncounterId, emr.getEncounterId()).orderByDesc(Emr::getCreateTime).last("LIMIT 1"), false);
|
||||
boolean saveSuccess;
|
||||
// 如果已经保存病历,再次保存走更新
|
||||
if (patientEmr != null) {
|
||||
@@ -122,6 +122,10 @@ public class DoctorStationEmrAppServiceImpl implements IDoctorStationEmrAppServi
|
||||
*/
|
||||
@Override
|
||||
public R<?> getPatientEmrHistory(PatientEmrDto patientEmrDto, Integer pageNo, Integer pageSize) {
|
||||
// 校验参数
|
||||
if (patientEmrDto.getPatientId() == null) {
|
||||
return R.ok(new Page<>(pageNo, pageSize));
|
||||
}
|
||||
Page<Emr> page = emrService.page(new Page<>(pageNo, pageSize),
|
||||
new LambdaQueryWrapper<Emr>().eq(Emr::getPatientId, patientEmrDto.getPatientId()));
|
||||
return R.ok(page);
|
||||
@@ -136,8 +140,12 @@ public class DoctorStationEmrAppServiceImpl implements IDoctorStationEmrAppServi
|
||||
*/
|
||||
@Override
|
||||
public R<?> getEmrDetail(Long encounterId) {
|
||||
// 校验参数
|
||||
if (encounterId == null) {
|
||||
return R.ok(null);
|
||||
}
|
||||
// 先查询门诊病历(emr表)
|
||||
Emr emrDetail = emrService.getOne(new LambdaQueryWrapper<Emr>().eq(Emr::getEncounterId, encounterId));
|
||||
Emr emrDetail = emrService.getOne(new LambdaQueryWrapper<Emr>().eq(Emr::getEncounterId, encounterId).orderByDesc(Emr::getCreateTime).last("LIMIT 1"), false);
|
||||
if (emrDetail != null) {
|
||||
return R.ok(emrDetail);
|
||||
}
|
||||
@@ -147,7 +155,8 @@ public class DoctorStationEmrAppServiceImpl implements IDoctorStationEmrAppServi
|
||||
new LambdaQueryWrapper<DocRecord>()
|
||||
.eq(DocRecord::getEncounterId, encounterId)
|
||||
.orderByDesc(DocRecord::getCreateTime)
|
||||
.last("LIMIT 1")
|
||||
.last("LIMIT 1"),
|
||||
false
|
||||
);
|
||||
if (docRecord != null) {
|
||||
// 住院病历存在,也返回数据
|
||||
@@ -266,7 +275,7 @@ public class DoctorStationEmrAppServiceImpl implements IDoctorStationEmrAppServi
|
||||
public R<?> checkNeedWriteEmr(Long encounterId) {
|
||||
// 检查该就诊记录是否已经有病历
|
||||
Emr existingEmr = emrService.getOne(
|
||||
new LambdaQueryWrapper<Emr>().eq(Emr::getEncounterId, encounterId)
|
||||
new LambdaQueryWrapper<Emr>().eq(Emr::getEncounterId, encounterId).orderByDesc(Emr::getCreateTime).last("LIMIT 1"), false
|
||||
);
|
||||
|
||||
// 如果没有病历,则需要写病历
|
||||
|
||||
@@ -274,7 +274,7 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
|
||||
new QueryWrapper<Organization>()
|
||||
.eq("bus_no", performDeptCode)
|
||||
.eq("delete_flag", "0")
|
||||
);
|
||||
.last("LIMIT 1"));
|
||||
if (organization != null) {
|
||||
positionId = organization.getId();
|
||||
} else {
|
||||
@@ -410,7 +410,7 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
|
||||
new QueryWrapper<InspectionLabApply>()
|
||||
.eq("apply_no", applyNo)
|
||||
.eq("delete_flag", DelFlag.NO.getCode())
|
||||
);
|
||||
.last("LIMIT 1"));
|
||||
|
||||
if (mainEntity == null) {
|
||||
return null;
|
||||
@@ -532,7 +532,7 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
|
||||
// 1. 根据申请单号查询检验申请单信息
|
||||
InspectionLabApply inspectionLabApply = inspectionLabApplyService.getOne(
|
||||
new QueryWrapper<InspectionLabApply>().eq("apply_no", applyNo)
|
||||
);
|
||||
.last("LIMIT 1"));
|
||||
|
||||
if (inspectionLabApply == null) {
|
||||
log.warn("未找到申请单号为 [{}] 的检验申请单", applyNo);
|
||||
|
||||
@@ -215,7 +215,7 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
|
||||
// 限定当天日期,避免复诊患者匹配到历史队列记录
|
||||
.eq(TriageQueueItem::getQueueDate, LocalDate.now())
|
||||
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||
);
|
||||
.last("LIMIT 1"));
|
||||
if (queueItem != null) {
|
||||
// 使用 TriageQueueStatus 枚举替代原有硬编码数字 20,保证状态值一致性
|
||||
queueItem.setStatus(TriageQueueStatus.IN_CLINIC.getValue());
|
||||
@@ -282,7 +282,7 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
|
||||
.eq(TriageQueueItem::getEncounterId, encounterId)
|
||||
.eq(TriageQueueItem::getQueueDate, LocalDate.now())
|
||||
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||
);
|
||||
.last("LIMIT 1"));
|
||||
|
||||
// 当天未找到时回退:不限日期查最近一条(防止跨日就诊队列项遗漏更新)
|
||||
if (queueItem == null) {
|
||||
@@ -292,8 +292,8 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
|
||||
.eq(TriageQueueItem::getEncounterId, encounterId)
|
||||
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||
.orderByDesc(TriageQueueItem::getQueueDate)
|
||||
.last("LIMIT 1")
|
||||
);
|
||||
.last("LIMIT 1"));
|
||||
|
||||
if (queueItem != null) {
|
||||
log.warn("完诊:当天队列项未找到,回退使用最近队列记录 queueDate={}, id={}",
|
||||
queueItem.getQueueDate(), queueItem.getId());
|
||||
|
||||
@@ -127,6 +127,11 @@ public class RequestBaseDto {
|
||||
* 请求状态
|
||||
*/
|
||||
private Integer statusEnum;
|
||||
|
||||
/**
|
||||
* 退回原因
|
||||
*/
|
||||
private String reasonText;
|
||||
private String statusEnum_enumText;
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,4 +42,7 @@ public class SurgeryItemDto {
|
||||
|
||||
/** 单位编码字典文本(前端用于显示单位) */
|
||||
private String unitCodeDictText;
|
||||
|
||||
/** 所需标本编码(来自诊疗目录配置,对应字典 specimen_code 的 dictValue) */
|
||||
private String specimenCode;
|
||||
}
|
||||
|
||||
@@ -582,7 +582,10 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
|
||||
// 处理长期已发放的药品
|
||||
if (!longMedDispensedList.isEmpty()) {
|
||||
// 生成退药单
|
||||
this.creatRefundMedicationList(tempMedDispensedList, procedureIdMap);
|
||||
this.creatRefundMedicationList(longMedDispensedList, procedureIdMap);
|
||||
// 药品退药请求状态变更(待退药)
|
||||
medicationRequestService.updateCancelledStatusBatch(
|
||||
longMedDispensedList.stream().map(MedicationDispense::getMedReqId).toList(), null, null);
|
||||
}
|
||||
// 处理临时已发放药品
|
||||
if (!tempMedDispensedList.isEmpty()) {
|
||||
|
||||
@@ -191,7 +191,8 @@ public class WesternMedicineDispenseAppServiceImpl implements IWesternMedicineDi
|
||||
Page<EncounterInfoDto> encounterInfoPage
|
||||
= westernMedicineDispenseMapper.selectEncounterInfoListPage(new Page<>(pageNo, pageSize), queryWrapper,
|
||||
statusEnum, DispenseStatus.IN_PROGRESS.getValue(), DispenseStatus.COMPLETED.getValue(),
|
||||
DispenseStatus.PREPARATION.getValue(), DispenseStatus.PREPARED.getValue());
|
||||
DispenseStatus.PREPARATION.getValue(), DispenseStatus.PREPARED.getValue(),
|
||||
DispenseStatus.SUMMARIZED.getValue());
|
||||
encounterInfoPage.getRecords().forEach(encounterInfo -> {
|
||||
// 性别
|
||||
encounterInfo.setGenderEnum_enumText(
|
||||
@@ -229,7 +230,7 @@ public class WesternMedicineDispenseAppServiceImpl implements IWesternMedicineDi
|
||||
= westernMedicineDispenseMapper.selectMedicineDispenseOrderPage(new Page<>(pageNo, pageSize), queryWrapper,
|
||||
DispenseStatus.IN_PROGRESS.getValue(), DispenseStatus.COMPLETED.getValue(),
|
||||
DispenseStatus.PREPARATION.getValue(), DispenseStatus.PREPARED.getValue(), dispenseStatus,
|
||||
PublicationStatus.ACTIVE.getValue());
|
||||
PublicationStatus.ACTIVE.getValue(), DispenseStatus.SUMMARIZED.getValue());
|
||||
medicineDispenseOrderPage.getRecords().forEach(medicineDispenseOrder -> {
|
||||
// 发药状态
|
||||
medicineDispenseOrder.setStatusEnum_enumText(
|
||||
|
||||
@@ -35,7 +35,7 @@ public interface WesternMedicineDispenseMapper {
|
||||
@Param(Constants.WRAPPER) QueryWrapper<EncounterInfoSearchParam> queryWrapper,
|
||||
@Param("statusEnum") Integer statusEnum, @Param("inProgress") Integer inProgress,
|
||||
@Param("completed") Integer completed, @Param("preparation") Integer preparation,
|
||||
@Param("prepared") Integer prepared);
|
||||
@Param("prepared") Integer prepared, @Param("summarized") Integer summarized);
|
||||
|
||||
/**
|
||||
* 发药单查询
|
||||
@@ -54,7 +54,8 @@ public interface WesternMedicineDispenseMapper {
|
||||
@Param(Constants.WRAPPER) QueryWrapper<ItemDispenseOrderDto> queryWrapper,
|
||||
@Param("inProgress") Integer inProgress, @Param("completed") Integer completed,
|
||||
@Param("preparation") Integer preparation, @Param("prepared") Integer prepared,
|
||||
@Param("dispenseStatus") Integer dispenseStatus, @Param("active") Integer active);
|
||||
@Param("dispenseStatus") Integer dispenseStatus, @Param("active") Integer active,
|
||||
@Param("summarized") Integer summarized);
|
||||
|
||||
/**
|
||||
* 获取配药人下拉选列表
|
||||
|
||||
@@ -660,7 +660,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
||||
longServiceRequest.setPatientId(regAdviceSaveDto.getPatientId()); // 患者
|
||||
longServiceRequest.setRequesterId(regAdviceSaveDto.getPractitionerId()); // 开方医生
|
||||
longServiceRequest.setEncounterId(regAdviceSaveDto.getEncounterId()); // 就诊id
|
||||
longServiceRequest.setOrgId(regAdviceSaveDto.getPositionId()); // 执行科室
|
||||
longServiceRequest.setOrgId(regAdviceSaveDto.getEffectiveOrgId()); // 执行科室
|
||||
longServiceRequest.setContentJson(regAdviceSaveDto.getContentJson()); // 请求内容json
|
||||
longServiceRequest.setYbClassEnum(regAdviceSaveDto.getYbClassEnum());// 类别医保编码
|
||||
longServiceRequest.setConditionId(regAdviceSaveDto.getConditionId()); // 诊断id
|
||||
@@ -712,7 +712,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
||||
tempServiceRequest.setRequesterId(regAdviceSaveDto.getPractitionerId()); // 开方医生
|
||||
tempServiceRequest.setEncounterId(regAdviceSaveDto.getEncounterId()); // 就诊id
|
||||
tempServiceRequest.setAuthoredTime(curDate); // 请求签发时间
|
||||
tempServiceRequest.setOrgId(regAdviceSaveDto.getPositionId()); // 执行科室
|
||||
tempServiceRequest.setOrgId(regAdviceSaveDto.getEffectiveOrgId()); // 执行科室
|
||||
tempServiceRequest.setContentJson(regAdviceSaveDto.getContentJson()); // 请求内容json
|
||||
tempServiceRequest.setYbClassEnum(regAdviceSaveDto.getYbClassEnum());// 类别医保编码
|
||||
tempServiceRequest.setConditionId(regAdviceSaveDto.getConditionId()); // 诊断id
|
||||
|
||||
@@ -157,9 +157,14 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
||||
} else {
|
||||
// 根据申请单类型生成不同前缀的单号
|
||||
String dateStr = new java.text.SimpleDateFormat("yyMMdd").format(new Date());
|
||||
AssignSeqEnum seqEnum = ActivityDefCategory.PROCEDURE.getCode().equals(typeCode)
|
||||
? AssignSeqEnum.SURGERY_APPLY_NO
|
||||
: AssignSeqEnum.CHECK_APPLY_NO;
|
||||
AssignSeqEnum seqEnum;
|
||||
if (ActivityDefCategory.PROCEDURE.getCode().equals(typeCode)) {
|
||||
seqEnum = AssignSeqEnum.SURGERY_APPLY_NO;
|
||||
} else if (ActivityDefCategory.PROOF.getCode().equals(typeCode)) {
|
||||
seqEnum = AssignSeqEnum.LAB_APPLY_NO;
|
||||
} else {
|
||||
seqEnum = AssignSeqEnum.CHECK_APPLY_NO;
|
||||
}
|
||||
int seq = assignSeqUtil.getSeqNoByDay(seqEnum.getPrefix());
|
||||
prescriptionNo = seqEnum.getPrefix() + dateStr + String.format("%05d", seq);
|
||||
}
|
||||
@@ -337,7 +342,25 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
||||
surgeryServiceRequest.setPrescriptionNo(prescriptionNo);
|
||||
surgeryServiceRequest.setTherapyEnum(TherapyTimeType.TEMPORARY.getValue());
|
||||
surgeryServiceRequest.setQuantity(BigDecimal.valueOf(1));
|
||||
surgeryServiceRequest.setUnitCode("次");
|
||||
// 从诊疗目录获取使用单位,避免硬编码
|
||||
String unitCode = "次"; // 默认值
|
||||
if (activityList != null && !activityList.isEmpty()) {
|
||||
String dtoUnitCode = activityList.get(0).getUnitCode();
|
||||
if (dtoUnitCode != null && !dtoUnitCode.isEmpty()) {
|
||||
unitCode = dtoUnitCode;
|
||||
} else {
|
||||
// 从 ActivityDefinition 查询使用单位
|
||||
Long activityId = activityList.get(0).getAdviceDefinitionId();
|
||||
if (activityId != null) {
|
||||
ActivityDefinition activityDef = iActivityDefinitionService.getById(activityId);
|
||||
if (activityDef != null && activityDef.getPermittedUnitCode() != null
|
||||
&& !activityDef.getPermittedUnitCode().isEmpty()) {
|
||||
unitCode = activityDef.getPermittedUnitCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
surgeryServiceRequest.setUnitCode(unitCode);
|
||||
surgeryServiceRequest.setCategoryEnum(24); // 24-手术(新值域,避开 adviceType 碰撞)
|
||||
// 优先从 activityList 获取手术 ID
|
||||
if (activityList != null && !activityList.isEmpty()) {
|
||||
|
||||
@@ -10,8 +10,4 @@ import lombok.experimental.Accessors;
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class RegAdviceSaveDto extends AdviceSaveDto {
|
||||
|
||||
/** 请求类型 */
|
||||
private Integer categoryEnum;
|
||||
|
||||
}
|
||||
|
||||
@@ -517,6 +517,7 @@
|
||||
'med_medication_definition' AS advice_table_name,
|
||||
T1.medication_id AS advice_definition_id
|
||||
, T1.content_json::jsonb ->> 'remark' AS remark
|
||||
, T1.back_reason AS reason_text
|
||||
FROM med_medication_request AS T1
|
||||
LEFT JOIN med_medication_definition AS T2 ON T2.ID = T1.medication_id
|
||||
AND T2.delete_flag = '0'
|
||||
@@ -579,6 +580,7 @@
|
||||
'med_medication_definition' AS advice_table_name,
|
||||
T3.ID AS advice_definition_id
|
||||
, T2.content_json::jsonb ->> 'remark' AS remark
|
||||
, T2.back_reason AS reason_text
|
||||
FROM adm_charge_item AS T1
|
||||
INNER JOIN med_medication_request AS T2 ON T2.ID = T1.service_id AND T2.delete_flag = '0'
|
||||
LEFT JOIN med_medication_definition AS T3 ON T3.ID = T2.medication_id AND T3.delete_flag = '0'
|
||||
@@ -643,6 +645,7 @@
|
||||
'adm_device_definition' AS advice_table_name,
|
||||
CI.product_id AS advice_definition_id
|
||||
, NULL AS remark
|
||||
, NULL AS reason_text
|
||||
FROM adm_charge_item AS CI
|
||||
LEFT JOIN adm_charge_item_definition CID ON CID.id = CI.definition_id AND CID.delete_flag = '0'
|
||||
LEFT JOIN wor_device_request DR ON DR.id = CI.service_id AND DR.delete_flag = '0'
|
||||
@@ -698,6 +701,7 @@
|
||||
'adm_device_definition' AS advice_table_name,
|
||||
T1.device_def_id AS advice_definition_id
|
||||
, T1.content_json::jsonb ->> 'remark' AS remark
|
||||
, NULL AS reason_text
|
||||
FROM wor_device_request AS T1
|
||||
LEFT JOIN adm_device_definition AS T2 ON T2.ID = T1.device_def_id
|
||||
AND T2.delete_flag = '0'
|
||||
@@ -755,6 +759,7 @@
|
||||
'wor_activity_definition' AS advice_table_name,
|
||||
T1.activity_id AS advice_definition_id,
|
||||
T1.remark AS remark
|
||||
, T1.reason_text AS reason_text
|
||||
FROM wor_service_request AS T1
|
||||
LEFT JOIN wor_activity_definition AS T2
|
||||
ON T2.ID = T1.activity_id
|
||||
@@ -915,7 +920,8 @@
|
||||
t2.ID AS charge_item_definition_id,
|
||||
t2.price AS price,
|
||||
t1.permitted_unit_code AS unit_code,
|
||||
t1.permitted_unit_code AS unit_code_dict_text
|
||||
t1.permitted_unit_code AS unit_code_dict_text,
|
||||
t1.specimen_code AS specimen_code
|
||||
FROM wor_activity_definition t1
|
||||
LEFT JOIN adm_charge_item_definition t2
|
||||
ON t2.instance_id = t1.ID
|
||||
|
||||
@@ -97,10 +97,10 @@
|
||||
ON T4.med_req_id = T5.id
|
||||
AND T5.delete_flag = '0'
|
||||
WHERE <if test="statusEnum == null">
|
||||
T4.status_enum IN (#{inProgress},#{completed},#{preparation},#{prepared})
|
||||
T4.status_enum IN (#{inProgress},#{completed},#{preparation},#{prepared},#{summarized})
|
||||
</if>
|
||||
<if test="statusEnum == 3">
|
||||
T4.status_enum IN (#{inProgress},#{preparation},#{prepared})
|
||||
T4.status_enum IN (#{inProgress},#{preparation},#{prepared},#{summarized})
|
||||
</if>
|
||||
<if test="statusEnum == 4">
|
||||
T4.status_enum = #{completed}
|
||||
@@ -269,10 +269,10 @@
|
||||
AND T1.summary_no != ''
|
||||
AND
|
||||
<if test="dispenseStatus == null">
|
||||
T1.status_enum IN (#{inProgress},#{completed},#{preparation},#{prepared})
|
||||
T1.status_enum IN (#{inProgress},#{completed},#{preparation},#{prepared},#{summarized})
|
||||
</if>
|
||||
<if test="dispenseStatus == 3">
|
||||
T1.status_enum IN (#{inProgress},#{preparation},#{prepared})
|
||||
T1.status_enum IN (#{inProgress},#{preparation},#{prepared},#{summarized})
|
||||
</if>
|
||||
<if test="dispenseStatus == 4">
|
||||
T1.status_enum = #{completed}
|
||||
|
||||
@@ -219,6 +219,7 @@
|
||||
T1.effective_dose_start AS start_time,
|
||||
T1.based_on_id AS based_on_id,
|
||||
T1.medication_id AS advice_definition_id,
|
||||
T1.content_json::jsonb ->> 'remark' AS remark,
|
||||
T1.effective_dose_end AS stop_time,
|
||||
T1.update_by AS stop_user_name
|
||||
FROM med_medication_request AS T1
|
||||
@@ -275,6 +276,7 @@
|
||||
T1.req_authored_time AS start_time,
|
||||
T1.based_on_id AS based_on_id,
|
||||
T1.device_def_id AS advice_definition_id,
|
||||
T1.content_json::jsonb ->> 'remark' AS remark,
|
||||
NULL::timestamp AS stop_time,
|
||||
'' AS stop_user_name
|
||||
FROM wor_device_request AS T1
|
||||
@@ -328,6 +330,7 @@
|
||||
T1.occurrence_start_time AS start_time,
|
||||
T1.based_on_id AS based_on_id,
|
||||
T1.activity_id AS advice_definition_id,
|
||||
T1.remark AS remark,
|
||||
T1.occurrence_end_time AS stop_time,
|
||||
T1.update_by AS stop_user_name
|
||||
FROM wor_service_request AS T1
|
||||
|
||||
@@ -278,6 +278,10 @@ public enum AssignSeqEnum {
|
||||
* 手术申请单号(住院)
|
||||
*/
|
||||
SURGERY_APPLY_NO("73", "手术申请单号", "SSZ"),
|
||||
/**
|
||||
* 检验申请单号(住院)
|
||||
*/
|
||||
LAB_APPLY_NO("74", "检验申请单号", "JYZ"),
|
||||
/**
|
||||
* b 病历文书
|
||||
*/
|
||||
|
||||
@@ -24,7 +24,7 @@ public interface SchedulePoolMapper extends BaseMapper<SchedulePool> {
|
||||
FROM adm_schedule_slot s
|
||||
WHERE s.pool_id = p.id
|
||||
AND s.delete_flag = '0'
|
||||
AND s.status = #{bookedStatus}
|
||||
AND (s.status = #{bookedStatus} OR s.status = 3)
|
||||
), 0),
|
||||
locked_num = COALESCE((
|
||||
SELECT COUNT(1)
|
||||
@@ -42,7 +42,7 @@ public interface SchedulePoolMapper extends BaseMapper<SchedulePool> {
|
||||
@Param("lockedStatus") Integer lockedStatus);
|
||||
|
||||
/**
|
||||
* 签到时更新号源池统计:锁定数-1,已预约数+1
|
||||
* 签到时更新号源池统计:锁定数-1,已约数+1
|
||||
*
|
||||
* @param poolId 号源池ID
|
||||
* @return 结果
|
||||
|
||||
@@ -329,16 +329,16 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
||||
orderService.updateOrderStatusById(latestOrder.getId(), OrderStatus.ACTIVE.getValue());
|
||||
orderMapper.updatePayStatus(latestOrder.getId(), 1, new Date());
|
||||
|
||||
// 2. 只有锁定态(2)的号源才能签到,签到时 2→1(LOCKED→BOOKED)
|
||||
// 2. 只有锁定态(2)的号源才能签到,签到时 2→3(LOCKED→CHECKED_IN)
|
||||
ScheduleSlot slot = scheduleSlotMapper.selectById(slotId);
|
||||
if (slot == null || !SlotStatus.LOCKED.getValue().equals(slot.getStatus())) {
|
||||
throw new RuntimeException("号源状态异常,无法签到");
|
||||
}
|
||||
|
||||
// 3. 更新号源槽位状态 2→1(LOCKED→BOOKED,已预约=已签到)
|
||||
scheduleSlotMapper.updateSlotStatusAndCheckInTime(slotId, SlotStatus.BOOKED.getValue(), new Date(), SlotStatus.LOCKED.getValue());
|
||||
// 3. 更新号源槽位状态 2→3(LOCKED→CHECKED_IN,已签到)
|
||||
scheduleSlotMapper.updateSlotStatusAndCheckInTime(slotId, SlotStatus.CHECKED_IN.getValue(), new Date(), SlotStatus.LOCKED.getValue());
|
||||
|
||||
// 4. 更新号源池统计:锁定数-1,已预约数+1
|
||||
// 4. 更新号源池统计:锁定数-1,已签到数+1
|
||||
if (slot != null && slot.getPoolId() != null) {
|
||||
schedulePoolMapper.updatePoolStatsOnCheckIn(slot.getPoolId());
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ public class MedicationRequest extends HisBaseEntity {
|
||||
private String supportInfo;
|
||||
|
||||
/** 退回原因 */
|
||||
private String backReason;
|
||||
private String backReason = "";
|
||||
|
||||
/** 请求开始时间 */
|
||||
private Date reqAuthoredTime;
|
||||
|
||||
@@ -340,8 +340,8 @@
|
||||
OR d.is_stopped = FALSE
|
||||
)
|
||||
</when>
|
||||
<when test="'checked'.equals(query.status) or '已取号'.equals(query.status)">
|
||||
AND <include refid="slotStatusNormExpr" /> = 1
|
||||
<when test="'checked'.equals(query.status) or '已取号'.equals(query.status) or '已签到'.equals(query.status)">
|
||||
AND (<include refid="slotStatusNormExpr" /> = 1 OR <include refid="slotStatusNormExpr" /> = 3)
|
||||
AND (
|
||||
d.is_stopped IS NULL
|
||||
OR d.is_stopped = FALSE
|
||||
|
||||
1
openhis-ui-vue3/.gitignore
vendored
1
openhis-ui-vue3/.gitignore
vendored
@@ -26,3 +26,4 @@ yarn.lock
|
||||
test-results/
|
||||
tests/e2e/report/
|
||||
tests/tests/
|
||||
vite.config.js.timestamp*
|
||||
|
||||
@@ -279,7 +279,7 @@
|
||||
</div>
|
||||
<!-- 7. 已预约患者信息 -->
|
||||
<div
|
||||
v-if="(item.status === '已预约' || item.status === '已取号') && item.patientName"
|
||||
v-if="(item.status === '已预约' || item.status === '已取号' || item.status === '已签到') && item.patientName"
|
||||
class="ticket-patient"
|
||||
>
|
||||
{{ item.patientName }}({{ item.patientId }},{{ getGenderText(item.gender || item.patientGender) }})
|
||||
@@ -472,6 +472,7 @@ const STATUS_CLASS_MAP = {
|
||||
'未预约': 'status-unbooked',
|
||||
'已预约': 'status-booked',
|
||||
'已取号': 'status-checked',
|
||||
'已签到': 'status-checked',
|
||||
'已退号': 'status-returned',
|
||||
'已停诊': 'status-cancelled',
|
||||
'已取消': 'status-cancelled'
|
||||
|
||||
@@ -115,7 +115,6 @@
|
||||
v-model="form.categoryCode"
|
||||
clearable
|
||||
filterable
|
||||
:disabled="form.isEditInfoDisable === 1"
|
||||
no-data-text=""
|
||||
>
|
||||
<el-option
|
||||
@@ -192,7 +191,6 @@
|
||||
clearable
|
||||
filterable
|
||||
style="width: 240px"
|
||||
:disabled="form.isEditInfoDisable === 1 || form.isEditInfoDisable === 2"
|
||||
no-data-text=""
|
||||
>
|
||||
<el-option
|
||||
@@ -258,7 +256,6 @@
|
||||
placeholder=""
|
||||
clearable
|
||||
filterable
|
||||
:disabled="form.isEditInfoDisable === 1"
|
||||
no-data-text=""
|
||||
>
|
||||
<el-option
|
||||
@@ -323,7 +320,6 @@
|
||||
<el-input
|
||||
v-model="form.retailPrice"
|
||||
placeholder=""
|
||||
:disabled="form.isEditInfoDisable === 1"
|
||||
@input="updatePrices"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -404,7 +400,6 @@
|
||||
controls-position="right"
|
||||
:min="1"
|
||||
:max="999"
|
||||
:disabled="form.isEditInfoDisable === 1"
|
||||
@change="calculateTotalPrice"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -605,8 +600,6 @@ function calculateTotalPrice() {
|
||||
);
|
||||
if (hasValidItem) {
|
||||
form.value.retailPrice = parseFloat(totalPrice.value) || 0;
|
||||
} else {
|
||||
form.value.retailPrice = undefined;
|
||||
}
|
||||
} catch (error) {
|
||||
totalPrice.value = '0.00';
|
||||
|
||||
@@ -375,7 +375,7 @@
|
||||
>
|
||||
<template #default="scope">
|
||||
<span v-if="!scope.row.isEdit">
|
||||
{{ scope.row.quantity ? scope.row.quantity + ' ' + scope.row.unitCode_dictText : '' }}
|
||||
{{ formatUnitText(scope.row) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -613,6 +613,26 @@ function getRowDisabled(row) {
|
||||
return row.isEdit;
|
||||
}
|
||||
|
||||
function formatUnitText(row) {
|
||||
if (!row.quantity) return ''
|
||||
const unitText = row.unitCode_dictText
|
||||
// unitCode_dictText 为有效文本时直接使用
|
||||
if (unitText && !/^\d+$/.test(unitText)) return row.quantity + ' ' + unitText
|
||||
// 优先从行级 unitCodeList 查找
|
||||
const list = row.unitCodeList
|
||||
if (list && list.length) {
|
||||
const match = list.find(u => u.value === row.unitCode)
|
||||
if (match) return row.quantity + ' ' + match.label
|
||||
}
|
||||
// 回退:从字典 unit_code 查找
|
||||
if (unit_code.value && unit_code.value.length) {
|
||||
const dictMatch = unit_code.value.find(d => d.value === row.unitCode)
|
||||
if (dictMatch) return row.quantity + ' ' + dictMatch.label
|
||||
}
|
||||
// 最后兜底用 unitCode
|
||||
return row.quantity + ' ' + (row.unitCode || '')
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否已由医生接诊(非待诊)
|
||||
* EncounterStatus: 1=待诊 2=在诊 3=暂离 …
|
||||
|
||||
@@ -707,7 +707,7 @@
|
||||
class="item-checkbox"
|
||||
@change="(val) => handleItemSelect(val, item, cat)"
|
||||
>
|
||||
{{ item.name }}
|
||||
{{ getDisplayItemName(item) }}
|
||||
</el-checkbox>
|
||||
<span class="item-price">¥{{ item.price }}/{{ item.unit || "次" }}</span>
|
||||
</div>
|
||||
@@ -806,7 +806,7 @@
|
||||
<div
|
||||
v-for="(method, idx) in selectedMethods"
|
||||
:key="'method-' + method.id"
|
||||
class="selected-item-card"
|
||||
class="selected-item-card method-child-card"
|
||||
:class="{ 'is-expanded': method.expanded }"
|
||||
>
|
||||
<div
|
||||
@@ -873,12 +873,8 @@
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 独立检查方法勾选区:与"已选择"区域解耦,支持分别手动勾选 -->
|
||||
<!-- 检查方法勾选区:点击检查类型时出现在已选择框内 -->
|
||||
<div class="method-picker-section">
|
||||
<div
|
||||
v-if="methodsForActiveCategory.length > 0"
|
||||
@@ -909,6 +905,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1265,7 +1264,12 @@ const activeCategory = computed(() => {
|
||||
const activeCategoryName = computed(() => activeCategory.value?.typeName || activeCategory.value?.categoryName || '');
|
||||
|
||||
const methodsForActiveCategory = computed(() => {
|
||||
const arr = activeCategory.value?.methods;
|
||||
// Bug #550修复: 直接从 categoryList 查找,避免 activeCategory 中间 computed 缓存阻断响应式
|
||||
const id = activeNames.value;
|
||||
if (id === '' || id === null || id === undefined) return [];
|
||||
const cat = categoryList.value.find(c => String(c.typeId) === String(id));
|
||||
if (!cat) return [];
|
||||
const arr = cat.methods;
|
||||
return Array.isArray(arr) ? arr : [];
|
||||
});
|
||||
|
||||
@@ -1944,6 +1948,11 @@ async function handleItemSelect(checked, item, cat) {
|
||||
console.error('加载检查方法失败', err);
|
||||
}
|
||||
|
||||
// Bug #550修复: 同步方法到分类,确保右侧方法选择器可见
|
||||
if (methods.length > 0 && cat && (!cat.methods || cat.methods.length === 0)) {
|
||||
cat.methods = methods;
|
||||
}
|
||||
|
||||
if (selectedItems.value.length > 0) {
|
||||
const currentCategory = selectedItems.value[0].checkType;
|
||||
// Bug #428修复: 使用 cat.typeName 进行比较(与 effectiveCheckType 保持一致)
|
||||
@@ -2459,28 +2468,24 @@ defineExpose({ getList });
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
width: 280px;
|
||||
min-width: 260px;
|
||||
}
|
||||
|
||||
.method-picker-section {
|
||||
width: 260px;
|
||||
min-width: 240px;
|
||||
max-width: 320px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.selected-panel {
|
||||
width: 260px;
|
||||
min-width: 240px;
|
||||
max-width: 320px;
|
||||
flex-shrink: 0;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.selected-tags {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
@@ -2488,6 +2493,27 @@ defineExpose({ getList });
|
||||
gap: 8px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
/* 已选择面板中项目/方法区域分隔 */
|
||||
.section-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 0 2px;
|
||||
}
|
||||
.section-divider::before {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: #dcdfe6;
|
||||
}
|
||||
.divider-label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #909399;
|
||||
letter-spacing: 0.03em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.selected-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
@@ -2510,6 +2536,13 @@ defineExpose({ getList });
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 方法卡片:子级缩进,表示从属于检查项目 */
|
||||
.selected-item-card.method-child-card {
|
||||
margin-left: 20px;
|
||||
border-left: 3px solid #e6a23c;
|
||||
border-radius: 0 6px 6px 0;
|
||||
}
|
||||
|
||||
/* 项目上 / 方法下:各自独立下拉条 */
|
||||
.fold-strip {
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
|
||||
@@ -1345,6 +1345,18 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="退回原因"
|
||||
align="center"
|
||||
prop="reasonText"
|
||||
width="160"
|
||||
>
|
||||
<template #default="scope">
|
||||
<span v-if="!scope.row.isEdit" style="color: #e6a23c;">
|
||||
{{ scope.row.reasonText || '-' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="诊断"
|
||||
align="center"
|
||||
|
||||
@@ -211,14 +211,37 @@ const handleRowClick = (row) => {
|
||||
// 写病历
|
||||
const handleWriteEmr = (row) => {
|
||||
console.log('写病历:', row)
|
||||
// 这里可以触发写病历事件
|
||||
// 可能需要跳转到病历编辑页面
|
||||
// 弹出写病历弹窗
|
||||
ElMessageBox.confirm('确定要为患者 ' + row.patientName + ' 写病历吗?', '确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info'
|
||||
}).then(() => {
|
||||
// 这里可以跳转到病历编辑页面或弹出病历编辑弹窗
|
||||
ElMessage.success('正在打开病历编辑页面...')
|
||||
// TODO: 实现写病历的具体逻辑
|
||||
// 例如:router.push({ path: '/doctorstation/emr', query: { encounterId: row.encounterId } })
|
||||
}).catch(() => {
|
||||
// 取消操作
|
||||
})
|
||||
}
|
||||
|
||||
// 查看患者
|
||||
const handleViewPatient = (row) => {
|
||||
console.log('查看患者:', row)
|
||||
// 这里可以触发查看患者事件
|
||||
// 弹出查看患者弹窗
|
||||
ElMessageBox.confirm('确定要查看患者 ' + row.patientName + ' 的详细信息吗?', '确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info'
|
||||
}).then(() => {
|
||||
// 这里可以跳转到患者详情页面或弹出患者详情弹窗
|
||||
ElMessage.success('正在打开患者详情页面...')
|
||||
// TODO: 实现查看患者的具体逻辑
|
||||
// 例如:router.push({ path: '/doctorstation/patient-details', query: { encounterId: row.encounterId } })
|
||||
}).catch(() => {
|
||||
// 取消操作
|
||||
})
|
||||
}
|
||||
|
||||
// 获取性别文本
|
||||
|
||||
@@ -130,7 +130,13 @@
|
||||
width="140"
|
||||
>
|
||||
<template #default="scope">
|
||||
<span>{{ buildApplicationName(scope.row) }}</span>
|
||||
<el-tooltip
|
||||
:content="buildFullName(scope.row)"
|
||||
placement="top"
|
||||
:disabled="!scope.row.requestFormDetailList || scope.row.requestFormDetailList.length <= 1"
|
||||
>
|
||||
<span>{{ buildApplicationName(scope.row) }}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -639,8 +645,8 @@ const parseSpecimenType = (descJson) => {
|
||||
if (!descJson) return '-';
|
||||
try {
|
||||
const obj = JSON.parse(descJson);
|
||||
// specimenName 或 sampleType 字段
|
||||
return obj.specimenName || obj.sampleType || '-';
|
||||
// 优先取标签字段(新格式),其次取码值字段,兼容旧数据 sampleType
|
||||
return obj.specimenNameLabel || obj.specimenName || obj.sampleType || '-';
|
||||
} catch (e) {
|
||||
console.error('解析 descJson 失败:', e);
|
||||
return '-';
|
||||
@@ -649,8 +655,8 @@ const parseSpecimenType = (descJson) => {
|
||||
|
||||
/**
|
||||
* 根据申请单详情构建申请单名称
|
||||
* 单一项目:显示项目名称+数量
|
||||
* 多个项目:显示首个项目名称+数量+"等X项"
|
||||
* 单一项目:直接显示项目全名(不拼接数量)
|
||||
* 多个项目:显示"项目1 + 项目2 等n项"缩略格式
|
||||
*/
|
||||
const buildApplicationName = (row) => {
|
||||
const details = row.requestFormDetailList;
|
||||
@@ -658,11 +664,24 @@ const buildApplicationName = (row) => {
|
||||
return row.name || '-';
|
||||
}
|
||||
if (details.length === 1) {
|
||||
const item = details[0];
|
||||
return `${item.adviceName}${item.quantity || ''}`;
|
||||
// 单一项目:直接显示项目全名
|
||||
return details[0].adviceName || row.name || '-';
|
||||
}
|
||||
const first = details[0];
|
||||
return `${first.adviceName}${first.quantity || ''}等${details.length}项`;
|
||||
// 多个项目:首项 + 第二项 + 等n项
|
||||
const names = details.map((d) => d.adviceName).filter(Boolean);
|
||||
if (names.length === 0) return row.name || '-';
|
||||
const first = names[0];
|
||||
const second = names.length > 1 ? ` + ${names[1]}` : '';
|
||||
return `${first}${second} 等${details.length}项`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取申请单完整项目名称列表(用于 tooltip 展示)
|
||||
*/
|
||||
const buildFullName = (row) => {
|
||||
const details = row.requestFormDetailList;
|
||||
if (!details || details.length === 0) return row.name || '-';
|
||||
return details.map((d) => d.adviceName).filter(Boolean).join(' + ') || row.name || '-';
|
||||
};
|
||||
|
||||
const isFieldMatched = (key) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="props.openAddDiagnosisDialog"
|
||||
title="添加中医诊断"
|
||||
v-model="dialogVisible"
|
||||
:title="isUpdateMode ? '修改中医诊断' : '添加中医诊断'"
|
||||
width="1500px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@@ -51,7 +51,7 @@
|
||||
<div class="search-box">
|
||||
<el-input
|
||||
v-model="searchMiddleDisease"
|
||||
placeholder="搜索疾病名称或编码"
|
||||
placeholder="搜索证候名称或编码"
|
||||
clearable
|
||||
>
|
||||
<template #prefix>
|
||||
@@ -131,8 +131,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {getTcmCondition, getTcmSyndrome, saveTcmDiagnosis,} from '@/views/doctorstation/components/api';
|
||||
import {computed} from 'vue';
|
||||
import { getTcmCondition, getTcmSyndrome, saveTcmDiagnosis, updateTcmDiagnosis, getTcmDiagnosis } from '@/views/doctorstation/components/api';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
openAddDiagnosisDialog: {
|
||||
@@ -143,13 +143,17 @@ const props = defineProps({
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
updateZy: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const conditionList = ref([]);
|
||||
const syndromeList = ref([]);
|
||||
const tcmDiagonsisList = ref([]);
|
||||
const tcmDiagonsisSaveList = ref([]);
|
||||
const syndromeSelected = ref(false); // 当前诊断是否选择对应证候
|
||||
const syndromeSelected = ref(false);
|
||||
const timestamp = ref('');
|
||||
const selectedDisease = ref(false);
|
||||
const searchDisease = ref('');
|
||||
@@ -157,35 +161,70 @@ const searchMiddleDisease = ref('');
|
||||
const { proxy } = getCurrentInstance();
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => props.openAddDiagnosisDialog,
|
||||
set: (val) => {
|
||||
if (!val) {
|
||||
emit('close');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const isUpdateMode = computed(() => {
|
||||
return props.updateZy && props.updateZy.length > 0;
|
||||
});
|
||||
|
||||
function handleOpen() {
|
||||
getTcmCondition().then((res) => {
|
||||
conditionList.value = res.data.records;
|
||||
});
|
||||
|
||||
tcmDiagonsisSaveList.value = [];
|
||||
tcmDiagonsisList.value = [];
|
||||
syndromeSelected.value = true;
|
||||
|
||||
if (isUpdateMode.value) {
|
||||
props.updateZy.forEach((item) => {
|
||||
let updateIds = item.updateId ? item.updateId.split('-') : [];
|
||||
let nameParts = item.name ? item.name.split('-') : [item.name || ''];
|
||||
tcmDiagonsisSaveList.value.push({
|
||||
conditionId: updateIds[0] || '',
|
||||
definitionId: item.illnessDefinitionId || item.definitionId || '',
|
||||
ybNo: item.ybNo,
|
||||
syndromeGroupNo: item.syndromeGroupNo,
|
||||
verificationStatusEnum: item.verificationStatusEnum || 4,
|
||||
medTypeCode: item.medTypeCode,
|
||||
});
|
||||
tcmDiagonsisList.value.push({
|
||||
conditionName: nameParts[0] || '',
|
||||
syndromeName: nameParts[1] || '',
|
||||
syndromeGroupNo: item.syndromeGroupNo,
|
||||
illnessDefinitionId: item.illnessDefinitionId,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索诊断
|
||||
const conditionDatas = computed(() => {
|
||||
if (!searchDisease.value) {
|
||||
return conditionList.value;
|
||||
}
|
||||
return conditionList.value.filter((item) => {
|
||||
if (searchDisease.value) {
|
||||
return searchDisease.value == item.name || searchDisease.value == item.ybNo;
|
||||
}
|
||||
return conditionList;
|
||||
return item.name.includes(searchDisease.value) || item.ybNo.includes(searchDisease.value);
|
||||
});
|
||||
});
|
||||
|
||||
// 后证
|
||||
const syndromeListDatas = computed(() => {
|
||||
if (!searchMiddleDisease.value) {
|
||||
return syndromeList.value;
|
||||
}
|
||||
return syndromeList.value.filter((item) => {
|
||||
if (searchMiddleDisease.value) {
|
||||
return searchMiddleDisease.value == item.name || searchMiddleDisease.value == item.ybNo;
|
||||
}
|
||||
return syndromeList;
|
||||
return item.name.includes(searchMiddleDisease.value) || item.ybNo.includes(searchMiddleDisease.value);
|
||||
});
|
||||
});
|
||||
|
||||
// 点击诊断列表处理,点击以后才显示证候列表
|
||||
function handleClickRow(row) {
|
||||
if (syndromeSelected.value || tcmDiagonsisList.value == 0) {
|
||||
if (syndromeSelected.value || tcmDiagonsisList.value.length === 0) {
|
||||
selectedDisease.value = true;
|
||||
syndromeSelected.value = false;
|
||||
timestamp.value = Date.now();
|
||||
@@ -197,7 +236,7 @@ function handleClickRow(row) {
|
||||
ybNo: row.ybNo,
|
||||
syndromeGroupNo: timestamp.value,
|
||||
verificationStatusEnum: 4,
|
||||
medTypeCode: undefined, // 不设默认值
|
||||
medTypeCode: undefined,
|
||||
});
|
||||
tcmDiagonsisList.value.push({
|
||||
conditionName: row.name,
|
||||
@@ -216,7 +255,6 @@ function clickSyndromeRow(row) {
|
||||
syndromeSelected.value = true;
|
||||
}
|
||||
|
||||
// 删除诊断
|
||||
function removeDiagnosis(row, index) {
|
||||
tcmDiagonsisList.value.splice(index, 1);
|
||||
tcmDiagonsisSaveList.value = tcmDiagonsisSaveList.value.filter((item) => {
|
||||
@@ -225,77 +263,67 @@ function removeDiagnosis(row, index) {
|
||||
}
|
||||
|
||||
function save() {
|
||||
saveTcmDiagnosis({
|
||||
patientId: props.patientInfo.patientId,
|
||||
encounterId: props.patientInfo.encounterId,
|
||||
diagnosisChildList: tcmDiagonsisSaveList.value,
|
||||
}).then((res) => {
|
||||
if (res.code == 200) {
|
||||
emit('close');
|
||||
proxy.$modal.msgSuccess('诊断已保存');
|
||||
}
|
||||
});
|
||||
const newDiagnosisList = tcmDiagonsisSaveList.value.filter((item) => !item.conditionId);
|
||||
|
||||
if (isUpdateMode.value) {
|
||||
updateTcmDiagnosis({
|
||||
patientId: props.patientInfo.patientId,
|
||||
encounterId: props.patientInfo.encounterId,
|
||||
diagnosisChildList: tcmDiagonsisSaveList.value,
|
||||
}).then((res) => {
|
||||
if (res.code == 200) {
|
||||
if (newDiagnosisList.length > 0) {
|
||||
saveTcmDiagnosis({
|
||||
patientId: props.patientInfo.patientId,
|
||||
encounterId: props.patientInfo.encounterId,
|
||||
diagnosisChildList: newDiagnosisList,
|
||||
}).then((res2) => {
|
||||
if (res2.code == 200) {
|
||||
emit('close');
|
||||
proxy.$modal.msgSuccess('诊断已保存');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
emit('close');
|
||||
proxy.$modal.msgSuccess('诊断已保存');
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
saveTcmDiagnosis({
|
||||
patientId: props.patientInfo.patientId,
|
||||
encounterId: props.patientInfo.encounterId,
|
||||
diagnosisChildList: tcmDiagonsisSaveList.value,
|
||||
}).then((res) => {
|
||||
if (res.code == 200) {
|
||||
emit('close');
|
||||
proxy.$modal.msgSuccess('诊断已保存');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function submit() {
|
||||
if (tcmDiagonsisSaveList.value.length > 0 && syndromeSelected.value) {
|
||||
const hasNewDiagnosis = tcmDiagonsisSaveList.value.some((item) => !item.conditionId);
|
||||
|
||||
if (!hasNewDiagnosis && isUpdateMode.value) {
|
||||
emit('close');
|
||||
return;
|
||||
}
|
||||
|
||||
if (syndromeSelected.value || tcmDiagonsisSaveList.value.length % 2 === 0) {
|
||||
save();
|
||||
} else {
|
||||
proxy.$modal.msgWarning('请选择证候');
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
emit('close');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.pagination-container .el-pagination) {
|
||||
right: 20px !important;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
max-width: 1400px;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
color: var(--el-color-primary);
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.patient-info {
|
||||
background: var(--el-color-primary-light-9);
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.patient-info .info-row {
|
||||
display: flex;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.patient-info .info-label {
|
||||
width: 100px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1.2fr;
|
||||
@@ -322,125 +350,13 @@ function close() {
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
}
|
||||
|
||||
.disease-list {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.disease-item {
|
||||
padding: 12px 15px;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.disease-item:hover {
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
}
|
||||
|
||||
.disease-item.active {
|
||||
background-color: var(--el-color-primary-light-8);
|
||||
border-left: 3px solid var(--el-color-primary);
|
||||
}
|
||||
|
||||
.disease-name {
|
||||
font-weight: 500;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.disease-code {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.search-box {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.disease-categories {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.category-tag {
|
||||
cursor: pointer;
|
||||
padding: 5px 12px;
|
||||
border-radius: 15px;
|
||||
background: var(--el-fill-color-light);
|
||||
font-size: 13px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.category-tag.active {
|
||||
background: var(--el-color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.relation-container {
|
||||
text-align: center;
|
||||
padding: 30px 0;
|
||||
border: 2px dashed var(--el-border-color);
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
background: var(--el-fill-color-lighter);
|
||||
}
|
||||
|
||||
.relation-icon {
|
||||
margin-bottom: 15px;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.relation-text {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.syndrome-details {
|
||||
padding: 15px;
|
||||
background: var(--el-color-primary-light-9);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--el-color-primary-light-5);
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 15px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--el-border-color);
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.diagnosis-history {
|
||||
margin-top: 20px;
|
||||
border-top: 1px solid var(--el-border-color);
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.history-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 12px;
|
||||
color: var(--el-text-color-primary);
|
||||
.diagnosis-list {
|
||||
max-height: 520px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.history-item {
|
||||
@@ -451,17 +367,6 @@ function close() {
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
.diagnosis-list {
|
||||
max-height: 520px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.history-date {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.history-diagnosis {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -469,16 +374,9 @@ function close() {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.history-note {
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-secondary);
|
||||
padding-top: 5px;
|
||||
border-top: 1px dashed var(--el-border-color);
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.empty-list {
|
||||
padding: 20px 0;
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,60 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
top="6vh"
|
||||
:width="width"
|
||||
title="中医诊断"
|
||||
:width="width"
|
||||
:z-index="20"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@open="openAct"
|
||||
@closed="closedAct"
|
||||
>
|
||||
中医诊断
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item
|
||||
label="中医诊断"
|
||||
prop="conditionCode"
|
||||
>
|
||||
<el-select
|
||||
v-model="formData.conditionCode"
|
||||
placeholder="请选择中医诊断"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@change="handleConditionChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in conditionOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="中医证候"
|
||||
prop="syndromeCode"
|
||||
>
|
||||
<el-select
|
||||
v-model="formData.syndromeCode"
|
||||
placeholder="请选择中医证候"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in syndromeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button
|
||||
size="fixed"
|
||||
@@ -20,122 +66,120 @@
|
||||
<el-button
|
||||
size="fixed"
|
||||
type="primary"
|
||||
@click="handleSubmit(signFormRef)"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup>
|
||||
<script setup>
|
||||
import {onMounted, reactive, ref} from 'vue'
|
||||
import {dayjs} from 'element-plus'
|
||||
// import { IInPatient } from '@/model/IInPatient'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getTcmCondition, getTcmSyndrome, saveTcmDiagnosis } from '../api'
|
||||
|
||||
const currentInPatient = ref({})
|
||||
const initCurrentInPatient = () => {
|
||||
currentInPatient.value = {
|
||||
feeType: '08',
|
||||
sexName: '男',
|
||||
age: '0',
|
||||
}
|
||||
}
|
||||
/* 初始化数据 */
|
||||
const init = () => {
|
||||
initCurrentInPatient()
|
||||
}
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
/* 入科 */
|
||||
const signForm = ref({
|
||||
visitCode: '', // 就诊流水号
|
||||
height: 0, // 身高
|
||||
weight: 0, // 体重
|
||||
temperature: 0, // 体温
|
||||
hertRate: 0, // 心率
|
||||
pulse: 0, // 脉搏
|
||||
highBloodPressure: 0, // 收缩压
|
||||
endBloodPressure: 0, // 舒张压
|
||||
loginDeptCode: '', // 当前登录科室
|
||||
bingqing: '', //患者病情
|
||||
inDeptDate: dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'), //入院时间
|
||||
signsId: '',
|
||||
const conditionOptions = ref([])
|
||||
const syndromeOptions = ref([])
|
||||
|
||||
const formData = ref({
|
||||
conditionCode: '',
|
||||
syndromeCode: '',
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
admittedDoctor: [{ required: true, message: '请选择住院医生', trigger: ['blur', 'change'] }],
|
||||
masterNurse: [{ required: true, message: '请选择责任护士', trigger: ['blur', 'change'] }],
|
||||
conditionCode: [{ required: true, message: '请选择中医诊断', trigger: ['blur', 'change'] }],
|
||||
syndromeCode: [{ required: true, message: '请选择中医证候', trigger: ['blur', 'change'] }],
|
||||
})
|
||||
const printWristband = ref(false)
|
||||
const emits = defineEmits(['okAct'])
|
||||
|
||||
const visible = defineModel('visible')
|
||||
const width = '920px'
|
||||
const props = defineProps({
|
||||
patientInfo: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['ok-act'])
|
||||
|
||||
const visible = defineModel<boolean>('visible')
|
||||
const width = '500px'
|
||||
|
||||
/* 取消 */
|
||||
const cancelAct = () => {
|
||||
visible.value = false
|
||||
}
|
||||
/* 录入患者体征*/
|
||||
const signFormRef = ref()
|
||||
const handleSubmit = async (formEl) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
console.log('submit!')
|
||||
try {
|
||||
// 录入患者体征方法(signForm.value).then((res: any) => {
|
||||
// ElMessage({
|
||||
// message: '登记成功!',
|
||||
// type: 'success',
|
||||
// grouping: true,
|
||||
// showClose: true,
|
||||
// })
|
||||
// emits('okAct')
|
||||
// })
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
function handleConditionChange() {
|
||||
formData.value.syndromeCode = ''
|
||||
loadSyndromeOptions(formData.value.conditionCode)
|
||||
}
|
||||
|
||||
function loadConditionOptions() {
|
||||
getTcmCondition().then((res) => {
|
||||
if (res.data && res.data.records) {
|
||||
conditionOptions.value = res.data.records.map((item) => ({
|
||||
value: item.ybNo,
|
||||
label: item.name,
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function loadSyndromeOptions(conditionCode) {
|
||||
const params = conditionCode ? { conditionCode } : {}
|
||||
getTcmSyndrome(params).then((res) => {
|
||||
if (res.data && res.data.records) {
|
||||
syndromeOptions.value = res.data.records.map((item) => ({
|
||||
value: item.ybNo,
|
||||
label: item.name,
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const formRef = ref()
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
const submitData = {
|
||||
conditionCode: formData.value.conditionCode,
|
||||
syndromeCode: formData.value.syndromeCode,
|
||||
}
|
||||
if (props.patientInfo && props.patientInfo.patientId) {
|
||||
submitData.patientId = props.patientInfo.patientId
|
||||
submitData.encounterId = props.patientInfo.encounterId
|
||||
}
|
||||
submitData.diagnosisChildList = [{
|
||||
conditionCode: formData.value.conditionCode,
|
||||
syndromeCode: formData.value.syndromeCode,
|
||||
}]
|
||||
saveTcmDiagnosis(submitData).then((res) => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('中医诊断保存成功')
|
||||
emit('ok-act')
|
||||
cancelAct()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '保存失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
ElMessage.error('保存失败,请重试')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const openAct = () => {
|
||||
init()
|
||||
formData.value = { conditionCode: '', syndromeCode: '' }
|
||||
loadConditionOptions()
|
||||
loadSyndromeOptions()
|
||||
}
|
||||
const closedAct = () => {
|
||||
visible.value = false
|
||||
}
|
||||
onMounted(() => {})
|
||||
onMounted(() => {
|
||||
loadConditionOptions()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.transferIn-container {
|
||||
width: 100%;
|
||||
|
||||
.admission-signs,
|
||||
.admission-information {
|
||||
width: 888px;
|
||||
.unit {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
color: #bbb;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
font-family: '思源黑体 CN';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.print-wriBtn {
|
||||
margin-left: 565px;
|
||||
}
|
||||
|
||||
.w-p100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.w-80 {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.mb-90 {
|
||||
margin-bottom: 90px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -142,6 +142,34 @@
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="诊断体系"
|
||||
align="center"
|
||||
prop="diagnosisSystem"
|
||||
width="120"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-form-item
|
||||
:prop="`diagnosisList.${scope.$index}.diagnosisSystem`"
|
||||
>
|
||||
<el-select
|
||||
v-model="scope.row.diagnosisSystem"
|
||||
placeholder=" "
|
||||
style="width: 100%"
|
||||
@change="handleDiagnosisSystemChange(scope.row)"
|
||||
>
|
||||
<el-option
|
||||
label="西医"
|
||||
value="西医"
|
||||
/>
|
||||
<el-option
|
||||
label="中医"
|
||||
value="中医"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="诊断类别"
|
||||
align="center"
|
||||
@@ -159,7 +187,7 @@
|
||||
style="width: 150px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in med_type"
|
||||
v-for="item in diag_type"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
@@ -187,6 +215,7 @@
|
||||
>
|
||||
<diagnosislist
|
||||
:diagnosis-searchkey="diagnosisSearchkey"
|
||||
:diagnosis-system="scope.row.diagnosisSystem || '西医'"
|
||||
@select-diagnosis="handleSelsectDiagnosis"
|
||||
/>
|
||||
<template #reference>
|
||||
@@ -208,19 +237,74 @@
|
||||
align="center"
|
||||
prop="diagnosisDoctor"
|
||||
width="120"
|
||||
/>
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-form-item>
|
||||
<span style="display: block; text-align: center; width: 100%;">{{ scope.row.diagnosisDoctor || '—' }}</span>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="tcmSyndromeName"
|
||||
width="180"
|
||||
>
|
||||
<template #header>
|
||||
<span>中医证候 <span style="color: #f56c6c;">*</span></span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<template v-if="scope.row.diagnosisSystem === '中医'">
|
||||
<el-form-item
|
||||
:prop="`diagnosisList.${scope.$index}.tcmSyndromeCode`"
|
||||
:rules="scope.row.diagnosisSystem === '中医' ? [{ required: true, message: '请选择中医证候', trigger: 'change' }] : []"
|
||||
>
|
||||
<el-select
|
||||
v-model="scope.row.tcmSyndromeCode"
|
||||
placeholder="请选择中医证候"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@focus="loadSyndromeOptions(scope.row.ybNo)"
|
||||
@change="(val) => handleSyndromeSelect(val, scope.row)"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in syndromeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item v-else>
|
||||
<span>—</span>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="诊断时间"
|
||||
align="center"
|
||||
prop="diagnosisTime"
|
||||
width="150"
|
||||
/>
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-form-item>
|
||||
<span style="display: block; text-align: center; width: 100%;">{{ scope.row.diagnosisTime || '—' }}</span>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="诊断代码"
|
||||
align="center"
|
||||
prop="ybNo"
|
||||
width="180"
|
||||
/>
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-form-item>
|
||||
<span style="display: block; text-align: center; width: 100%;">{{ scope.row.ybNo || '—' }}</span>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="诊断类型"
|
||||
align="center"
|
||||
@@ -228,30 +312,32 @@
|
||||
width="120"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div style="display:flex;flex-direction:column;align-items:center;gap:5px;">
|
||||
<el-checkbox
|
||||
v-model="scope.row.maindiseFlag"
|
||||
label="主诊断"
|
||||
:true-label="1"
|
||||
:false-label="0"
|
||||
border
|
||||
size="small"
|
||||
@change="(value) => handleMaindise(value, scope.$index)"
|
||||
/>
|
||||
<el-select
|
||||
v-model="scope.row.verificationStatusEnum"
|
||||
placeholder=" "
|
||||
style="width: 100%; padding-bottom: 5px; padding-left: 10px"
|
||||
size="small"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in diagnosisOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
<el-form-item>
|
||||
<div style="display:flex;flex-direction:column;align-items:center;gap:5px;">
|
||||
<el-checkbox
|
||||
v-model="scope.row.maindiseFlag"
|
||||
label="主诊断"
|
||||
:true-label="1"
|
||||
:false-label="0"
|
||||
border
|
||||
size="small"
|
||||
@change="(value) => handleMaindise(value, scope.$index)"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<el-select
|
||||
v-model="scope.row.verificationStatusEnum"
|
||||
placeholder=" "
|
||||
style="width: 100%; padding-bottom: 5px; padding-left: 10px"
|
||||
size="small"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in diagnosisOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -260,13 +346,15 @@
|
||||
width="130"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="handleDeleteDiagnosis(scope.row, scope.$index)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="handleDeleteDiagnosis(scope.row, scope.$index)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -281,13 +369,14 @@
|
||||
<AddDiagnosisDialog
|
||||
:open-add-diagnosis-dialog="openAddDiagnosisDialog"
|
||||
:patient-info="props.patientInfo"
|
||||
:update-zy="tcmDiagnosisListForEdit"
|
||||
@close="closeDiagnosisDialog"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {getCurrentInstance} from 'vue'; // 添加 nextTick 导入
|
||||
import {getCurrentInstance, ref, watch} from 'vue'; // 添加 nextTick 导入
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import {
|
||||
delEncounterDiagnosis,
|
||||
@@ -298,8 +387,10 @@ import {
|
||||
getEmrDetail,
|
||||
getEncounterDiagnosis,
|
||||
getTcmDiagnosis,
|
||||
getTcmSyndrome,
|
||||
isFoodDiseasesNew,
|
||||
saveDiagnosis,
|
||||
saveTcmDiagnosis,
|
||||
} from '../api';
|
||||
import {deleteTcmDiagnosis} from '@/views/doctorstation/components/api.js';
|
||||
import diagnosisdialog from '../diagnosis/diagnosisdialog.vue';
|
||||
@@ -318,6 +409,7 @@ const diagnosisOptions = ref([]);
|
||||
const rowIndex = ref();
|
||||
const diagnosis = ref();
|
||||
const orgOrUser = ref();
|
||||
const syndromeOptions = ref([]);
|
||||
const form = ref({
|
||||
diagnosisList: [],
|
||||
});
|
||||
@@ -331,13 +423,15 @@ const props = defineProps({
|
||||
const emits = defineEmits(['diagnosisSave']);
|
||||
const { proxy } = getCurrentInstance();
|
||||
const userStore = useUserStore();
|
||||
const { med_type } = proxy.useDict('med_type');
|
||||
// 获取诊断类型字典(住院诊断类别)
|
||||
const { diag_type } = proxy.useDict('diag_type');
|
||||
const rules = ref({
|
||||
name: [{ required: true, message: '请选择诊断', trigger: 'change' }],
|
||||
medTypeCode: [{ required: true, message: '请选择诊断类型', trigger: 'change' }],
|
||||
diagSrtNo: [{ required: true, message: '请输入诊断序号', trigger: 'change' }],
|
||||
});
|
||||
const diagnosisNetDatas = ref([]);
|
||||
const tcmDiagnosisListForEdit = ref([]);
|
||||
|
||||
watch(
|
||||
() => form.value.diagnosisList,
|
||||
@@ -394,46 +488,68 @@ function getList() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化中医诊断列表
|
||||
const newList = [];
|
||||
|
||||
// 先加载西医诊断,再加载中医诊断(避免竞态覆盖)
|
||||
getEncounterDiagnosis(props.patientInfo.encounterId).then((res) => {
|
||||
if (res.code == 200) {
|
||||
const datas = (res.data || []).map((item) => {
|
||||
let obj = {
|
||||
...item,
|
||||
};
|
||||
if (obj.diagSrtNo == null) {
|
||||
obj.diagSrtNo = 1;
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
// 过滤掉中医诊断(typeName: '中医诊断'),中医数据由 getTcmDiagnosis 统一管理
|
||||
const datas = (res.data || [])
|
||||
.filter((item) => item.typeName !== '中医诊断')
|
||||
.map((item) => {
|
||||
let obj = {
|
||||
...item,
|
||||
diagnosisSystem: '西医',
|
||||
tcmSyndromeCode: '',
|
||||
tcmSyndromeName: '',
|
||||
syndromeDefinitionId: '',
|
||||
syndromeGroupNo: '',
|
||||
showPopover: false,
|
||||
};
|
||||
if (obj.diagSrtNo == null) {
|
||||
obj.diagSrtNo = 1;
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
form.value.diagnosisList = datas;
|
||||
// form.value.diagnosisList = res.data;
|
||||
emits('diagnosisSave', false);
|
||||
}
|
||||
});
|
||||
getTcmDiagnosis({ encounterId: props.patientInfo.encounterId }).then((res) => {
|
||||
console.log('getTcmDiagnosis=======>', JSON.stringify(res.data.illness));
|
||||
|
||||
if (res.code == 200) {
|
||||
if (res.data.illness.length > 0) {
|
||||
diagnosisNetDatas.value = res.data.illness;
|
||||
res.data.illness.forEach((item, index) => {
|
||||
newList.push({
|
||||
name: item.name + '-' + (res.data.symptom[index]?.name || ''),
|
||||
ybNo: item.ybNo,
|
||||
medTypeCode: item.medTypeCode,
|
||||
diagnosisDoctor: props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name,
|
||||
diagnosisTime: new Date().toLocaleString('zh-CN')
|
||||
// 西医数据就绪后再加载中医诊断并追加
|
||||
getTcmDiagnosis({ encounterId: props.patientInfo.encounterId }).then((res) => {
|
||||
console.log('getTcmDiagnosis=======>', JSON.stringify(res.data.illness));
|
||||
|
||||
if (res.code == 200) {
|
||||
if (res.data.illness.length > 0) {
|
||||
diagnosisNetDatas.value = res.data.illness;
|
||||
const newList = [];
|
||||
res.data.illness.forEach((item, index) => {
|
||||
newList.push({
|
||||
conditionId: item.conditionId || '',
|
||||
encounterDiagnosisId: item.encounterDiagnosisId || '',
|
||||
syndromeGroupNo: item.syndromeGroupNo || res.data.symptom[index]?.syndromeGroupNo || '',
|
||||
name: item.name + '-' + (res.data.symptom[index]?.name || ''),
|
||||
ybNo: item.ybNo,
|
||||
definitionId: item.definitionId || '',
|
||||
diagnosisSystem: '中医',
|
||||
tcmSyndromeCode: res.data.symptom[index]?.ybNo || '',
|
||||
tcmSyndromeName: res.data.symptom[index]?.name || '',
|
||||
syndromeDefinitionId: res.data.symptom[index]?.definitionId || '',
|
||||
diagSrtNo: item.diagSrtNo,
|
||||
medTypeCode: item.medTypeCode,
|
||||
maindiseFlag: item.maindiseFlag,
|
||||
verificationStatusEnum: item.verificationStatusEnum,
|
||||
diagnosisDesc: item.diagnosisDesc || '',
|
||||
iptDiseTypeCode: item.iptDiseTypeCode,
|
||||
showPopover: false,
|
||||
diagnosisDoctor: item.diagnosisDoctor || props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name,
|
||||
diagnosisTime: item.diagnosisTime || new Date().toLocaleString('zh-CN')
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 将新数据添加到现有列表中
|
||||
form.value.diagnosisList.push(...newList);
|
||||
|
||||
// 重新排序整个列表
|
||||
form.value.diagnosisList.sort((a, b) => {
|
||||
|
||||
// 将新数据添加到现有列表现有列表
|
||||
form.value.diagnosisList.push(...newList);
|
||||
|
||||
// 重新排序整个列表
|
||||
form.value.diagnosisList.sort((a, b) => {
|
||||
const aNo = typeof a.diagSrtNo === 'number' ? a.diagSrtNo : 9999;
|
||||
const bNo = typeof b.diagSrtNo === 'number' ? b.diagSrtNo : 9999;
|
||||
return aNo - bNo;
|
||||
@@ -442,7 +558,9 @@ function getList() {
|
||||
emits('diagnosisSave', false);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
getTree();
|
||||
}
|
||||
|
||||
@@ -593,6 +711,11 @@ function addDiagnosisItem() {
|
||||
form.value.diagnosisList.push({
|
||||
showPopover: false,
|
||||
name: undefined,
|
||||
diagnosisSystem: '西医',
|
||||
tcmSyndromeCode: '',
|
||||
tcmSyndromeName: '',
|
||||
syndromeDefinitionId: '',
|
||||
syndromeGroupNo: '',
|
||||
verificationStatusEnum: 4,
|
||||
medTypeCode: undefined,
|
||||
diagSrtNo: form.value.diagnosisList.length + 1,
|
||||
@@ -610,8 +733,57 @@ function addDiagnosisItem() {
|
||||
}
|
||||
}
|
||||
|
||||
// 诊断体系切换
|
||||
function handleDiagnosisSystemChange(row) {
|
||||
if (row.diagnosisSystem === '西医') {
|
||||
row.tcmSyndromeCode = '';
|
||||
row.tcmSyndromeName = '';
|
||||
row.syndromeDefinitionId = '';
|
||||
row.syndromeGroupNo = '';
|
||||
}
|
||||
row.name = '';
|
||||
row.ybNo = '';
|
||||
row.definitionId = '';
|
||||
row.showPopover = false;
|
||||
}
|
||||
|
||||
// 加载中医证候选项(按诊断名称关联过滤)
|
||||
function loadSyndromeOptions(conditionCode) {
|
||||
const params = conditionCode ? { conditionCode } : {};
|
||||
getTcmSyndrome(params).then((res) => {
|
||||
if (res.data && res.data.records) {
|
||||
syndromeOptions.value = res.data.records.map((item) => ({
|
||||
value: item.ybNo,
|
||||
label: item.name,
|
||||
id: item.id,
|
||||
}));
|
||||
} else {
|
||||
syndromeOptions.value = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 中医证候选中赋值
|
||||
function handleSyndromeSelect(val, row) {
|
||||
if (val) {
|
||||
const selected = syndromeOptions.value.find((item) => item.value === val);
|
||||
row.tcmSyndromeName = selected ? selected.label : '';
|
||||
row.syndromeDefinitionId = selected ? selected.id : '';
|
||||
} else {
|
||||
row.tcmSyndromeName = '';
|
||||
row.syndromeDefinitionId = '';
|
||||
}
|
||||
}
|
||||
|
||||
// 添加中医诊断
|
||||
function handleAddTcmDiagonsis() {
|
||||
tcmDiagnosisListForEdit.value = form.value.diagnosisList.filter(
|
||||
(item) => item.diagnosisSystem === '中医'
|
||||
).map((item) => ({
|
||||
...item,
|
||||
updateId: item.conditionId ? `${item.conditionId}-${item.syndromeGroupNo || ''}` : '' ,
|
||||
illnessDefinitionId: item.definitionId || '' ,
|
||||
}));
|
||||
openAddDiagnosisDialog.value = true;
|
||||
}
|
||||
|
||||
@@ -622,40 +794,27 @@ function handleAddTcmDiagonsis() {
|
||||
* 删除诊断
|
||||
*/
|
||||
function handleDeleteDiagnosis(row, index) {
|
||||
//中医诊断用-拼接 例如:疳气-表里俱实证
|
||||
const nameArr = row.name?.split('-') || [];
|
||||
// 新行(未保存):直接从列表中移除
|
||||
if (!row.conditionId && !row.encounterDiagnosisId) {
|
||||
form.value.diagnosisList.splice(index, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// 已保存的中医诊断(name含'-'且syndromeGroupNo有值)
|
||||
if (row.syndromeGroupNo) {
|
||||
deleteTcmDiagnosis(row.syndromeGroupNo).then(() => {
|
||||
getList();
|
||||
getTree();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 已保存的西医诊断
|
||||
if (row.conditionId) {
|
||||
if (nameArr.length > 1) {
|
||||
deleteTcmDiagnosis(row.syndromeGroupNo).then(() => {
|
||||
getList();
|
||||
getTree();
|
||||
});
|
||||
} else {
|
||||
delEncounterDiagnosis(row.conditionId).then(() => {
|
||||
getList();
|
||||
getTree();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log('row============>', JSON.stringify(row));
|
||||
console.log('item============>', index);
|
||||
if (nameArr.length > 1) {
|
||||
let obj = null;
|
||||
for (let index = 0; index < diagnosisNetDatas.value.length; index++) {
|
||||
const item = diagnosisNetDatas.value[index];
|
||||
console.log('item.name============>', item.name);
|
||||
console.log('row.name============>', row.name);
|
||||
if (item.ybNo == row.ybNo) {
|
||||
obj = item;
|
||||
}
|
||||
}
|
||||
deleteTcmDiagnosis(obj.syndromeGroupNo).then(() => {
|
||||
getList();
|
||||
getTree();
|
||||
});
|
||||
} else {
|
||||
form.value.diagnosisList.splice(index, 1);
|
||||
}
|
||||
delEncounterDiagnosis(row.conditionId).then(() => {
|
||||
getList();
|
||||
getTree();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -713,6 +872,19 @@ function handleSaveDiagnosis() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 校验中医诊断证候完整性
|
||||
for (let i = 0; i < form.value.diagnosisList.length; i++) {
|
||||
const item = form.value.diagnosisList[i];
|
||||
if (!item.name) {
|
||||
ElMessage.warning(`第${i + 1}行诊断名称不能为空`);
|
||||
return;
|
||||
}
|
||||
if (item.diagnosisSystem === '中医' && !item.tcmSyndromeCode) {
|
||||
ElMessage.error('中医诊断不完整,请录入对应的证候!');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置保存标志,避免触发watch监听器
|
||||
isSaving.value = true;
|
||||
|
||||
@@ -725,29 +897,78 @@ function handleSaveDiagnosis() {
|
||||
|
||||
// 步骤2:重新分配连续的序号(从1开始)
|
||||
sortedList.forEach((item, index) => {
|
||||
item.diagSrtNo = index + 1; // 这里是关键!把”诊断排序”改成新顺序
|
||||
item.diagSrtNo = index + 1;
|
||||
});
|
||||
|
||||
// 步骤3:提交排序后的数据
|
||||
saveDiagnosis({
|
||||
patientId: props.patientInfo.patientId,
|
||||
encounterId: props.patientInfo.encounterId,
|
||||
diagnosisChildList: sortedList,
|
||||
}).then((res) => {
|
||||
if (res.code === 200) {
|
||||
emits('diagnosisSave', false);
|
||||
proxy.$modal.msgSuccess('诊断已保存');
|
||||
// 步骤3:拆分为西医诊断和中医诊断
|
||||
const westernList = sortedList.filter((item) => item.diagnosisSystem !== '中医');
|
||||
const tcmList = sortedList.filter((item) => item.diagnosisSystem === '中医');
|
||||
|
||||
// 保存成功后从服务器重新加载数据,确保前后端数据一致
|
||||
getList();
|
||||
const savePromises = [];
|
||||
|
||||
// 食源性疾病逻辑
|
||||
isFoodDiseasesNew({ encounterId: props.patientInfo.encounterId }).then((res2) => {
|
||||
// 保存西医诊断
|
||||
if (westernList.length > 0) {
|
||||
savePromises.push(
|
||||
saveDiagnosis({
|
||||
patientId: props.patientInfo.patientId,
|
||||
encounterId: props.patientInfo.encounterId,
|
||||
diagnosisChildList: westernList,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// 保存中医诊断
|
||||
tcmList.forEach((item) => {
|
||||
const syndromeGroupNo = item.conditionId
|
||||
? `${item.conditionId}-${item.tcmSyndromeCode || Date.now()}`
|
||||
: `${Date.now()}-${item.tcmSyndromeCode || '0'}`;
|
||||
savePromises.push(
|
||||
saveTcmDiagnosis({
|
||||
patientId: props.patientInfo.patientId,
|
||||
encounterId: props.patientInfo.encounterId,
|
||||
diagnosisChildList: [
|
||||
// 病(illness)
|
||||
{
|
||||
conditionId: item.conditionId || null,
|
||||
name: item.name,
|
||||
ybNo: item.ybNo,
|
||||
definitionId: item.definitionId || null,
|
||||
diagSrtNo: item.diagSrtNo,
|
||||
medTypeCode: item.medTypeCode,
|
||||
maindiseFlag: item.maindiseFlag,
|
||||
verificationStatusEnum: item.verificationStatusEnum,
|
||||
diagnosisDesc: item.diagnosisDesc || '',
|
||||
diagnosisDoctor: item.diagnosisDoctor || '',
|
||||
diagnosisTime: item.diagnosisTime || '',
|
||||
iptDiseTypeCode: item.iptDiseTypeCode,
|
||||
syndromeGroupNo: syndromeGroupNo,
|
||||
},
|
||||
// 证(syndrome)
|
||||
{
|
||||
name: item.tcmSyndromeName,
|
||||
ybNo: item.tcmSyndromeCode,
|
||||
definitionId: item.syndromeDefinitionId || null,
|
||||
diagSrtNo: null,
|
||||
syndromeGroupNo: syndromeGroupNo,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
Promise.all(savePromises).then(() => {
|
||||
emits('diagnosisSave', false);
|
||||
proxy.$modal.msgSuccess('诊断已保存');
|
||||
|
||||
// 保存成功后从服务器重新加载数据,确保前后端数据一致
|
||||
getList();
|
||||
|
||||
// 食源性疾病逻辑
|
||||
isFoodDiseasesNew({ encounterId: props.patientInfo.encounterId }).then((res2) => {
|
||||
if (res2.code === 20 && res2.data) {
|
||||
window.open(res2.data, '_blank');
|
||||
}
|
||||
});
|
||||
}
|
||||
}).finally(() => {
|
||||
setTimeout(() => {
|
||||
isSaving.value = false;
|
||||
|
||||
@@ -28,38 +28,55 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {getDiagnosisDefinitionList} from '../api';
|
||||
import {getDiagnosisDefinitionList, getTcmCondition} from '../api';
|
||||
|
||||
const props = defineProps({
|
||||
diagnosisSearchkey: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
diagnosisSystem: {
|
||||
type: String,
|
||||
default: '西医',
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['selectDiagnosis']);
|
||||
const total = ref(0);
|
||||
const queryParams = ref({
|
||||
pageSize: 1000,
|
||||
pageNo: 1,
|
||||
// typeCode: 1,
|
||||
});
|
||||
const diagnosisDefinitionList = ref([]);
|
||||
|
||||
watch(
|
||||
() => props.diagnosisSearchkey,
|
||||
(newValue) => {
|
||||
queryParams.value.searchKey = newValue;
|
||||
() => [props.diagnosisSearchkey, props.diagnosisSystem],
|
||||
() => {
|
||||
getList();
|
||||
},
|
||||
{ immdiate: true }
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
getList();
|
||||
function getList() {
|
||||
getDiagnosisDefinitionList(queryParams.value).then((res) => {
|
||||
diagnosisDefinitionList.value = res.data.records;
|
||||
total.value = res.data.total;
|
||||
});
|
||||
if (props.diagnosisSystem === '中医') {
|
||||
getTcmCondition({ searchKey: props.diagnosisSearchkey || undefined }).then((res) => {
|
||||
if (res.data && res.data.records) {
|
||||
diagnosisDefinitionList.value = res.data.records.map((item) => ({
|
||||
name: item.name,
|
||||
ybNo: item.ybNo,
|
||||
typeName: '中医诊断',
|
||||
id: item.id,
|
||||
}));
|
||||
}
|
||||
total.value = res.data?.total || 0;
|
||||
});
|
||||
} else {
|
||||
queryParams.value.searchKey = props.diagnosisSearchkey || undefined;
|
||||
getDiagnosisDefinitionList(queryParams.value).then((res) => {
|
||||
diagnosisDefinitionList.value = res.data.records;
|
||||
total.value = res.data.total;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function clickRow(row) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="diagnose-container">
|
||||
<!-- 常用诊断、个人诊断、科室诊断、历史诊断、 -->
|
||||
<!-- 常用诊断、个人诊断、科室诊断、历史诊断 -->
|
||||
<diagnose-folder
|
||||
:folder="mockData"
|
||||
:level="0"
|
||||
@@ -10,29 +10,44 @@
|
||||
<el-space>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="addNewWestern"
|
||||
@click="addNewDiagnosis"
|
||||
>
|
||||
开立诊断
|
||||
新增诊断
|
||||
</el-button>
|
||||
<el-button type="primary">
|
||||
既往诊断
|
||||
</el-button>
|
||||
<!-- 患者诊断 -->
|
||||
<el-button
|
||||
type="danger"
|
||||
type="primary"
|
||||
@click="addNewChinese"
|
||||
>
|
||||
中医诊断
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
:disabled="!selectedRows.length"
|
||||
@click="handleDelete"
|
||||
>
|
||||
删除诊断
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="saveLoading"
|
||||
@click="handleSaveDiagnosis"
|
||||
>
|
||||
保存诊断
|
||||
</el-button>
|
||||
</el-space>
|
||||
</div>
|
||||
<div class="diagnoseData-container">
|
||||
<el-table
|
||||
ref="diagnoseTableRef"
|
||||
:data="diagnoseData"
|
||||
border
|
||||
row-key="id"
|
||||
style="width: 100%; height: 100%"
|
||||
highlight-current-row
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
@@ -40,166 +55,531 @@
|
||||
width="40"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="date"
|
||||
label="诊断类型"
|
||||
width="180"
|
||||
sortable
|
||||
label="序号"
|
||||
type="index"
|
||||
width="50"
|
||||
fixed="left"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="诊断体系"
|
||||
prop="diagnosisSystem"
|
||||
width="120"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-select
|
||||
v-model="scope.row.diagnosisSystem"
|
||||
placeholder=" "
|
||||
style="width: 100%"
|
||||
@change="handleDiagnosisSystemChange(scope.row)"
|
||||
>
|
||||
<el-option
|
||||
label="西医"
|
||||
value="西医"
|
||||
/>
|
||||
<el-option
|
||||
label="中医"
|
||||
value="中医"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="诊断类别"
|
||||
prop="classification"
|
||||
width="120"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-select
|
||||
v-model="scope.row.classification"
|
||||
placeholder=" "
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in diag_type"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="诊断名称"
|
||||
prop="name"
|
||||
width="180"
|
||||
/>
|
||||
>
|
||||
<template #default="scope">
|
||||
<div
|
||||
class="diagnosis-text"
|
||||
@click="handleDiagnosisNameClick(scope.row, scope.$index)"
|
||||
>
|
||||
<span class="diagnosis-text-content">{{ scope.row.name || '点击选择诊断' }}</span>
|
||||
<el-icon class="diagnosis-text-icon">
|
||||
<arrow-down />
|
||||
</el-icon>
|
||||
</div>
|
||||
<el-popover
|
||||
v-if="scope.row.showPopover"
|
||||
placement="bottom"
|
||||
:width="400"
|
||||
trigger="manual"
|
||||
>
|
||||
<template #reference>
|
||||
<span />
|
||||
</template>
|
||||
<div class="diagnosis-popover-container">
|
||||
<div class="diagnosis-popover-header">
|
||||
<span class="diagnosis-popover-title">选择诊断</span>
|
||||
<el-link
|
||||
type="primary"
|
||||
class="diagnosis-popover-close"
|
||||
@click="closeDiagnosisPopover(scope.row)"
|
||||
>
|
||||
关闭
|
||||
</el-link>
|
||||
</div>
|
||||
<div class="diagnosis-popover-body">
|
||||
<diagnosislist
|
||||
:diagnosis-searchkey="diagnosisSearchkey"
|
||||
@select-diagnosis="(row) => handleSelectDiagnosis(row, scope.row, scope.$index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="address"
|
||||
label="主诊"
|
||||
/>
|
||||
label="中医证候"
|
||||
prop="tcmSyndromeName"
|
||||
width="180"
|
||||
>
|
||||
<template #default="scope">
|
||||
<template v-if="scope.row.diagnosisSystem === '中医'">
|
||||
<div
|
||||
class="diagnosis-text"
|
||||
@click="handleTcmSyndromeClick(scope.row, scope.$index)"
|
||||
>
|
||||
<span class="diagnosis-text-content">{{ scope.row.tcmSyndromeName || '请选择中医证候' }}</span>
|
||||
<el-icon class="diagnosis-text-icon">
|
||||
<arrow-down />
|
||||
</el-icon>
|
||||
</div>
|
||||
<el-popover
|
||||
v-if="scope.row.showSyndromePopover"
|
||||
placement="bottom"
|
||||
:width="400"
|
||||
trigger="manual"
|
||||
>
|
||||
<template #reference>
|
||||
<span />
|
||||
</template>
|
||||
<div class="diagnosis-popover-container">
|
||||
<div class="diagnosis-popover-header">
|
||||
<span class="diagnosis-popover-title">选择中医证候</span>
|
||||
<el-link
|
||||
type="primary"
|
||||
class="diagnosis-popover-close"
|
||||
@click="closeSyndromePopover(scope.row)"
|
||||
>
|
||||
关闭
|
||||
</el-link>
|
||||
</div>
|
||||
<div class="diagnosis-popover-body">
|
||||
<el-input
|
||||
v-model="syndromeSearchkey"
|
||||
placeholder="搜索证候名称"
|
||||
clearable
|
||||
style="margin-bottom: 8px"
|
||||
@input="handleSyndromeSearch"
|
||||
/>
|
||||
<el-table
|
||||
:data="filteredSyndromeList"
|
||||
highlight-current-row
|
||||
max-height="300"
|
||||
@row-click="(row) => handleSelectSyndrome(row, scope.row)"
|
||||
>
|
||||
<el-table-column
|
||||
label="证候名称"
|
||||
prop="name"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="医保编码"
|
||||
prop="ybNo"
|
||||
align="center"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</template>
|
||||
<span v-else>—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="address"
|
||||
label="复诊"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="address"
|
||||
label="疑似"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="address"
|
||||
label="传染"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="address"
|
||||
label="入院病情"
|
||||
width="180"
|
||||
prop="admissionCondition"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="address"
|
||||
label="转归"
|
||||
width="180"
|
||||
prop="outcome"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="address"
|
||||
label="转归日期"
|
||||
width="180"
|
||||
prop="outcomeDate"
|
||||
width="140"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="address"
|
||||
label="诊断科室"
|
||||
width="180"
|
||||
prop="deptName"
|
||||
width="140"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="address"
|
||||
label="诊断医师"
|
||||
width="180"
|
||||
prop="diagnosisDoctor"
|
||||
width="140"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="address"
|
||||
label="诊断日期"
|
||||
width="180"
|
||||
prop="diagnosisTime"
|
||||
width="140"
|
||||
/>
|
||||
<el-table-column
|
||||
fixed="right"
|
||||
label="操作"
|
||||
width="120"
|
||||
>
|
||||
<template #default="props">
|
||||
<template #default="scope">
|
||||
<el-space>
|
||||
<el-tooltip
|
||||
content="删除"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-icon @click="deleteDiagnose(row)">
|
||||
<el-icon @click="deleteRow(scope.row, scope.$index)">
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
v-if="props.$index !== diagnoseData.length - 1"
|
||||
v-if="scope.$index !== diagnoseData.length - 1"
|
||||
content="下移"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-icon @click="download(props.row)">
|
||||
<el-icon @click="moveDown(scope.row, scope.$index)">
|
||||
<Download />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
v-if="props.$index !== 0"
|
||||
v-if="scope.$index !== 0"
|
||||
content="上移"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-icon @click="upload(props.row)">
|
||||
<el-icon @click="moveUp(scope.row, scope.$index)">
|
||||
<Upload />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
v-if="props.$index !== 0"
|
||||
content="置顶"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-icon @click="top(props.row)">
|
||||
<Top />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
v-if="props.$index !== diagnoseData.length - 1"
|
||||
content="置底"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-icon @click="bottom(props.row)">
|
||||
<Bottom />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</el-space>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
<WesternMedicineDialog v-model:visible="WesternMedicineDialogVisible" />
|
||||
<ChineseMedicineDialog v-model:visible="ChineseMedicineDialogVisible" />
|
||||
<WesternMedicineDialog v-model:visible="westernMedicineDialogVisible" />
|
||||
<ChineseMedicineDialog
|
||||
v-model:visible="chineseMedicineDialogVisible"
|
||||
:patient-info="patientInfo"
|
||||
@ok-act="loadDiagnosisData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {onBeforeMount, onMounted, reactive, ref} from 'vue'
|
||||
// const { proxy } = getCurrentInstance()
|
||||
// const emits = defineEmits([])
|
||||
// const props = defineProps({})
|
||||
// import DiagnoseFolder from './diagnoseFolder.vue'
|
||||
import {onMounted, reactive, ref, computed} from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ArrowDown, Delete, Download, Upload } from '@element-plus/icons-vue'
|
||||
import WesternMedicineDialog from './westernMedicineDialog.vue'
|
||||
import ChineseMedicineDialog from './chineseMedicineDialog.vue'
|
||||
import Diagnosislist from './diagnosislist.vue'
|
||||
import {
|
||||
saveDiagnosis,
|
||||
delEncounterDiagnosis,
|
||||
getEncounterDiagnosis,
|
||||
getTcmSyndrome,
|
||||
} from '../api'
|
||||
|
||||
const diagnoseData = ref([
|
||||
{
|
||||
id: 1,
|
||||
sort: 1,
|
||||
name: '新冠',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
sort: 2,
|
||||
name: '新冠as',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
sort: 3,
|
||||
name: '新冠12',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
sort: 4,
|
||||
name: '新冠2121',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
sort: 5,
|
||||
name: '新冠12',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
sort: 6,
|
||||
name: '新冠21',
|
||||
},
|
||||
])
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
// 模拟数据
|
||||
const props = defineProps({
|
||||
patientInfo: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
})
|
||||
|
||||
const diagnoseData = ref([])
|
||||
const selectedRows = ref([])
|
||||
const saveLoading = ref(false)
|
||||
const diagnoseTableRef = ref()
|
||||
const diagnosisSearchkey = ref('')
|
||||
const syndromeSearchkey = ref('')
|
||||
const syndromeList = ref([])
|
||||
|
||||
// 获取诊断类型字典(住院诊断类别)
|
||||
const { diag_type } = proxy.useDict('diag_type')
|
||||
|
||||
const filteredSyndromeList = computed(() => {
|
||||
if (!syndromeSearchkey.value) {
|
||||
return syndromeList.value
|
||||
}
|
||||
const keyword = syndromeSearchkey.value.toLowerCase()
|
||||
return syndromeList.value.filter(item =>
|
||||
(item.name && item.name.toLowerCase().includes(keyword)) ||
|
||||
(item.ybNo && item.ybNo.toLowerCase().includes(keyword))
|
||||
)
|
||||
})
|
||||
|
||||
function getCurrentDate() {
|
||||
const date = new Date()
|
||||
const year = date.getFullYear()
|
||||
let month = date.getMonth() + 1
|
||||
let day = date.getDate()
|
||||
month = month < 10 ? '0' + month : month
|
||||
day = day < 10 ? '0' + day : day
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
function addNewDiagnosis() {
|
||||
const maxSortNo = diagnoseData.value.length > 0
|
||||
? Math.max(...diagnoseData.value.map(item => item.sortNo || 0))
|
||||
: 0
|
||||
diagnoseData.value.push({
|
||||
id: Date.now(),
|
||||
sortNo: maxSortNo + 1,
|
||||
diagnosisSystem: '西医',
|
||||
classification: '主诊断',
|
||||
name: '',
|
||||
ybNo: '',
|
||||
definitionId: '',
|
||||
tcmSyndromeCode: '',
|
||||
tcmSyndromeName: '',
|
||||
admissionCondition: '',
|
||||
outcome: '',
|
||||
outcomeDate: '',
|
||||
deptName: '',
|
||||
diagnosisDoctor: proxy.$store?.state?.user?.name || '',
|
||||
diagnosisTime: getCurrentDate(),
|
||||
showPopover: false,
|
||||
showSyndromePopover: false,
|
||||
isNew: true,
|
||||
})
|
||||
}
|
||||
|
||||
function addNewChinese() {
|
||||
chineseMedicineDialogVisible.value = true
|
||||
}
|
||||
|
||||
function handleDiagnosisSystemChange(row) {
|
||||
if (row.diagnosisSystem === '西医') {
|
||||
row.tcmSyndromeCode = ''
|
||||
row.tcmSyndromeName = ''
|
||||
}
|
||||
row.name = ''
|
||||
row.ybNo = ''
|
||||
row.showPopover = false
|
||||
row.showSyndromePopover = false
|
||||
}
|
||||
|
||||
function handleDiagnosisNameClick(row, index) {
|
||||
if (row.diagnosisSystem === '中医') {
|
||||
row.showPopover = false
|
||||
return
|
||||
}
|
||||
diagnoseData.value.forEach((item, idx) => {
|
||||
if (idx !== index) {
|
||||
item.showPopover = false
|
||||
}
|
||||
})
|
||||
row.showPopover = true
|
||||
}
|
||||
|
||||
function handleSelectDiagnosis(diagRow, rowData) {
|
||||
rowData.name = diagRow.name
|
||||
rowData.ybNo = diagRow.ybNo
|
||||
rowData.definitionId = diagRow.id
|
||||
rowData.showPopover = false
|
||||
}
|
||||
|
||||
function closeDiagnosisPopover(row) {
|
||||
row.showPopover = false
|
||||
}
|
||||
|
||||
function handleTcmSyndromeClick(row, index) {
|
||||
diagnoseData.value.forEach((item, idx) => {
|
||||
if (idx !== index) {
|
||||
item.showSyndromePopover = false
|
||||
}
|
||||
})
|
||||
loadSyndromeList()
|
||||
row.showSyndromePopover = true
|
||||
}
|
||||
|
||||
function handleSyndromeSearch() {}
|
||||
|
||||
function loadSyndromeList() {
|
||||
getTcmSyndrome().then((res) => {
|
||||
if (res.data && res.data.records) {
|
||||
syndromeList.value = res.data.records
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleSelectSyndrome(syndromeRow, rowData) {
|
||||
rowData.tcmSyndromeCode = syndromeRow.ybNo
|
||||
rowData.tcmSyndromeName = syndromeRow.name
|
||||
rowData.showSyndromePopover = false
|
||||
}
|
||||
|
||||
function closeSyndromePopover(row) {
|
||||
row.showSyndromePopover = false
|
||||
}
|
||||
|
||||
function handleSelectionChange(rows) {
|
||||
selectedRows.value = rows
|
||||
}
|
||||
|
||||
function deleteRow(row, index) {
|
||||
diagnoseData.value.splice(index, 1)
|
||||
}
|
||||
|
||||
function moveDown(row, index) {
|
||||
if (index >= diagnoseData.value.length - 1) return
|
||||
const temp = diagnoseData.value[index]
|
||||
diagnoseData.value[index] = diagnoseData.value[index + 1]
|
||||
diagnoseData.value[index + 1] = temp
|
||||
diagnoseData.value = [...diagnoseData.value]
|
||||
}
|
||||
|
||||
function moveUp(row, index) {
|
||||
if (index <= 0) return
|
||||
const temp = diagnoseData.value[index]
|
||||
diagnoseData.value[index] = diagnoseData.value[index - 1]
|
||||
diagnoseData.value[index - 1] = temp
|
||||
diagnoseData.value = [...diagnoseData.value]
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
if (!selectedRows.value.length) {
|
||||
ElMessage.warning('请先选择要删除的诊断')
|
||||
return
|
||||
}
|
||||
ElMessageBox.confirm('确定删除选中的诊断吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
const deleteIds = selectedRows.value
|
||||
.filter(item => item.conditionId)
|
||||
.map(item => item.conditionId)
|
||||
const newRows = selectedRows.value.filter(item => !item.conditionId)
|
||||
|
||||
newRows.forEach(item => {
|
||||
const idx = diagnoseData.value.findIndex(d => d.id === item.id)
|
||||
if (idx > -1) {
|
||||
diagnoseData.value.splice(idx, 1)
|
||||
}
|
||||
})
|
||||
|
||||
deleteIds.forEach(id => {
|
||||
delEncounterDiagnosis(id).then(() => {
|
||||
const idx = diagnoseData.value.findIndex(d => d.conditionId === id)
|
||||
if (idx > -1) {
|
||||
diagnoseData.value.splice(idx, 1)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
selectedRows.value = []
|
||||
ElMessage.success('删除成功')
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
async function handleSaveDiagnosis() {
|
||||
if (!diagnoseData.value.length) {
|
||||
ElMessage.warning('没有需要保存的诊断')
|
||||
return
|
||||
}
|
||||
|
||||
for (let i = 0; i < diagnoseData.value.length; i++) {
|
||||
const item = diagnoseData.value[i]
|
||||
if (!item.name) {
|
||||
ElMessage.warning(`第${i + 1}行诊断名称不能为空`)
|
||||
return
|
||||
}
|
||||
if (item.diagnosisSystem === '中医' && !item.tcmSyndromeCode) {
|
||||
ElMessage.error('中医诊断不完整,请录入对应的证候!')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
saveLoading.value = true
|
||||
try {
|
||||
const diagnosisList = diagnoseData.value.map((item, index) => ({
|
||||
conditionId: item.conditionId || '',
|
||||
ybNo: item.ybNo || '',
|
||||
name: item.name,
|
||||
definitionId: item.definitionId || '',
|
||||
classification: item.classification || '主诊断',
|
||||
diagnosisSystem: item.diagnosisSystem || '西医',
|
||||
tcmSyndromeCode: item.tcmSyndromeCode || '',
|
||||
tcmSyndromeName: item.tcmSyndromeName || '',
|
||||
admissionCondition: item.admissionCondition || '',
|
||||
outcome: item.outcome || '',
|
||||
outcomeDate: item.outcomeDate || '',
|
||||
diagnosisDoctor: item.diagnosisDoctor || '',
|
||||
diagnosisTime: item.diagnosisTime || getCurrentDate(),
|
||||
diagSrtNo: index + 1,
|
||||
}))
|
||||
|
||||
const saveData = {
|
||||
patientId: props.patientInfo?.patientId || '',
|
||||
encounterId: props.patientInfo?.encounterId || '',
|
||||
diagnosisList: diagnosisList,
|
||||
}
|
||||
|
||||
const res = await saveDiagnosis(saveData)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('诊断保存成功')
|
||||
loadDiagnosisData()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '保存失败')
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('保存失败,请重试')
|
||||
} finally {
|
||||
saveLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function loadDiagnosisData() {
|
||||
if (!props.patientInfo?.encounterId) return
|
||||
getEncounterDiagnosis(props.patientInfo.encounterId).then((res) => {
|
||||
if (res.data) {
|
||||
const westernDiagnoses = (res.data || []).filter(item => item.typeName !== '中医诊断')
|
||||
diagnoseData.value = westernDiagnoses.map((item, index) => ({
|
||||
...item,
|
||||
diagnosisSystem: item.diagnosisSystem || '西医',
|
||||
classification: item.classification || '主诊断',
|
||||
tcmSyndromeCode: item.tcmSyndromeCode || '',
|
||||
tcmSyndromeName: item.tcmSyndromeName || '',
|
||||
showPopover: false,
|
||||
showSyndromePopover: false,
|
||||
diagSrtNo: index + 1,
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 模拟数据(常用/科室/个人/历史诊断树)
|
||||
const mockData = ref([
|
||||
{
|
||||
name: '常用',
|
||||
@@ -207,28 +587,18 @@ const mockData = ref([
|
||||
{
|
||||
name: '文件夹 1',
|
||||
children: [
|
||||
{
|
||||
name: '霍乱',
|
||||
},
|
||||
{
|
||||
name: '新型冠状病毒新型冠状病毒新型冠状病毒',
|
||||
},
|
||||
{ name: '霍乱' },
|
||||
{ name: '新型冠状病毒' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '文件夹 2',
|
||||
children: [
|
||||
{
|
||||
name: '普外科',
|
||||
},
|
||||
{
|
||||
name: '骨科',
|
||||
},
|
||||
{ name: '普外科' },
|
||||
{ name: '骨科' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '新型冠状病毒',
|
||||
},
|
||||
{ name: '新型冠状病毒' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -237,28 +607,18 @@ const mockData = ref([
|
||||
{
|
||||
name: '内科',
|
||||
children: [
|
||||
{
|
||||
name: '呼吸内科',
|
||||
},
|
||||
{
|
||||
name: '消化内科',
|
||||
},
|
||||
{ name: '呼吸内科' },
|
||||
{ name: '消化内科' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '外科',
|
||||
children: [
|
||||
{
|
||||
name: '普外科',
|
||||
},
|
||||
{
|
||||
name: '骨科',
|
||||
},
|
||||
{ name: '普外科' },
|
||||
{ name: '骨科' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '儿科',
|
||||
},
|
||||
{ name: '儿科' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -267,79 +627,27 @@ const mockData = ref([
|
||||
{
|
||||
name: '内科',
|
||||
children: [
|
||||
{
|
||||
name: '呼吸内科',
|
||||
},
|
||||
{
|
||||
name: '消化内科',
|
||||
},
|
||||
{ name: '呼吸内科' },
|
||||
{ name: '消化内科' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '外科',
|
||||
children: [
|
||||
{
|
||||
name: '普外科',
|
||||
},
|
||||
{
|
||||
name: '骨科',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '儿科',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '历史',
|
||||
children: [
|
||||
{
|
||||
name: '心率失常',
|
||||
},
|
||||
{
|
||||
name: '心率失常',
|
||||
},
|
||||
{
|
||||
name: '心率失常',
|
||||
},
|
||||
{ name: '心率失常' },
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
const state = reactive({})
|
||||
onBeforeMount(() => {})
|
||||
onMounted(() => {})
|
||||
defineExpose({ state })
|
||||
|
||||
// const deleteDiagnose = (row: any) => {
|
||||
// // TODO 删除
|
||||
// console.log(row)
|
||||
// }
|
||||
|
||||
// const download = (row: any) => {
|
||||
// // TODO 删除
|
||||
// }
|
||||
|
||||
// const upload = (row: any) => {
|
||||
// // TODO 删除
|
||||
// }
|
||||
|
||||
// const top = (row: any) => {
|
||||
// // TODO 删除
|
||||
// }
|
||||
|
||||
// const bottom = (row: any) => {
|
||||
// // TODO 删除
|
||||
// }
|
||||
|
||||
const addNewWestern = () => {
|
||||
WesternMedicineDialogVisible.value = true
|
||||
}
|
||||
const addNewChinese = () => {
|
||||
ChineseMedicineDialogVisible.value = true
|
||||
}
|
||||
const WesternMedicineDialogVisible = ref(false)
|
||||
const ChineseMedicineDialogVisible = ref(false)
|
||||
onMounted(() => {
|
||||
if (props.patientInfo?.encounterId) {
|
||||
loadDiagnosisData()
|
||||
}
|
||||
})
|
||||
defineExpose({ state, loadDiagnosisData })
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.diagnose-container {
|
||||
@@ -365,4 +673,79 @@ const ChineseMedicineDialogVisible = ref(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.diagnosis-text {
|
||||
min-height: 32px;
|
||||
line-height: 1.4;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
max-width: 200px;
|
||||
transition: border-color 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.diagnosis-text:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.diagnosis-text-content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.diagnosis-text-icon {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.diagnosis-text:hover .diagnosis-text-icon {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.diagnosis-popover-container {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.diagnosis-popover-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.diagnosis-popover-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.diagnosis-popover-close {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.diagnosis-popover-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.diagnosis-text:empty::before {
|
||||
content: '点击选择诊断';
|
||||
color: #a8abb2;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -200,6 +200,9 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注:" prop="remark">
|
||||
<el-input v-model="row.remark" maxlength="50" placeholder="最多50字" style="width: 200px" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -294,9 +297,6 @@
|
||||
</el-select>
|
||||
</template>
|
||||
</div>
|
||||
<el-form-item label="备注:" prop="remark">
|
||||
<el-input v-model="row.remark" maxlength="50" placeholder="最多50字" style="width: 200px" />
|
||||
</el-form-item>
|
||||
<div class="form-actions">
|
||||
<el-button type="primary" @click="handleSave">确定</el-button>
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
|
||||
@@ -147,8 +147,6 @@ onBeforeMount(() => {});
|
||||
onMounted(() => {});
|
||||
const applicationFormNameRef = ref();
|
||||
const submitApplicationForm = () => {
|
||||
console.log(applicationFormNameRef.value);
|
||||
|
||||
if (applicationFormNameRef.value?.submit) {
|
||||
applicationFormNameRef.value.submit();
|
||||
}
|
||||
|
||||
@@ -31,9 +31,10 @@
|
||||
<span class="total-count">共 {{ totalCount }} 项</span>
|
||||
</div>
|
||||
<el-transfer
|
||||
v-model="transferValue"
|
||||
:model-value="transferState.selected"
|
||||
:data="transferData"
|
||||
:titles="['未选择', '已选择']"
|
||||
@change="onTransferChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="bloodTransfusion-form">
|
||||
@@ -182,44 +183,10 @@
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
label="血液"
|
||||
value="血液"
|
||||
/>
|
||||
<el-option
|
||||
label="尿液"
|
||||
value="尿液"
|
||||
/>
|
||||
<el-option
|
||||
label="粪便"
|
||||
value="粪便"
|
||||
/>
|
||||
<el-option
|
||||
label="痰液"
|
||||
value="痰液"
|
||||
/>
|
||||
<el-option
|
||||
label="咽拭子"
|
||||
value="咽拭子"
|
||||
/>
|
||||
<el-option
|
||||
label="脑脊液"
|
||||
value="脑脊液"
|
||||
/>
|
||||
<el-option
|
||||
label="胸腹水"
|
||||
value="胸腹水"
|
||||
/>
|
||||
<el-option
|
||||
label="关节液"
|
||||
value="关节液"
|
||||
/>
|
||||
<el-option
|
||||
label="分泌物"
|
||||
value="分泌物"
|
||||
/>
|
||||
<el-option
|
||||
label="其他"
|
||||
value="其他"
|
||||
v-for="item in specimenDictOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -247,11 +214,12 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup name="LaboratoryTests">
|
||||
import {getCurrentInstance, nextTick, onMounted, reactive, ref, watch, computed} from 'vue';
|
||||
import {getCurrentInstance, nextTick, onMounted, reactive, ref, watch, watchEffect, computed} from 'vue';
|
||||
import {patientInfo} from '../../../store/patient.js';
|
||||
import {getExaminationPage, saveInspection} from './api';
|
||||
import {ActivityCategory} from '@/utils/medicalConstants';
|
||||
import {getDepartmentList} from '@/api/public.js';
|
||||
import {getDicts} from '@/api/system/dict/data';
|
||||
import {getEncounterDiagnosis} from '../../api.js';
|
||||
import {ElMessage} from 'element-plus';
|
||||
|
||||
@@ -284,6 +252,10 @@ const searchKey = ref('');
|
||||
const totalCount = ref(0);
|
||||
const skipDeptAutoFill = ref(false);
|
||||
|
||||
// 标本类型字典
|
||||
const specimenDictMap = ref({}); // dictValue → dictLabel 映射
|
||||
const specimenDictOptions = ref([]); // 下拉选项列表
|
||||
|
||||
// 将已加载的全部数据转为 transfer 组件所需的格式
|
||||
const buildTransferData = (records) => {
|
||||
return records.map((item) => {
|
||||
@@ -347,7 +319,28 @@ const handleSearch = () => {
|
||||
};
|
||||
// 编辑初始化标志:避免 applyEditTransferSelection 设置 transferValue 时触发 projectWithDepartment 覆盖 descJson 中的科室值
|
||||
const isInitializing = ref(false);
|
||||
// el-transfer v-model 在某些环境下不触发响应式更新,
|
||||
// 使用 reactive 数组 + @change 事件双重保障
|
||||
const transferState = reactive({ selected: [] });
|
||||
const transferValue = ref([]);
|
||||
// 单向同步:程序设置 transferValue(如编辑回显)→ transferState.selected
|
||||
watch(transferValue, (val) => {
|
||||
if (JSON.stringify(val) !== JSON.stringify(transferState.selected)) {
|
||||
transferState.selected = [...val];
|
||||
}
|
||||
}, { deep: true });
|
||||
// 格式化当前时间为 yyyy-MM-dd HH:mm:ss
|
||||
const formatCurrentDateTime = () => {
|
||||
const now = new Date();
|
||||
const y = now.getFullYear();
|
||||
const m = String(now.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(now.getDate()).padStart(2, '0');
|
||||
const h = String(now.getHours()).padStart(2, '0');
|
||||
const min = String(now.getMinutes()).padStart(2, '0');
|
||||
const s = String(now.getSeconds()).padStart(2, '0');
|
||||
return `${y}-${m}-${d} ${h}:${min}:${s}`;
|
||||
};
|
||||
|
||||
const form = reactive({
|
||||
// categoryType: '', // 项目类别
|
||||
targetDepartment: '', // 发往科室
|
||||
@@ -358,12 +351,30 @@ const form = reactive({
|
||||
relatedResult: '', // 相关结果
|
||||
attention: '', // 注意事项
|
||||
applicationType: 0, // 申请类型 0-普通 1-急诊
|
||||
specimenName: '血液', // 标本类型
|
||||
executeTime: null, // 执行时间
|
||||
specimenName: '', // 标本类型(由选择诊疗项目后自动带出)
|
||||
executeTime: formatCurrentDateTime(), // 执行时间,默认当前系统时间
|
||||
primaryDiagnosisList: [], //主诊断目录
|
||||
otherDiagnosisList: [], //其他断目录
|
||||
});
|
||||
const rules = reactive({});
|
||||
const validateExecuteTime = (rule, value, callback) => {
|
||||
if (!value) {
|
||||
callback(new Error('请选择执行时间'));
|
||||
return;
|
||||
}
|
||||
const now = new Date();
|
||||
const selectedTime = new Date(value);
|
||||
if (selectedTime < now) {
|
||||
callback(new Error('执行时间不可早于当前时间'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
};
|
||||
const rules = reactive({
|
||||
executeTime: [
|
||||
{ required: true, message: '请选择执行时间', trigger: 'change' },
|
||||
{ validator: validateExecuteTime, trigger: 'change' },
|
||||
],
|
||||
});
|
||||
|
||||
const normalizeOrgTreeIds = (nodes) => {
|
||||
if (!Array.isArray(nodes)) return [];
|
||||
@@ -386,10 +397,30 @@ const applyTargetDepartmentEcho = () => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 加载所需标本字典(dictType = specimen_code) */
|
||||
const loadSpecimenDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('specimen_code');
|
||||
if (res?.code === 200 && Array.isArray(res.data)) {
|
||||
const map = {};
|
||||
const options = [];
|
||||
res.data.forEach((item) => {
|
||||
map[item.dictValue] = item.dictLabel;
|
||||
options.push({ value: item.dictValue, label: item.dictLabel });
|
||||
});
|
||||
specimenDictMap.value = map;
|
||||
specimenDictOptions.value = options;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('加载所需标本字典失败:', e);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getLocationInfo();
|
||||
getDiagnosisList();
|
||||
getList();
|
||||
loadSpecimenDict();
|
||||
});
|
||||
/**
|
||||
* type(1:watch监听类型 2:点击保存类型)
|
||||
@@ -469,12 +500,29 @@ const projectWithDepartment = (selectProjectIds, type) => {
|
||||
}
|
||||
return isRelease;
|
||||
};
|
||||
// 监听选择项目变化
|
||||
/** el-transfer @change: 穿梭框数据变化(右侧完整列表) */
|
||||
const onTransferChange = (newRightValue) => {
|
||||
if (!Array.isArray(newRightValue)) return;
|
||||
transferState.selected = [...newRightValue];
|
||||
newRightValue.forEach((id) => {
|
||||
if (!selectedItemsCache.value.has(id)) {
|
||||
const item = applicationListAll.value.find((i) => i.adviceDefinitionId == id);
|
||||
if (item) selectedItemsCache.value.set(id, item);
|
||||
}
|
||||
});
|
||||
if (!isInitializing.value && !skipDeptAutoFill.value) {
|
||||
projectWithDepartment(newRightValue, 1);
|
||||
autoFillSpecimenType(newRightValue);
|
||||
}
|
||||
};
|
||||
|
||||
// 监听 transferValue 变化(el-select multiple v-model 可靠)
|
||||
watch(
|
||||
() => transferValue.value,
|
||||
(newValue) => {
|
||||
if (skipDeptAutoFill.value) return;
|
||||
if (isInitializing.value) return;
|
||||
console.log('[标本联动] watch 触发, transferValue:', newValue);
|
||||
if (skipDeptAutoFill.value || isInitializing.value) return;
|
||||
if (!newValue || newValue.length === 0) return;
|
||||
newValue.forEach((id) => {
|
||||
if (!selectedItemsCache.value.has(id)) {
|
||||
const item = applicationListAll.value.find((i) => i.adviceDefinitionId == id);
|
||||
@@ -482,9 +530,45 @@ watch(
|
||||
}
|
||||
});
|
||||
projectWithDepartment(newValue, 1);
|
||||
autoFillSpecimenType(newValue);
|
||||
}
|
||||
);
|
||||
|
||||
/** 根据选中的检验项目自动带出标本类型(直接使用字典码,与 el-option :value 对齐) */
|
||||
const autoFillSpecimenType = (selectedIds) => {
|
||||
if (!selectedIds || selectedIds.length === 0) return;
|
||||
const specimens = [];
|
||||
selectedIds.forEach((id) => {
|
||||
let item = selectedItemsCache.value.get(id);
|
||||
if (!item) {
|
||||
item = applicationListAll.value.find((i) => i.adviceDefinitionId == id);
|
||||
}
|
||||
if (item?.specimenCode) {
|
||||
const code = String(item.specimenCode);
|
||||
if (code && !specimens.includes(code)) {
|
||||
specimens.push(code);
|
||||
}
|
||||
}
|
||||
});
|
||||
// 所有选中项目标本类型一致时才自动填充
|
||||
if (specimens.length === 1) {
|
||||
form.specimenName = specimens[0];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 从可能的历史数据中解析标本字典码
|
||||
* 旧数据可能存储的是显示名称(如 "血液"),需转换为字典码(如 "1")
|
||||
*/
|
||||
const resolveSpecimenCode = (value) => {
|
||||
if (!value) return '';
|
||||
if (specimenDictMap.value[value]) return value; // 已经是有效的字典码
|
||||
for (const opt of specimenDictOptions.value) {
|
||||
if (opt.label === value) return String(opt.value); // 标签 → 码值
|
||||
}
|
||||
return String(value); // 兜底:直接转字符串
|
||||
};
|
||||
|
||||
/** 编辑弹窗:根据申请单明细把右侧「已选择」与 transferValue 对齐(依赖 applicationListAll 已加载) */
|
||||
const applyEditTransferSelection = () => {
|
||||
const newData = props.editData
|
||||
@@ -525,6 +609,10 @@ const applyEditTransferSelection = () => {
|
||||
skipDeptAutoFill.value = false
|
||||
})
|
||||
isInitializing.value = false
|
||||
// 编辑初始化后显式填充标本类型(watch 被 skipDeptAutoFill 守卫跳过了)
|
||||
if (uniq.length > 0) {
|
||||
autoFillSpecimenType(uniq)
|
||||
}
|
||||
if (newData.requestFormDetailList.length && uniq.length === 0) {
|
||||
console.warn(
|
||||
'[LaboratoryTests] 申请单明细未能在项目字典中匹配到项,请核对 activityId / 项目名称',
|
||||
@@ -544,7 +632,12 @@ watch(
|
||||
const obj = JSON.parse(newData.descJson)
|
||||
Object.keys(form).forEach((key) => {
|
||||
if (obj[key] !== undefined) {
|
||||
form[key] = obj[key]
|
||||
// 标本类型:兼容旧数据中存的是显示名称(如 "血液"),转为字典码(如 "1")
|
||||
if (key === 'specimenName') {
|
||||
form[key] = resolveSpecimenCode(obj[key])
|
||||
} else {
|
||||
form[key] = obj[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
applyTargetDepartmentEcho()
|
||||
@@ -591,13 +684,17 @@ watch(
|
||||
);
|
||||
|
||||
const submit = () => {
|
||||
if (transferValue.value.length == 0) {
|
||||
// 使用 transferState.selected(来自 el-transfer @change),兜底 transferValue
|
||||
const selected = transferState.selected.length > 0 ? transferState.selected : transferValue.value;
|
||||
if (selected.length == 0) {
|
||||
return proxy.$message.error('请选择申请单');
|
||||
}
|
||||
if (!projectWithDepartment(transferValue.value, 2)) {
|
||||
// 提交前自动带出标本类型(必须在 projectWithDepartment 之前,否则科室校验失败直接 return 不会执行)
|
||||
autoFillSpecimenType(selected);
|
||||
if (!projectWithDepartment(selected, 2)) {
|
||||
return;
|
||||
}
|
||||
let applicationListAllFilter = transferValue.value.map((id) => {
|
||||
let applicationListAllFilter = selected.map((id) => {
|
||||
let item = applicationListAll.value.find((i) => i.adviceDefinitionId == id);
|
||||
if (!item) {
|
||||
item = selectedItemsCache.value.get(id);
|
||||
@@ -627,7 +724,11 @@ const submit = () => {
|
||||
organizationId: patientInfo.value.inHospitalOrgId, // 医疗机构ID
|
||||
requestFormId: isEditMode.value ? props.editData.requestFormId : '', // 申请单ID(编辑模式传入,新增为空)
|
||||
name: '检验申请单',
|
||||
descJson: JSON.stringify(form),
|
||||
descJson: JSON.stringify({
|
||||
...form,
|
||||
// 标本类型显示名称(供申请单查看页使用,避免查看页依赖字典加载)
|
||||
specimenNameLabel: specimenDictMap.value[form.specimenName] || form.specimenName,
|
||||
}),
|
||||
categoryEnum: '21', // 21 检验 22 检查 23 输血 24 手术(避开 adviceType 1-6 碰撞)
|
||||
};
|
||||
saveInspection(params).then((res) => {
|
||||
@@ -681,7 +782,14 @@ function getDiagnosisList() {
|
||||
}
|
||||
});
|
||||
}
|
||||
defineExpose({ state, submit, getLocationInfo, getDiagnosisList, getList });
|
||||
// 暴露给父组件:允许父组件在调用 submit 前读取选中项并执行联动
|
||||
const getSelectedItems = () => transferValue.value;
|
||||
const getSpecimenMap = () => specimenDictMap.value;
|
||||
const getApplicationListAll = () => applicationListAll.value;
|
||||
const fillSpecimenBySelected = () => { autoFillSpecimenType(transferValue.value); };
|
||||
|
||||
defineExpose({ state, submit, getLocationInfo, getDiagnosisList, getList,
|
||||
getSelectedItems, getSpecimenMap, getApplicationListAll, fillSpecimenBySelected });
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.LaboratoryTests-container {
|
||||
|
||||
@@ -287,7 +287,7 @@
|
||||
<el-table-column label="药房/科室" align="center" prop="" width="240">
|
||||
<template #default="scope">
|
||||
<span v-if="!scope.row.isEdit">
|
||||
{{ scope.row.positionName }}
|
||||
{{ scope.row.positionName || scope.row.orgName }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -342,6 +342,13 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" width="150" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span v-if="!scope.row.isEdit">
|
||||
{{ scope.row.remark || '-' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="诊断" align="center" prop="diagnosisName" width="150">
|
||||
<template #default="scope">
|
||||
<span v-if="!scope.row.isEdit">
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
<div style="height: calc(100vh - 126px)">
|
||||
<div
|
||||
style="
|
||||
height: 51px;
|
||||
min-height: 51px;
|
||||
border-bottom: 2px solid #e4e7ed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
"
|
||||
>
|
||||
<div>
|
||||
<div style="display: flex; align-items: center; flex-wrap: nowrap; flex-shrink: 0">
|
||||
<span class="descriptions-item-label">截止时间:</span>
|
||||
<el-date-picker
|
||||
v-model="deadline"
|
||||
@@ -22,6 +22,7 @@
|
||||
<el-radio-group
|
||||
v-model="therapyEnum"
|
||||
class="ml20"
|
||||
style="flex-shrink: 0"
|
||||
@change="handleRadioChange"
|
||||
>
|
||||
<el-radio :value="undefined">
|
||||
@@ -36,6 +37,7 @@
|
||||
</el-radio-group>
|
||||
<el-button
|
||||
class="ml20"
|
||||
style="flex-shrink: 0"
|
||||
type="primary"
|
||||
plain
|
||||
@click="handleGetPrescription"
|
||||
@@ -43,7 +45,7 @@
|
||||
查询
|
||||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<div style="display: flex; align-items: center; flex-wrap: nowrap; flex-shrink: 0">
|
||||
<span class="descriptions-item-label">实际执行时间:</span>
|
||||
<el-date-picker
|
||||
v-model="exeDate"
|
||||
@@ -56,10 +58,12 @@
|
||||
<span class="descriptions-item-label">全选:</span>
|
||||
<el-switch
|
||||
v-model="chooseAll"
|
||||
style="flex-shrink: 0"
|
||||
@change="handelSwicthChange"
|
||||
/>
|
||||
<el-button
|
||||
class="ml20"
|
||||
style="flex-shrink: 0"
|
||||
type="primary"
|
||||
:disabled="props.exeStatus == 6"
|
||||
@click="handleExecute"
|
||||
@@ -68,6 +72,7 @@
|
||||
</el-button>
|
||||
<el-button
|
||||
class="ml20"
|
||||
style="flex-shrink: 0"
|
||||
type="primary"
|
||||
:disabled="props.exeStatus == 6"
|
||||
@click="handleNoExecute"
|
||||
@@ -75,7 +80,8 @@
|
||||
不执行
|
||||
</el-button>
|
||||
<el-button
|
||||
class="ml20 mr20"
|
||||
class="ml20"
|
||||
style="flex-shrink: 0"
|
||||
type="danger"
|
||||
:disabled="props.exeStatus != 6"
|
||||
plain
|
||||
|
||||
@@ -111,6 +111,7 @@ function handleClick(tabName) {
|
||||
break;
|
||||
case 'cancel':
|
||||
exeStatus.value = 9;
|
||||
requestStatus.value = RequestStatus.CANCELLED;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -2339,6 +2339,10 @@ function handleMedicalAdvice(row) {
|
||||
const draftItems = filteredItems.filter(item => item.statusEnum === 1)
|
||||
const activeItems = filteredItems.filter(item => item.statusEnum === 2)
|
||||
|
||||
if (activeItems.length > 0) {
|
||||
temporarySigned.value = true
|
||||
}
|
||||
|
||||
// 🔧 修复:限制返回数量,最多显示前100条,避免数据过多导致页面卡死
|
||||
const maxItems = 100
|
||||
if (draftItems.length > maxItems) {
|
||||
@@ -2414,9 +2418,9 @@ function handleMedicalAdvice(row) {
|
||||
const contentData = jsonContent ? JSON.parse(jsonContent) : {};
|
||||
const medicineName = contentData.adviceName || contentData.advice_name || item.adviceName || item.advice_name || '';
|
||||
const spec = contentData.volume || contentData.specification || item.volume || item.specification || '';
|
||||
const specMatch = spec.match(/(\d+)(\D+)/)
|
||||
const specValue = specMatch ? parseInt(specMatch[1]) : 1
|
||||
const specUnit = specMatch ? specMatch[2] : 'ml'
|
||||
const specMatch = spec.match(/([\d.]+)\s*([a-zA-Z一-龥]+)/)
|
||||
const specValue = specMatch ? parseFloat(specMatch[1]) : 1
|
||||
const specUnit = specMatch ? specMatch[2] : ''
|
||||
const dosage = specValue * (contentData.quantity || item.quantity || 1)
|
||||
|
||||
let usageCode = contentData.methodCode || 'iv'
|
||||
@@ -2434,8 +2438,8 @@ function handleMedicalAdvice(row) {
|
||||
unit: specUnit,
|
||||
usage: usageCode,
|
||||
usageLabel,
|
||||
frequency: '临时',
|
||||
executeTime: new Date().toLocaleString('zh-CN'),
|
||||
frequency: '立即',
|
||||
executeTime: '',
|
||||
originalMedicine: {
|
||||
...item,
|
||||
medicineName: medicineName,
|
||||
@@ -2449,8 +2453,8 @@ function handleMedicalAdvice(row) {
|
||||
id: index + 1,
|
||||
adviceName: item.adviceName || item.advice_name || '',
|
||||
dosage: 1, unit: 'ml', usage: 'iv', usageLabel: '静脉注射',
|
||||
frequency: '临时',
|
||||
executeTime: new Date().toLocaleString('zh-CN'),
|
||||
frequency: '立即',
|
||||
executeTime: '',
|
||||
originalMedicine: {
|
||||
...item,
|
||||
medicineName: item.adviceName || item.advice_name || '',
|
||||
@@ -2584,14 +2588,14 @@ function handleTemporaryMedicalSubmit(data) {
|
||||
let usageCode = contentData.methodCode || 'iv'
|
||||
return {
|
||||
id: index + 1, adviceName: medicineName, dosage, unit: specUnit,
|
||||
usage: usageCode, frequency: '临时',
|
||||
executeTime: new Date().toLocaleString('zh-CN'),
|
||||
usage: usageCode, frequency: '立即',
|
||||
executeTime: '',
|
||||
originalMedicine: { ...item, medicineName, specification: spec, quantity: contentData.quantity || item.quantity || 1, encounterId: row.visitId }
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
id: index + 1, adviceName: item.adviceName || '', dosage: 1, unit: 'ml',
|
||||
usage: 'iv', frequency: '临时', executeTime: new Date().toLocaleString('zh-CN'),
|
||||
usage: 'iv', frequency: '立即', executeTime: '',
|
||||
originalMedicine: { ...item, medicineName: item.adviceName || '', specification: item.volume || '', quantity: item.quantity || 1, encounterId: row.visitId }
|
||||
}
|
||||
}
|
||||
@@ -2705,8 +2709,8 @@ function handleQuoteBilling() {
|
||||
else if (usageCode === 'po' && (medicineName.includes('片') || medicineName.includes('胶囊'))) { usageLabel = '口服' }
|
||||
return {
|
||||
id: index + 1, adviceName: medicineName, dosage, unit: specUnit,
|
||||
usage: usageCode, usageLabel, frequency: '临时',
|
||||
executeTime: new Date().toLocaleString('zh-CN'),
|
||||
usage: usageCode, usageLabel, frequency: '立即',
|
||||
executeTime: '',
|
||||
originalMedicine: {
|
||||
...item,
|
||||
medicineName: medicineName,
|
||||
@@ -2719,7 +2723,7 @@ function handleQuoteBilling() {
|
||||
return {
|
||||
id: index + 1, adviceName: item.adviceName || item.advice_name || '',
|
||||
dosage: 1, unit: 'ml', usage: 'iv', usageLabel: '静脉注射',
|
||||
frequency: '临时', executeTime: new Date().toLocaleString('zh-CN'),
|
||||
frequency: '立即', executeTime: '',
|
||||
originalMedicine: {
|
||||
...item,
|
||||
medicineName: item.adviceName || item.advice_name || '',
|
||||
|
||||
@@ -173,6 +173,8 @@
|
||||
border
|
||||
style="width: 100%;"
|
||||
fit
|
||||
highlight-current-row
|
||||
@row-click="handleAdviceRowClick"
|
||||
>
|
||||
<el-table-column
|
||||
label="序号"
|
||||
@@ -257,7 +259,7 @@
|
||||
<span
|
||||
class="info-value"
|
||||
:class="{ 'unsigned': !isSigned }"
|
||||
>{{ isSigned ? currentUser.name : '未签名' }}</span>
|
||||
>{{ isSigned ? signatureDoctor : '未签名' }}</span>
|
||||
</div>
|
||||
<div class="signature-info">
|
||||
<span class="info-label">签名时间:</span>
|
||||
@@ -398,6 +400,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { checkPassword } from '@/api/surgicalschedule'
|
||||
import { savePrescription } from '@/views/clinicmanagement/bargain/component/api.js'
|
||||
import { parseTime } from '@/utils/openhis'
|
||||
|
||||
// 定义props
|
||||
const props = defineProps({
|
||||
@@ -507,16 +510,11 @@ const displayAdvicesList = computed(() => {
|
||||
return advicesExpanded.value ? all : all.slice(0, PAGE_SIZE)
|
||||
})
|
||||
|
||||
// 响应式数据 - isSigned 从父组件传入的 prop 初始化
|
||||
const isSigned = ref(props.isSignedProp)
|
||||
|
||||
// 🔧 修复 Bug #446: 同步父组件 isSignedProp 的变化到本地 isSigned
|
||||
// ref(props.isSignedProp) 只在初始化时读取一次,父组件后续更新不会自动同步
|
||||
watch(() => props.isSignedProp, (newVal) => {
|
||||
isSigned.value = newVal
|
||||
})
|
||||
const isSigned = ref(false)
|
||||
|
||||
const signatureDoctor = ref(userStore.nickName || userStore.name || '未知用户')
|
||||
const signatureTime = ref('')
|
||||
|
||||
const showSignDialog = ref(false)
|
||||
const signPassword = ref('')
|
||||
const showEditDialog = ref(false)
|
||||
@@ -531,7 +529,7 @@ const editForm = ref({
|
||||
|
||||
// 计算属性
|
||||
const currentUser = computed(() => ({
|
||||
name: userStore.name || '未知用户',
|
||||
name: userStore.nickName || userStore.name || '未知用户',
|
||||
id: userStore.id
|
||||
}))
|
||||
|
||||
@@ -554,10 +552,10 @@ const totalAmount = computed(() => {
|
||||
// 将计费药品转换为临时医嘱数据
|
||||
const convertedAdvices = computed(() => {
|
||||
return props.billingMedicines.map((medicine, index) => {
|
||||
// 解析规格中的数值和单位
|
||||
const specMatch = medicine.specification ? medicine.specification.match(/(\d+)(\D+)/) : null
|
||||
const specValue = specMatch ? parseInt(specMatch[1]) : 1
|
||||
const specUnit = specMatch ? specMatch[2] : 'ml'
|
||||
// 解析规格中的数值和单位(支持小数,去除 ×、:、/、* 等多余字符)
|
||||
const specMatch = medicine.specification ? medicine.specification.match(/([\d.]+)\s*([a-zA-Z一-龥]+)/) : null
|
||||
const specValue = specMatch ? parseFloat(specMatch[1]) : 1
|
||||
const specUnit = specMatch ? specMatch[2] : ''
|
||||
|
||||
// 计算剂量 = 规格数值 × 数量
|
||||
const dosage = specValue * (medicine.quantity || 1)
|
||||
@@ -583,8 +581,8 @@ const convertedAdvices = computed(() => {
|
||||
unit: specUnit,
|
||||
usage: usageCode, // 🔧 修复:使用后端字典的正确编码
|
||||
usageLabel: usageLabel, // 🔧 新增:保存显示名称
|
||||
frequency: '临时',
|
||||
executeTime: new Date().toLocaleString('zh-CN'),
|
||||
frequency: '立即',
|
||||
executeTime: '',
|
||||
originalMedicine: medicine
|
||||
}
|
||||
})
|
||||
@@ -640,6 +638,24 @@ const handleSign = () => {
|
||||
showSignDialog.value = true
|
||||
}
|
||||
|
||||
// 点击已生成列表行 → 回显该行的签名信息
|
||||
const handleAdviceRowClick = (row) => {
|
||||
const om = row?.originalMedicine
|
||||
if (!om) return
|
||||
const contentJson = om.contentJson || om.content_json
|
||||
if (!contentJson) return
|
||||
try {
|
||||
const cd = typeof contentJson === 'string' ? JSON.parse(contentJson) : contentJson
|
||||
if (cd.signDoctorName) {
|
||||
signatureDoctor.value = cd.signDoctorName
|
||||
}
|
||||
if (cd.signDate) {
|
||||
signatureTime.value = cd.signDate
|
||||
}
|
||||
isSigned.value = true
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// 编辑医嘱
|
||||
const handleEditAdvice = (index) => {
|
||||
const advice = displayAdvices.value[index]
|
||||
@@ -662,7 +678,7 @@ const handleEditAdvice = (index) => {
|
||||
}
|
||||
|
||||
// 保存编辑
|
||||
const handleSaveEdit = () => {
|
||||
const handleSaveEdit = async () => {
|
||||
if (!editForm.value.dosage && editForm.value.dosage !== 0) {
|
||||
ElMessage.warning('请填写剂量')
|
||||
return
|
||||
@@ -720,8 +736,8 @@ const handleSaveEdit = () => {
|
||||
|
||||
// 如果用户修改了剂量,重新计算数量
|
||||
if (originalMedicine.specification) {
|
||||
const specMatch = originalMedicine.specification.match(/(\d+)(\D+)/)
|
||||
const specValue = specMatch ? parseInt(specMatch[1]) : 1
|
||||
const specMatch = originalMedicine.specification.match(/([\d.]+)\s*([a-zA-Z一-龥]+)/)
|
||||
const specValue = specMatch ? parseFloat(specMatch[1]) : 1
|
||||
if (specValue > 0) {
|
||||
const newQuantity = editForm.value.dosage / specValue
|
||||
contentData.quantity = newQuantity
|
||||
@@ -731,12 +747,9 @@ const handleSaveEdit = () => {
|
||||
updatedAdvice.quantity = newQuantity
|
||||
}
|
||||
}
|
||||
|
||||
// 更新 contentJson
|
||||
updatedAdvice.originalMedicine = {
|
||||
...originalMedicine,
|
||||
contentJson: JSON.stringify(contentData)
|
||||
}
|
||||
|
||||
// 🔧 修复:原地修改 contentJson,保存使用最新数据
|
||||
originalMedicine.contentJson = JSON.stringify(contentData)
|
||||
} catch (e) {
|
||||
console.error('解析 originalMedicine.contentJson 失败', e)
|
||||
}
|
||||
@@ -750,7 +763,49 @@ const handleSaveEdit = () => {
|
||||
emit('update:temporary-advices', updatedAdvices)
|
||||
|
||||
showEditDialog.value = false
|
||||
ElMessage.success('编辑成功(已暂存本地,请点击"一键签名并生成医嘱"按钮提交到服务器)')
|
||||
|
||||
// 🔧 修复 Bug #604: 编辑保存后直接提交到服务器
|
||||
const editMedicine = updatedAdvice.originalMedicine
|
||||
if (editMedicine) {
|
||||
let contentJsonData = {}
|
||||
try { contentJsonData = JSON.parse(editMedicine.contentJson || '{}') } catch (e) {}
|
||||
const quantity = editMedicine.quantity || contentJsonData.quantity || 1
|
||||
const unitPrice = editMedicine.unitPrice || contentJsonData.unitPrice || 0
|
||||
contentJsonData.dose = editForm.value.dosage
|
||||
contentJsonData.doseUnitCode = editForm.value.unit
|
||||
contentJsonData.methodCode = updatedAdvice.usage
|
||||
contentJsonData.quantity = quantity
|
||||
contentJsonData.totalPrice = unitPrice * quantity
|
||||
contentJsonData.adviceName = updatedAdvice.adviceName
|
||||
|
||||
const saveItem = {
|
||||
...contentJsonData,
|
||||
dbOpType: editMedicine.requestId ? '2' : '1',
|
||||
adviceType: editMedicine.adviceType || 1,
|
||||
requestId: editMedicine.requestId,
|
||||
chargeItemId: editMedicine.chargeItemId,
|
||||
contentJson: JSON.stringify(contentJsonData),
|
||||
quantity,
|
||||
unitCode: editMedicine.unitCode || editForm.value.unit,
|
||||
unitPrice,
|
||||
totalPrice: unitPrice * quantity,
|
||||
adviceName: updatedAdvice.adviceName,
|
||||
patientId: props.patientInfo.patientId,
|
||||
encounterId: props.patientInfo.visitId,
|
||||
orgId: props.patientInfo.orgId,
|
||||
methodCode: updatedAdvice.usage,
|
||||
dose: editForm.value.dosage,
|
||||
doseUnitCode: editForm.value.unit,
|
||||
generateSourceEnum: 6,
|
||||
sourceBillNo: props.patientInfo?.operCode || ''
|
||||
}
|
||||
try {
|
||||
await savePrescription({ organizationId: props.patientInfo.orgId || 1, adviceSaveList: [saveItem] }, '2')
|
||||
ElMessage.success('医嘱修改已保存到服务器')
|
||||
} catch (e) {
|
||||
ElMessage.error('保存失败,请重试')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 取消编辑
|
||||
@@ -762,7 +817,7 @@ const handleCancelEdit = () => {
|
||||
dosage: '',
|
||||
unit: '',
|
||||
usage: '',
|
||||
frequency: '临时'
|
||||
frequency: '立即'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -777,10 +832,10 @@ const confirmSign = async () => {
|
||||
const response = await checkPassword({
|
||||
password: signPassword.value
|
||||
})
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
isSigned.value = true
|
||||
signatureTime.value = new Date().toLocaleString('zh-CN')
|
||||
signatureDoctor.value = userStore.nickName || userStore.name
|
||||
signatureTime.value = parseTime(new Date())
|
||||
showSignDialog.value = false
|
||||
signPassword.value = ''
|
||||
ElMessage.success('签名成功')
|
||||
@@ -799,10 +854,8 @@ const confirmSign = async () => {
|
||||
|
||||
const handleSignAndSubmit = () => {
|
||||
if (isSigned.value) {
|
||||
// 如果已经签名,直接提交
|
||||
handleSubmit()
|
||||
} else {
|
||||
// 如果未签名,打开签名弹窗
|
||||
handleSign()
|
||||
}
|
||||
}
|
||||
@@ -907,6 +960,8 @@ const handleSubmit = async () => {
|
||||
contentJsonData.dose = advice.dosage;
|
||||
contentJsonData.doseUnitCode = advice.unit;
|
||||
contentJsonData.rateCode = advice.frequency;
|
||||
contentJsonData.signDoctorName = signatureDoctor.value
|
||||
contentJsonData.signDate = signatureTime.value
|
||||
|
||||
// 重新序列化contentJson
|
||||
const updatedContentJson = JSON.stringify(contentJsonData);
|
||||
@@ -993,7 +1048,7 @@ const handleSubmit = async () => {
|
||||
billingMedicines: props.billingMedicines,
|
||||
temporaryAdvices: itemsToSign,
|
||||
signature: {
|
||||
doctorName: currentUser.value.name,
|
||||
doctorName: signatureDoctor.value,
|
||||
signatureTime: signatureTime.value
|
||||
}
|
||||
}
|
||||
|
||||
47
openhis-ui-vue3/tests/e2e/specs/bug-466.spec.ts
Normal file
47
openhis-ui-vue3/tests/e2e/specs/bug-466.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/LoginPage';
|
||||
|
||||
/**
|
||||
* Bug #466: Bug #466 待确认标题
|
||||
* 自动生成: 2026-06-01 09:36:17
|
||||
*/
|
||||
test.describe('🐛 Bug#466', () => {
|
||||
let loginPage: LoginPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
await loginPage.goto();
|
||||
await loginPage.login(
|
||||
process.env.TEST_USERNAME || 'admin',
|
||||
process.env.TEST_PASSWORD || 'admin123'
|
||||
);
|
||||
await loginPage.expectLoginSuccess();
|
||||
});
|
||||
|
||||
test('#466 Bug #466 待确认标题 @bug466 @regression', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
|
||||
// 检查页面正常加载(非登录页)
|
||||
await expect(page).not.toHaveURL(/.*login.*/);
|
||||
|
||||
// 检查无 JS 错误
|
||||
const jsErrors: string[] = [];
|
||||
page.on('pageerror', (err) => jsErrors.push(err.message));
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 页面基本可交互
|
||||
const body = page.locator('body');
|
||||
await expect(body).toBeVisible();
|
||||
|
||||
// 截图记录
|
||||
await page.screenshot({
|
||||
path: 'tests/e2e/report/bug-466-result.png',
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
// 无 JS 错误
|
||||
expect(jsErrors).toEqual([]);
|
||||
});
|
||||
});
|
||||
47
openhis-ui-vue3/tests/e2e/specs/bug-467.spec.ts
Normal file
47
openhis-ui-vue3/tests/e2e/specs/bug-467.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/LoginPage';
|
||||
|
||||
/**
|
||||
* Bug #467: Bug #467 待确认标题
|
||||
* 自动生成: 2026-06-01 09:36:17
|
||||
*/
|
||||
test.describe('🐛 Bug#467', () => {
|
||||
let loginPage: LoginPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
await loginPage.goto();
|
||||
await loginPage.login(
|
||||
process.env.TEST_USERNAME || 'admin',
|
||||
process.env.TEST_PASSWORD || 'admin123'
|
||||
);
|
||||
await loginPage.expectLoginSuccess();
|
||||
});
|
||||
|
||||
test('#467 Bug #467 待确认标题 @bug467 @regression', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
|
||||
// 检查页面正常加载(非登录页)
|
||||
await expect(page).not.toHaveURL(/.*login.*/);
|
||||
|
||||
// 检查无 JS 错误
|
||||
const jsErrors: string[] = [];
|
||||
page.on('pageerror', (err) => jsErrors.push(err.message));
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 页面基本可交互
|
||||
const body = page.locator('body');
|
||||
await expect(body).toBeVisible();
|
||||
|
||||
// 截图记录
|
||||
await page.screenshot({
|
||||
path: 'tests/e2e/report/bug-467-result.png',
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
// 无 JS 错误
|
||||
expect(jsErrors).toEqual([]);
|
||||
});
|
||||
});
|
||||
47
openhis-ui-vue3/tests/e2e/specs/bug-610.spec.ts
Normal file
47
openhis-ui-vue3/tests/e2e/specs/bug-610.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/LoginPage';
|
||||
|
||||
/**
|
||||
* Bug #610: Bug #610 待确认标题
|
||||
* 自动生成: 2026-06-01 09:36:17
|
||||
*/
|
||||
test.describe('🐛 Bug#610', () => {
|
||||
let loginPage: LoginPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
await loginPage.goto();
|
||||
await loginPage.login(
|
||||
process.env.TEST_USERNAME || 'admin',
|
||||
process.env.TEST_PASSWORD || 'admin123'
|
||||
);
|
||||
await loginPage.expectLoginSuccess();
|
||||
});
|
||||
|
||||
test('#610 Bug #610 待确认标题 @bug610 @regression', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
|
||||
// 检查页面正常加载(非登录页)
|
||||
await expect(page).not.toHaveURL(/.*login.*/);
|
||||
|
||||
// 检查无 JS 错误
|
||||
const jsErrors: string[] = [];
|
||||
page.on('pageerror', (err) => jsErrors.push(err.message));
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 页面基本可交互
|
||||
const body = page.locator('body');
|
||||
await expect(body).toBeVisible();
|
||||
|
||||
// 截图记录
|
||||
await page.screenshot({
|
||||
path: 'tests/e2e/report/bug-610-result.png',
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
// 无 JS 错误
|
||||
expect(jsErrors).toEqual([]);
|
||||
});
|
||||
});
|
||||
47
openhis-ui-vue3/tests/e2e/specs/bug-611.spec.ts
Normal file
47
openhis-ui-vue3/tests/e2e/specs/bug-611.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/LoginPage';
|
||||
|
||||
/**
|
||||
* Bug #611: Bug #611 待确认标题
|
||||
* 自动生成: 2026-06-01 09:36:17
|
||||
*/
|
||||
test.describe('🐛 Bug#611', () => {
|
||||
let loginPage: LoginPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
await loginPage.goto();
|
||||
await loginPage.login(
|
||||
process.env.TEST_USERNAME || 'admin',
|
||||
process.env.TEST_PASSWORD || 'admin123'
|
||||
);
|
||||
await loginPage.expectLoginSuccess();
|
||||
});
|
||||
|
||||
test('#611 Bug #611 待确认标题 @bug611 @regression', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
|
||||
// 检查页面正常加载(非登录页)
|
||||
await expect(page).not.toHaveURL(/.*login.*/);
|
||||
|
||||
// 检查无 JS 错误
|
||||
const jsErrors: string[] = [];
|
||||
page.on('pageerror', (err) => jsErrors.push(err.message));
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 页面基本可交互
|
||||
const body = page.locator('body');
|
||||
await expect(body).toBeVisible();
|
||||
|
||||
// 截图记录
|
||||
await page.screenshot({
|
||||
path: 'tests/e2e/report/bug-611-result.png',
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
// 无 JS 错误
|
||||
expect(jsErrors).toEqual([]);
|
||||
});
|
||||
});
|
||||
47
openhis-ui-vue3/tests/e2e/specs/bug-613.spec.ts
Normal file
47
openhis-ui-vue3/tests/e2e/specs/bug-613.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/LoginPage';
|
||||
|
||||
/**
|
||||
* Bug #613: Bug #613 待确认标题
|
||||
* 自动生成: 2026-06-01 09:36:17
|
||||
*/
|
||||
test.describe('🐛 Bug#613', () => {
|
||||
let loginPage: LoginPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
await loginPage.goto();
|
||||
await loginPage.login(
|
||||
process.env.TEST_USERNAME || 'admin',
|
||||
process.env.TEST_PASSWORD || 'admin123'
|
||||
);
|
||||
await loginPage.expectLoginSuccess();
|
||||
});
|
||||
|
||||
test('#613 Bug #613 待确认标题 @bug613 @regression', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
|
||||
// 检查页面正常加载(非登录页)
|
||||
await expect(page).not.toHaveURL(/.*login.*/);
|
||||
|
||||
// 检查无 JS 错误
|
||||
const jsErrors: string[] = [];
|
||||
page.on('pageerror', (err) => jsErrors.push(err.message));
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 页面基本可交互
|
||||
const body = page.locator('body');
|
||||
await expect(body).toBeVisible();
|
||||
|
||||
// 截图记录
|
||||
await page.screenshot({
|
||||
path: 'tests/e2e/report/bug-613-result.png',
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
// 无 JS 错误
|
||||
expect(jsErrors).toEqual([]);
|
||||
});
|
||||
});
|
||||
47
openhis-ui-vue3/tests/e2e/specs/bug-614.spec.ts
Normal file
47
openhis-ui-vue3/tests/e2e/specs/bug-614.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/LoginPage';
|
||||
|
||||
/**
|
||||
* Bug #614: Bug #614 待确认标题
|
||||
* 自动生成: 2026-06-01 09:36:18
|
||||
*/
|
||||
test.describe('🐛 Bug#614', () => {
|
||||
let loginPage: LoginPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
await loginPage.goto();
|
||||
await loginPage.login(
|
||||
process.env.TEST_USERNAME || 'admin',
|
||||
process.env.TEST_PASSWORD || 'admin123'
|
||||
);
|
||||
await loginPage.expectLoginSuccess();
|
||||
});
|
||||
|
||||
test('#614 Bug #614 待确认标题 @bug614 @regression', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
|
||||
// 检查页面正常加载(非登录页)
|
||||
await expect(page).not.toHaveURL(/.*login.*/);
|
||||
|
||||
// 检查无 JS 错误
|
||||
const jsErrors: string[] = [];
|
||||
page.on('pageerror', (err) => jsErrors.push(err.message));
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 页面基本可交互
|
||||
const body = page.locator('body');
|
||||
await expect(body).toBeVisible();
|
||||
|
||||
// 截图记录
|
||||
await page.screenshot({
|
||||
path: 'tests/e2e/report/bug-614-result.png',
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
// 无 JS 错误
|
||||
expect(jsErrors).toEqual([]);
|
||||
});
|
||||
});
|
||||
47
openhis-ui-vue3/tests/e2e/specs/bug-615.spec.ts
Normal file
47
openhis-ui-vue3/tests/e2e/specs/bug-615.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/LoginPage';
|
||||
|
||||
/**
|
||||
* Bug #615: Bug #615 待确认标题
|
||||
* 自动生成: 2026-06-01 09:36:18
|
||||
*/
|
||||
test.describe('🐛 Bug#615', () => {
|
||||
let loginPage: LoginPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
await loginPage.goto();
|
||||
await loginPage.login(
|
||||
process.env.TEST_USERNAME || 'admin',
|
||||
process.env.TEST_PASSWORD || 'admin123'
|
||||
);
|
||||
await loginPage.expectLoginSuccess();
|
||||
});
|
||||
|
||||
test('#615 Bug #615 待确认标题 @bug615 @regression', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
|
||||
// 检查页面正常加载(非登录页)
|
||||
await expect(page).not.toHaveURL(/.*login.*/);
|
||||
|
||||
// 检查无 JS 错误
|
||||
const jsErrors: string[] = [];
|
||||
page.on('pageerror', (err) => jsErrors.push(err.message));
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 页面基本可交互
|
||||
const body = page.locator('body');
|
||||
await expect(body).toBeVisible();
|
||||
|
||||
// 截图记录
|
||||
await page.screenshot({
|
||||
path: 'tests/e2e/report/bug-615-result.png',
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
// 无 JS 错误
|
||||
expect(jsErrors).toEqual([]);
|
||||
});
|
||||
});
|
||||
47
openhis-ui-vue3/tests/e2e/specs/bug-616.spec.ts
Normal file
47
openhis-ui-vue3/tests/e2e/specs/bug-616.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/LoginPage';
|
||||
|
||||
/**
|
||||
* Bug #616: Bug #616 待确认标题
|
||||
* 自动生成: 2026-06-01 09:36:18
|
||||
*/
|
||||
test.describe('🐛 Bug#616', () => {
|
||||
let loginPage: LoginPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
await loginPage.goto();
|
||||
await loginPage.login(
|
||||
process.env.TEST_USERNAME || 'admin',
|
||||
process.env.TEST_PASSWORD || 'admin123'
|
||||
);
|
||||
await loginPage.expectLoginSuccess();
|
||||
});
|
||||
|
||||
test('#616 Bug #616 待确认标题 @bug616 @regression', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
|
||||
// 检查页面正常加载(非登录页)
|
||||
await expect(page).not.toHaveURL(/.*login.*/);
|
||||
|
||||
// 检查无 JS 错误
|
||||
const jsErrors: string[] = [];
|
||||
page.on('pageerror', (err) => jsErrors.push(err.message));
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 页面基本可交互
|
||||
const body = page.locator('body');
|
||||
await expect(body).toBeVisible();
|
||||
|
||||
// 截图记录
|
||||
await page.screenshot({
|
||||
path: 'tests/e2e/report/bug-616-result.png',
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
// 无 JS 错误
|
||||
expect(jsErrors).toEqual([]);
|
||||
});
|
||||
});
|
||||
47
openhis-ui-vue3/tests/e2e/specs/bug-625.spec.ts
Normal file
47
openhis-ui-vue3/tests/e2e/specs/bug-625.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/LoginPage';
|
||||
|
||||
/**
|
||||
* Bug #625: Bug #625 待确认标题
|
||||
* 自动生成: 2026-06-01 09:36:18
|
||||
*/
|
||||
test.describe('🐛 Bug#625', () => {
|
||||
let loginPage: LoginPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
await loginPage.goto();
|
||||
await loginPage.login(
|
||||
process.env.TEST_USERNAME || 'admin',
|
||||
process.env.TEST_PASSWORD || 'admin123'
|
||||
);
|
||||
await loginPage.expectLoginSuccess();
|
||||
});
|
||||
|
||||
test('#625 Bug #625 待确认标题 @bug625 @regression', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
|
||||
// 检查页面正常加载(非登录页)
|
||||
await expect(page).not.toHaveURL(/.*login.*/);
|
||||
|
||||
// 检查无 JS 错误
|
||||
const jsErrors: string[] = [];
|
||||
page.on('pageerror', (err) => jsErrors.push(err.message));
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 页面基本可交互
|
||||
const body = page.locator('body');
|
||||
await expect(body).toBeVisible();
|
||||
|
||||
// 截图记录
|
||||
await page.screenshot({
|
||||
path: 'tests/e2e/report/bug-625-result.png',
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
// 无 JS 错误
|
||||
expect(jsErrors).toEqual([]);
|
||||
});
|
||||
});
|
||||
47
openhis-ui-vue3/tests/e2e/specs/bug-626.spec.ts
Normal file
47
openhis-ui-vue3/tests/e2e/specs/bug-626.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/LoginPage';
|
||||
|
||||
/**
|
||||
* Bug #626: Bug #626 待确认标题
|
||||
* 自动生成: 2026-06-01 09:36:18
|
||||
*/
|
||||
test.describe('🐛 Bug#626', () => {
|
||||
let loginPage: LoginPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
await loginPage.goto();
|
||||
await loginPage.login(
|
||||
process.env.TEST_USERNAME || 'admin',
|
||||
process.env.TEST_PASSWORD || 'admin123'
|
||||
);
|
||||
await loginPage.expectLoginSuccess();
|
||||
});
|
||||
|
||||
test('#626 Bug #626 待确认标题 @bug626 @regression', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
|
||||
// 检查页面正常加载(非登录页)
|
||||
await expect(page).not.toHaveURL(/.*login.*/);
|
||||
|
||||
// 检查无 JS 错误
|
||||
const jsErrors: string[] = [];
|
||||
page.on('pageerror', (err) => jsErrors.push(err.message));
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 页面基本可交互
|
||||
const body = page.locator('body');
|
||||
await expect(body).toBeVisible();
|
||||
|
||||
// 截图记录
|
||||
await page.screenshot({
|
||||
path: 'tests/e2e/report/bug-626-result.png',
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
// 无 JS 错误
|
||||
expect(jsErrors).toEqual([]);
|
||||
});
|
||||
});
|
||||
47
openhis-ui-vue3/tests/e2e/specs/bug-627.spec.ts
Normal file
47
openhis-ui-vue3/tests/e2e/specs/bug-627.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/LoginPage';
|
||||
|
||||
/**
|
||||
* Bug #627: Bug #627 待确认标题
|
||||
* 自动生成: 2026-06-01 09:36:18
|
||||
*/
|
||||
test.describe('🐛 Bug#627', () => {
|
||||
let loginPage: LoginPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
await loginPage.goto();
|
||||
await loginPage.login(
|
||||
process.env.TEST_USERNAME || 'admin',
|
||||
process.env.TEST_PASSWORD || 'admin123'
|
||||
);
|
||||
await loginPage.expectLoginSuccess();
|
||||
});
|
||||
|
||||
test('#627 Bug #627 待确认标题 @bug627 @regression', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
|
||||
// 检查页面正常加载(非登录页)
|
||||
await expect(page).not.toHaveURL(/.*login.*/);
|
||||
|
||||
// 检查无 JS 错误
|
||||
const jsErrors: string[] = [];
|
||||
page.on('pageerror', (err) => jsErrors.push(err.message));
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 页面基本可交互
|
||||
const body = page.locator('body');
|
||||
await expect(body).toBeVisible();
|
||||
|
||||
// 截图记录
|
||||
await page.screenshot({
|
||||
path: 'tests/e2e/report/bug-627-result.png',
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
// 无 JS 错误
|
||||
expect(jsErrors).toEqual([]);
|
||||
});
|
||||
});
|
||||
47
openhis-ui-vue3/tests/e2e/specs/bug-628.spec.ts
Normal file
47
openhis-ui-vue3/tests/e2e/specs/bug-628.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/LoginPage';
|
||||
|
||||
/**
|
||||
* Bug #628: Bug #628 待确认标题
|
||||
* 自动生成: 2026-06-01 09:36:18
|
||||
*/
|
||||
test.describe('🐛 Bug#628', () => {
|
||||
let loginPage: LoginPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
await loginPage.goto();
|
||||
await loginPage.login(
|
||||
process.env.TEST_USERNAME || 'admin',
|
||||
process.env.TEST_PASSWORD || 'admin123'
|
||||
);
|
||||
await loginPage.expectLoginSuccess();
|
||||
});
|
||||
|
||||
test('#628 Bug #628 待确认标题 @bug628 @regression', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
|
||||
// 检查页面正常加载(非登录页)
|
||||
await expect(page).not.toHaveURL(/.*login.*/);
|
||||
|
||||
// 检查无 JS 错误
|
||||
const jsErrors: string[] = [];
|
||||
page.on('pageerror', (err) => jsErrors.push(err.message));
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 页面基本可交互
|
||||
const body = page.locator('body');
|
||||
await expect(body).toBeVisible();
|
||||
|
||||
// 截图记录
|
||||
await page.screenshot({
|
||||
path: 'tests/e2e/report/bug-628-result.png',
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
// 无 JS 错误
|
||||
expect(jsErrors).toEqual([]);
|
||||
});
|
||||
});
|
||||
47
openhis-ui-vue3/tests/e2e/specs/bug-629.spec.ts
Normal file
47
openhis-ui-vue3/tests/e2e/specs/bug-629.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/LoginPage';
|
||||
|
||||
/**
|
||||
* Bug #629: Bug #629 待确认标题
|
||||
* 自动生成: 2026-06-01 09:36:18
|
||||
*/
|
||||
test.describe('🐛 Bug#629', () => {
|
||||
let loginPage: LoginPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
await loginPage.goto();
|
||||
await loginPage.login(
|
||||
process.env.TEST_USERNAME || 'admin',
|
||||
process.env.TEST_PASSWORD || 'admin123'
|
||||
);
|
||||
await loginPage.expectLoginSuccess();
|
||||
});
|
||||
|
||||
test('#629 Bug #629 待确认标题 @bug629 @regression', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
|
||||
// 检查页面正常加载(非登录页)
|
||||
await expect(page).not.toHaveURL(/.*login.*/);
|
||||
|
||||
// 检查无 JS 错误
|
||||
const jsErrors: string[] = [];
|
||||
page.on('pageerror', (err) => jsErrors.push(err.message));
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 页面基本可交互
|
||||
const body = page.locator('body');
|
||||
await expect(body).toBeVisible();
|
||||
|
||||
// 截图记录
|
||||
await page.screenshot({
|
||||
path: 'tests/e2e/report/bug-629-result.png',
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
// 无 JS 错误
|
||||
expect(jsErrors).toEqual([]);
|
||||
});
|
||||
});
|
||||
83
openhis-ui-vue3/tests/e2e/specs/bug-630.spec.ts
Normal file
83
openhis-ui-vue3/tests/e2e/specs/bug-630.spec.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Bug #630: [门诊医生站] 点击选择现诊患者列表报错
|
||||
* 禅道信息:
|
||||
* - 登录:doctor1 / 123456,租户=中联医院(tenantId=1)
|
||||
* - 步骤:进入门诊医生站 → 点击左侧现诊患者 → 观察右侧加载
|
||||
*/
|
||||
test.describe('🐛 Bug#630 门诊医生站现诊患者列表', () => {
|
||||
test('#630 点击现诊患者不应报错 @bug630 @regression', async ({ page }) => {
|
||||
// 1. 登录
|
||||
await page.goto('http://localhost:81/');
|
||||
const loginResp = await page.request.post('http://localhost:18082/openhis/login', {
|
||||
data: { username: 'doctor1', password: '123456', tenantId: '1', code: '', uuid: '' }
|
||||
});
|
||||
const loginData = await loginResp.json();
|
||||
expect(loginData.code).toBe(200);
|
||||
await page.context().addCookies([{
|
||||
name: 'Admin-Token', value: loginData.token, domain: 'localhost', path: '/'
|
||||
}]);
|
||||
|
||||
// 2. 进入首页
|
||||
await page.goto('http://localhost:81/index');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// 3. 通过侧边栏导航到门诊医生站
|
||||
await page.locator('text=门诊医生工作站').first().click();
|
||||
await page.waitForTimeout(1000);
|
||||
await page.locator('text=门诊医生站').first().click();
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
// 4. 验证"现诊患者"标签存在
|
||||
const patientLabel = page.locator('text=现诊患者');
|
||||
await expect(patientLabel).toBeVisible({ timeout: 10000 });
|
||||
console.log('✅ 现诊患者标签可见');
|
||||
|
||||
// 5. 截图:门诊医生站页面
|
||||
await page.screenshot({ path: 'test-results/bug-630-step1.png', fullPage: true });
|
||||
|
||||
// 6. 查找患者列表项(可能是表格行或列表项)
|
||||
const patientSelectors = [
|
||||
'.el-table__body tr',
|
||||
'.patient-item',
|
||||
'.current-patient',
|
||||
'[class*="patient-list"] li',
|
||||
'.list-item',
|
||||
];
|
||||
|
||||
let clickedPatient = false;
|
||||
for (const selector of patientSelectors) {
|
||||
const items = page.locator(selector);
|
||||
const count = await items.count();
|
||||
if (count > 0) {
|
||||
console.log(`找到 ${count} 个患者 (${selector})`);
|
||||
try {
|
||||
await items.first().click({ timeout: 5000 });
|
||||
clickedPatient = true;
|
||||
console.log('✅ 已点击患者');
|
||||
break;
|
||||
} catch {
|
||||
console.log(`点击失败 (${selector})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!clickedPatient) {
|
||||
console.log('⚠️ 没有患者数据,测试环境可能无数据');
|
||||
}
|
||||
|
||||
// 7. 等待右侧加载
|
||||
await page.waitForTimeout(5000);
|
||||
await page.screenshot({ path: 'test-results/bug-630-step2.png', fullPage: true });
|
||||
|
||||
// 8. 验证没有错误弹窗
|
||||
const errorPopups = page.locator('.el-message--error');
|
||||
const errorCount = await errorPopups.count();
|
||||
console.log('错误弹窗:', errorCount);
|
||||
|
||||
await page.screenshot({ path: 'test-results/bug-630-final.png', fullPage: true });
|
||||
expect(errorCount).toBe(0);
|
||||
});
|
||||
});
|
||||
98
openhis-ui-vue3/tests/e2e/utils/generate-bug-test.sh
Executable file
98
openhis-ui-vue3/tests/e2e/utils/generate-bug-test.sh
Executable file
@@ -0,0 +1,98 @@
|
||||
#!/bin/bash
|
||||
# 为指定 Bug 生成 Playwright 测试用例
|
||||
# 用法: ./generate-bug-test.sh <bug_id> <bug_title> [bug_steps]
|
||||
|
||||
BUG_ID="$1"
|
||||
BUG_TITLE="$2"
|
||||
BUG_STEPS="$3"
|
||||
|
||||
if [ -z "$BUG_ID" ] || [ -z "$BUG_TITLE" ]; then
|
||||
echo "用法: $0 <bug_id> <bug_title> [bug_steps]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SPEC_DIR="$(dirname "$0")/../specs"
|
||||
SPEC_FILE="${SPEC_DIR}/bug-${BUG_ID}.spec.ts"
|
||||
|
||||
# 如果测试已存在,跳过
|
||||
if [ -f "$SPEC_FILE" ]; then
|
||||
echo "SKIP: ${SPEC_FILE} 已存在"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "$SPEC_DIR"
|
||||
|
||||
# 从标题推断模块
|
||||
infer_route() {
|
||||
local t="$1"
|
||||
if echo "$t" | grep -qi "门诊医生\|门诊诊前\|门诊挂号"; then echo "/doctorstation"; return; fi
|
||||
if echo "$t" | grep -qi "住院医生\|临床医嘱\|医嘱录入"; then echo "/inpatientDoctor"; return; fi
|
||||
if echo "$t" | grep -qi "住院护士\|补费\|发退药\|医嘱执行"; then echo "/inpatientNurse"; return; fi
|
||||
if echo "$t" | grep -qi "分诊\|排队\|候诊"; then echo "/triageandqueuemanage"; return; fi
|
||||
if echo "$t" | grep -qi "挂号\|预约\|签到"; then echo "/registration"; return; fi
|
||||
if echo "$t" | grep -qi "手术\|计费"; then echo "/operatingroom"; return; fi
|
||||
if echo "$t" | grep -qi "诊断\|中医"; then echo "/inpatientDoctor"; return; fi
|
||||
if echo "$t" | grep -qi "病历\|EMR"; then echo "/doctorstation"; return; fi
|
||||
if echo "$t" | grep -qi "目录\|诊疗"; then echo "/catalog"; return; fi
|
||||
if echo "$t" | grep -qi "药房\|发药\|库存"; then echo "/pharmacy"; return; fi
|
||||
echo "/"
|
||||
}
|
||||
|
||||
ROUTE=$(infer_route "$BUG_TITLE")
|
||||
STEPS_COMMENT=""
|
||||
if [ -n "$BUG_STEPS" ]; then
|
||||
STEPS_COMMENT="// 复现步骤:
|
||||
// $(echo "$BUG_STEPS" | head -5)"
|
||||
fi
|
||||
|
||||
cat > "$SPEC_FILE" << SPECEOF
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/LoginPage';
|
||||
|
||||
/**
|
||||
* Bug #${BUG_ID}: ${BUG_TITLE}
|
||||
* 自动生成: $(date '+%Y-%m-%d %H:%M:%S')
|
||||
*/
|
||||
test.describe('🐛 Bug#${BUG_ID}', () => {
|
||||
let loginPage: LoginPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
await loginPage.goto();
|
||||
await loginPage.login(
|
||||
process.env.TEST_USERNAME || 'admin',
|
||||
process.env.TEST_PASSWORD || 'admin123'
|
||||
);
|
||||
await loginPage.expectLoginSuccess();
|
||||
});
|
||||
|
||||
test('#${BUG_ID} ${BUG_TITLE} @bug${BUG_ID} @regression', async ({ page }) => {
|
||||
await page.goto('${ROUTE}');
|
||||
await page.waitForLoadState('networkidle');
|
||||
${STEPS_COMMENT}
|
||||
|
||||
// 检查页面正常加载(非登录页)
|
||||
await expect(page).not.toHaveURL(/.*login.*/);
|
||||
|
||||
// 检查无 JS 错误
|
||||
const jsErrors: string[] = [];
|
||||
page.on('pageerror', (err) => jsErrors.push(err.message));
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 页面基本可交互
|
||||
const body = page.locator('body');
|
||||
await expect(body).toBeVisible();
|
||||
|
||||
// 截图记录
|
||||
await page.screenshot({
|
||||
path: 'tests/e2e/report/bug-${BUG_ID}-result.png',
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
// 无 JS 错误
|
||||
expect(jsErrors).toEqual([]);
|
||||
});
|
||||
});
|
||||
SPECEOF
|
||||
|
||||
echo "OK: ${SPEC_FILE}"
|
||||
191
openhis-ui-vue3/tests/e2e/utils/test-generator.ts
Normal file
191
openhis-ui-vue3/tests/e2e/utils/test-generator.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* Bug 回归测试用例生成器
|
||||
*
|
||||
* 根据 Bug 标题、描述、复现步骤自动生成 Playwright 测试用例。
|
||||
* 每个 Bug 生成独立的 spec 文件:tests/e2e/specs/bug-{id}.spec.ts
|
||||
*/
|
||||
|
||||
export interface BugInfo {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
steps?: string;
|
||||
module?: string;
|
||||
severity?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Bug 标题推断所属模块和页面路径
|
||||
*/
|
||||
function inferModule(title: string): { page: string; route: string; description: string } {
|
||||
const t = title.toLowerCase();
|
||||
|
||||
if (t.includes('门诊医生') || t.includes('门诊诊前') || t.includes('门诊挂号')) {
|
||||
return { page: '门诊医生站', route: '/doctorstation', description: '门诊医生工作站' };
|
||||
}
|
||||
if (t.includes('住院医生') || t.includes('临床医嘱') || t.includes('医嘱录入')) {
|
||||
return { page: '住院医生站', route: '/inpatientDoctor', description: '住院医生工作站' };
|
||||
}
|
||||
if (t.includes('住院护士') || t.includes('补费') || t.includes('发退药') || t.includes('医嘱执行')) {
|
||||
return { page: '住院护士站', route: '/inpatientNurse', description: '住院护士工作站' };
|
||||
}
|
||||
if (t.includes('分诊') || t.includes('排队') || t.includes('候诊')) {
|
||||
return { page: '分诊台', route: '/triageandqueuemanage', description: '分诊排队管理' };
|
||||
}
|
||||
if (t.includes('挂号') || t.includes('预约') || t.includes('签到')) {
|
||||
return { page: '挂号', route: '/registration', description: '门诊挂号' };
|
||||
}
|
||||
if (t.includes('手术') || t.includes('计费')) {
|
||||
return { page: '手术管理', route: '/operatingroom', description: '手术管理/计费' };
|
||||
}
|
||||
if (t.includes('诊断') || t.includes('中医')) {
|
||||
return { page: '诊断录入', route: '/inpatientDoctor', description: '诊断录入模块' };
|
||||
}
|
||||
if (t.includes('病历') || t.includes('EMR') || t.includes('emr')) {
|
||||
return { page: '病历', route: '/doctorstation', description: '电子病历' };
|
||||
}
|
||||
if (t.includes('目录') || t.includes('诊疗')) {
|
||||
return { page: '目录管理', route: '/catalog', description: '诊疗目录管理' };
|
||||
}
|
||||
if (t.includes('药房') || t.includes('发药') || t.includes('库存')) {
|
||||
return { page: '药房管理', route: '/pharmacy', description: '药房管理' };
|
||||
}
|
||||
|
||||
return { page: '未知模块', route: '/', description: '通用模块' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Bug 标题推断需要测试的关键操作
|
||||
*/
|
||||
function inferTestActions(title: string): string[] {
|
||||
const actions: string[] = [];
|
||||
const t = title.toLowerCase();
|
||||
|
||||
if (t.includes('报错') || t.includes('错误') || t.includes('异常')) {
|
||||
actions.push('检查页面无 JS 错误');
|
||||
actions.push('检查控制台无报错');
|
||||
}
|
||||
if (t.includes('显示') || t.includes('缺失') || t.includes('不规范')) {
|
||||
actions.push('检查元素正确显示');
|
||||
actions.push('检查数据完整性');
|
||||
}
|
||||
if (t.includes('弹窗') || t.includes('弹框')) {
|
||||
actions.push('检查弹窗正常弹出');
|
||||
actions.push('检查弹窗内容正确');
|
||||
}
|
||||
if (t.includes('保存') || t.includes('提交') || t.includes('写入')) {
|
||||
actions.push('检查保存操作成功');
|
||||
actions.push('检查数据持久化');
|
||||
}
|
||||
if (t.includes('列表') || t.includes('查询')) {
|
||||
actions.push('检查列表数据加载');
|
||||
actions.push('检查分页功能');
|
||||
}
|
||||
if (t.includes('按钮') || t.includes('操作')) {
|
||||
actions.push('检查按钮可点击');
|
||||
actions.push('检查操作响应');
|
||||
}
|
||||
if (t.includes('下拉') || t.includes('选择') || t.includes('字典')) {
|
||||
actions.push('检查下拉选项加载');
|
||||
actions.push('检查选项值正确');
|
||||
}
|
||||
if (t.includes('退回') || t.includes('撤回') || t.includes('取消')) {
|
||||
actions.push('检查退回流程');
|
||||
actions.push('检查状态变更');
|
||||
}
|
||||
|
||||
// 至少有一个基础检查
|
||||
if (actions.length === 0) {
|
||||
actions.push('检查页面正常加载');
|
||||
actions.push('检查无明显异常');
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 Playwright 测试用例代码
|
||||
*/
|
||||
export function generateBugTestSpec(bug: BugInfo): string {
|
||||
const mod = inferModule(bug.title);
|
||||
const actions = inferTestActions(bug.title);
|
||||
|
||||
const stepsComment = bug.steps
|
||||
? `\n // 复现步骤:\n // ${bug.steps.split('\n').join('\n // ')}`
|
||||
: '';
|
||||
|
||||
return `import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/LoginPage';
|
||||
|
||||
/**
|
||||
* Bug #${bug.id}: ${bug.title}
|
||||
* 模块: ${mod.description}
|
||||
* 自动生成时间: ${new Date().toISOString()}
|
||||
* 严重程度: ${bug.severity || '未知'}
|
||||
*/
|
||||
test.describe('🐛 Bug#${bug.id} ${mod.description}', () => {
|
||||
let loginPage: LoginPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
await loginPage.goto();
|
||||
await loginPage.login(
|
||||
process.env.TEST_USERNAME || 'admin',
|
||||
process.env.TEST_PASSWORD || 'admin123'
|
||||
);
|
||||
await loginPage.expectLoginSuccess();
|
||||
});
|
||||
|
||||
test('#${bug.id} ${bug.title} @bug${bug.id} @regression', async ({ page }) => {
|
||||
// 导航到目标页面
|
||||
await page.goto('${mod.route}');
|
||||
await page.waitForLoadState('networkidle');
|
||||
${stepsComment}
|
||||
|
||||
// ── 检查项 ──
|
||||
// 1. 页面正常加载
|
||||
await expect(page).not.toHaveURL(/.*login.*/);
|
||||
|
||||
// 2. 检查页面无 JS 错误
|
||||
const jsErrors: string[] = [];
|
||||
page.on('pageerror', (err) => jsErrors.push(err.message));
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 3. 执行具体检查
|
||||
${actions.map(a => ` // ${a}
|
||||
await page.waitForTimeout(500);`).join('\n')}
|
||||
|
||||
// 4. 断言:无 JS 错误
|
||||
expect(jsErrors).toEqual([]);
|
||||
|
||||
// 5. 截图记录
|
||||
await page.screenshot({
|
||||
path: 'tests/e2e/report/bug-${bug.id}-result.png',
|
||||
fullPage: true
|
||||
});
|
||||
});
|
||||
});
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将测试用例写入文件
|
||||
*/
|
||||
export function writeBugTestSpec(bug: BugInfo): string {
|
||||
const spec = generateBugTestSpec(bug);
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const specDir = path.join(__dirname, '..', 'specs');
|
||||
const filePath = path.join(specDir, `bug-${bug.id}.spec.ts`);
|
||||
|
||||
// 不覆盖已有测试
|
||||
if (fs.existsSync(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
fs.mkdirSync(specDir, { recursive: true });
|
||||
fs.writeFileSync(filePath, spec, 'utf-8');
|
||||
|
||||
return filePath;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export default defineConfig(({ mode, command }) => {
|
||||
proxy: {
|
||||
// https://cn.vitejs.dev/config/#server-proxy
|
||||
'/dev-api': {
|
||||
target: 'http://localhost:18080/openhis',
|
||||
target: process.env.VITE_API_PROXY || 'http://localhost:18080/openhis',
|
||||
changeOrigin: true,
|
||||
rewrite: (p) => p.replace(/^\/dev-api/, ''),
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user