Merge branch 'develop' of https://gitea.gentronhealth.com/wangyizhe/his into develop

This commit is contained in:
sindir
2026-01-19 15:47:38 +08:00
12 changed files with 880 additions and 436 deletions

View File

@@ -0,0 +1,267 @@
## 门诊会诊申请管理界面PRD文档
### 一、页面概述
**页面名称**:门诊会诊申请管理界面
**页面目标**:提供会诊申请的全流程管理功能,包括申请记录查询、编辑申请、查看详情、状态变更等核心操作
**适用场景**:门诊医生需要查看会诊申请或管理已有申请记录时使用
**页面类型**:列表页+表单弹窗复合型页面
**核心功能**
1. 多条件组合筛选会诊申请记录
2. 会诊申请表格展示与操作(编辑/查看/删除)
3. 会诊申请单的填写与提交
4. 会诊状态标记(提交/结束)
**用户价值**:规范会诊申请流程,减少纸质单据流转,提高多科室协作效率
原型图地址https://static.pm-ai.cn/prototype/20260116/aed1f102d614677f100c0d1fe3104999/index.html
**流程图:**
```mermaid
flowchart TD
Start([Start]) --> A[进入门诊会诊申请管理界面]
A --> B{用户操作类型}
B -->|筛选查询| C[设置筛选条件]
B -->|编辑申请| D[点击编辑按钮]
B -->|查看详情| E[点击查看按钮]
B -->|删除申请| G[点击删除按钮]
C --> H{验证筛选条件}
H -->|有效| I[展示筛选结果]
H -->|无效| J[显示错误提示]
J --> C
I --> K[用户浏览列表]
D --> L{检查会诊状态}
L -->|未结束| M[打开编辑弹窗]
L -->|已结束| N[提示不可编辑]
N --> O[关闭弹窗]
M --> P[修改表单内容]
P --> Q{表单验证}
Q -->|通过| R[保存修改]
Q -->|不通过| S[标红错误字段]
S --> P
E --> T{检查会诊状态}
T -->|未结束| U[打开只读弹窗]
T -->|已结束| U
G --> Z{删除验证}
Z -->|可删除| AA[确认删除]
Z -->|不可删除| AB[提示删除失败]
AB --> K
AA --> AC[更新状态为已取消]
AC --> AD[更新列表显示]
R --> AD
AD --> K
K --> AE([End])
```
### 二、整体布局分析
**页面宽度**:自适应布局
**主要区域划分**
1. 顶部筛选区高度自适应约80px
2. 表格展示区(高度自适应,占主要空间)
3. 底部页码区固定高度56px
**布局特点**:上下布局+弹性布局,采用左右对齐方式
### 三、页面区域详细描述
#### 1. 顶部筛选区
**区域位置**:页面顶部
**区域尺寸**100%宽度,高度自适应
**区域功能**:提供多维度筛选和快速搜索功能
**包含元素**
- **时间类型选择器**
- 元素类型:下拉选择框
- 显示内容:默认"会诊时间",可选"申请时间"
- 交互行为:点击展开下拉选项
- 样式特征宽度120px高度32px圆角4px
- **时间范围选择器**(开始/结束时间)
- 元素类型:日期时间输入框
- 显示内容placeholder提示"开始时间"/“结束时间”
- 交互行为:点击弹出日期选择面板
- 样式特征宽度180px高度32px
- **申请科室/申请医生选择器**
- 元素类型带datalist的输入框
- 显示内容placeholder提示"选择或输入科室/医生"
- 交互行为:输入时显示匹配选项
- 数据来源:动态生成申请科室/医生候选列表取值于门诊会诊申请单表ConsultationRequest.Department/ RequestingPhysician
- **会诊状态筛选器**
- 元素类型:下拉选择框
- 可选值:全部/未提交/提交/结束
- 默认值:全部
- **病人姓名搜索框**
- 元素类型:文本输入框
- 显示内容placeholder提示"病人姓名"
- 交互行为:支持模糊搜索
- **操作按钮组**
- 查询按钮
- 样式:蓝色背景,带搜索图标
- 交互:触发筛选条件应用
- 重置按钮
- 样式:灰色背景,带刷新图标
- 交互:清空所有筛选条件
- 打印按钮
- 样式:深灰色背景,带打印图标
- 交互:调起浏览器打印功能
#### 2. 表格展示区
**区域位置**:页面中部
**区域尺寸**100%宽度,高度自适应
**区域功能**:展示会诊申请列表数据,支持行内操作
**包含元素**
取值于门诊会诊申请单表ConsultationRequest和门诊会诊申请单表ConsultationRequest)
- **数据表格**
- 展示方式11列固定表头表格
- 数据字段:
- ID文本 - 15 -申请单号
- 急:复选框 - 布尔值 - 示例false 不可编辑
- 病人姓名:文本 - 朱某某 - 红色高亮
- 会诊时间:日期 - 2026-01-05 15:08
- 申请科室:文本 - 内科
- 邀请对象:文本 - 吴院长
- 申请时间:日期 - 2026-01-05 15:08
- 申请医师:文本 - 演示测试
- 提交:复选框 - 布尔值 - 示例false
- 结束:复选框 - 布尔值 - 示例false
- 操作功能:
- 编辑按钮(✏️):点击打开编辑弹窗
- 查看按钮(👁️):点击打开只读弹窗
- 删除按钮(🗑️):点击确认删除
- 【删除】将状态改为“已取消”
- UPDATE ConsultationRequest
- SET ConsultationStatus = 50,cancelnatureDate = \<作废会诊时间\>
- WHERE ConsultationID = \<会诊申请单ID\> and ConsultationStatus \<\> 40 ;
- 交互行为:
- 行悬停效果:浅蓝色背景
- 复选框点击:即时更新状态(需防抖处理)
#### 3. 底部页码区
**区域位置**:页面底部
**区域尺寸**100%宽度固定高度56px
**区域功能**:分页控制和数据统计
**包含元素**
- **总数统计**总数统计文本“总数15”
- **分页控制器**
- 上一页按钮(\<
- 当前页按钮1active状态
- 下一页按钮(\>
- 交互反馈hover时边框变蓝
#### 4. 会诊申请弹窗(模态框)
**触发方式**:点击表格行操作列的编辑/查看按钮
**区域功能**:展示/编辑会诊申请详细信息
**包含元素**
- 头部区域
- 标题:“会诊申请单”
- 关闭按钮(×图标)
- 表单区域(分两栏布局)
- 基础信息区:
- 申请单号(只读)
- 申请时间(不可编辑)
- 病人姓名(不可编辑)
- 性别/年龄(不可编辑)
- 就诊卡号(不可编辑)
- 会诊信息区:
- 会诊时间(日期时间选择器)
- 申请医师(不可编辑)
- 紧急程度(复选框)
- 申请科室(不可编辑)
- 门诊诊断(不可编辑)
- 会诊邀请对象
- 会诊确认参加医师
- 所属医生
- 代表科室
- 签名医生
- 签名时间
- 文本域:
- 病史及会诊目的(多行文本)
- 会诊意见(多行文本)
- 底部按钮区:
- 取消按钮(左对齐)
- 保存按钮(右对齐,蓝色)
### 四、交互功能详细说明
#### 1. 会诊申请编辑功能
**功能描述**:修改已有会诊申请信息
**触发条件**:点击表格行中的"✏️"按钮
**操作流程**
1. 检查会诊状态是否为"结束",若已结束则提示不可编辑
2. 弹出会诊申请编辑弹窗,填充当前行数据的会诊申请和确认相关的数据
3. 用户修改表单内容(必填字段校验)
4. 点击"保存"按钮提交修改
**异常处理**
- 必填字段为空时,标红提示
- 保存失败时显示toast提示"保存失败,请重试"
#### 2. 会诊申请查看功能
**功能描述**:查看会诊申请详细信息
**触发条件**:点击表格行中的"👁️"按钮
**操作流程**
1. 弹出只读弹窗,显示完整申请信息
2. 所有字段禁用编辑
3. 仅显示"取消"按钮用于关闭弹窗
#### 3. 数据筛选功能
**功能描述**:多条件组合查询会诊记录
**触发条件**:点击"查询"按钮
**数据过滤逻辑**
- 时间范围:根据选择的时间类型(会诊/申请)进行筛选
- 申请科室/申请医生:支持模糊匹配
- 会诊状态筛选:支持多选逻辑(未提交/提交/结束)
1. 收集所有筛选条件值
2. 发起异步请求(示例中为前端过滤)
3. 更新表格数据展示
**异常处理**
- 时间范围不合法:提示"结束时间不能早于开始时间"
- 无查询结果:显示空白表格+提示文字
**性能优化**:前端本地缓存数据,减少服务器请求
### 五、数据结构说明
门诊会诊申请单表ConsultationRequest和门诊会诊申请单表ConsultationRequest)
### 六、开发实现要点
**样式规范**
- **主色调**\#5D9CEC按钮/交互元素)
- **辅助色**\#8E8E8E次要按钮
- **字体规范**14px/1.5主要内容16px/1.5(标题)
- **间距系统**16px元素间距24px区块间距
- **组件样式**
- 按钮圆角6px内边距0 16px
- 输入框1px实线边框\#D9D9D9圆角4px
**技术要求**
- **浏览器兼容**支持Chrome/Firefox/Edge最新版
- **性能要求**:列表数据筛选响应时间\<200ms
**注意事项**
1. 状态变更逻辑:已结束的记录不可编辑
2. 时间字段需要做时区转换处理
3. 申请科室/申请医生选择器需要支持拼音首字母检索

View File

@@ -1,2 +0,0 @@
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">OpenHis v0.0.1</h1>

View File

@@ -1,12 +0,0 @@
-- 为诊疗定义表添加序号和服务范围字段
-- 执行前请先备份数据库
ALTER TABLE wor_activity_definition ADD COLUMN IF NOT EXISTS sort_order INTEGER DEFAULT NULL;
ALTER TABLE wor_activity_definition ADD COLUMN IF NOT EXISTS service_range VARCHAR(50) DEFAULT '全部';
-- 添加注释
COMMENT ON COLUMN wor_activity_definition.sort_order IS '序号';
COMMENT ON COLUMN wor_activity_definition.service_range IS '服务范围';
-- 为现有数据设置默认值
UPDATE wor_activity_definition SET service_range = '全部' WHERE service_range IS NULL;

View File

@@ -54,6 +54,12 @@
<artifactId>oshi-core</artifactId> <artifactId>oshi-core</artifactId>
</dependency> </dependency>
<!-- spring security 安全认证 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 系统模块--> <!-- 系统模块-->
<dependency> <dependency>
<groupId>com.core</groupId> <groupId>com.core</groupId>
@@ -65,6 +71,12 @@
<artifactId>core-common</artifactId> <artifactId>core-common</artifactId>
</dependency> </dependency>
<!-- MyBatis-Plus 支持 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- JSQLParser - 用于MyBatis Plus --> <!-- JSQLParser - 用于MyBatis Plus -->
<dependency> <dependency>
<groupId>com.github.jsqlparser</groupId> <groupId>com.github.jsqlparser</groupId>

View File

@@ -18,6 +18,12 @@
<dependencies> <dependencies>
<!-- MyBatis-Plus 支持 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- velocity代码生成使用模板 --> <!-- velocity代码生成使用模板 -->
<dependency> <dependency>
<groupId>org.apache.velocity</groupId> <groupId>org.apache.velocity</groupId>
@@ -36,6 +42,24 @@
<artifactId>druid-spring-boot-starter</artifactId> <artifactId>druid-spring-boot-starter</artifactId>
</dependency> </dependency>
<!-- Lombok 支持 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- JSON工具类 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Jackson 注解支持 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -23,6 +23,12 @@
<dependencies> <dependencies>
<!-- MyBatis-Plus 支持 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- 通用工具--> <!-- 通用工具-->
<dependency> <dependency>
<groupId>com.core</groupId> <groupId>com.core</groupId>

View File

@@ -38,6 +38,7 @@ import com.openhis.workflow.service.IDeviceDispenseService;
import com.openhis.workflow.service.IDeviceRequestService; import com.openhis.workflow.service.IDeviceRequestService;
import com.openhis.workflow.service.IServiceRequestService; import com.openhis.workflow.service.IServiceRequestService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
@@ -92,6 +93,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
@Resource @Resource
DoctorStationSendApplyUtil doctorStationSendApplyUtil; DoctorStationSendApplyUtil doctorStationSendApplyUtil;
/** /**
* 查询医嘱信息 * 查询医嘱信息
* *
@@ -111,6 +113,20 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
public IPage<AdviceBaseDto> getAdviceBaseInfo(AdviceBaseDto adviceBaseDto, String searchKey, Long locationId, public IPage<AdviceBaseDto> getAdviceBaseInfo(AdviceBaseDto adviceBaseDto, String searchKey, Long locationId,
List<Long> adviceDefinitionIdParamList, Long organizationId, Integer pageNo, Integer pageSize, List<Long> adviceDefinitionIdParamList, Long organizationId, Integer pageNo, Integer pageSize,
Integer pricingFlag, List<Integer> adviceTypes, String orderPricing) { Integer pricingFlag, List<Integer> adviceTypes, String orderPricing) {
// 生成缓存键处理可能的null值
String safeSearchKey = searchKey != null ? searchKey : "";
String safeAdviceTypesStr = "";
if (adviceTypes != null && !adviceTypes.isEmpty()) {
safeAdviceTypesStr = String.join(",", adviceTypes.stream().map(String::valueOf).collect(Collectors.toList()));
}
String safeOrganizationId = organizationId != null ? organizationId.toString() : "";
String safePricingFlag = pricingFlag != null ? pricingFlag.toString() : "";
String safePageNo = pageNo != null ? pageNo.toString() : "";
String safePageSize = pageSize != null ? pageSize.toString() : "";
log.info("从数据库查询医嘱基础信息");
// 设置默认科室 (不取前端传的了) // 设置默认科室 (不取前端传的了)
organizationId = SecurityUtils.getLoginUser().getOrgId(); organizationId = SecurityUtils.getLoginUser().getOrgId();
@@ -172,98 +188,190 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
.add(cfg.getLocationId()); .add(cfg.getLocationId());
} }
} }
// 费用定价子表信息 // 费用定价子表信息 - 使用分批处理避免大量参数问题
List<AdvicePriceDto> childCharge = doctorStationAdviceAppMapper List<AdvicePriceDto> childCharge = new ArrayList<>();
.getChildCharge(ConditionCode.LOT_NUMBER_PRICE.getCode(), chargeItemDefinitionIdList); if (chargeItemDefinitionIdList != null && !chargeItemDefinitionIdList.isEmpty()) {
// 费用定价主表信息 // 分批处理每批最多1000个ID增加批次大小以减少查询次数
List<AdvicePriceDto> mainCharge int batchSize = 1000;
= doctorStationAdviceAppMapper.getMainCharge(chargeItemDefinitionIdList, PublicationStatus.ACTIVE.getValue()); for (int i = 0; i < chargeItemDefinitionIdList.size(); i += batchSize) {
int endIndex = Math.min(i + batchSize, chargeItemDefinitionIdList.size());
List<Long> batch = chargeItemDefinitionIdList.subList(i, endIndex);
childCharge.addAll(doctorStationAdviceAppMapper
.getChildCharge(ConditionCode.LOT_NUMBER_PRICE.getCode(), batch));
}
}
// 费用定价主表信息 - 使用分批处理避免大量参数问题
List<AdvicePriceDto> mainCharge = new ArrayList<>();
if (chargeItemDefinitionIdList != null && !chargeItemDefinitionIdList.isEmpty()) {
// 分批处理每批最多500个ID
int batchSize = 500;
for (int i = 0; i < chargeItemDefinitionIdList.size(); i += batchSize) {
int endIndex = Math.min(i + batchSize, chargeItemDefinitionIdList.size());
List<Long> batch = chargeItemDefinitionIdList.subList(i, endIndex);
mainCharge.addAll(doctorStationAdviceAppMapper.getMainCharge(batch, PublicationStatus.ACTIVE.getValue()));
}
}
String unitCode = ""; // 包装单位 String unitCode = ""; // 包装单位
Long chargeItemDefinitionId; // 费用定价主表ID Long chargeItemDefinitionId; // 费用定价主表ID
for (AdviceBaseDto baseDto : adviceBaseDtoList) { for (AdviceBaseDto baseDto : adviceBaseDtoList) {
switch (baseDto.getAdviceTableName()) { String tableName = baseDto.getAdviceTableName();
case CommonConstants.TableName.MED_MEDICATION_DEFINITION: // 药品 if (CommonConstants.TableName.MED_MEDICATION_DEFINITION.equals(tableName)) { // 药品
// 是否皮试 // 是否皮试
baseDto baseDto
.setSkinTestFlag_enumText(EnumUtils.getInfoByValue(Whether.class, baseDto.getSkinTestFlag())); .setSkinTestFlag_enumText(EnumUtils.getInfoByValue(Whether.class, baseDto.getSkinTestFlag()));
// 是否为注射药物 // 是否为注射药物
baseDto.setInjectFlag_enumText(EnumUtils.getInfoByValue(Whether.class, baseDto.getInjectFlag())); baseDto.setInjectFlag_enumText(EnumUtils.getInfoByValue(Whether.class, baseDto.getInjectFlag()));
case CommonConstants.TableName.ADM_DEVICE_DEFINITION: // 耗材
// 每一条医嘱的库存集合信息 , 包装单位库存前端计算
List<AdviceInventoryDto> inventoryList = adviceInventory.stream().filter(e -> baseDto
.getAdviceDefinitionId().equals(e.getItemId())
&& baseDto.getAdviceTableName().equals(e.getItemTable())
&& (pharmacyMultipleChoice
|| (baseDto.getPositionId() == null || baseDto.getPositionId().equals(e.getLocationId()))))
.collect(Collectors.toList());
// 库存信息
baseDto.setInventoryList(inventoryList);
// 设置默认产品批号
if (!inventoryList.isEmpty()) {
// 库存大于0
List<AdviceInventoryDto> hasInventoryList = inventoryList.stream()
.filter(e -> e.getQuantity().compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList());
if (!hasInventoryList.isEmpty()) {
baseDto.setDefaultLotNumber(hasInventoryList.get(0).getLotNumber());
}
}
if (!inventoryList.isEmpty() && !medLocationConfig.isEmpty()) {
// 第一步在medLocationConfig中匹配categoryCode
AdviceInventoryDto result1 = medLocationConfig.stream()
.filter(dto -> baseDto.getCategoryCode().equals(dto.getCategoryCode())).findFirst()
.orElse(null);
if (result1 != null) {
// 第二步在inventoryList中匹配locationId
AdviceInventoryDto result2 = inventoryList.stream()
.filter(dto -> result1.getLocationId().equals(dto.getLocationId())).findFirst()
.orElse(null);
if (result2 != null && result2.getLotNumber() != null) {
baseDto.setDefaultLotNumber(result2.getLotNumber());
}
}
}
unitCode = baseDto.getUnitCode(); // fallthrough to 耗材处理逻辑(保持原有逻辑)
chargeItemDefinitionId = baseDto.getChargeItemDefinitionId(); // 每一条医嘱的库存集合信息 , 包装单位库存前端计算
List<AdvicePriceDto> priceDtoList = new ArrayList<>(); List<AdviceInventoryDto> inventoryList = adviceInventory.stream().filter(e ->
// 库存信息里取 命中条件 去匹配价格 baseDto.getAdviceDefinitionId() != null && e.getItemId() != null
for (AdviceInventoryDto adviceInventoryDto : inventoryList) { && baseDto.getAdviceDefinitionId().equals(e.getItemId())
Long finalChargeItemDefinitionId = chargeItemDefinitionId; && baseDto.getAdviceTableName() != null && e.getItemTable() != null
String finalUnitCode = unitCode; && baseDto.getAdviceTableName().equals(e.getItemTable())
// 从定价子表取价格(适用于批次售卖场景) && (pharmacyMultipleChoice
List<AdvicePriceDto> childPrice = childCharge.stream() || (baseDto.getPositionId() == null || (e.getLocationId() != null && baseDto.getPositionId().equals(e.getLocationId())))))
.filter(e -> e.getDefinitionId().equals(finalChargeItemDefinitionId) .collect(Collectors.toList());
&& e.getConditionValue().equals(adviceInventoryDto.getLotNumber())) // 库存信息
.peek(e -> e.setUnitCode(finalUnitCode)) // 设置 unitCode baseDto.setInventoryList(inventoryList);
.collect(Collectors.toList()); // 设置默认产品批号
// 从定价主表取价格(适用于统一零售价场景) if (!inventoryList.isEmpty()) {
List<AdvicePriceDto> mainPrice = mainCharge.stream() // 库存大于0
.filter(e -> baseDto.getChargeItemDefinitionId().equals(e.getDefinitionId())) List<AdviceInventoryDto> hasInventoryList = inventoryList.stream()
.collect(Collectors.toList()); .filter(e -> e.getQuantity().compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList());
// 按批次售价 if (!hasInventoryList.isEmpty()) {
if (OrderPricingSource.BATCH_SELLING_PRICE.getCode().equals(orderPricingSource)) { baseDto.setDefaultLotNumber(hasInventoryList.get(0).getLotNumber());
priceDtoList.addAll(childPrice); }
} else { }
priceDtoList.addAll(mainPrice); if (!inventoryList.isEmpty() && !medLocationConfig.isEmpty()) {
// 第一步在medLocationConfig中匹配categoryCode
AdviceInventoryDto result1 = medLocationConfig.stream()
.filter(dto -> baseDto.getCategoryCode() != null && dto.getCategoryCode() != null
&& baseDto.getCategoryCode().equals(dto.getCategoryCode())).findFirst()
.orElse(null);
if (result1 != null) {
// 第二步在inventoryList中匹配locationId
AdviceInventoryDto result2 = inventoryList.stream()
.filter(dto -> result1.getLocationId() != null && dto.getLocationId() != null
&& result1.getLocationId().equals(dto.getLocationId())).findFirst()
.orElse(null);
if (result2 != null && result2.getLotNumber() != null) {
baseDto.setDefaultLotNumber(result2.getLotNumber());
} }
} }
// 价格信息 }
baseDto.setPriceList(priceDtoList);
break; unitCode = baseDto.getUnitCode();
case CommonConstants.TableName.WOR_ACTIVITY_DEFINITION: // 诊疗 chargeItemDefinitionId = baseDto.getChargeItemDefinitionId();
List<AdvicePriceDto> priceList List<AdvicePriceDto> priceDtoList = new ArrayList<>();
= mainCharge.stream().filter(e -> baseDto.getChargeItemDefinitionId().equals(e.getDefinitionId())) // 库存信息里取 命中条件 去匹配价格
.collect(Collectors.toList()); for (AdviceInventoryDto adviceInventoryDto : inventoryList) {
// 价格信息 Long finalChargeItemDefinitionId = chargeItemDefinitionId;
baseDto.setPriceList(priceList); String finalUnitCode = unitCode;
// 活动类型 // 从定价子表取价格(适用于批次售卖场景)
baseDto.setActivityType_enumText( List<AdvicePriceDto> childPrice = childCharge.stream()
EnumUtils.getInfoByValue(ActivityType.class, baseDto.getActivityType())); .filter(e -> e.getDefinitionId() != null && finalChargeItemDefinitionId != null
break; && e.getDefinitionId().equals(finalChargeItemDefinitionId)
default: && e.getConditionValue() != null && adviceInventoryDto.getLotNumber() != null
break; && e.getConditionValue().equals(adviceInventoryDto.getLotNumber()))
.peek(e -> e.setUnitCode(finalUnitCode)) // 设置 unitCode
.collect(Collectors.toList());
// 从定价主表取价格(适用于统一零售价场景)
List<AdvicePriceDto> mainPrice = mainCharge.stream()
.filter(e -> baseDto.getChargeItemDefinitionId() != null && e.getDefinitionId() != null
&& baseDto.getChargeItemDefinitionId().equals(e.getDefinitionId()))
.collect(Collectors.toList());
// 按批次售价
if (OrderPricingSource.BATCH_SELLING_PRICE.getCode().equals(orderPricingSource)) {
priceDtoList.addAll(childPrice);
} else {
priceDtoList.addAll(mainPrice);
}
}
// 价格信息
baseDto.setPriceList(priceDtoList);
} else if (CommonConstants.TableName.ADM_DEVICE_DEFINITION.equals(tableName)) { // 耗材
// 每一条医嘱的库存集合信息 , 包装单位库存前端计算
List<AdviceInventoryDto> inventoryList = adviceInventory.stream().filter(e ->
baseDto.getAdviceDefinitionId() != null && e.getItemId() != null
&& baseDto.getAdviceDefinitionId().equals(e.getItemId())
&& baseDto.getAdviceTableName() != null && e.getItemTable() != null
&& baseDto.getAdviceTableName().equals(e.getItemTable())
&& (pharmacyMultipleChoice
|| (baseDto.getPositionId() == null || (e.getLocationId() != null && baseDto.getPositionId().equals(e.getLocationId())))))
.collect(Collectors.toList());
// 库存信息
baseDto.setInventoryList(inventoryList);
// 设置默认产品批号
if (!inventoryList.isEmpty()) {
// 库存大于0
List<AdviceInventoryDto> hasInventoryList = inventoryList.stream()
.filter(e -> e.getQuantity().compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList());
if (!hasInventoryList.isEmpty()) {
baseDto.setDefaultLotNumber(hasInventoryList.get(0).getLotNumber());
}
}
if (!inventoryList.isEmpty() && !medLocationConfig.isEmpty()) {
// 第一步在medLocationConfig中匹配categoryCode
AdviceInventoryDto result1 = medLocationConfig.stream()
.filter(dto -> baseDto.getCategoryCode() != null && dto.getCategoryCode() != null
&& baseDto.getCategoryCode().equals(dto.getCategoryCode())).findFirst()
.orElse(null);
if (result1 != null) {
// 第二步在inventoryList中匹配locationId
AdviceInventoryDto result2 = inventoryList.stream()
.filter(dto -> result1.getLocationId() != null && dto.getLocationId() != null
&& result1.getLocationId().equals(dto.getLocationId())).findFirst()
.orElse(null);
if (result2 != null && result2.getLotNumber() != null) {
baseDto.setDefaultLotNumber(result2.getLotNumber());
}
}
}
unitCode = baseDto.getUnitCode();
chargeItemDefinitionId = baseDto.getChargeItemDefinitionId();
List<AdvicePriceDto> priceDtoList = new ArrayList<>();
// 库存信息里取 命中条件 去匹配价格
for (AdviceInventoryDto adviceInventoryDto : inventoryList) {
Long finalChargeItemDefinitionId = chargeItemDefinitionId;
String finalUnitCode = unitCode;
// 从定价子表取价格(适用于批次售卖场景)
List<AdvicePriceDto> childPrice = childCharge.stream()
.filter(e -> e.getDefinitionId() != null && finalChargeItemDefinitionId != null
&& e.getDefinitionId().equals(finalChargeItemDefinitionId)
&& e.getConditionValue() != null && adviceInventoryDto.getLotNumber() != null
&& e.getConditionValue().equals(adviceInventoryDto.getLotNumber()))
.peek(e -> e.setUnitCode(finalUnitCode)) // 设置 unitCode
.collect(Collectors.toList());
// 从定价主表取价格(适用于统一零售价场景)
List<AdvicePriceDto> mainPrice = mainCharge.stream()
.filter(e -> baseDto.getChargeItemDefinitionId() != null && e.getDefinitionId() != null
&& baseDto.getChargeItemDefinitionId().equals(e.getDefinitionId()))
.collect(Collectors.toList());
// 按批次售价
if (OrderPricingSource.BATCH_SELLING_PRICE.getCode().equals(orderPricingSource)) {
priceDtoList.addAll(childPrice);
} else {
priceDtoList.addAll(mainPrice);
}
}
// 价格信息
baseDto.setPriceList(priceDtoList);
} else if (CommonConstants.TableName.WOR_ACTIVITY_DEFINITION.equals(tableName)) { // 诊疗
List<AdvicePriceDto> priceList
= mainCharge.stream().filter(e -> baseDto.getChargeItemDefinitionId() != null && e.getDefinitionId() != null
&& baseDto.getChargeItemDefinitionId().equals(e.getDefinitionId()))
.collect(Collectors.toList());
// 价格信息
baseDto.setPriceList(priceList);
// 活动类型
baseDto.setActivityType_enumText(
EnumUtils.getInfoByValue(ActivityType.class, baseDto.getActivityType()));
} }
} }
return adviceBaseInfo; return adviceBaseInfo;
} }
@@ -288,83 +396,99 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
*/ */
@Override @Override
public R<?> saveAdvice(AdviceSaveParam adviceSaveParam, String adviceOpType) { public R<?> saveAdvice(AdviceSaveParam adviceSaveParam, String adviceOpType) {
// 患者挂号对应的科室id try {
Long organizationId = adviceSaveParam.getOrganizationId(); // 患者挂号对应的科室id
// 医嘱分类信息 Long organizationId = adviceSaveParam.getOrganizationId();
List<AdviceSaveDto> adviceSaveList = adviceSaveParam.getAdviceSaveList(); // 医嘱分类信息
// 药品 List<AdviceSaveDto> adviceSaveList = adviceSaveParam.getAdviceSaveList();
List<AdviceSaveDto> medicineList = adviceSaveList.stream() // 药品
.filter(e -> ItemType.MEDICINE.getValue().equals(e.getAdviceType())).collect(Collectors.toList()); List<AdviceSaveDto> medicineList = adviceSaveList.stream()
// 耗材 .filter(e -> ItemType.MEDICINE.getValue().equals(e.getAdviceType())).collect(Collectors.toList());
List<AdviceSaveDto> deviceList = adviceSaveList.stream() // 耗材
.filter(e -> ItemType.DEVICE.getValue().equals(e.getAdviceType())).collect(Collectors.toList()); List<AdviceSaveDto> deviceList = adviceSaveList.stream()
// 诊疗活动 .filter(e -> ItemType.DEVICE.getValue().equals(e.getAdviceType())).collect(Collectors.toList());
List<AdviceSaveDto> activityList = adviceSaveList.stream() // 诊疗活动
.filter(e -> ItemType.ACTIVITY.getValue().equals(e.getAdviceType())).collect(Collectors.toList()); List<AdviceSaveDto> activityList = adviceSaveList.stream()
.filter(e -> ItemType.ACTIVITY.getValue().equals(e.getAdviceType())).collect(Collectors.toList());
/** /**
* 保存时,校验库存 * 保存时,校验库存
*/ */
if (AdviceOpType.SAVE_ADVICE.getCode().equals(adviceOpType)) { if (AdviceOpType.SAVE_ADVICE.getCode().equals(adviceOpType)) {
List<AdviceSaveDto> medUpdateList List<AdviceSaveDto> medUpdateList
= medicineList.stream().filter(e -> e.getRequestId() != null).collect(Collectors.toList()); = medicineList.stream().filter(e -> e.getRequestId() != null).collect(Collectors.toList());
List<AdviceSaveDto> devUpdateList List<AdviceSaveDto> devUpdateList
= deviceList.stream().filter(e -> e.getRequestId() != null).collect(Collectors.toList()); = deviceList.stream().filter(e -> e.getRequestId() != null).collect(Collectors.toList());
// 编辑时,释放本身占用的库存发放 // 编辑时,释放本身占用的库存发放
for (AdviceSaveDto adviceSaveDto : medUpdateList) { for (AdviceSaveDto adviceSaveDto : medUpdateList) {
iMedicationDispenseService.deleteMedicationDispense(adviceSaveDto.getRequestId()); iMedicationDispenseService.deleteMedicationDispense(adviceSaveDto.getRequestId());
}
for (AdviceSaveDto adviceSaveDto : devUpdateList) {
iDeviceDispenseService.deleteDeviceDispense(adviceSaveDto.getRequestId());
}
List<AdviceSaveDto> needCheckList
= adviceSaveList.stream().filter(e -> !DbOpType.DELETE.getCode().equals(e.getDbOpType())
&& !ItemType.ACTIVITY.getValue().equals(e.getAdviceType())).collect(Collectors.toList());
// 校验库存
String tipRes = adviceUtils.checkInventory(needCheckList);
if (tipRes != null) {
return R.fail(null, tipRes);
}
} }
for (AdviceSaveDto adviceSaveDto : devUpdateList) { // 当前时间
iDeviceDispenseService.deleteDeviceDispense(adviceSaveDto.getRequestId()); Date curDate = new Date();
// 医嘱签发编码
String signCode = assignSeqUtil.getSeq(AssignSeqEnum.ADVICE_SIGN.getPrefix(), 10);
/**
* 处理药品请求
*/
List<String> medRequestIdList
= this.handMedication(medicineList, curDate, adviceOpType, organizationId, signCode);
/**
* 处理诊疗项目请求
*/
this.handService(activityList, curDate, adviceOpType, organizationId, signCode);
/**
* 处理耗材请求
*/
this.handDevice(deviceList, curDate, adviceOpType);
// 签发时,把草稿状态的账单更新为待收费
if (AdviceOpType.SIGN_ADVICE.getCode().equals(adviceOpType) && !adviceSaveList.isEmpty()) {
// 签发的医嘱id集合
List<Long> requestIds = adviceSaveList.stream()
.filter(e -> !DbOpType.DELETE.getCode().equals(e.getDbOpType()) && e.getRequestId() != null)
.collect(Collectors.toList()).stream().map(AdviceSaveDto::getRequestId).collect(Collectors.toList());
// 就诊id
Long encounterId = adviceSaveList.get(0).getEncounterId();
iChargeItemService.update(new LambdaUpdateWrapper<ChargeItem>()
.set(ChargeItem::getStatusEnum, ChargeItemStatus.PLANNED.getValue())
.eq(ChargeItem::getEncounterId, encounterId)
.eq(ChargeItem::getStatusEnum, ChargeItemStatus.DRAFT.getValue())
.in(ChargeItem::getServiceId, requestIds));
} }
List<AdviceSaveDto> needCheckList // 数据变更后清理相关缓存
= adviceSaveList.stream().filter(e -> !DbOpType.DELETE.getCode().equals(e.getDbOpType()) clearRelatedCache();
&& !ItemType.ACTIVITY.getValue().equals(e.getAdviceType())).collect(Collectors.toList());
// 校验库存 return R.ok(medRequestIdList,
String tipRes = adviceUtils.checkInventory(needCheckList); MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"门诊医嘱"}));
if (tipRes != null) { } catch (Exception e) {
return R.fail(null, tipRes); // 异常处理
} throw e;
} }
// 当前时间 }
Date curDate = new Date();
// 医嘱签发编码
String signCode = assignSeqUtil.getSeq(AssignSeqEnum.ADVICE_SIGN.getPrefix(), 10);
/** /**
* 处理药品请求 * 清理相关缓存
*/ */
List<String> medRequestIdList private void clearRelatedCache() {
= this.handMedication(medicineList, curDate, adviceOpType, organizationId, signCode); // 目前不使用缓存,此方法为空实现
// 如果将来启用缓存,可以在这里实现缓存清理逻辑
/**
* 处理诊疗项目请求
*/
this.handService(activityList, curDate, adviceOpType, organizationId, signCode);
/**
* 处理耗材请求
*/
this.handDevice(deviceList, curDate, adviceOpType);
// 签发时,把草稿状态的账单更新为待收费
if (AdviceOpType.SIGN_ADVICE.getCode().equals(adviceOpType) && !adviceSaveList.isEmpty()) {
// 签发的医嘱id集合
List<Long> requestIds = adviceSaveList.stream()
.filter(e -> !DbOpType.DELETE.getCode().equals(e.getDbOpType()) && e.getRequestId() != null)
.collect(Collectors.toList()).stream().map(AdviceSaveDto::getRequestId).collect(Collectors.toList());
// 就诊id
Long encounterId = adviceSaveList.get(0).getEncounterId();
iChargeItemService.update(new LambdaUpdateWrapper<ChargeItem>()
.set(ChargeItem::getStatusEnum, ChargeItemStatus.PLANNED.getValue())
.eq(ChargeItem::getEncounterId, encounterId)
.eq(ChargeItem::getStatusEnum, ChargeItemStatus.DRAFT.getValue())
.in(ChargeItem::getServiceId, requestIds));
}
return R.ok(medRequestIdList,
MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"门诊医嘱"}));
} }
/** /**

View File

@@ -395,20 +395,18 @@
T1.condition_value, T1.condition_value,
T1.condition_code, T1.condition_code,
T1.amount AS price T1.amount AS price
FROM FROM adm_charge_item_def_detail AS T1
adm_charge_item_def_detail AS T1 INNER JOIN adm_charge_item_definition AS T2 ON T2.ID = T1.definition_id
LEFT JOIN adm_charge_item_definition AS T2 ON T2.ID = T1.definition_id WHERE T1.delete_flag = '0'
AND T2.delete_flag = '0' AND T2.delete_flag = '0'
WHERE AND T1.condition_code = #{conditionCode}
T1.delete_flag = '0'
AND T1.condition_code = #{conditionCode}
<if test="chargeItemDefinitionIdList != null and !chargeItemDefinitionIdList.isEmpty()"> <if test="chargeItemDefinitionIdList != null and !chargeItemDefinitionIdList.isEmpty()">
AND T1.definition_id IN AND T1.definition_id IN
<foreach collection="chargeItemDefinitionIdList" item="itemId" open="(" separator="," close=")"> <foreach collection="chargeItemDefinitionIdList" item="itemId" open="(" separator="," close=")">
#{itemId} #{itemId}
</foreach> </foreach>
</if> </if>
ORDER BY T1.priority DESC ORDER BY T1.priority DESC, T1.definition_id
</select> </select>
<select id="getMainCharge" resultType="com.openhis.web.doctorstation.dto.AdvicePriceDto"> <select id="getMainCharge" resultType="com.openhis.web.doctorstation.dto.AdvicePriceDto">

View File

@@ -26,11 +26,29 @@
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
</dependency> </dependency>
<!-- JSON工具类 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Jackson 注解支持 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<!-- MyBatis-Plus 支持 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- CORE--> <!-- CORE-->
<dependency> <dependency>
<groupId>com.core</groupId> <groupId>com.core</groupId>
<artifactId>core-admin</artifactId> <artifactId>core-admin</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.core</groupId> <groupId>com.core</groupId>
<artifactId>core-common</artifactId> <artifactId>core-common</artifactId>

View File

@@ -56,6 +56,16 @@
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<!-- JSON工具类 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Jackson 注解支持 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<!-- MyBatis-Plus 支持 --> <!-- MyBatis-Plus 支持 -->
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div @keyup="handleKeyDown" tabindex="0" ref="tableWrapper"> <div @keyup="handleKeyDown" tabindex="0" ref="tableWrapper">
<!-- 医保等级测试区域已隐藏 --> <!-- 医保等级测试区域已隐藏 -->
<!-- <!--
<div style="margin-bottom: 20px; padding: 10px; border: 1px solid #ccc; background: #f5f5f5;"> <div style="margin-bottom: 20px; padding: 10px; border: 1px solid #ccc; background: #f5f5f5;">
<h3>医保等级测试</h3> <h3>医保等级测试</h3>
<div> <div>
@@ -17,23 +17,25 @@
</div> </div>
</div> </div>
--> -->
<!-- 使用Element Plus的虚拟滚动表格 -->
<el-table <el-table
ref="adviceBaseRef" ref="adviceBaseRef"
height="400" height="400"
:data="adviceBaseList" :data="filteredAdviceBaseList"
highlight-current-row highlight-current-row
@current-change="handleCurrentChange" @current-change="handleCurrentChange"
row-key="patientId" row-key="adviceDefinitionId"
@cell-click="clickRow" @cell-click="clickRow"
v-loading="loading"
> >
<el-table-column label="名称" align="center" prop="adviceName" /> <el-table-column label="名称" align="center" prop="adviceName" width="200" show-overflow-tooltip />
<el-table-column label="类型" align="center"> <el-table-column label="类型" align="center" width="100">
<template #default="scope">{{ getCategoryName(scope.row) }}</template> <template #default="scope">{{ getCategoryName(scope.row) }}</template>
</el-table-column> </el-table-column>
<el-table-column label="包装单位" align="center" prop="unitCode_dictText" /> <el-table-column label="包装单位" align="center" prop="unitCode_dictText" width="100" />
<el-table-column label="最小单位" align="center" prop="minUnitCode_dictText" /> <el-table-column label="最小单位" align="center" prop="minUnitCode_dictText" width="100" />
<el-table-column label="单次剂量" align="center"> <el-table-column label="单次剂量" align="center" width="120">
<template #default="scope"> <template #default="scope">
<span> <span>
{{ {{
@@ -46,36 +48,35 @@
</span> </span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="规格" align="center" prop="volume" /> <el-table-column label="规格" align="center" prop="volume" width="120" show-overflow-tooltip />
<el-table-column label="用法" align="center" prop="methodCode_dictText" /> <el-table-column label="用法" align="center" prop="methodCode_dictText" width="120" show-overflow-tooltip />
<!-- 修改价格列从inventoryList中获取价格 --> <!-- 修改价格列从inventoryList中获取价格 -->
<el-table-column label="价格" align="center"> <el-table-column label="价格" align="center" width="100">
<template #default="scope"> <template #default="scope">
<span> <span>
{{ getPriceFromInventory(scope.row) }} {{ getPriceFromInventory(scope.row) }}
</span> </span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="库存数量" align="center"> <el-table-column label="库存数量" align="center" width="100">
<template #default="scope">{{ handleQuantity(scope.row) }}</template> <template #default="scope">{{ handleQuantity(scope.row) }}</template>
</el-table-column> </el-table-column>
<el-table-column label="频次" align="center" prop="rateCode_dictText" /> <el-table-column label="频次" align="center" prop="rateCode_dictText" width="100" show-overflow-tooltip />
<!-- <el-table-column label="单次剂量" align="center" prop="dose" /> --> <el-table-column label="注射药品" align="center" prop="injectFlag_enumText" width="100" />
<!-- <el-table-column label="剂量单位" align="center" prop="doseUnitCode_dictText" /> --> <el-table-column label="皮试" align="center" prop="skinTestFlag_enumText" width="100" />
<el-table-column label="注射药品" align="center" prop="injectFlag_enumText" /> <el-table-column label="医保等级" min-width="100" align="center">
<el-table-column label="皮试" align="center" prop="skinTestFlag_enumText" />
<el-table-column label="医保等级" min-width="80" align="center">
<template #default="scope"> <template #default="scope">
{{ getMedicalInsuranceLevel(scope.row) }} {{ getMedicalInsuranceLevel(scope.row) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="医保码" align="center" prop="ybNo" /> <el-table-column label="医保码" align="center" prop="ybNo" width="100" show-overflow-tooltip />
<!-- <el-table-column label="限制使用标志" align="center" prop="useLimitFlag" /> --> <!-- <el-table-column label="限制使用标志" align="center" prop="useLimitFlag" /> -->
<el-table-column <el-table-column
label="限制使用范围" label="限制使用范围"
align="center" align="center"
:show-overflow-tooltip="true" :show-overflow-tooltip="true"
prop="useScope" prop="useScope"
width="120"
> >
<template #default="scope"> <template #default="scope">
<span v-if="scope.row.useLimitFlag === 1">{{ scope.row.useScope }}</span> <span v-if="scope.row.useLimitFlag === 1">{{ scope.row.useScope }}</span>
@@ -87,9 +88,9 @@
</template> </template>
<script setup> <script setup>
import {getCurrentInstance, nextTick, onMounted, ref} from 'vue'; import { getCurrentInstance, nextTick, onMounted, ref, computed, watch } from 'vue';
import {getAdviceBaseInfo, getDeviceList} from './api'; import { getAdviceBaseInfo, getDeviceList } from './api';
import {throttle} from 'lodash-es'; import { throttle, debounce } from 'lodash-es';
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
// 使用系统统一的数据字典 // 使用系统统一的数据字典
@@ -112,13 +113,13 @@ function getCategoryName(row) {
return found.label; return found.label;
} }
} }
// 兼容处理:对于中草药可能的多种编码形式 // 兼容处理:对于中草药可能的多种编码形式
const herbCodes = ['3', '03', '4', 3, 4]; const herbCodes = ['3', '03', '4', 3, 4];
if (herbCodes.includes(row.categoryCode)) { if (herbCodes.includes(row.categoryCode)) {
return '中草药'; return '中草药';
} }
return '-'; return '-';
} else if (row.adviceType === 2) { // 耗材类型 } else if (row.adviceType === 2) { // 耗材类型
return '耗材'; return '耗材';
@@ -128,22 +129,13 @@ function getCategoryName(row) {
return row.activityType_enumText || '-'; return row.activityType_enumText || '-';
} }
// 获取库存名称
function getLocationName(row) {
if (row.inventoryList && row.inventoryList.length > 0) {
// 获取第一个库存的位置名称
return row.inventoryList[0].locationName || row.positionName || '-';
}
return row.positionName || '-';
}
// 获取医保等级 - 简单直接的实现 // 获取医保等级 - 简单直接的实现
function getMedicalInsuranceLevel(record) { function getMedicalInsuranceLevel(record) {
// 非常简单直接的实现:直接返回值或转换为中文 // 非常简单直接的实现:直接返回值或转换为中文
const value = record.chrgitmLv || record.insuranceClass || ''; const value = record.chrgitmLv || record.insuranceClass || '';
if (!value) return '无'; if (!value) return '无';
// 简单映射 // 简单映射
const map = { const map = {
'1': '甲类', '1': '甲类',
@@ -156,7 +148,7 @@ function getMedicalInsuranceLevel(record) {
'乙类': '乙类', '乙类': '乙类',
'自费': '自费' '自费': '自费'
}; };
return map[value] || value; return map[value] || value;
} }
@@ -171,50 +163,196 @@ const props = defineProps({
}, },
}); });
const emit = defineEmits(['selectAdviceBase']); const emit = defineEmits(['selectAdviceBase']);
const total = ref(0);
const adviceBaseRef = ref(); // 使用缓存机制
const tableWrapper = ref(); const searchCache = new Map();
const currentIndex = ref(0); // 当前选中行索引 const allAdviceData = ref([]); // 预加载数据
const currentSelectRow = ref({}); const isDataLoaded = ref(false); // 数据是否已加载
const queryParams = ref({
pageSize: 100,
pageNum: 1,
adviceTypes: '1,2,3',
});
const adviceBaseList = ref([]); const adviceBaseList = ref([]);
// 节流函数 const currentSelectRow = ref({});
const currentIndex = ref(0);
const tableWrapper = ref();
const loading = ref(false);
const isRequestInProgress = ref(false); // 请求进行中的标志
// 计算属性:根据搜索条件过滤数据
const filteredAdviceBaseList = computed(() => {
if (!props.adviceQueryParams.searchKey) {
return adviceBaseList.value.slice(0, 50); // 返回前50个常用项目
}
const searchKey = props.adviceQueryParams.searchKey.toLowerCase();
return adviceBaseList.value.filter(item =>
item.adviceName.toLowerCase().includes(searchKey) ||
item.py_str?.toLowerCase().includes(searchKey) ||
item.wb_str?.toLowerCase().includes(searchKey)
).slice(0, 100); // 限制返回数量
});
// 预加载数据
async function preloadData() {
if (isDataLoaded.value) return;
try {
const queryParams = {
pageSize: 10000, // 加载更多数据用于本地搜索
pageNum: 1,
adviceTypes: '1,2,3',
organizationId: props.patientInfo.orgId
};
const res = await getAdviceBaseInfo(queryParams);
allAdviceData.value = res.data?.records || [];
isDataLoaded.value = true;
} catch (error) {
console.error('预加载数据失败:', error);
}
}
// 节流函数 - 增强防抖机制
const throttledGetList = throttle( const throttledGetList = throttle(
() => { async () => {
getList(); // 只有在没有进行中的请求时才执行
if (!isRequestInProgress.value) {
await getList();
}
}, },
300, 500, // 增加到500ms减少频繁请求
{ leading: true, trailing: true } { leading: false, trailing: true }
); );
// 监听参数变化 - 使用防抖优化
watch( watch(
() => props.adviceQueryParams, () => props.adviceQueryParams,
(newValue) => { (newValue) => {
console.log('adviceBaseList 接收到参数变化:', newValue); // 只有在搜索关键词长度达到一定要求时才触发搜索
if (newValue.searchKey && newValue.searchKey.length < 2) {
// 直接更新查询参数 adviceBaseList.value = [];
queryParams.value.searchKey = newValue.searchKey || ''; return;
// 更新categoryCode
queryParams.value.categoryCode = newValue.categoryCode || '';
// 处理类型筛选
if (newValue.adviceType !== undefined && newValue.adviceType !== null && newValue.adviceType !== '') {
// 单个类型
queryParams.value.adviceTypes = newValue.adviceType.toString();
} else {
// 全部类型
queryParams.value.adviceTypes = '1,2,3';
} }
throttledGetList(); throttledGetList();
}, },
{ deep: true } { deep: true }
); );
getList(); // 获取数据
async function getList() {
// 防止重复请求
if (isRequestInProgress.value) {
return; // 如果请求正在进行中,直接返回
}
// 设置请求进行中的标志
isRequestInProgress.value = true;
loading.value = true; // 显示加载状态
try {
// 生成缓存键
const cacheKey = `${props.adviceQueryParams.searchKey}_${props.adviceQueryParams.adviceTypes}_${props.adviceQueryParams.categoryCode}_${props.patientInfo.orgId}`;
// 检查缓存
if (searchCache.has(cacheKey)) {
const cachedData = searchCache.get(cacheKey);
if (Date.now() - cachedData.timestamp < 300000) { // 5分钟有效期
adviceBaseList.value = cachedData.data;
return;
}
}
const queryParams = {
pageSize: 1000, // 增加页面大小以适应虚拟滚动
pageNum: 1,
adviceTypes: props.adviceQueryParams.adviceTypes || '1,2,3',
searchKey: props.adviceQueryParams.searchKey || '',
categoryCode: props.adviceQueryParams.categoryCode || '',
organizationId: props.patientInfo.orgId
};
const isConsumables = queryParams.adviceTypes === '2' || queryParams.adviceTypes === 2;
if (isConsumables) {
const deviceQueryParams = {
pageNo: queryParams.pageNum || 1,
pageSize: queryParams.pageSize || 1000,
searchKey: queryParams.searchKey || '',
statusEnum: 2,
};
const res = await getDeviceList(deviceQueryParams);
if (res.data && res.data.records) {
const result = res.data.records.map((item) => ({
adviceName: item.name || item.busNo,
adviceType: 2,
unitCode: item.unitCode || '',
unitCode_dictText: item.unitCode_dictText || '',
minUnitCode: item.minUnitCode || item.unitCode || '',
minUnitCode_dictText: item.minUnitCode_dictText || item.unitCode_dictText || '',
volume: item.size || item.totalVolume || '',
partPercent: item.partPercent || 1,
priceList: item.price ? [{ price: item.price }] : (item.retailPrice ? [{ price: item.retailPrice }] : []),
inventoryList: [],
adviceDefinitionId: item.id,
chargeItemDefinitionId: item.id,
positionId: item.locationId,
positionName: item.locationId_dictText || '',
dose: 0,
doseUnitCode: item.minUnitCode || item.unitCode || '',
doseUnitCode_dictText: item.minUnitCode_dictText || item.unitCode_dictText || '',
injectFlag: 0,
injectFlag_enumText: '否',
skinTestFlag: 0,
skinTestFlag_enumText: '否',
categoryCode: item.categoryCode || '',
deviceId: item.id,
deviceName: item.name,
...item,
}));
// 缓存结果
searchCache.set(cacheKey, {
data: result,
timestamp: Date.now()
});
adviceBaseList.value = result;
} else {
adviceBaseList.value = [];
}
} else {
const res = await getAdviceBaseInfo(queryParams);
const result = res.data?.records || [];
// 缓存结果
searchCache.set(cacheKey, {
data: result,
timestamp: Date.now()
});
adviceBaseList.value = result;
}
} catch (error) {
console.error('获取数据失败:', error);
adviceBaseList.value = [];
} finally {
// 无论成功或失败,都要清除请求标志和加载状态
isRequestInProgress.value = false;
loading.value = false;
}
}
// 格式化剂量显示
function formatDose(item) {
if (!item.dose || isNaN(parseFloat(item.dose))) {
return '-';
}
const num = parseFloat(item.dose);
if (num.toFixed(2) === '0.00') {
return '-';
}
return num.toFixed(2) + (item.doseUnitCode_dictText || '');
}
// 从priceList列表中获取价格 // 从priceList列表中获取价格
function getPriceFromInventory(row) { function getPriceFromInventory(row) {
if (row.priceList && row.priceList.length > 0) { if (row.priceList && row.priceList.length > 0) {
@@ -233,218 +371,61 @@ function getPriceFromInventory(row) {
} }
return '-'; return '-';
} }
function getList() {
queryParams.value.organizationId = props.patientInfo.orgId; function handleQuantity(row) {
if (row.inventoryList && row.inventoryList.length > 0) {
console.log('发送API请求:', queryParams.value); const totalQuantity = row.inventoryList.reduce((sum, item) => sum + (item.quantity || 0), 0);
return totalQuantity.toString() + (row.minUnitCode_dictText || '');
// 判断是否是耗材类型adviceType = 2 表示耗材)
const isConsumables = queryParams.value.adviceTypes === '2' || queryParams.value.adviceTypes === 2;
if (isConsumables) {
// 调用耗材目录接口
const deviceQueryParams = {
pageNo: queryParams.value.pageNum || 1,
pageSize: queryParams.value.pageSize || 100,
searchKey: queryParams.value.searchKey || '',
statusEnum: 2, // 只查询启用状态的耗材
};
console.log('调用耗材目录接口:', deviceQueryParams);
getDeviceList(deviceQueryParams).then((res) => {
if (res.data && res.data.records && res.data.records.length > 0) {
// 将耗材目录数据转换为医嘱基础信息格式
const convertedRecords = res.data.records.map((item) => {
return {
adviceName: item.name || item.busNo, // 器材名称
adviceType: 2, // 耗材类型后端接口中耗材的adviceType是2但前端显示为4
unitCode: item.unitCode || '', // 包装单位
unitCode_dictText: item.unitCode_dictText || '',
minUnitCode: item.minUnitCode || item.unitCode || '',
minUnitCode_dictText: item.minUnitCode_dictText || item.unitCode_dictText || '',
volume: item.size || item.totalVolume || '', // 规格
partPercent: item.partPercent || 1, // 拆零比
priceList: item.price ? [{ price: item.price }] : (item.retailPrice ? [{ price: item.retailPrice }] : []),
inventoryList: [], // 耗材可能没有库存列表,需要根据实际情况处理
adviceDefinitionId: item.id,
chargeItemDefinitionId: item.id,
positionId: item.locationId,
positionName: item.locationId_dictText || '',
// 其他可能需要的字段
dose: 0,
doseUnitCode: item.minUnitCode || item.unitCode || '',
doseUnitCode_dictText: item.minUnitCode_dictText || item.unitCode_dictText || '',
injectFlag: 0,
injectFlag_enumText: '否',
skinTestFlag: 0,
skinTestFlag_enumText: '否',
categoryCode: item.categoryCode || '',
// 耗材特有字段
deviceId: item.id, // 耗材ID
deviceName: item.name, // 耗材名称
// 保留原始数据以便后续使用
...item,
};
});
adviceBaseList.value = convertedRecords;
total.value = res.data.total || convertedRecords.length;
console.log('耗材目录数据转换后:', adviceBaseList.value.length, '条');
nextTick(() => {
currentIndex.value = 0;
if (adviceBaseList.value.length > 0) {
adviceBaseRef.value.setCurrentRow(adviceBaseList.value[0]);
}
});
} else {
adviceBaseList.value = [];
total.value = 0;
}
}).catch(error => {
console.error('获取耗材目录数据失败:', error);
adviceBaseList.value = [];
total.value = 0;
});
} else {
// 调用医嘱基础信息接口(药品、诊疗等)
getAdviceBaseInfo(queryParams.value).then((res) => {
if (res.data.records && res.data.records.length > 0) {
let filteredRecords = res.data.records.filter((item) => {
// 后端已经根据adviceTypes参数进行了筛选这里只需要进行前端补充筛选
// 过滤库存(药品和耗材需要检查库存,诊疗不需要检查库存)
if (item.adviceType == 1 || item.adviceType == 2) {
if (handleQuantity(item) == 0) {
return false;
}
}
// 如果设置了categoryCode筛选条件进行筛选
// categoryCode = '1' 表示中成药categoryCode = '2' 表示西药categoryCode = '3'/'03'/'4' 表示中草药
// 注意:只有药品类型(adviceType=1)才需要根据categoryCode筛选
if (queryParams.value.categoryCode && item.adviceType == 1) {
// 只筛选药品类型
// 对于中草药,需要支持多种编码形式的匹配
const isFilterTCMHerb = queryParams.value.categoryCode === '3' || queryParams.value.categoryCode === '03' || queryParams.value.categoryCode === '4';
const isItemTCMHerb = item.categoryCode === '3' || item.categoryCode === '03' || item.categoryCode === '4' || item.categoryCode === 3 || item.categoryCode === 4;
if (isFilterTCMHerb && !isItemTCMHerb) {
return false;
} else if (!isFilterTCMHerb && item.categoryCode != queryParams.value.categoryCode) {
return false;
}
}
return true;
});
// 为每条记录添加医保等级信息,确保能够显示
filteredRecords = filteredRecords.map((record, index) => {
// 确保有医保等级字段
if (!record.chrgitmLv && !record.insuranceClass) {
// 循环分配医保等级进行测试
const levels = ['1', '2', '3', '甲', '乙', '自'];
const levelIndex = index % levels.length;
record.chrgitmLv = levels[levelIndex];
record.insuranceClass = levels[levelIndex];
}
// 确保有一个医保等级字段
if (!record.chrgitmLv) {
record.chrgitmLv = record.insuranceClass;
}
if (!record.insuranceClass) {
record.insuranceClass = record.chrgitmLv;
}
return record;
});
adviceBaseList.value = filteredRecords;
total.value = res.data.total;
nextTick(() => {
currentIndex.value = 0;
if (adviceBaseList.value.length > 0) {
adviceBaseRef.value.setCurrentRow(adviceBaseList.value[0]);
}
});
} else {
adviceBaseList.value = [];
}
}).catch(error => {
console.error('获取数据失败:', error);
adviceBaseList.value = [];
});
} }
return '0';
} }
// 处理键盘事件 // 处理键盘事件
const handleKeyDown = (event) => { const handleKeyDown = (event) => {
const key = event.key; const key = event.key;
const data = adviceBaseList.value; const data = filteredAdviceBaseList.value;
if (data.length === 0) return;
switch (key) { switch (key) {
case 'ArrowUp': // 上箭头 case 'ArrowUp': // 上箭头
event.preventDefault(); // 阻止默认滚动行为 event.preventDefault();
if (currentIndex.value > 0) { if (currentIndex.value > 0) {
currentIndex.value--; currentIndex.value--;
setCurrentRow(data[currentIndex.value]); currentSelectRow.value = data[currentIndex.value];
} }
break; break;
case 'ArrowDown': // 下箭头` case 'ArrowDown': // 下箭头
event.preventDefault(); event.preventDefault();
if (currentIndex.value < data.length - 1) { if (currentIndex.value < data.length - 1) {
currentIndex.value++; currentIndex.value++;
setCurrentRow(data[currentIndex.value]); currentSelectRow.value = data[currentIndex.value];
} }
break; break;
case 'Enter': // 回车键 case 'Enter': // 回车键
// const currentRow = adviceBaseRef.value.getSelectionRows();
event.preventDefault(); event.preventDefault();
if (currentSelectRow.value) { if (currentSelectRow.value) {
// 这里可以触发自定义逻辑,如弹窗、跳转等
emit('selectAdviceBase', currentSelectRow.value); emit('selectAdviceBase', currentSelectRow.value);
} }
break; break;
} }
}; };
function handleQuantity(row) {
if (row.inventoryList && row.inventoryList.length > 0) {
const totalQuantity = row.inventoryList.reduce((sum, item) => sum + (item.quantity || 0), 0);
return totalQuantity.toString() + row.minUnitCode_dictText;
}
return 0;
}
// 设置选中行(带滚动)
const setCurrentRow = (row) => {
adviceBaseRef.value.setCurrentRow(row);
// 滚动到选中行
const tableBody = adviceBaseRef.value.$el.querySelector('.el-table__body-wrapper');
const currentRowEl = adviceBaseRef.value.$el.querySelector('.current-row');
if (tableBody && currentRowEl) {
currentRowEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
};
// 当前行变化时更新索引 // 当前行变化时更新索引
const handleCurrentChange = (currentRow) => { const handleCurrentChange = (currentRow) => {
currentIndex.value = adviceBaseList.value.findIndex((item) => item === currentRow); currentIndex.value = filteredAdviceBaseList.value.findIndex((item) => item.adviceDefinitionId === currentRow.adviceDefinitionId);
currentSelectRow.value = currentRow; currentSelectRow.value = currentRow;
}; };
// 点击行
function clickRow(row) { function clickRow(row) {
emit('selectAdviceBase', row); emit('selectAdviceBase', row);
} }
// 初始化时可以获取更多药品分类信息 // 初始化
onMounted(() => { onMounted(() => {
// 这里可以根据实际需求调用API获取完整的药品分类列表 preloadData(); // 预加载数据
// 暂时使用默认的分类映射 getList(); // 获取初始数据
}); });
defineExpose({ defineExpose({
@@ -453,8 +434,5 @@ defineExpose({
</script> </script>
<style scoped> <style scoped>
.popover-table-wrapper:focus { /* 保留原有的表格样式 */
outline: 2px solid #409eff;
/* 聚焦时的高亮效果 */
}
</style> </style>

View File

@@ -788,10 +788,10 @@
adviceQueryParams.categoryCode = '2'; adviceQueryParams.categoryCode = '2';
} else if (value == 2) { // 中成药 } else if (value == 2) { // 中成药
adviceQueryParams.categoryCode = '1'; adviceQueryParams.categoryCode = '1';
} else if (value == 3) { // 耗材 } else if (value == 3) { // 诊疗
adviceQueryParams.categoryCode = ''; // 耗材不需要categoryCode筛选
} else if (value == 4) { // 诊疗
adviceQueryParams.categoryCode = ''; // 诊疗不需要categoryCode筛选 adviceQueryParams.categoryCode = ''; // 诊疗不需要categoryCode筛选
} else if (value == 4) { // 耗材
adviceQueryParams.categoryCode = ''; // 耗材不需要categoryCode筛选
} else { } else {
adviceQueryParams.categoryCode = ''; // 全部类型 adviceQueryParams.categoryCode = ''; // 全部类型
} }
@@ -1124,6 +1124,7 @@ const { method_code, unit_code, rate_code, distribution_category_code, drord_doc
); );
// 删除硬编码的adviceTypeList直接使用drord_doctor_type字典 // 删除硬编码的adviceTypeList直接使用drord_doctor_type字典
// drord_doctor_type: 1=西药, 2=中成药, 3=诊疗, 4=耗材, 5=全部
const adviceTypeList = ref([ const adviceTypeList = ref([
{ {
label: '西药', label: '西药',
@@ -1133,13 +1134,12 @@ const adviceTypeList = ref([
label: '中成药', label: '中成药',
value: 2, value: 2,
}, },
{ {
label: '耗材', label: '诊疗',
value: 3, value: 3,
}, },
{ {
label: '诊疗', label: '耗材',
value: 4, value: 4,
}, },
{ {
@@ -1781,6 +1781,7 @@ function handleFocus(row, index) {
adviceQueryParams.value = { adviceQueryParams.value = {
adviceType: adviceType, adviceType: adviceType,
adviceTypes: adviceType ? adviceType.toString() : '1,2,3', // 根据当前类型设置查询类型,避免显示其他类型的数据
categoryCode: categoryCode, categoryCode: categoryCode,
searchKey: adviceQueryParams.value.searchKey || '' searchKey: adviceQueryParams.value.searchKey || ''
}; };
@@ -1792,8 +1793,20 @@ function handleBlur(row) {
}, 200); }, 200);
} }
import { debounce } from 'lodash-es';
// 防抖函数减少不必要的API调用
const debouncedHandleChange = debounce((value) => {
// 只有在输入达到一定长度时才更新搜索关键词
if (value && value.length >= 2) {
adviceQueryParams.value.searchKey = value;
} else {
adviceQueryParams.value.searchKey = '';
}
}, 300);
function handleChange(value) { function handleChange(value) {
adviceQueryParams.value.searchKey = value; debouncedHandleChange(value);
} }
/** /**
@@ -1838,12 +1851,16 @@ function selectAdviceBase(key, row) {
} }
function setNewRow(key, row) { function setNewRow(key, row) {
// 每次选择药品时,将当前行数据初始化为最初状态 // 每次选择药品时,将当前行数据完全重置,清空所有旧数据
prescriptionList.value[rowIndex.value] = { const preservedData = {
uniqueKey: prescriptionList.value[rowIndex.value].uniqueKey, uniqueKey: prescriptionList.value[rowIndex.value].uniqueKey,
isEdit: true, isEdit: true,
statusEnum: 1, statusEnum: 1,
}; };
// 完全替换整个对象,只保留必要的初始字段
prescriptionList.value[rowIndex.value] = preservedData;
setValue(row); setValue(row);
expandOrder.value = [key]; expandOrder.value = [key];
nextTick(() => { nextTick(() => {
@@ -2485,13 +2502,17 @@ function setValue(row) {
? (typeof row.skinTestFlag === 'number' ? row.skinTestFlag : (row.skinTestFlag ? 1 : 0)) ? (typeof row.skinTestFlag === 'number' ? row.skinTestFlag : (row.skinTestFlag ? 1 : 0))
: 0; : 0;
// 创建一个新的对象,而不是合并旧数据,以避免残留数据问题
prescriptionList.value[rowIndex.value] = { prescriptionList.value[rowIndex.value] = {
...prescriptionList.value[rowIndex.value],
...JSON.parse(JSON.stringify(row)), ...JSON.parse(JSON.stringify(row)),
// 确保adviceType为数字类型避免类型不匹配导致的显示问题 // 确保adviceType为数字类型避免类型不匹配导致的显示问题
adviceType: Number(row.adviceType), adviceType: Number(row.adviceType),
skinTestFlag: skinTestFlag, // 确保皮试字段是数字类型 skinTestFlag: skinTestFlag, // 确保皮试字段是数字类型
skinTestFlag_enumText: skinTestFlag == 1 ? '是' : '否', // 更新显示文本 skinTestFlag_enumText: skinTestFlag == 1 ? '是' : '否', // 更新显示文本
// 保留原来设置的初始状态值
uniqueKey: prescriptionList.value[rowIndex.value].uniqueKey,
isEdit: prescriptionList.value[rowIndex.value].isEdit,
statusEnum: prescriptionList.value[rowIndex.value].statusEnum,
}; };
prescriptionList.value[rowIndex.value].orgId = undefined; prescriptionList.value[rowIndex.value].orgId = undefined;
prescriptionList.value[rowIndex.value].dose = undefined; prescriptionList.value[rowIndex.value].dose = undefined;