1 Commits

Author SHA1 Message Date
6d85686b06 测试合并v14 2026-01-15 17:06:32 +08:00
64 changed files with 958 additions and 2938 deletions

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试合并11111</title>
<title>测试合并11112</title>
</head>
<body>

View File

@@ -1,387 +0,0 @@
## 门诊医生站开立会诊申请单界面PRD文档
### 一、页面概述
**页面名称**:门诊医生站开立会诊申请单界面**页面目标**:帮助门诊医生完成会诊申请单的创建、编辑、提交和作废操作,实现多科室会诊流程的电子化管理**适用场景**
1. 门诊医生需要邀请其他科室专家进行会诊时
2. 会诊申请单需要修改或补充信息时
3. 会诊流程需要跟踪管理时
**页面类型**:表单页+列表页复合型界面
**核心功能**
1. 会诊申请单的新增、保存、提交、作废功能
2. 会诊科室/专家可视化选择
3. 申请单数据表格展示与交互
4. 表单数据自动填充与校验
5. 申请单打印输出
**用户价值**
- 规范会诊申请流程,减少纸质单据使用
- 通过智能填充减少医生重复录入
- 实时查看会诊申请状态(新开/已提交/已确认/已签名/已完成/已取消)
原型图地址https://static.pm-ai.cn/prototype/20260115/4eb1bd5367f9d5610b32c0ecc6c793f5/index.html
流程图:
```mermaid
flowchart TD
%% ---------- 开始 ----------
START(["开始"]) --> A["医生进入会诊申请单界面"]
%% ---------- 操作选择 ----------
A --> B{"操作选择"}
B -->|"打印"| C["选择已有申请单"]
B -->|"提交/取消提交"| D{"校验状态为“已提交”?"}
B -->|"删除"| E["弹出确认对话框"]
B -->|"结束"| F{"校验状态为“已提交”?"}
B -->|"编辑"| G["修改表单内容"]
B -->|"新增"| H["清空表单(保留患者信息)"]
%% ---------- 打印分支 ----------
C --> I["高亮选中行"]
I --> J["生成打印视图"]
J --> K["输出打印样式"]
K --> L(["取消"])
%% ---------- 提交/取消提交分支 ----------
D -->|"不通过"| M["提示“请完善必填信息”"]
D -->|"通过"| N["更新状态为“已提交/新开”"]
%% ---------- 删除分支 ----------
E --> O{"确认?"}
O -->|"是"| P["标记状态为“已取消”"]
O -->|"否"| L
%% ---------- 结束分支 ----------
F -->|"不通过"| Q["提示“请先提交申请”"]
F -->|"通过"| R["标记状态为“已完成”"]
%% ---------- 编辑分支 ----------
G --> S{"校验必填字段"}
S -->|"不通过"| M
S -->|"通过"| T["保存到表格"]
%% ---------- 新增/保存通用路径 ----------
H --> U["填写表单"]
U --> V["选择会诊科室/专家"]
V --> W["自动填充邀请对象"]
W --> X["填写病史及目的"]
X --> Y["点击保存"]
Y --> Z{"校验必填字段"}
Z -->|"不通过"| M
Z -->|"通过"| AA["生成会诊申请记录"]
AA --> AB["保存到表格"]
AB --> AC["新增/更新记录"]
%% ---------- 循环 ----------
AC --> A
N --> A
P --> A
R --> A
T --> A
M --> A
Q --> A
L --> A
```
### 二、整体布局分析
**页面宽度**自适应宽度主内容区采用7:3比例分割
**主要区域划分**
1. 顶部操作栏48px固定高度
2. 会诊申请单列表区(高度自适应)
3. 主内容区分左右结构7:3比例
- 左侧:会诊申请单表单区
- 右侧:会诊科室/专家选择区
**布局特点**:响应式上下+左右混合布局,主要对齐方式为左对齐
### 三、页面区域详细描述
#### 1. 顶部操作栏区域
**区域位置**:页面顶部固定位置**区域尺寸**高度48px宽度100%**区域功能**:提供全局操作功能入口**包含元素**
- **打印按钮**
- 元素类型:操作按钮
- 显示内容:“打印”
- 交互行为点击后生成A4打印视图自动适配医院抬头格式
- 样式特征:绿色背景(\#13C2C2)圆角4px32px高度
- **新增按钮**
- 元素类型:操作按钮
- 显示内容:“新增”
- 交互行为:点击清空表单(保留当前患者基本信息)
- 样式特征:蓝色背景(\#1890FF)
- **结束按钮**
- 元素类型:危险操作按钮
- 显示内容:“结束”
- 交互行为:点击结束已提交的会诊流程,标记申请单状态为"已结束",禁用后续操作
- 样式特征:红色背景(\#FF4D4F)
- 限制条件:需先选中已提交的会诊单
- **保存按钮**
- 元素类型:主要操作按钮
- 显示内容:“保存”
- 交互行为:点击保存当前表单数据,校验必填字段后保存至表格,自动生成时间戳
- 样式特征:绿色背景(\#52C41A)
#### 2. 会诊申请单列表区
**区域位置**:顶部操作栏下方**区域尺寸**高度自适应宽度100%**区域功能**:展示当前医生的会诊申请记录**包含元素**
- **申请单表格**
- 展示方式:带边框表格
- 数据字段:
- 序号:文本 - 自增序号 - 不可操作
- 急:布尔 - ✓表示紧急 - 不可操作
- 申请单号:文本 - CS20260105001 - 不可操作
- 会诊时间:日期 - 2026-01-05 15:08 - 不可操作
- 邀请对象:文本 - 吴院长 - 不可操作
- 申请科室:文本 - 内科 - 不可操作
- 申请医师:文本 - 张医生 - 不可操作
- 申请时间:日期 - 2026-01-05 15:08 - 不可操作
- 提交状态:布尔 - 复选框 - 仅查看
- 结束状态:布尔 - 复选框 - 仅查看
- 操作功能:
- - o 提交/取消提交按钮
```
样式要求:蓝色小按钮,禁用状态显示灰色
```
```
交互行为:切换提交状态,需二次确认
```
```
o 删除图标
```
```
样式要求红色垃圾桶图标hover时放大10%
```
```
交互行为:弹出确认对话框后作废该记录
```
[删除]**将状态改为“已取消”****
UPDATE ConsultationRequest
SET ConsultationStatus = 50,cancelnatureDate = <作废会诊时间>
WHERE ConsultationID = <会诊申请单ID> and ConsultationStatus <> 40 ;
- 交互特性:
- 行点击选中效果(蓝色高亮+左侧边框)
- 行hover浅灰色背景
- 提交按钮状态联动(切换提交状态,需二次确认)
#### 3. 会诊申请单表单区
**区域位置**:主内容区左侧**区域尺寸**占主内容区70%宽度**区域功能**:会诊申请单的详细表单填写**包含元素**
- **基础信息区**
- 申请单号只读文本【保存】时自动生成规则CS+年月日时分秒+4位随机数
- 申请时间:只读文本,自动获取系统当前时间
- 病人信息:病人姓名/性别/年龄/就诊卡号/申请医师/申请科室(不可编辑),自动获取当前患者档案信息。
- **会诊信息区**
- 会诊时间:时间控件可编辑
- 紧急标识:复选框控件
- 申请医师:默认当前登录医生
- 申请科室:默认当前医生登录的开单科室
- 门诊诊断:自动获取医生开立的门诊诊断(主诊断)
- **病史及目的**
- 多行文本域最小高度100px
- **会诊邀请**
- 会诊邀请对象:支持多选(逗号分隔)-》(可从右侧会诊邀请对象选择)
- **会诊记录区**
- 会诊意见:只读文本域
- 会诊确认参加医师:只读字段
- 所属医生、代表科室、签名医生、签名时间:只读字段
#### 4. 会诊邀请对象选择区(侧边栏)
**区域位置**:主内容区右侧**区域尺寸**占主内容区30%宽度**区域功能**:快速选择会诊科室和专家**包含元素**
- **会诊科室列表**
- 展示方式:带边框可滚动列表
- 交互行为:选择科室后动态加载对应专家
- **会诊专家列表**
- 展示方式:带边框可滚动列表
- 交互行为:点击专家自动填入会诊邀请对象字段(防重复:已选专家提示"请勿重复选择"
- 特殊逻辑:支持多选(自动用逗号分隔)
### 四、交互功能详细说明
#### 1. 会诊申请单提交流程
**功能描述**:完成会诊申请单的提交操作**触发条件**:点击表格行的"提交"按钮**操作流程**
1. 医生点击行内"提交"按钮
2. 系统校验必填字段(会诊时间、邀请对象)
3. 提交状态复选框变为已勾选
4. 按钮文字变为"取消提交"
5. 禁用该行编辑功能
【提交】**将状态从“新开”改为“已提交”**
UPDATE ConsultationRequest
SET ConsultationStatus = 10,ConfirmingPhysician = <提交会诊医生姓名> ,ConfirmingPhysicianID = <提交会诊医生ID> ,ConfirmingDate = <提交会诊时间>
WHERE ConsultationID = <会诊申请单ID> and ConsultationStatus = 0 ;
【取消提交】**将状态从“已提交”改为“新开”**
UPDATE ConsultationRequest
SET ConsultationStatus = 0,ConfirmingPhysician = '',ConfirmingPhysicianID = '',ConfirmingDate = ''
WHERE ConsultationID = <会诊申请单ID> and ConsultationStatus = 10 ;
**异常处理**
- 必填字段缺失:弹出"请完善会诊时间和邀请对象信息"
- 重复提交:提示"该申请已提交,请勿重复操作"
#### 2. 会诊流程结束功能
**功能描述**:标记会诊流程已结束**触发条件**:选中已提交的申请单后点击顶部"结束"按钮**操作流程**
1. 医生选中已提交的申请单(行高亮)
2. 点击顶部"结束"按钮
3. 系统校验提交状态为已提交
4. 结束状态复选框变为已勾选
5. 禁用该行的取消提交功能
【结束】**将状态从“已签名”改为“已完成”**
UPDATE ConsultationRequest
SET ConsultationStatus = 40,Signature = <结束会诊医生姓名> ,SignatureDate=<结束会诊时间>
WHERE ConsultationID = <会诊申请单ID> and ConsultationStatus = 30 ;
**异常处理**
- 未选中记录:提示"请先选择要结束的会诊申请"
- 未提交记录:提示"请先提交该会诊申请"
#### 3. 申请单保存功能
**功能描述**:保存会诊申请单数据**触发条件**:点击顶部"保存"按钮**操作流程**
1. 系统自动生成申请单号(如为空)
2. 保存当前表单所有字段值
3. 新增记录插入表格末尾
4. 已有记录更新对应行数据
【保存】
①、写入门诊医嘱表(医嘱状态为新开,医嘱名称为"门诊会诊"
②、写入门诊会诊申请单表ConsultationRequest
**数据校验**
- 必填字段:病人姓名、会诊时间、申请科室、会诊时间、会诊邀请对象、简要病史及会诊目的
- 未选会诊对象:提示"请至少选择1位会诊专家"
- 过期时间:提示"会诊时间不能早于当前时间"
#### 4. 会诊邀请对象选择联动
**触发方式**:点击科室列表项
**数据联动**
1. 根据选中会诊科室过滤会诊专家列表
2. 记忆已选专家(跨科室切换时不丢失)
**技术要点**
- 使用对象存储会诊科室-会诊专家映射关系
- 采用事件委托处理动态生成的列表项
### 五、数据结构说明
门诊会诊申请单表ConsultationRequest
| **字段名称** | **数据类型** | **长度** | **描述** | **取值范围** |
|-----------------------------| ------------ | -------- |----------------| --------------------------------------------------------- |
| **PatientID** | Text | 20 | 患者唯一标识 | 患者就诊卡号 (取值患者档案) |
| **ConsultationID** | Text | 20 | 会诊申请单唯一标识 | 系统自动生成的唯一编号生成规则CS+年月日时分秒+4位随机数 |
| **VisitID** | BIGINT | 20 | 门诊就诊流水号(逻辑外键) | 取值于本次门诊就诊记录表的主键 |
| **OrderID** | BIGINT | 20 | 门诊医嘱表主键(一对一外键) | 门诊医嘱表 |
| **PatientName** | Text | 50 | 患者姓名 | 患者的姓名 (取值患者档案) |
| **Gender** | Text | 10 | 患者性别 | 男/女/其他 (取值患者档案) |
| **Age** | Integer | - | 患者年龄 | 取值患者档案 |
| **Department** | Text | 50 | 申请会诊的科室 | 当前科室名称 |
| **RequestingPhysician** | Text | 50 | 申请会诊的医生 | 当前医生姓名 |
| **ConsultationrequestDate** | DateTime | - | 会诊申请时间 | YYYY-MM-DD HH:MM:SS
| **ConsultationPurpose** | Text | 255 | 简要病史及会诊目的 | 文本描述,自定义编辑 |
| **ProvisionalDiagnosis** | Text | 255 | 门诊诊断 | 文本描述,自动获取医生开立的门诊诊断(主诊断) |
| **ConsultationDate** | DateTime | - | 会诊时间 | YYYY-MM-DD HH:MM:SS |
| **ConsultationStatus** | Text | 20 | 会诊状态 | 新开/已提交/已确认/已签名/已完成/已取消 |
| **ConsultationUrgency** | Text | 20 | 是否紧急 | 勾选框:一般/紧急 |
| **ConsultationOpinion** | Text | 255 | 会诊意见 | 文本描述 |
| **ConfirmingPhysician** | Text | 50 | 提交会诊的医生 | 医生姓名 |
| **ConfirmingPhysicianID** | Text | 20 | 提交会诊的医生ID | 医生唯一标识 |
| **ConfirmingDate** | DateTime | - | 提交会诊日期 | YYYY-MM-DD HH:MM:SS |
| **Signature** | Text | 50 | 结束会诊医生 | 医生姓名 |
| **SignatureDate** | DateTime | - | 结束会诊日期 | YYYY-MM-DD HH:MM:SS |
| **cancelnatureDate** | DateTime | - | 作废会诊日期 | YYYY-MM-DD HH:MM:SS |
| InvitedObject | Text | 50 | 会诊邀请对象 | |
**诊状态用于记录会诊申请在不同阶段的状态,以下是常见的会诊状态及其说明:**
| **状态名称** | **状态值** | **描述** |
| ------------ | ---------- | ---------------------------------------------------------------------- |
| **新开** | 0 | 会诊申请单已保存 |
| **已提交** | 10 | 会诊申请已提交,但尚未被会诊医生确认。 |
| **已确认** | 20 | 会诊医生已确认会诊申请,并准备进行会诊。 |
| **已签名** | 30 | 会诊完成后进行签名 |
| **已完成** | 40 | 会诊已经完成,会诊意见已记录。 |
| **已取消** | 50 | 会诊申请被取消,可能由于患者情况变化或其他原因,申请医生进行作废操作。 |
**门诊医嘱表在相关会诊操作步骤的相关事务**
把“门诊会诊申请”当成**一种特殊医嘱**OrderType = 'Consult')由系统**在同一事务内**自动插入 门诊医嘱表,再挂到 `ConsultationRequest` **注意:按照现有系统的门诊医嘱表进行设置相关字段的值**
| **节点** | **是否自动** | **说明** |
| --------------------- | ------------ | --------------------------------------------------------------------------------------------------- |
| 医生点击【保存】 | ✅ | 后台事务:先插门诊医嘱表(医嘱状态为“新开”),再插`ConsultationRequest`.Status=0 |
| 医生点击【提交】 | ✅ | 仅更新两表状态 → 门诊医嘱表的医嘱状态和`ConsultationRequest.Status=10` (已提交),不重复生成医嘱 |
| 医生点击【作废/删除】 | ✅ | 自动将门诊医嘱表的医嘱状态字段置为“作废”,级联`ConsultationRequest.Status=50` |
| 医生点击【结束】 | ✅ | 将 门诊医嘱表的医嘱状态字段置为“已完成”,同时写`ConsultationRequest.Status=40` |
### 六、开发实现要点
**样式规范**
- **主色调**\#1890FF操作按钮
- **辅助色**\#13C2C2打印、\#52C41A保存、\#FF4D4F结束
- **字体规范**14px/1.5,中文字体优先使用"PingFang SC"
- **间距系统**16px基准表单行间距12px
- **组件样式**
- 按钮4px圆角32px高度
- 输入框4px圆角1px \#D9D9D9边框
- 表格行:选中状态\#E6F7FF背景+左侧3px蓝色边框
**技术要求**
- **浏览器兼容**支持Chrome/Firefox/Edge最新版
- **性能要求**:表单提交响应时间\<1秒
**注意事项**
1. 时间字段需统一处理为YYYY-MM-DD HH:mm:ss格式
2. 申请单号生成需加锁防止重复
3. 移动端需优化表格横向滚动体验
4. 打印功能需特殊样式处理(隐藏操作按钮)

View File

@@ -1,310 +0,0 @@
## 门诊医生站会诊申请确认界面PRD文档
### 一、页面概述
**页面名称**:门诊医生站会诊申请确认界面
**页面目标**:帮助医生完成会诊申请的确认、签名和打印操作,展示会诊申请详细信息
**适用场景**:医生在收到会诊申请后,查看申请信息并给出会诊意见
**页面类型**:表单页+列表页复合型页面
**核心功能**
1. 会诊申请单列表展示与选择
2. 会诊确认与取消确认功能
3. 签名功能
4. 会诊记录单打印
5. 会诊意见编辑与保存
**用户价值**
- 规范会诊申请流程
- 电子化确认和签名提高效率
- 完整记录会诊意见便于后续诊疗
- 打印功能满足纸质存档需求
**原型图地址:**https://static.pm-ai.cn/prototype/20260115/7c45e175239257e0f04c9081bf2ca204/index.html
**流程图:**
```mermaid
flowchart TD
Start(["医生进入会诊申请确认界面"]) --> LoadList["加载会诊申请列表"]
LoadList --> HasUntreated{"是否有未处理申请?"}
HasUntreated -- "否" --> ShowNoTip["显示无申请提示"]
HasUntreated -- "是" --> SelectApp["医生选择会诊申请"]
SelectApp --> ShowDetail["显示会诊申请详情"]
ShowDetail --> EditOpinion["医生编辑会诊意见"]
EditOpinion --> ConfirmClick{"点击确认按钮?"}
ConfirmClick -- "否" --> SignClick{"点击签名按钮?"}
ConfirmClick -- "是" --> ValidateConfirm{"校验必填字段"}
ValidateConfirm -- "不通过" --> TipFill["提示\n请先填写会诊意见"]
ValidateConfirm -- "通过" --> CheckConfirmed{"是否已确认?"}
CheckConfirmed -- "是" --> UpdateConfirmed["更新状态为\n已确认"]
UpdateConfirmed --> AutoFill["自动填充医生科室信息"]
AutoFill --> DisableCancel["禁用取消确认功能"]
CheckConfirmed -- "否" --> KeepState["保持当前状态"]
SignClick -- "否" --> PrintClick{"点击打印按钮?"}
SignClick -- "是" --> ValidateSign{"校验通过?"}
ValidateSign -- "不通过" --> TipConfirmFirst["提示\n请先确认会诊申请"]
ValidateSign -- "通过" --> UpdateSigned["更新状态为\n已签名"]
UpdateSigned --> RecordSign["记录签名医生和时间"]
PrintClick -- "否" --> RefreshClick{"点击刷新按钮?"}
PrintClick -- "是" --> GenPrintView["生成打印优化视图"]
GenPrintView --> BrowserPrint["调用浏览器打印功能"]
RefreshClick -- "是" --> LoadList
RefreshClick -- "否" --> KeepState
TipFill --> EditOpinion
TipConfirmFirst --> EditOpinion
KeepState --> End(["结束"])
BrowserPrint --> End
DisableCancel --> End
```
### 二、整体布局分析
**页面宽度**:自适应布局
**主要区域划分**
1. 顶部标签导航高度48px
2. 操作按钮区高度36px+间距)
3. 会诊申请列表区(高度自适应)
4. 会诊记录单表单区(高度自适应)
**布局特点**:上下布局,采用网格系统对齐,左侧对齐为主
### 三、页面区域详细描述
#### 1. 顶部标签导航区域
**区域位置**:页面顶部
**区域尺寸**高度48px宽度100%
**区域功能**:页面导航标识
**包含元素**
- **会诊确认标签**
- 元素类型:文本标签
- 显示内容:“会诊确认”
- 交互行为:无点击交互(当前页面)
- 样式特征蓝色下划线16px字体700字重
#### 2. 操作按钮区域
**区域位置**:标签导航下方
**区域尺寸**高度36px宽度100%
**区域功能**:提供页面主要操作入口
**包含元素**
- **打印按钮**
- 元素类型:操作按钮
- 显示内容:“打印”
- 交互行为:点击触发打印会诊记录单
- 样式特征绿色背景白色文字圆角6px
- **刷新按钮**
- 元素类型:操作按钮
- 显示内容:“刷新”
- 交互行为:点击重新加载页面数据
- 样式特征:白色背景,灰色边框,黑色文字
- **确认按钮**
- 元素类型:状态切换按钮
- 显示内容:“确认”/“取消确认”
- 交互行为:
- 点击后变为"取消确认"状态(红色样式)
- 已签名时禁用取消操作
- 样式特征:蓝色背景,白色文字
- 限制条件:需选中表格行才可操作
- **签名按钮**
- 元素类型:操作按钮
- 显示内容:“签名”
- 交互行为:
- 需先确认才能签名
- 签名后自动记录签名时间和签名医生
- 样式特征:蓝色背景,白色文字
- 限制条件:需先完成确认操作
#### 3. 会诊申请列表区域
**区域位置**:按钮区域下方
**区域尺寸**高度自适应宽度100%
**区域功能**:展示待处理的会诊申请列表
**包含元素**
- **申请列表表格** 取值于门诊会诊申请单表ConsultationRequest
- 检索要求:医生登录门诊医生站打开会诊申请确认界面时只能检索出当前登录医生姓名包含在会诊邀请对象内(只能查看自己受会诊邀请对象)
- 展示方式:带斑马纹表格
- 表头字段:
- 序号 \| 紧急 \| 申请单号 \| 病人姓名 \| 会诊时间 \| 邀请对象 \| 申请科室 \| 申请医师 \| 申请时间 \| 确认 \| 签名
- 数据字段:
- 序号:文本 - 自动编号 - “1” - 不可操作
- 紧急:复选框 - 布尔值 - 未勾选 - 可操作
- 申请单号:文本 - 字符串 - “CS20250812001” - 不可操作
- 病人姓名:文本 - 字符串 - “陈明” - 不可操作
- 会诊时间:日期 - 日期时间 - “2025-08-12 17:48” - 不可操作
- 邀请对象:文本 - 字符串 - “演示测试” - 不可操作
- 申请科室:文本 - 字符串 - “内科” - 不可操作
- 申请医师:文本 - 字符串 - “徐斌” - 不可操作
- 申请时间:日期 - 日期时间 - “2025-08-12 17:48” - 不可操作
- 确认:复选框 - 布尔值 - 勾选框 不可操作
- 签名:复选框 - 布尔值 - 勾选框 不可操作
- 操作功能:点击行选中查看会诊申请详情
- 样式特征:斑马纹交替背景,悬停高亮
#### 4. 会诊记录单表单区域
**区域位置**:列表区域下方
**区域尺寸**高度自适应宽度100%
**区域功能**:展示和编辑会诊详细信息
**包含元素**
- **基础信息区**
- 布局方式8列网格
- 包含字段:
- 病人姓名/性别/年龄/就诊卡号
- 申请单号/申请科室
- 会诊时间/紧急标志
- 会诊邀请对象
- 提交医生/提交时间
- **病史及目的区**
- 元素类型:文本区域
- 显示内容:患者主诉和会诊目的
- 交互行为:只读展示
- **会诊确认参加医师**
- **会诊意见区**
- 元素类型:可编辑文本域
- 显示内容:会诊意见文本
- 交互行为:支持多行编辑
- 样式特征浅灰色背景120px最小高度
- **确认/签名信息区**
- 包含字段:
- 所属医生/代表科室(确认后自动填充当前医生和科室)
- 签名医生/签名时间(自动填充签名医生和签名时间(系统当前时间))
### 四、交互功能详细说明
#### 1. 会诊申请选择功能
**触发方式**:点击表格行
**执行流程**
1. 高亮选中行(浅蓝色背景)
2. 同步该行数据到下方表单
3. 根据确认状态更新按钮文字
4. 加载存储的会诊意见到文本域
**异常处理**
- 无选中行时禁用确认/签名按钮
- 已签名行禁止取消确认
#### 2. 会诊确认功能
**触发方式**:点击确认按钮
**执行流程**
1. 校验必填字段(会诊意见、会诊确认参加医师)
2. 保存会诊意见等相关内容到行数据写入门诊会诊申请确认表ConsultationConfirmation
3. 勾选确认复选框
4. 更新按钮为"取消确认"状态
5. 所属医生和代表科室(自动填充当前医生和科室)
**异常处理**
- 未填写会诊意见时提示"请先填写会诊意见"
- 保存失败时保持原状态并提示错误
#### 2. 电子签名功能
**功能描述**:医生对确认的会诊进行电子签名
**触发条件**:已确认的会诊申请点击"签名"按钮
**操作流程**
1. 医生确认会诊申请
2. 点击"签名"按钮
3. 校验确认状态
4. 表格中"签名"列复选框被勾选
5. 自动记录签名医生(当前用户)
6. 自动填充签名时间为系统时间
7. 禁用取消确认功能
**成功反馈**:表单区显示签名信息
**失败处理**:提示"请先确认会诊申请"
#### 3. 打印会诊记录单
**功能描述**:打印格式化的会诊记录
**触发条件**:点击"打印"按钮
**操作流程**
1. 点击"打印"按钮
2. 系统生成打印优化视图
3. 调用浏览器打印功能
**特殊处理**:隐藏交互元素,优化打印布局
### 五、数据结构说明
**门诊会诊申请确认表(**ConsultationConfirmation****
| **字段名称** | **数据类型** | **长度** | **描述** | **约束/说明** |
|-----------------------------|--------------|----------|--------------------|------------------------------------------------------------------------------------|
| **ConsultationID** | INTEGER | 20 | 会诊申请单唯一标识 | FOREIGN KEY REFERENCES ConsultationRequest(ConsultationID) |
| **ConfirmingPhysicianID** | TEXT | -20 | 确认会诊的医生ID | 操作【确认】按钮的当前医生ID |
| **ConfirmingPhysicianName** | TEXT | -20 | 确认会诊的医生姓名 | 操作【确认】按钮的当前医生姓名 |
| **ConfirmingDeptName** | TEXT | 20 | 代表科室 | 操作【确认】按钮的当前开单科室 |
| **ConfirmingDate** | DateTime | - | 确认会诊的日期 | 操作【确认】按钮当前系统时间 |
| **ConsultationStatus** | TEXT | 20 | 会诊状态 | CHECK (ConsultationStatus IN ('已确认', '取消确认', '已签名', '已完成')), NOT NULL |
| **ConsultationOpinion** | TEXT | 500 | 会诊意见 | |
| **ConfirmingPhysician** | TEXT | 100 | 会诊确认参加医师 | |
| **Signature** | TEXT | 20 | 签名医生 | |
| **SignatureDate** | DateTime | - | 签名时间 | - |
ConsultationConfirmation.ConsultationStatu会诊状态
| **状态值** | **状态名** | **描述** |
|------------|------------|-----------------------------------|
| **0** | 取消确认 | 作废 |
| **20** | 已确认 | 会诊医生已查看/同意,可写初步意见 |
| **30** | 已签名 | 已电子签名,意见最终生效 |
| **40** | 已完成 | 会诊报告已回写,流程关闭 |
**按钮涉及的事务**
| **按钮** | **涉及表** | **执行事务** | **锁/并发** | **成功状态** | **失败处理** |
|--------------|----------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------|------------------|--------------------------|
| **确认** | 1、ConsultationRequest<br>2、门诊医嘱<br>3、ConsultationConfirmation | 1、ConsultationRequest.ConsultationStatus =20<br>2、医嘱 状态='已执行'<br>3、写入ConsultationConfirmation表相关的数据 | SELECT ... FOR UPDATE | 已提交 → 已确认 | 任何异常 → 整体 ROLLBACK |
| **取消确认** | 1、ConsultationRequest<br>2、门诊医嘱<br>3、ConsultationConfirmation | 1、ConsultationRequest.ConsultationStatus =10<br>2、医嘱 状态='已提交'<br>3、ConsultationConfirmation. ConsultationStatus = 0 | 同上 | 已确认→ 取消确认 | 同上回滚 |
| **签名** | 1、ConsultationRequest<br>2、门诊医嘱<br>3、ConsultationConfirmation | 1、ConsultationRequest.ConsultationStatus =30<br>2、医嘱 Status='已完成'<br>3、写入ConsultationConfirmation. Signature, SignatureDate,ConsultationStatus =30 | 同上 | 已确认 → 已签名 | 同上回滚 |
### 六、开发实现要点
**样式规范**
- **主色调**\#4A89DC按钮蓝色
- **辅助色**\#4CAF50成功绿色
- **字体规范**14px/1.5 常规16px 标题
- **间距系统**8px基础间距24px区块间距
- **组件样式**
- 按钮6px圆角1px边框
- 输入框4px圆角1px \#E0E0E0边框
**技术要求**
- **浏览器兼容**Chrome/Firefox/Edge最新版
- **性能要求**:列表加载时间\<1s
**注意事项**
1. 确认和签名状态需要联动控制
2. 打印功能需要特殊样式处理
3. 时间字段需统一使用YYYY-MM-DD HH:mm:ss格式
4. 移动端需优化表单布局

View File

@@ -1,267 +0,0 @@
## 门诊会诊申请管理界面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

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

View File

@@ -21,15 +21,13 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
<compilerArgs>
<arg>-parameters</arg>
<arg>--add-modules</arg>
<arg>java.base</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>

View File

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

View File

@@ -18,12 +18,6 @@
<dependencies>
<!-- MyBatis-Plus 支持 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- velocity代码生成使用模板 -->
<dependency>
<groupId>org.apache.velocity</groupId>
@@ -42,24 +36,6 @@
<artifactId>druid-spring-boot-starter</artifactId>
</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>
</project>

View File

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

View File

@@ -28,10 +28,7 @@ import com.openhis.web.datadictionary.dto.DiagnosisTreatmentDto;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentSelParam;
import com.openhis.web.datadictionary.mapper.ActivityDefinitionManageMapper;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@@ -48,8 +45,6 @@ import java.util.List;
@Service
public class LisConfigManageAppServiceImpl implements ILisConfigManageAppService {
private static final Logger log = LoggerFactory.getLogger(LisConfigManageAppServiceImpl.class);
@Resource
private ActivityDefinitionManageMapper activityDefinitionManageMapper;
@Resource
@@ -125,73 +120,31 @@ public class LisConfigManageAppServiceImpl implements ILisConfigManageAppServic
}
@Override
@Transactional
public R<?> saveAll(LisConfigManageDto manageDto) {
try {
// 先全部删除项目下详情
activityDefDeviceDefMapper.delete(new QueryWrapper<ActivityDefDeviceDef>().eq("activity_definition_id", manageDto.getId()));
activityDefObservationDefMapper.delete(new QueryWrapper<ActivityDefObservationDef>().eq("activity_definition_id", manageDto.getId()));
activityDefSpecimenDefMapper.delete(new QueryWrapper<ActivityDefSpecimenDef>().eq("activity_definition_id", manageDto.getId()));
// 获取租户ID并验证
Integer tenantId = null;
try {
tenantId = SecurityUtils.getLoginUser().getTenantId();
} catch (Exception e) {
log.warn("获取租户ID失败使用默认值", e);
}
// 根据ID查询【诊疗目录】详情
DiagnosisTreatmentDto diseaseTreatmentOne = activityDefinitionManageMapper.getDiseaseTreatmentOne(manageDto.getId(), tenantId);
if (diseaseTreatmentOne == null) {
log.warn("未找到诊疗目录id={}, tenantId={}", manageDto.getId(), tenantId);
// 即使未找到诊疗目录也继续保存使用ID作为名称
String activityDefinitionName = String.valueOf(manageDto.getId());
manageDto.getActivityDefDeviceDefs().forEach(activityDefDeviceDef -> {
activityDefDeviceDef.setActivityDefinitionId(manageDto.getId());
activityDefDeviceDef.setActivityDefinitionName(activityDefinitionName);
activityDefDeviceDefMapper.insert(activityDefDeviceDef);
});
manageDto.getActivityDefObservationDefs().forEach(activityDefObservationDef -> {
activityDefObservationDef.setActivityDefinitionId(manageDto.getId());
activityDefObservationDef.setActivityDefinitionName(activityDefinitionName);
activityDefObservationDefMapper.insert(activityDefObservationDef);
});
manageDto.getActivityDefSpecimenDefs().forEach(activityDefSpecimenDef -> {
activityDefSpecimenDef.setActivityDefinitionId(manageDto.getId());
activityDefSpecimenDef.setActivityDefinitionName(activityDefinitionName);
activityDefSpecimenDefMapper.insert(activityDefSpecimenDef);
});
} else {
// 正常保存
manageDto.getActivityDefDeviceDefs().forEach(activityDefDeviceDef -> {
activityDefDeviceDef.setActivityDefinitionId(manageDto.getId());
activityDefDeviceDef.setActivityDefinitionName(diseaseTreatmentOne.getName());
activityDefDeviceDefMapper.insert(activityDefDeviceDef);
});
manageDto.getActivityDefObservationDefs().forEach(activityDefObservationDef -> {
activityDefObservationDef.setActivityDefinitionId(manageDto.getId());
activityDefObservationDef.setActivityDefinitionName(diseaseTreatmentOne.getName());
activityDefObservationDefMapper.insert(activityDefObservationDef);
});
manageDto.getActivityDefSpecimenDefs().forEach(activityDefSpecimenDef -> {
activityDefSpecimenDef.setActivityDefinitionId(manageDto.getId());
activityDefSpecimenDef.setActivityDefinitionName(diseaseTreatmentOne.getName());
activityDefSpecimenDefMapper.insert(activityDefSpecimenDef);
});
}
log.info("保存检验项目设置成功id={}, deviceCount={}, observationCount={}, specimenCount={}",
manageDto.getId(),
manageDto.getActivityDefDeviceDefs().size(),
manageDto.getActivityDefObservationDefs().size(),
manageDto.getActivityDefSpecimenDefs().size());
return R.ok("保存成功");
} catch (Exception e) {
log.error("保存检验项目设置失败id={}, error={}", manageDto.getId(), e.getMessage(), e);
return R.fail("保存失败:" + e.getMessage());
}
//先全部删除项目下详情
activityDefDeviceDefMapper.delete(new QueryWrapper<ActivityDefDeviceDef>().eq("activity_definition_id", manageDto.getId()));
activityDefObservationDefMapper.delete(new QueryWrapper<ActivityDefObservationDef>().eq("activity_definition_id", manageDto.getId()));
activityDefSpecimenDefMapper.delete(new QueryWrapper<ActivityDefSpecimenDef>().eq("activity_definition_id", manageDto.getId()));
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
// 根据ID查询【诊疗目录】详情
DiagnosisTreatmentDto diseaseTreatmentOne = activityDefinitionManageMapper.getDiseaseTreatmentOne(manageDto.getId(), tenantId);
manageDto.getActivityDefDeviceDefs().forEach(activityDefDeviceDef -> {
activityDefDeviceDef.setActivityDefinitionId(manageDto.getId());
activityDefDeviceDef.setActivityDefinitionName(diseaseTreatmentOne.getName());
activityDefDeviceDefMapper.insert(activityDefDeviceDef);
});
manageDto.getActivityDefObservationDefs().forEach(activityDefObservationDef -> {
activityDefObservationDef.setActivityDefinitionId(manageDto.getId());
activityDefObservationDef.setActivityDefinitionName(diseaseTreatmentOne.getName());
activityDefObservationDefMapper.insert(activityDefObservationDef);
});
manageDto.getActivityDefSpecimenDefs().forEach(activityDefSpecimenDef -> {
activityDefSpecimenDef.setActivityDefinitionId(manageDto.getId());
activityDefSpecimenDef.setActivityDefinitionName(diseaseTreatmentOne.getName());
activityDefSpecimenDefMapper.insert(activityDefSpecimenDef);
});
return R.ok();
}
@Override

View File

@@ -22,8 +22,6 @@ import com.openhis.web.Inspection.dto.ObservationDefManageDto;
import com.openhis.web.Inspection.dto.ObservationDefManageInitDto;
import com.openhis.web.Inspection.dto.ObservationDefSelParam;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
@@ -44,8 +42,6 @@ import java.util.stream.Stream;
@RequiredArgsConstructor
public class ObservationManageAppServiceImpl implements IObservationManageAppService
{
private static final Logger log = LoggerFactory.getLogger(ObservationManageAppServiceImpl.class);
private final ObservationDefinitionMapper observationDefinitionMapper;
private final IObservationDefinitionService observationDefinitionService;
@@ -92,23 +88,9 @@ public class ObservationManageAppServiceImpl implements IObservationManageAppSer
@Override
public R<?> updateOrAddObservationDef(ObservationDefinition Observation) {
try {
Observation.setDeleteFlag(DelFlag.NO.getCode());
boolean result = observationDefinitionService.saveOrUpdate(Observation);
if (result) {
log.info("保存检验项目成功name={}, code={}, id={}",
Observation.getName(), Observation.getCode(), Observation.getId());
return R.ok("添加成功");
} else {
log.warn("保存检验项目失败name={}, code={}",
Observation.getName(), Observation.getCode());
return R.fail("添加失败:保存操作未成功");
}
} catch (Exception e) {
log.error("保存检验项目异常name={}, code={}, error={}",
Observation.getName(), Observation.getCode(), e.getMessage(), e);
return R.fail("添加失败:" + e.getMessage());
}
Observation.setDeleteFlag(DelFlag.NO.getCode());
observationDefinitionService.saveOrUpdate(Observation);
return R.ok(" 添加成功");
}
@Override

View File

@@ -23,7 +23,7 @@ public interface IOrganizationAppService {
* @param request 请求数据
* @return 机构树分页列表
*/
Page<OrganizationDto> getOrganizationTree(Integer pageNo, Integer pageSize, String name, Integer typeEnum, String classEnum,
Page<OrganizationDto> getOrganizationTree(Integer pageNo, Integer pageSize, String name, Integer typeEnum, Integer classEnum,
String sortField, String sortOrder, HttpServletRequest request);
/**

View File

@@ -39,7 +39,7 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
private AssignSeqUtil assignSeqUtil;
@Override
public Page<OrganizationDto> getOrganizationTree(Integer pageNo, Integer pageSize, String name, Integer typeEnum, String classEnum,
public Page<OrganizationDto> getOrganizationTree(Integer pageNo, Integer pageSize, String name, Integer typeEnum, Integer classEnum,
String sortField, String sortOrder, HttpServletRequest request) {
// 创建查询条件
LambdaQueryWrapper<Organization> queryWrapper = new LambdaQueryWrapper<>();
@@ -51,24 +51,8 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
if (typeEnum != null) {
queryWrapper.eq(Organization::getTypeEnum, typeEnum);
}
if (StringUtils.isNotEmpty(classEnum)) {
// 对于多选,需要处理逗号分隔的值
queryWrapper.and(wrapper -> {
String[] classEnums = classEnum.split(",");
for (String cls : classEnums) {
String trimmedCls = cls.trim();
// 使用OR连接多个条件来匹配逗号分隔的值
wrapper.or().and(subWrapper -> {
subWrapper.eq(Organization::getClassEnum, trimmedCls)
.or()
.likeRight(Organization::getClassEnum, trimmedCls + ",")
.or()
.likeLeft(Organization::getClassEnum, "," + trimmedCls)
.or()
.like(Organization::getClassEnum, "," + trimmedCls + ",");
});
}
});
if (classEnum != null) {
queryWrapper.eq(Organization::getClassEnum, classEnum);
}
// 创建Page对象
@@ -105,7 +89,7 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
OrganizationDto node = new OrganizationDto();
BeanUtils.copyProperties(record, node);
node.setTypeEnum_dictText(EnumUtils.getInfoByValue(OrganizationType.class, node.getTypeEnum()));
node.setClassEnum_dictText(formatClassEnumDictText(node.getClassEnum()));
node.setClassEnum_dictText(EnumUtils.getInfoByValue(OrganizationClass.class, node.getClassEnum()));
node.setActiveFlag_dictText(EnumUtils.getInfoByValue(AccountStatus.class, node.getActiveFlag()));
// 将当前节点加入映射
nodeMap.put(bNo, node);
@@ -146,7 +130,7 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
OrganizationDto organizationDto = new OrganizationDto();
BeanUtils.copyProperties(organization, organizationDto);
organizationDto.setTypeEnum_dictText(EnumUtils.getInfoByValue(OrganizationType.class, organizationDto.getTypeEnum()));
organizationDto.setClassEnum_dictText(formatClassEnumDictText(organizationDto.getClassEnum()));
organizationDto.setClassEnum_dictText(EnumUtils.getInfoByValue(OrganizationClass.class, organizationDto.getClassEnum()));
organizationDto.setActiveFlag_dictText(EnumUtils.getInfoByValue(AccountStatus.class, organizationDto.getActiveFlag()));
return R.ok(organizationDto, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[] {"机构信息查询"}));
@@ -237,35 +221,6 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
: R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00007, new Object[] {"机构信息停用"}));
}
/**
* 格式化多选的分类字典文本
*/
private String formatClassEnumDictText(String classEnum) {
if (StringUtils.isEmpty(classEnum)) {
return "";
}
String[] classEnums = classEnum.split(",");
List<String> dictTexts = new ArrayList<>();
for (String cls : classEnums) {
String trimmedCls = cls.trim();
if (StringUtils.isNotEmpty(trimmedCls)) {
try {
Integer enumValue = Integer.parseInt(trimmedCls);
String dictText = EnumUtils.getInfoByValue(OrganizationClass.class, enumValue);
if (dictText != null) {
dictTexts.add(dictText);
}
} catch (NumberFormatException e) {
// 如果转换失败,跳过该值
}
}
}
return String.join(",", dictTexts);
}
/**
* 校验字段是否为指定类中的有效属性
*/

View File

@@ -54,7 +54,7 @@ public class OrganizationController {
@RequestParam(value = "pageSize", defaultValue = "100") Integer pageSize,
@RequestParam(value = "name", required = false) String name,
@RequestParam(value = "typeEnum", required = false) Integer typeEnum,
@RequestParam(value = "classEnum", required = false) String classEnum,
@RequestParam(value = "classEnum", required = false) Integer classEnum,
@RequestParam(value = "sortField", required = false) String sortField,
@RequestParam(value = "sortOrder", required = false) String sortOrder, HttpServletRequest request) {
Page<OrganizationDto> organizationTree =

View File

@@ -3,7 +3,6 @@
*/
package com.openhis.web.basedatamanage.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
@@ -40,8 +39,7 @@ public class OrganizationDto {
private String typeEnum_dictText;
/** 机构分类枚举 */
@JsonFormat(shape = JsonFormat.Shape.STRING)
private String classEnum;
private Integer classEnum;
private String classEnum_dictText;
/** 拼音码 */

View File

@@ -157,7 +157,7 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
@Override
public List<OrgMetadata> getOrgMetadata() {
List<Organization> list =
iOrganizationService.getList(OrganizationType.DEPARTMENT.getValue(), String.valueOf(OrganizationClass.CLINIC.getValue()));
iOrganizationService.getList(OrganizationType.DEPARTMENT.getValue(), OrganizationClass.CLINIC.getValue());
List<OrgMetadata> orgMetadataList = new ArrayList<>();
OrgMetadata orgMetadata;
for (Organization organization : list) {

View File

@@ -27,7 +27,6 @@ import com.openhis.web.datadictionary.dto.*;
import com.openhis.web.datadictionary.mapper.ActivityDefinitionManageMapper;
import com.openhis.workflow.domain.ActivityDefinition;
import com.openhis.workflow.domain.ServiceRequest;
import com.openhis.workflow.mapper.ActivityDefinitionMapper;
import com.openhis.workflow.service.IActivityDefinitionService;
import com.openhis.workflow.service.IServiceRequestService;
import com.openhis.yb.service.YbManager;
@@ -64,8 +63,6 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
@Resource
private ActivityDefinitionManageMapper activityDefinitionManageMapper;
@Resource
private ActivityDefinitionMapper activityDefinitionMapper;
@Resource
private IItemDefinitionService itemDefinitionService;
@Resource
private ISysDictTypeService sysDictTypeService;
@@ -237,11 +234,7 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
ActivityDefinition activityDefinition = new ActivityDefinition();
BeanUtils.copyProperties(diagnosisTreatmentUpDto, activityDefinition);
// 显式设置新增的字段
activityDefinition.setSortOrder(diagnosisTreatmentUpDto.getSortOrder());
activityDefinition.setServiceRange(diagnosisTreatmentUpDto.getServiceRange());
// 拼音码
activityDefinition.setPyStr(ChineseConvertUtils.toPinyinFirstLetter(activityDefinition.getName()));
// 五笔码
@@ -259,31 +252,12 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
}
}
// 查询现有的价格定义
LambdaQueryWrapper<ChargeItemDefinition> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ChargeItemDefinition::getInstanceId, diagnosisTreatmentUpDto.getId())
.eq(ChargeItemDefinition::getInstanceTable, CommonConstants.TableName.WOR_ACTIVITY_DEFINITION);
ChargeItemDefinition existingItem = chargeItemDefinitionService.getOne(queryWrapper);
ChargeItemDefinition chargeItemDefinition = new ChargeItemDefinition();
chargeItemDefinition.setInstanceTable(CommonConstants.TableName.WOR_ACTIVITY_DEFINITION)
chargeItemDefinition.setYbType(diagnosisTreatmentUpDto.getYbType())
.setTypeCode(diagnosisTreatmentUpDto.getItemTypeCode())
.setInstanceTable(CommonConstants.TableName.WOR_ACTIVITY_DEFINITION)
.setInstanceId(diagnosisTreatmentUpDto.getId()).setPrice(diagnosisTreatmentUpDto.getRetailPrice())
.setPriceCode(diagnosisTreatmentUpDto.getPriceCode()).setChargeName(diagnosisTreatmentUpDto.getName());
// 如果前端没有提交财务类别,则保留原有的值
if (StringUtils.isEmpty(diagnosisTreatmentUpDto.getItemTypeCode()) && existingItem != null) {
chargeItemDefinition.setTypeCode(existingItem.getTypeCode());
} else {
chargeItemDefinition.setTypeCode(diagnosisTreatmentUpDto.getItemTypeCode());
}
// 如果前端没有提交医保类别,则保留原有的值
if (StringUtils.isEmpty(diagnosisTreatmentUpDto.getYbType()) && existingItem != null) {
chargeItemDefinition.setYbType(existingItem.getYbType());
} else {
chargeItemDefinition.setYbType(diagnosisTreatmentUpDto.getYbType());
}
// 插入操作记录
operationRecordService.addEntityOperationRecord(DbOpType.UPDATE.getCode(),
CommonConstants.TableName.WOR_ACTIVITY_DEFINITION, activityDefinition);
@@ -293,12 +267,9 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
// 更新子表,修改零售价,条件:单位
boolean upItemDetail1 = itemDefinitionService.updateItemDetail(chargeItemDefinition,
diagnosisTreatmentUpDto.getRetailPrice(), ConditionCode.UNIT.getCode());
// 更新子表,修改最高零售价,条件:限制只有当最高零售价不为null时才更新
boolean upItemDetail2 = true;
if (diagnosisTreatmentUpDto.getMaximumRetailPrice() != null) {
upItemDetail2 = itemDefinitionService.updateItemDetail(chargeItemDefinition,
diagnosisTreatmentUpDto.getMaximumRetailPrice(), ConditionCode.LIMIT.getCode());
}
// 更新子表,修改最高零售价,条件:限制
boolean upItemDetail2 = itemDefinitionService.updateItemDetail(chargeItemDefinition,
diagnosisTreatmentUpDto.getMaximumRetailPrice(), ConditionCode.LIMIT.getCode());
// 更新价格表
return upItemDef && upItemDetail1 && upItemDetail2
@@ -382,16 +353,9 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
ActivityDefinition activityDefinition = new ActivityDefinition();
BeanUtils.copyProperties(diagnosisTreatmentUpDto, activityDefinition);
// 显式设置新增的字段
activityDefinition.setSortOrder(diagnosisTreatmentUpDto.getSortOrder());
activityDefinition.setServiceRange(diagnosisTreatmentUpDto.getServiceRange());
// 如果前端没有传入编码则使用10位数基础采番
if (StringUtils.isEmpty(activityDefinition.getBusNo())) {
String code = assignSeqUtil.getSeq(AssignSeqEnum.ACTIVITY_DEFINITION_NUM.getPrefix(), 10);
activityDefinition.setBusNo(code);
}
// 使用10位数基础采番
String code = assignSeqUtil.getSeq(AssignSeqEnum.ACTIVITY_DEFINITION_NUM.getPrefix(), 10);
activityDefinition.setBusNo(code);
// 拼音码
activityDefinition.setPyStr(ChineseConvertUtils.toPinyinFirstLetter(activityDefinition.getName()));
// 五笔码
@@ -399,16 +363,6 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
// 新增外来诊疗目录
activityDefinition.setStatusEnum(PublicationStatus.ACTIVE.getValue());
// 检查编码是否已存在
List<ActivityDefinition> existingDefinitions = activityDefinitionMapper.selectList(
new LambdaQueryWrapper<ActivityDefinition>()
.eq(ActivityDefinition::getBusNo, activityDefinition.getBusNo())
);
if (!existingDefinitions.isEmpty()) {
return R.fail(null, "诊疗编码已存在:" + activityDefinition.getBusNo());
}
if (activityDefinitionService.addDiagnosisTreatment(activityDefinition)) {
// 调用医保目录对照接口
String ybSwitch = SecurityUtils.getLoginUser().getOptionJson().getString(CommonConstants.Option.YB_SWITCH); // 医保开关

View File

@@ -125,9 +125,4 @@ public class DiagnosisTreatmentDto {
*/
private String priceCode;
/** 序号 */
private Integer sortOrder;
/** 服务范围 */
private String serviceRange;
}

View File

@@ -112,9 +112,4 @@ public class DiagnosisTreatmentUpDto {
*/
private String priceCode;
/** 序号 */
private Integer sortOrder;
/** 服务范围 */
private String serviceRange;
}

View File

@@ -13,7 +13,7 @@ import javax.annotation.Resource;
import java.util.List;
@Service
public class DoctorPhraseAppServiceImpl implements IDoctorPhraseAppService {
public class DoctorPhraesAppServiceImpl implements IDoctorPhraseAppService {
@Resource
private IDoctorPhraseService doctorPhraseService;

View File

@@ -38,7 +38,6 @@ import com.openhis.workflow.service.IDeviceDispenseService;
import com.openhis.workflow.service.IDeviceRequestService;
import com.openhis.workflow.service.IServiceRequestService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -93,7 +92,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
@Resource
DoctorStationSendApplyUtil doctorStationSendApplyUtil;
/**
* 查询医嘱信息
*
@@ -113,20 +111,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
public IPage<AdviceBaseDto> getAdviceBaseInfo(AdviceBaseDto adviceBaseDto, String searchKey, Long locationId,
List<Long> adviceDefinitionIdParamList, Long organizationId, Integer pageNo, Integer pageSize,
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();
@@ -188,190 +172,98 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
.add(cfg.getLocationId());
}
}
// 费用定价子表信息 - 使用分批处理避免大量参数问题
List<AdvicePriceDto> childCharge = new ArrayList<>();
if (chargeItemDefinitionIdList != null && !chargeItemDefinitionIdList.isEmpty()) {
// 分批处理每批最多1000个ID增加批次大小以减少查询次数
int batchSize = 1000;
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()));
}
}
// 费用定价子表信息
List<AdvicePriceDto> childCharge = doctorStationAdviceAppMapper
.getChildCharge(ConditionCode.LOT_NUMBER_PRICE.getCode(), chargeItemDefinitionIdList);
// 费用定价主表信息
List<AdvicePriceDto> mainCharge
= doctorStationAdviceAppMapper.getMainCharge(chargeItemDefinitionIdList, PublicationStatus.ACTIVE.getValue());
String unitCode = ""; // 包装单位
Long chargeItemDefinitionId; // 费用定价主表ID
for (AdviceBaseDto baseDto : adviceBaseDtoList) {
String tableName = baseDto.getAdviceTableName();
if (CommonConstants.TableName.MED_MEDICATION_DEFINITION.equals(tableName)) { // 药品
// 是否皮试
baseDto
.setSkinTestFlag_enumText(EnumUtils.getInfoByValue(Whether.class, baseDto.getSkinTestFlag()));
// 是否为注射药物
baseDto.setInjectFlag_enumText(EnumUtils.getInfoByValue(Whether.class, baseDto.getInjectFlag()));
// fallthrough to 耗材处理逻辑(保持原有逻辑)
// 每一条医嘱的库存集合信息 , 包装单位库存前端计算
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());
switch (baseDto.getAdviceTableName()) {
case CommonConstants.TableName.MED_MEDICATION_DEFINITION: // 药品
// 是否皮试
baseDto
.setSkinTestFlag_enumText(EnumUtils.getInfoByValue(Whether.class, baseDto.getSkinTestFlag()));
// 是否为注射药物
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());
}
}
}
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.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()
if (!inventoryList.isEmpty() && !medLocationConfig.isEmpty()) {
// 第一步在medLocationConfig中匹配categoryCode
AdviceInventoryDto result1 = medLocationConfig.stream()
.filter(dto -> baseDto.getCategoryCode().equals(dto.getCategoryCode())).findFirst()
.orElse(null);
if (result2 != null && result2.getLotNumber() != null) {
baseDto.setDefaultLotNumber(result2.getLotNumber());
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();
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()))
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().equals(finalChargeItemDefinitionId)
&& e.getConditionValue().equals(adviceInventoryDto.getLotNumber()))
.peek(e -> e.setUnitCode(finalUnitCode)) // 设置 unitCode
.collect(Collectors.toList());
// 价格信息
baseDto.setPriceList(priceList);
// 活动类型
baseDto.setActivityType_enumText(
EnumUtils.getInfoByValue(ActivityType.class, baseDto.getActivityType()));
// 从定价主表取价格(适用于统一零售价场景)
List<AdvicePriceDto> mainPrice = mainCharge.stream()
.filter(e -> 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);
break;
case CommonConstants.TableName.WOR_ACTIVITY_DEFINITION: // 诊疗
List<AdvicePriceDto> priceList
= mainCharge.stream().filter(e -> baseDto.getChargeItemDefinitionId().equals(e.getDefinitionId()))
.collect(Collectors.toList());
// 价格信息
baseDto.setPriceList(priceList);
// 活动类型
baseDto.setActivityType_enumText(
EnumUtils.getInfoByValue(ActivityType.class, baseDto.getActivityType()));
break;
default:
break;
}
}
return adviceBaseInfo;
}
@@ -396,99 +288,83 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
*/
@Override
public R<?> saveAdvice(AdviceSaveParam adviceSaveParam, String adviceOpType) {
try {
// 患者挂号对应的科室id
Long organizationId = adviceSaveParam.getOrganizationId();
// 医嘱分类信息
List<AdviceSaveDto> adviceSaveList = adviceSaveParam.getAdviceSaveList();
// 药品
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> activityList = adviceSaveList.stream()
.filter(e -> ItemType.ACTIVITY.getValue().equals(e.getAdviceType())).collect(Collectors.toList());
// 患者挂号对应的科室id
Long organizationId = adviceSaveParam.getOrganizationId();
// 医嘱分类信息
List<AdviceSaveDto> adviceSaveList = adviceSaveParam.getAdviceSaveList();
// 药品
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> activityList = adviceSaveList.stream()
.filter(e -> ItemType.ACTIVITY.getValue().equals(e.getAdviceType())).collect(Collectors.toList());
/**
* 保存时,校验库存
*/
if (AdviceOpType.SAVE_ADVICE.getCode().equals(adviceOpType)) {
List<AdviceSaveDto> medUpdateList
= medicineList.stream().filter(e -> e.getRequestId() != null).collect(Collectors.toList());
List<AdviceSaveDto> devUpdateList
= deviceList.stream().filter(e -> e.getRequestId() != null).collect(Collectors.toList());
// 编辑时,释放本身占用的库存发放
for (AdviceSaveDto adviceSaveDto : medUpdateList) {
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);
}
/**
* 保存时,校验库存
*/
if (AdviceOpType.SAVE_ADVICE.getCode().equals(adviceOpType)) {
List<AdviceSaveDto> medUpdateList
= medicineList.stream().filter(e -> e.getRequestId() != null).collect(Collectors.toList());
List<AdviceSaveDto> devUpdateList
= deviceList.stream().filter(e -> e.getRequestId() != null).collect(Collectors.toList());
// 编辑时,释放本身占用的库存发放
for (AdviceSaveDto adviceSaveDto : medUpdateList) {
iMedicationDispenseService.deleteMedicationDispense(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));
for (AdviceSaveDto adviceSaveDto : devUpdateList) {
iDeviceDispenseService.deleteDeviceDispense(adviceSaveDto.getRequestId());
}
// 数据变更后清理相关缓存
clearRelatedCache();
return R.ok(medRequestIdList,
MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"门诊医嘱"}));
} catch (Exception e) {
// 异常处理
throw e;
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);
}
}
}
// 当前时间
Date curDate = new Date();
// 医嘱签发编码
String signCode = assignSeqUtil.getSeq(AssignSeqEnum.ADVICE_SIGN.getPrefix(), 10);
/**
* 清理相关缓存
*/
private void clearRelatedCache() {
// 目前不使用缓存,此方法为空实现
// 如果将来启用缓存,可以在这里实现缓存清理逻辑
/**
* 处理药品请求
*/
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));
}
return R.ok(medRequestIdList,
MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"门诊医嘱"}));
}
/**
@@ -1080,12 +956,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
*/
@Override
public R<?> getProofResult(Long encounterId) {
// 检查参数
if (encounterId == null) {
log.warn("获取检验结果时就诊ID为空");
return R.ok(new ArrayList<>());
}
// LIS查看报告地址
String lisReportUrl = TenantOptionUtil.getOptionContent(TenantOptionDict.LIS_REPORT_URL);
if (StringUtils.isEmpty(lisReportUrl)) {
@@ -1110,12 +980,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
*/
@Override
public R<?> getTestResult(Long encounterId) {
// 检查参数
if (encounterId == null) {
log.warn("获取检查结果时就诊ID为空");
return R.ok(new ArrayList<>());
}
// PACS查看报告地址
String pacsReportUrl = TenantOptionUtil.getOptionContent(TenantOptionDict.PACS_REPORT_URL);
if (StringUtils.isEmpty(pacsReportUrl)) {

View File

@@ -22,10 +22,8 @@ import com.openhis.web.doctorstation.dto.PrescriptionInfoBaseDto;
import com.openhis.web.doctorstation.dto.PrescriptionInfoDetailDto;
import com.openhis.web.doctorstation.dto.ReceptionStatisticsDto;
import com.openhis.web.doctorstation.mapper.DoctorStationMainAppMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@@ -35,8 +33,8 @@ import java.util.stream.Collectors;
/**
* 医生站-主页面 应用实现类
*/
//@Slf4j
@Service
@Slf4j
public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppService {
@Resource
@@ -59,9 +57,6 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
@Resource
IDoctorStationChineseMedicalAppService iDoctorStationChineseMedicalAppService;
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 查询就诊患者信息
*
@@ -102,6 +97,9 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
ParticipantType.REGISTRATION_DOCTOR.getCode(), ParticipantType.ADMITTER.getCode(), userId,
currentUserOrganizationId, pricingFlag, EncounterStatus.PLANNED.getValue(),
EncounterActivityStatus.ACTIVE.getValue(), queryWrapper);
//日志输出就诊患者信息,patientInfo
// log.debug("就诊患者信息: 总数={}, 记录数={}, 数据={}",
// patientInfo.getTotal(), patientInfo.getRecords().size(), patientInfo.getRecords());
patientInfo.getRecords().forEach(e -> {
// 性别
e.setGenderEnum_enumText(EnumUtils.getInfoByValue(AdministrativeGender.class, e.getGenderEnum()));
@@ -165,51 +163,11 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
*/
@Override
public R<?> completeEncounter(Long encounterId) {
// 1. 检查当前患者状态是否为就诊中20
Encounter encounter = encounterMapper.selectById(encounterId);
if (encounter == null) {
return R.fail("就诊记录不存在");
}
// 检查状态是否为就诊中
if (!EncounterStatus.IN_PROGRESS.getValue().equals(encounter.getStatusEnum())) {
return R.fail("当前患者不在就诊中状态");
}
// 2. 更新状态为已完成30并写入完成时间
Date now = new Date();
int update = encounterMapper.update(null,
new LambdaUpdateWrapper<Encounter>().eq(Encounter::getId, encounterId)
.set(Encounter::getStatusEnum, EncounterStatus.DISCHARGED.getValue())
.set(Encounter::getSubjectStatusEnum, EncounterSubjectStatus.DEPARTED.getValue())
.set(Encounter::getEndTime, now));
if (update <= 0) {
return R.fail("更新状态失败");
}
// 3. 写入审计日志
try {
String username = SecurityUtils.getUsernameSafe();
String sql = "INSERT INTO sys_oper_log "
+ "(title,oper_time,method,request_method,oper_name,oper_url,oper_param,json_result) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
jdbcTemplate.update(sql,
"完诊操作",
now,
"DoctorStationMainAppServiceImpl.completeEncounter()",
"POST",
username,
"/doctorstation/main/complete-encounter",
"{\"encounterId\": " + encounterId + "}",
"{\"code\": 200, \"msg\": \"就诊完成\", \"data\": null}");
} catch (Exception e) {
log.error("写入完诊审计日志失败", e);
// 审计日志失败不影响主流程
}
return R.ok("就诊完成");
.set(Encounter::getSubjectStatusEnum, EncounterSubjectStatus.DEPARTED.getValue()));
return update > 0 ? R.ok() : R.fail();
}
/**

View File

@@ -34,10 +34,6 @@ public class DoctorStationPtDetailsAppServiceImpl implements IDoctorStationPtDet
*/
@Override
public R<?> getPtDetails(Long encounterId) {
// 检查参数
if (encounterId == null) {
return R.fail("就诊ID不能为空");
}
// 收费状态List(1:待收费,2:待结算,5:已结算)
List<Integer> statusList = new ArrayList<>();
@@ -53,10 +49,6 @@ public class DoctorStationPtDetailsAppServiceImpl implements IDoctorStationPtDet
ChargeItemContext.ACTIVITY.getValue(), ClinicalStatus.ACTIVE.getValue(), LocationForm.BED.getValue(),
ParticipantType.ADMITTER.getCode(), statusList);
if (patientDetailsDto == null) {
return R.fail("未找到患者详情信息");
}
// 住院的场合,获取现在时间,计算住院天数
if (patientDetailsDto.getClassEnum() == EncounterClass.IMP.getValue()) {
// 截至时间,用于计算当前时刻下显示的住院天数

View File

@@ -106,7 +106,7 @@ public class DoctorStationAdviceController {
* @return 医嘱请求数据
*/
@GetMapping(value = "/request-base-info")
public R<?> getRequestBaseInfo(@RequestParam(required = false) Long encounterId) {
public R<?> getRequestBaseInfo(@RequestParam Long encounterId) {
return iDoctorStationAdviceAppService.getRequestBaseInfo(encounterId);
}
@@ -114,11 +114,10 @@ public class DoctorStationAdviceController {
* 查询历史医嘱请求数据
*
* @param patientId 病人id
* @param encounterId 就诊id
* @return 历史医嘱请求数据
*/
@GetMapping(value = "/request-history-info")
public R<?> getRequestHistoryInfo(@RequestParam Long patientId, @RequestParam(required = false) Long encounterId) {
public R<?> getRequestHistoryInfo(@RequestParam Long patientId, Long encounterId) {
return iDoctorStationAdviceAppService.getRequestHistoryInfo(patientId, encounterId);
}
@@ -139,7 +138,7 @@ public class DoctorStationAdviceController {
* @return 就诊费用性质
*/
@GetMapping(value = "/get-encounter-contract")
public R<?> getEncounterContract(@RequestParam(required = false) Long encounterId) {
public R<?> getEncounterContract(@RequestParam Long encounterId) {
return iDoctorStationAdviceAppService.getEncounterContract(encounterId);
}
@@ -163,7 +162,7 @@ public class DoctorStationAdviceController {
* @return 检验url相关参数
*/
@GetMapping(value = "/proof-result")
public R<?> getProofResult(@RequestParam(value = "encounterId", required = false) Long encounterId) {
public R<?> getProofResult(@RequestParam(value = "encounterId") Long encounterId) {
return iDoctorStationAdviceAppService.getProofResult(encounterId);
}
@@ -174,7 +173,7 @@ public class DoctorStationAdviceController {
* @return 检查url相关参数
*/
@GetMapping(value = "/test-result")
public R<?> getTestResult(@RequestParam(value = "encounterId", required = false) Long encounterId) {
public R<?> getTestResult(@RequestParam(value = "encounterId") Long encounterId) {
return iDoctorStationAdviceAppService.getTestResult(encounterId);
}

View File

@@ -88,7 +88,7 @@ public class DoctorStationChineseMedicalController {
* @return 中医就诊诊断信息
*/
@GetMapping(value = "/get-tcm-encounter-diagnosis")
public R<?> getTcmEncounterDiagnosis(@RequestParam(required = false) Long encounterId) {
public R<?> getTcmEncounterDiagnosis(@RequestParam Long encounterId) {
return iDoctorStationChineseMedicalAppService.getTcmEncounterDiagnosis(encounterId);
}
@@ -158,7 +158,7 @@ public class DoctorStationChineseMedicalController {
* @return 医嘱请求数据
*/
@GetMapping(value = "/tcm-request-base-info")
public R<?> getTcmRequestBaseInfo(@RequestParam(required = false) Long encounterId) {
public R<?> getTcmRequestBaseInfo(@RequestParam Long encounterId) {
return iDoctorStationChineseMedicalAppService.getTcmRequestBaseInfo(encounterId);
}
@@ -170,7 +170,7 @@ public class DoctorStationChineseMedicalController {
* @return 中医历史医嘱请求数据
*/
@GetMapping(value = "/tcm-request-history-info")
public R<?> getTcmRequestHistoryInfo(@RequestParam Long patientId, @RequestParam(required = false) Long encounterId) {
public R<?> getTcmRequestHistoryInfo(@RequestParam Long patientId, Long encounterId) {
return iDoctorStationChineseMedicalAppService.getTcmRequestHistoryInfo(patientId, encounterId);
}

View File

@@ -162,12 +162,12 @@ public class DoctorStationDiagnosisController {
/**
* 查询就诊诊断信息
*
*
* @param encounterId 就诊id
* @return 就诊诊断信息
*/
@GetMapping(value = "/get-encounter-diagnosis")
public R<?> getEncounterDiagnosis(@RequestParam(required = false) Long encounterId) {
public R<?> getEncounterDiagnosis(@RequestParam Long encounterId) {
return iDoctorStationDiagnosisAppService.getEncounterDiagnosis(encounterId);
}
@@ -178,7 +178,7 @@ public class DoctorStationDiagnosisController {
* @return 就诊诊断信息
*/
@GetMapping(value = "/get-encounter-diagnosis-ele")
public R<?> getEncounterDiagnosisByEncounterId(@RequestParam(required = false) Long encounterId,@RequestParam String searchKey) {
public R<?> getEncounterDiagnosisByEncounterId(@RequestParam Long encounterId,@RequestParam String searchKey) {
return iDoctorStationDiagnosisAppService.getEncounterDiagnosisByEncounterId(encounterId,searchKey);
}

View File

@@ -56,7 +56,7 @@ public class DoctorStationEmrController {
* @return 病历详情
*/
@GetMapping("/emr-detail")
public R<?> getEmrDetail(@RequestParam(value = "encounterId", required = false) Long encounterId) {
public R<?> getEmrDetail(@RequestParam(value = "encounterId") Long encounterId) {
return iDoctorStationEmrAppService.getEmrDetail(encounterId);
}

View File

@@ -64,16 +64,8 @@ public class DoctorStationMainController {
* @return 结果
*/
@GetMapping(value = "/receive-encounter")
public R<?> receiveEncounter(@RequestParam(value = "encounterId", required = false) String encounterId) {
if (encounterId == null || "undefined".equals(encounterId) || "null".equals(encounterId)) {
return R.fail("就诊ID不能为空");
}
try {
Long id = Long.parseLong(encounterId);
return iDoctorStationMainAppService.receiveEncounter(id);
} catch (NumberFormatException e) {
return R.fail("就诊ID格式错误");
}
public R<?> receiveEncounter(@RequestParam Long encounterId) {
return iDoctorStationMainAppService.receiveEncounter(encounterId);
}
/**
@@ -83,16 +75,8 @@ public class DoctorStationMainController {
* @return 结果
*/
@GetMapping(value = "/leave-encounter")
public R<?> leaveEncounter(@RequestParam(value = "encounterId", required = false) String encounterId) {
if (encounterId == null || "undefined".equals(encounterId) || "null".equals(encounterId)) {
return R.fail("就诊ID不能为空");
}
try {
Long id = Long.parseLong(encounterId);
return iDoctorStationMainAppService.leaveEncounter(id);
} catch (NumberFormatException e) {
return R.fail("就诊ID格式错误");
}
public R<?> leaveEncounter(@RequestParam Long encounterId) {
return iDoctorStationMainAppService.leaveEncounter(encounterId);
}
/**
@@ -102,16 +86,8 @@ public class DoctorStationMainController {
* @return 结果
*/
@GetMapping(value = "/complete-encounter")
public R<?> completeEncounter(@RequestParam(value = "encounterId", required = false) String encounterId) {
if (encounterId == null || "undefined".equals(encounterId) || "null".equals(encounterId)) {
return R.fail("就诊ID不能为空");
}
try {
Long id = Long.parseLong(encounterId);
return iDoctorStationMainAppService.completeEncounter(id);
} catch (NumberFormatException e) {
return R.fail("就诊ID格式错误");
}
public R<?> completeEncounter(@RequestParam Long encounterId) {
return iDoctorStationMainAppService.completeEncounter(encounterId);
}
/**
@@ -121,16 +97,8 @@ public class DoctorStationMainController {
* @return 结果
*/
@GetMapping(value = "/cancel-encounter")
public R<?> cancelEncounter(@RequestParam(value = "encounterId", required = false) String encounterId) {
if (encounterId == null || "undefined".equals(encounterId) || "null".equals(encounterId)) {
return R.fail("就诊ID不能为空");
}
try {
Long id = Long.parseLong(encounterId);
return iDoctorStationMainAppService.cancelEncounter(id);
} catch (NumberFormatException e) {
return R.fail("就诊ID格式错误");
}
public R<?> cancelEncounter(@RequestParam Long encounterId) {
return iDoctorStationMainAppService.cancelEncounter(encounterId);
}
/**

View File

@@ -32,7 +32,7 @@ public class DoctorStationPtDetailsController {
* @return 患者详情
*/
@GetMapping(value = "/patient-details")
public R<?> getPtDetails(@RequestParam(required = false) Long encounterId) {
public R<?> getPtDetails(@RequestParam Long encounterId) {
return doctorStationPtDetailsAppService.getPtDetails(encounterId);
}

View File

@@ -89,18 +89,15 @@ public class TodayOutpatientController {
/**
* 获取患者就诊详情
*
*
* @param encounterId 就诊记录ID
* @param request HTTP请求
* @return 患者就诊详情
*/
@GetMapping("/patients/{encounterId}")
public R<TodayOutpatientPatientDto> getPatientDetail(
@PathVariable("encounterId") Long encounterId,
@PathVariable("encounterId") Long encounterId,
HttpServletRequest request) {
if (encounterId == null) {
return R.fail("就诊记录ID不能为空");
}
TodayOutpatientPatientDto patient = todayOutpatientService.getPatientDetail(encounterId, request);
return R.ok(patient);
}

View File

@@ -187,7 +187,7 @@ public class PatientHomeAppServiceImpl implements IPatientHomeAppService {
@Override
public List<OrgMetadata> getCaty() {
List<Organization> list = iOrganizationService.getList(OrganizationType.DEPARTMENT.getValue(),
OrganizationClass.INPATIENT.getCode());
OrganizationClass.INPATIENT.getValue());
List<OrgMetadata> orgMetadataList = new ArrayList<>();
OrgMetadata orgMetadata;
for (Organization organization : list) {
@@ -265,19 +265,16 @@ public class PatientHomeAppServiceImpl implements IPatientHomeAppService {
encounterService.saveOrUpdateEncounter(encounter);
// 2.就诊位置表变更
// 直接更新指定ID的就诊位置记录
// 就诊位置ID变更
EncounterLocation encounterLocation = new EncounterLocation();
encounterLocation.setId(encounterLocationId)
// 设置就诊ID
.setEncounterId(encounterId)
// 设置位置ID
.setLocationId(locationId)
// 设置状态枚举
.setStatusEnum(EncounterActivityStatus.COMPLETED.getValue())
// 设置物理枚举为 8:病床
.setFormEnum(LocationForm.BED.getValue());
// 直接更新指定ID的记录
encounterSuccess = encounterLocationService.updateById(encounterLocation);
encounterLocationService.saveOrUpdateEncounterLocation(encounterLocation);
// 3.位置表
// 旧病床状态变更(空闲)

View File

@@ -94,7 +94,7 @@ public class MedicalDeviceDispenseAppServiceImpl implements IMedicalDeviceDispen
// 获取科室下拉选列表
List<Organization> organizationList
= organizationService.getList(OrganizationType.DEPARTMENT.getValue(), String.valueOf(OrganizationClass.CLINIC.getValue()));
= organizationService.getList(OrganizationType.DEPARTMENT.getValue(), OrganizationClass.CLINIC.getValue());
List<DispenseInitDto.DepartmentOption> organizationOptions = organizationList.stream()
.map(organization -> new DispenseInitDto.DepartmentOption(organization.getId(), organization.getName()))
.collect(Collectors.toList());

View File

@@ -130,7 +130,7 @@ public class ReturnMedicineAppServiceImpl implements IReturnMedicineAppService {
// 获取科室下拉选列表
List<Organization> organizationList
= iOrganizationService.getList(OrganizationType.DEPARTMENT.getValue(), String.valueOf(OrganizationClass.CLINIC.getValue()));
= iOrganizationService.getList(OrganizationType.DEPARTMENT.getValue(), OrganizationClass.CLINIC.getValue());
List<ReturnMedicineInitDto.DepartmentOption> organizationOptions = organizationList.stream().map(
organization -> new ReturnMedicineInitDto.DepartmentOption(organization.getId(), organization.getName()))
.collect(Collectors.toList());

View File

@@ -127,7 +127,7 @@ public class WesternMedicineDispenseAppServiceImpl implements IWesternMedicineDi
// 获取科室下拉选列表
List<Organization> organizationList
= organizationService.getList(OrganizationType.DEPARTMENT.getValue(), String.valueOf(OrganizationClass.CLINIC.getValue()));
= organizationService.getList(OrganizationType.DEPARTMENT.getValue(), OrganizationClass.CLINIC.getValue());
List<DispenseInitDto.DepartmentOption> organizationOptions = organizationList.stream()
.map(organization -> new DispenseInitDto.DepartmentOption(organization.getId(), organization.getName()))
.collect(Collectors.toList());

View File

@@ -221,11 +221,6 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
*/
@Override
public List<RequestFormQueryDto> getRequestForm(Long encounterId, String typeCode) {
// 检查参数
if (encounterId == null) {
return new java.util.ArrayList<>(); // 返回空列表而不是查询数据库
}
List<RequestFormQueryDto> requestFormList = requestFormManageAppMapper.getRequestForm(encounterId, typeCode);
for (RequestFormQueryDto requestFormQueryDto : requestFormList) {
// 查询处方详情

View File

@@ -70,15 +70,12 @@ public class RequestFormManageController {
/**
* 查询检查申请单
*
*
* @param encounterId 就诊id
* @return 检查申请单
*/
@GetMapping(value = "/get-check")
public R<?> getCheckRequestForm(@RequestParam(required = false) Long encounterId) {
if (encounterId == null) {
return R.fail("就诊ID不能为空");
}
public R<?> getCheckRequestForm(@RequestParam Long encounterId) {
return R.ok(iRequestFormManageAppService.getRequestForm(encounterId, ActivityDefCategory.TEST.getCode()));
}
@@ -89,10 +86,7 @@ public class RequestFormManageController {
* @return 检验申请单
*/
@GetMapping(value = "/get-inspection")
public R<?> getInspectionRequestForm(@RequestParam(required = false) Long encounterId) {
if (encounterId == null) {
return R.fail("就诊ID不能为空");
}
public R<?> getInspectionRequestForm(@RequestParam Long encounterId) {
return R.ok(iRequestFormManageAppService.getRequestForm(encounterId, ActivityDefCategory.PROOF.getCode()));
}
@@ -103,10 +97,7 @@ public class RequestFormManageController {
* @return 输血申请单
*/
@GetMapping(value = "/get-blood-transfusion")
public R<?> getBloodTransfusionRequestForm(@RequestParam(required = false) Long encounterId) {
if (encounterId == null) {
return R.fail("就诊ID不能为空");
}
public R<?> getBloodTransfusionRequestForm(@RequestParam Long encounterId) {
return R.ok(iRequestFormManageAppService.getRequestForm(encounterId, ActivityDefCategory.METACHYSIS.getCode()));
}
@@ -117,10 +108,7 @@ public class RequestFormManageController {
* @return 手术申请单
*/
@GetMapping(value = "/get-surgery")
public R<?> getSurgeryRequestForm(@RequestParam(required = false) Long encounterId) {
if (encounterId == null) {
return R.fail("就诊ID不能为空");
}
public R<?> getSurgeryRequestForm(@RequestParam Long encounterId) {
return R.ok(iRequestFormManageAppService.getRequestForm(encounterId, ActivityDefCategory.PROCEDURE.getCode()));
}

View File

@@ -45,10 +45,6 @@ public class IPrintReportAppServiceImpl implements IPrintReportAppService {
*/
@Override
public R<?> disposalPrint(Long encounterId) {
// 检查参数
if (encounterId == null) {
return R.ok(new java.util.ArrayList<>()); // 返回空列表而不是错误
}
List<DisposalDto> disposalList = printReportMapper.getDisposalList(encounterId);
@@ -75,10 +71,6 @@ public class IPrintReportAppServiceImpl implements IPrintReportAppService {
*/
@Override
public R<?> checkApplicationPrint(Long encounterId) {
// 检查参数
if (encounterId == null) {
return R.ok(new java.util.ArrayList<>()); // 返回空列表而不是错误
}
List<CkInspAppDto> checkList =
printReportMapper.getCheckInspectionList(encounterId, YbRxItemTypeCode.MEDICAL_IMAGING.getValue());
@@ -106,11 +98,6 @@ public class IPrintReportAppServiceImpl implements IPrintReportAppService {
*/
@Override
public R<?> inspectionApplicationPrint(Long encounterId) {
// 检查参数
if (encounterId == null) {
return R.ok(new java.util.ArrayList<>()); // 返回空列表而不是错误
}
List<CkInspAppDto> inspectionList =
printReportMapper.getCheckInspectionList(encounterId, YbRxItemTypeCode.LAB_TEST.getValue());
@@ -138,10 +125,6 @@ public class IPrintReportAppServiceImpl implements IPrintReportAppService {
*/
@Override
public R<?> prescriptionPrint(String prescriptionNo, Long encounterId) {
// 检查参数
if (encounterId == null) {
return R.ok(new java.util.ArrayList<>()); // 返回空列表而不是错误
}
List<PrescriptionPrintDto> list = printReportMapper.getPrescriptionList(prescriptionNo, encounterId);
// 获取所属医院id

View File

@@ -52,7 +52,7 @@ public class RegisterReportAppServiceImpl implements IRegisterReportAppService {
RegisterReportInitDto initDto = new RegisterReportInitDto();
// 查询科室列表
List<Organization> organizationList =
organizationService.getList(OrganizationType.DEPARTMENT.getValue(), String.valueOf(OrganizationClass.CLINIC.getValue()));
organizationService.getList(OrganizationType.DEPARTMENT.getValue(), OrganizationClass.CLINIC.getValue());
// 科室
List<RegisterReportInitDto.longCommonStatusOption> departmentOptions = organizationList.stream()
.map(organization -> new RegisterReportInitDto.longCommonStatusOption(organization.getId(),

View File

@@ -34,7 +34,7 @@ public class PrintReportController {
* @return 处置单信息
*/
@GetMapping(value = "/disposal-print")
public R<?> disposalPrint(@RequestParam(required = false) Long encounterId) {
public R<?> disposalPrint(@RequestParam Long encounterId) {
return printReportService.disposalPrint(encounterId);
}
@@ -46,7 +46,7 @@ public class PrintReportController {
* @return 检验申请单信息
*/
@GetMapping(value = "/check-print")
public R<?> checkApplicationPrint(@RequestParam(required = false) Long encounterId) {
public R<?> checkApplicationPrint(@RequestParam Long encounterId) {
return printReportService.checkApplicationPrint(encounterId);
}
@@ -57,7 +57,7 @@ public class PrintReportController {
* @return 检验申请单信息
*/
@GetMapping(value = "/inspection-print")
public R<?> inspectionApplicationPrint(@RequestParam(required = false) Long encounterId) {
public R<?> inspectionApplicationPrint(@RequestParam Long encounterId) {
return printReportService.inspectionApplicationPrint(encounterId);
}
@@ -69,7 +69,7 @@ public class PrintReportController {
* @return 处方单信息
*/
@GetMapping(value = "/prescription-print")
public R<?> prescriptionPrint(@RequestParam String prescriptionNo, @RequestParam(required = false) Long encounterId) {
public R<?> prescriptionPrint(@RequestParam String prescriptionNo, @RequestParam Long encounterId) {
return printReportService.prescriptionPrint(prescriptionNo, encounterId);
}

View File

@@ -31,9 +31,7 @@
T3.maximum_retail_price,
T3.chrgitm_lv,
T3.children_json,
T3.pricing_flag,
T3.sort_order,
T3.service_range
T3.pricing_flag
FROM
(
SELECT
@@ -63,9 +61,7 @@
T2.price as retail_price,
T4.amount as maximum_retail_price,
T1.children_json,
T1.pricing_flag,
T1.sort_order,
T1.service_range
T1.pricing_flag
FROM wor_activity_definition T1
LEFT JOIN adm_charge_item_definition T2 ON T1.id = T2.instance_id
LEFT JOIN adm_charge_item_definition T5 ON T5.instance_id = T1.id AND T5.instance_table = 'wor_activity_definition'
@@ -124,9 +120,7 @@
) as maximum_retail_price,
T1.chrgitm_lv,
T1.children_json,
T1.pricing_flag,
T1.sort_order,
T1.service_range
T1.pricing_flag
FROM wor_activity_definition T1
LEFT JOIN adm_charge_item_definition T2 ON T1.id = T2.instance_id
<where>

View File

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

View File

@@ -26,29 +26,11 @@
<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>
<!-- MyBatis-Plus 支持 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- CORE-->
<dependency>
<groupId>com.core</groupId>
<artifactId>core-admin</artifactId>
</dependency>
</dependency>
<dependency>
<groupId>com.core</groupId>
<artifactId>core-common</artifactId>

View File

@@ -56,16 +56,6 @@
<artifactId>lombok</artifactId>
<scope>compile</scope>
</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>

View File

@@ -73,12 +73,4 @@ public class OperatingRoom extends HisBaseEntity {
public OperatingRoom() {
this.statusEnum = LocationStatus.ACTIVE.getValue();
}
public Integer getRoomTypeEnum() {
return roomTypeEnum;
}
public void setRoomTypeEnum(Integer roomTypeEnum) {
this.roomTypeEnum = roomTypeEnum;
}
}

View File

@@ -4,7 +4,6 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
@@ -41,8 +40,7 @@ public class Organization extends HisBaseEntity {
private Integer typeEnum;
/** 机构分类枚举 */
@JsonFormat(shape = JsonFormat.Shape.STRING)
private String classEnum;
private Integer classEnum;
/** 拼音码 */
private String pyStr;

View File

@@ -37,7 +37,7 @@ public interface IOrganizationService extends IService<Organization> {
* @param organizationClass 机构分类
* @return 机构下拉列表
*/
List<Organization> getList(Integer organizationType, String organizationClass);
List<Organization> getList(Integer organizationType, Integer organizationClass);
/**
* 根据id查询科室集合

View File

@@ -61,28 +61,12 @@ public class OrganizationServiceImpl extends ServiceImpl<OrganizationMapper, Org
* @return 机构下拉列表
*/
@Override
public List<Organization> getList(Integer organizationType, String organizationClass) {
LambdaQueryWrapper<Organization> queryWrapper = new LambdaQueryWrapper<Organization>()
public List<Organization> getList(Integer organizationType, Integer organizationClass) {
return baseMapper.selectList(new LambdaQueryWrapper<Organization>()
.select(Organization::getId, Organization::getName, Organization::getDisplayOrder)
.eq(Organization::getTypeEnum, organizationType)
.orderByAsc(Organization::getDisplayOrder); // 按 displayOrder 升序排序
// 如果organizationClass不为null则添加查询条件
if (organizationClass != null) {
// 支持多选值使用LIKE操作符进行查询适用于PostgreSQL
String classValue = organizationClass.toString();
queryWrapper.and(subWrapper -> {
subWrapper.eq(Organization::getClassEnum, classValue)
.or()
.likeRight(Organization::getClassEnum, classValue + ",")
.or()
.likeLeft(Organization::getClassEnum, "," + classValue)
.or()
.like(Organization::getClassEnum, "," + classValue + ",");
});
}
return baseMapper.selectList(queryWrapper);
.eq(organizationClass != null, Organization::getClassEnum, organizationClass)
.orderByAsc(Organization::getDisplayOrder)); // 按 displayOrder 升序排序
}
/**

View File

@@ -1,14 +1,12 @@
package com.openhis.template.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotBlank;
import java.time.LocalDateTime;
@Data
@@ -30,13 +28,8 @@ public class DoctorPhrase {
private Integer phraseType;
/** 业务分类(主诉/现病史等) */
@NotBlank(message = "业务分类不能为空")
private String phraseCategory;
// 非数据库字段,用于前端展示名称
@TableField(exist = false)
private String businessTypeName;
/** 模板内容 */
private String phraseContent;

View File

@@ -1,40 +0,0 @@
package com.openhis.template.enums;
// 枚举类不需要任何Lombok注解@Data/@AllArgsConstructor/@NoArgsConstructor都删掉
public enum DoctorPhraseBizTypeEnum {
// 1. 枚举项直接传值不用code:xxx直接写字符串
MAIN_COMPLAINT("MAIN_COMPLAINT", "主诉"),
PRESENT_HISTORY("PRESENT_HISTORY", "现病史"),
PRE_OPERATION("PRE_OPERATION", "术前"),
POST_OPERATION("POST_OPERATION", "术后"),
PAST_HISTORY("PAST_HISTORY", "既往史");
// 2. 定义枚举的成员变量private final 保证不可变)
private final String code; // 数据库存储的编码
private final String name; // 前端展示的名称
// 3. 手动写私有构造器(枚举构造器必须私有,且要给变量赋值)
DoctorPhraseBizTypeEnum(String code, String name) {
this.code = code;
this.name = name;
}
// 4. 提供getter方法枚举没有setter因为枚举项是常量不能改
public String getCode() {
return code;
}
public String getName() {
return name;
}
// 【可选】添加工具方法根据code找对应的枚举前端传code时后端快速匹配
public static DoctorPhraseBizTypeEnum getByCode(String code) {
for (DoctorPhraseBizTypeEnum enumObj : values()) {
if (enumObj.getCode().equals(code)) {
return enumObj;
}
}
return null; // 或抛异常throw new IllegalArgumentException("无效的业务分类编码:" + code);
}
}

View File

@@ -83,10 +83,4 @@ public class ActivityDefinition extends HisBaseEntity {
/** 划价标记 */
private Integer pricingFlag;
/** 序号 */
private Integer sortOrder;
/** 服务范围 */
private String serviceRange;
}

View File

@@ -24,7 +24,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version> <!-- 将21改为17 -->
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<druid.version>1.2.27</druid.version>
<bitwalker.version>1.21</bitwalker.version>
@@ -380,8 +380,6 @@
<compilerArgs>
<arg>-parameters</arg>
<arg>-Xlint:unchecked</arg>
<arg>--add-modules</arg>
<arg>java.base</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>

View File

@@ -93,16 +93,6 @@
</el-tag>
</div>
</div>
<!-- 添加入院日期等关键信息 -->
<div class="admission-info" v-if="item.admissionDate">
<span class="admission-date">入院日期{{ item.admissionDate }}</span>
</div>
<!-- 添加主治医生信息 -->
<div class="attending-doctor" v-if="item.attendingDoctorName">
<span class="doctor-name">主管医生{{ item.attendingDoctorName }}</span>
</div>
</div>
</div>
</div>
@@ -466,9 +456,7 @@ watch(
padding: 8px 12px 10px;
.personal-info-container {
display: flex;
flex-direction: column;
gap: 4px;
display: block;
.name-container {
display: flex;
@@ -481,10 +469,6 @@ watch(
color: #111827;
font-weight: 600;
font-size: 16px;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.age {
@@ -501,19 +485,6 @@ watch(
}
}
}
.admission-info, .attending-doctor {
display: flex;
font-size: 12px;
color: #6b7280;
margin-top: 2px;
.admission-date, .doctor-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
}

View File

@@ -22,7 +22,7 @@
</el-select>
</el-form-item>
<el-form-item label="科室分类" prop="classEnum">
<el-select v-model="queryParams.classEnum" placeholder="请选择科室分类" clearable multiple style="width: 200px">
<el-select v-model="queryParams.classEnum" placeholder="请选择科室分类" clearable style="width: 200px">
<el-option
v-for="item in classEnumOption"
:key="item.value"
@@ -75,21 +75,7 @@
<el-table-column type="selection" width="55" />
<el-table-column label="科室名称" align="left" prop="name" />
<el-table-column label="科室类型" align="center" prop="typeEnum_dictText" />
<el-table-column label="科室分类" align="center">
<template #default="scope">
<span v-if="scope.row.classEnum_dictText">
<el-tag
v-for="item in scope.row.classEnum_dictText.split(',')"
:key="item"
size="small"
style="margin-right: 2px;"
>
{{ item }}
</el-tag>
</span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="科室分类" align="center" prop="classEnum_dictText" />
<el-table-column label="医保码" align="center" prop="ybNo" />
<el-table-column label="医保名称" align="center" prop="ybName" />
<el-table-column label="挂号科室" align="center">
@@ -168,7 +154,6 @@
v-model="form.classEnum"
placeholder="请选择科室分类"
clearable
multiple
style="width: 100%"
:disabled="form.typeEnum != 2"
>
@@ -357,32 +342,17 @@ function reset() {
// 从字典数据中查找对应的值,处理类型转换
function getDictLabel(value) {
if (!value || !organization_class.value || organization_class.value.length === 0) return '';
// 尝试进行类型转换比较,处理可能的字符串/数字不匹配问题
const stringValue = String(value);
const dict = organization_class.value.find(item => {
// 比较转换后的字符串值
return String(item.value) === stringValue;
});
return dict ? dict.label : '';
}
// 解析科室分类值,处理字符串或数组格式
function parseClassEnumValues(value) {
if (!value) return [];
if (Array.isArray(value)) {
return value.filter(item => item !== null && item !== undefined && item !== '');
} else if (typeof value === 'string') {
// 如果是逗号分隔的字符串,分割并过滤空值
return value.split(',').map(item => item.trim()).filter(item => item !== '');
} else {
// 如果是单个值,转换为字符串
return [String(value)].filter(item => item !== '');
}
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNo = 1;
@@ -405,21 +375,14 @@ function getPageList() {
const processedData = res.data.records.map(item => {
// 保留原有显示文本作为基础
const originalText = item.classEnum_dictText || '';
// 如果系统标准字典存在,尝试使用字典中的文本覆盖原有文本
if (organization_class.value && organization_class.value.length > 0) {
// 处理多选值的情况
let newText = '';
if (item.classEnum) {
// 如果classEnum是逗号分隔的字符串则处理每个值
const classEnumValues = parseClassEnumValues(item.classEnum);
const labels = classEnumValues.map(val => getDictLabel(val)).filter(label => label);
newText = labels.join(',');
}
const dictLabel = getDictLabel(item.classEnum);
// 只有在字典中找到匹配值时才替换,否则保留原有文本
return {
...item,
classEnum_dictText: newText || originalText
classEnum_dictText: dictLabel || originalText
};
}
return item;
@@ -460,16 +423,8 @@ function handelEdit(row) {
form.value.ybNo = orgInfo.ybNo;
form.value.ybName = orgInfo.ybName;
form.value.typeEnum = orgInfo.typeEnum;
// 处理多选的科室分类,如果是逗号分隔的字符串则转换为数组
if (orgInfo.classEnum) {
if (typeof orgInfo.classEnum === 'string' && orgInfo.classEnum.includes(',')) {
form.value.classEnum = orgInfo.classEnum.split(',').map(item => item.trim());
} else {
form.value.classEnum = [String(orgInfo.classEnum)];
}
} else {
form.value.classEnum = [];
}
// 确保科室分类值的类型正确,使其能正确匹配下拉选项中的值
form.value.classEnum = orgInfo.classEnum !== undefined ? String(orgInfo.classEnum) : undefined;
form.value.busNoParent = orgInfo.busNo.split('.').length > 1 ? orgInfo.busNo.split('.')[0] : undefined;
form.value.registerFlag = !!orgInfo.registerFlag;
form.value.location = orgInfo.location;
@@ -497,16 +452,15 @@ function submitForm() {
// 确保registerFlag从布尔值转换为整数true=1, false=0
formData.registerFlag = Number(formData.registerFlag ? 1 : 0);
// 处理多选的科室分类,如果是数组则转换为逗号分隔的字符串
if (Array.isArray(formData.classEnum)) {
formData.classEnum = formData.classEnum.length > 0 ? formData.classEnum.join(',') : null;
}
// 确保classEnum字段有值数据库必填
// 如果未定义设置默认值1
if (formData.classEnum === undefined || formData.classEnum === null || formData.classEnum === '') {
formData.classEnum = null;
formData.classEnum = 1;
}
// 确保classEnum为数字类型
formData.classEnum = Number(formData.classEnum);
// 验证提交数据
console.log('提交的数据:', formData);

View File

@@ -1,7 +1,7 @@
<template>
<div @keyup="handleKeyDown" tabindex="0" ref="tableWrapper">
<!-- 医保等级测试区域已隐藏 -->
<!--
<!--
<div style="margin-bottom: 20px; padding: 10px; border: 1px solid #ccc; background: #f5f5f5;">
<h3>医保等级测试</h3>
<div>
@@ -17,25 +17,23 @@
</div>
</div>
-->
<!-- 使用Element Plus的虚拟滚动表格 -->
<el-table
ref="adviceBaseRef"
height="400"
:data="filteredAdviceBaseList"
:data="adviceBaseList"
highlight-current-row
@current-change="handleCurrentChange"
row-key="adviceDefinitionId"
row-key="patientId"
@cell-click="clickRow"
v-loading="loading"
>
<el-table-column label="名称" align="center" prop="adviceName" width="200" show-overflow-tooltip />
<el-table-column label="类型" align="center" width="100">
<el-table-column label="名称" align="center" prop="adviceName" />
<el-table-column label="类型" align="center">
<template #default="scope">{{ getCategoryName(scope.row) }}</template>
</el-table-column>
<el-table-column label="包装单位" align="center" prop="unitCode_dictText" width="100" />
<el-table-column label="最小单位" align="center" prop="minUnitCode_dictText" width="100" />
<el-table-column label="单次剂量" align="center" width="120">
<el-table-column label="包装单位" align="center" prop="unitCode_dictText" />
<el-table-column label="最小单位" align="center" prop="minUnitCode_dictText" />
<el-table-column label="单次剂量" align="center">
<template #default="scope">
<span>
{{
@@ -48,35 +46,36 @@
</span>
</template>
</el-table-column>
<el-table-column label="规格" align="center" prop="volume" width="120" show-overflow-tooltip />
<el-table-column label="用法" align="center" prop="methodCode_dictText" width="120" show-overflow-tooltip />
<el-table-column label="规格" align="center" prop="volume" />
<el-table-column label="用法" align="center" prop="methodCode_dictText" />
<!-- 修改价格列从inventoryList中获取价格 -->
<el-table-column label="价格" align="center" width="100">
<el-table-column label="价格" align="center">
<template #default="scope">
<span>
{{ getPriceFromInventory(scope.row) }}
</span>
</template>
</el-table-column>
<el-table-column label="库存数量" align="center" width="100">
<el-table-column label="库存数量" align="center">
<template #default="scope">{{ handleQuantity(scope.row) }}</template>
</el-table-column>
<el-table-column label="频次" align="center" prop="rateCode_dictText" width="100" show-overflow-tooltip />
<el-table-column label="注射药品" align="center" prop="injectFlag_enumText" width="100" />
<el-table-column label="皮试" align="center" prop="skinTestFlag_enumText" width="100" />
<el-table-column label="医保等级" min-width="100" align="center">
<el-table-column label="频次" align="center" prop="rateCode_dictText" />
<!-- <el-table-column label="单次剂量" align="center" prop="dose" /> -->
<!-- <el-table-column label="剂量单位" align="center" prop="doseUnitCode_dictText" /> -->
<el-table-column label="注射药品" align="center" prop="injectFlag_enumText" />
<el-table-column label="皮试" align="center" prop="skinTestFlag_enumText" />
<el-table-column label="医保等级" min-width="80" align="center">
<template #default="scope">
{{ getMedicalInsuranceLevel(scope.row) }}
</template>
</el-table-column>
<el-table-column label="医保码" align="center" prop="ybNo" width="100" show-overflow-tooltip />
<el-table-column label="医保码" align="center" prop="ybNo" />
<!-- <el-table-column label="限制使用标志" align="center" prop="useLimitFlag" /> -->
<el-table-column
label="限制使用范围"
align="center"
:show-overflow-tooltip="true"
prop="useScope"
width="120"
>
<template #default="scope">
<span v-if="scope.row.useLimitFlag === 1">{{ scope.row.useScope }}</span>
@@ -88,9 +87,9 @@
</template>
<script setup>
import { getCurrentInstance, nextTick, onMounted, ref, computed, watch } from 'vue';
import { getAdviceBaseInfo, getDeviceList } from './api';
import { throttle, debounce } from 'lodash-es';
import {getCurrentInstance, nextTick, onMounted, ref} from 'vue';
import {getAdviceBaseInfo, getDeviceList} from './api';
import {throttle} from 'lodash-es';
const { proxy } = getCurrentInstance();
// 使用系统统一的数据字典
@@ -113,13 +112,13 @@ function getCategoryName(row) {
return found.label;
}
}
// 兼容处理:对于中草药可能的多种编码形式
const herbCodes = ['3', '03', '4', 3, 4];
if (herbCodes.includes(row.categoryCode)) {
return '中草药';
}
return '-';
} else if (row.adviceType === 2) { // 耗材类型
return '耗材';
@@ -129,13 +128,22 @@ function getCategoryName(row) {
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) {
// 非常简单直接的实现:直接返回值或转换为中文
const value = record.chrgitmLv || record.insuranceClass || '';
if (!value) return '无';
// 简单映射
const map = {
'1': '甲类',
@@ -148,7 +156,7 @@ function getMedicalInsuranceLevel(record) {
'乙类': '乙类',
'自费': '自费'
};
return map[value] || value;
}
@@ -163,196 +171,50 @@ const props = defineProps({
},
});
const emit = defineEmits(['selectAdviceBase']);
// 使用缓存机制
const searchCache = new Map();
const allAdviceData = ref([]); // 预加载数据
const isDataLoaded = ref(false); // 数据是否已加载
const adviceBaseList = ref([]);
const currentSelectRow = ref({});
const currentIndex = ref(0);
const total = ref(0);
const adviceBaseRef = ref();
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); // 限制返回数量
const currentIndex = ref(0); // 当前选中行索引
const currentSelectRow = ref({});
const queryParams = ref({
pageSize: 100,
pageNum: 1,
adviceTypes: '1,2,3',
});
// 预加载数据
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 adviceBaseList = ref([]);
// 节流函数
const throttledGetList = throttle(
async () => {
// 只有在没有进行中的请求时才执行
if (!isRequestInProgress.value) {
await getList();
}
() => {
getList();
},
500, // 增加到500ms减少频繁请求
{ leading: false, trailing: true }
300,
{ leading: true, trailing: true }
);
// 监听参数变化 - 使用防抖优化
watch(
() => props.adviceQueryParams,
(newValue) => {
// 只有在搜索关键词长度达到一定要求时才触发搜索
if (newValue.searchKey && newValue.searchKey.length < 2) {
adviceBaseList.value = [];
return;
}
console.log('adviceBaseList 接收到参数变化:', newValue);
// 直接更新查询参数
queryParams.value.searchKey = newValue.searchKey || '';
// 更新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();
},
{ deep: true }
);
// 获取数据
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 || '');
}
getList();
// 从priceList列表中获取价格
function getPriceFromInventory(row) {
if (row.priceList && row.priceList.length > 0) {
@@ -371,61 +233,218 @@ function getPriceFromInventory(row) {
}
return '-';
}
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 || '');
function getList() {
queryParams.value.organizationId = props.patientInfo.orgId;
console.log('发送API请求:', queryParams.value);
// 判断是否是耗材类型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 key = event.key;
const data = filteredAdviceBaseList.value;
if (data.length === 0) return;
const data = adviceBaseList.value;
switch (key) {
case 'ArrowUp': // 上箭头
event.preventDefault();
event.preventDefault(); // 阻止默认滚动行为
if (currentIndex.value > 0) {
currentIndex.value--;
currentSelectRow.value = data[currentIndex.value];
setCurrentRow(data[currentIndex.value]);
}
break;
case 'ArrowDown': // 下箭头
case 'ArrowDown': // 下箭头`
event.preventDefault();
if (currentIndex.value < data.length - 1) {
currentIndex.value++;
currentSelectRow.value = data[currentIndex.value];
setCurrentRow(data[currentIndex.value]);
}
break;
case 'Enter': // 回车键
// const currentRow = adviceBaseRef.value.getSelectionRows();
event.preventDefault();
if (currentSelectRow.value) {
// 这里可以触发自定义逻辑,如弹窗、跳转等
emit('selectAdviceBase', currentSelectRow.value);
}
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) => {
currentIndex.value = filteredAdviceBaseList.value.findIndex((item) => item.adviceDefinitionId === currentRow.adviceDefinitionId);
currentIndex.value = adviceBaseList.value.findIndex((item) => item === currentRow);
currentSelectRow.value = currentRow;
};
// 点击行
function clickRow(row) {
emit('selectAdviceBase', row);
}
// 初始化
// 初始化时可以获取更多药品分类信息
onMounted(() => {
preloadData(); // 预加载数据
getList(); // 获取初始数据
// 这里可以根据实际需求调用API获取完整的药品分类列表
// 暂时使用默认的分类映射
});
defineExpose({
@@ -434,5 +453,8 @@ defineExpose({
</script>
<style scoped>
/* 保留原有的表格样式 */
.popover-table-wrapper:focus {
outline: 2px solid #409eff;
/* 聚焦时的高亮效果 */
}
</style>

View File

@@ -801,16 +801,6 @@ export function getTestResult(queryParams) {
* 获取检验申请单列表
*/
export function getInspectionApplicationList(queryParams) {
// 确保参数有效
if (!queryParams || !queryParams.encounterId) {
console.warn('获取检验申请单列表时缺少必要参数 encounterId');
// 返回一个resolved的Promise模拟空数据
return Promise.resolve({
code: 200,
data: [],
message: '参数不足,返回空数据'
});
}
return request({
url: '/reg-doctorstation/request-form/get-inspection',
method: 'get',

View File

@@ -107,12 +107,11 @@
trigger="manual"
:width="800"
>
<template #default>
<diagnosislist
:diagnosisSearchkey="diagnosisSearchkey"
@selectDiagnosis="handleSelsectDiagnosis"
/>
</template>
<diagnosislist
:diagnosisSearchkey="diagnosisSearchkey"
@selectDiagnosis="handleSelsectDiagnosis"
/>
<template #reference>
<el-input v-model="scope.row.name" placeholder="请选择诊断" @input="handleChange"
@focus="handleFocus(scope.row, scope.$index)" @blur="handleBlur(scope.row)" />
@@ -375,32 +374,29 @@ function getTree() {
const patientId = props.patientInfo?.patientId || '';
getConditionDefinitionInfo(patientId).then((res) => {
if (res.code == 200) {
// 确保数据结构正确,避免直接修改数组对象
const patientHistoryList = Array.isArray(res.data.patientHistoryList) ? res.data.patientHistoryList : [];
const doctorCommonUseList = Array.isArray(res.data.doctorCommonUseList) ? res.data.doctorCommonUseList : [];
const userPersonalList = Array.isArray(res.data.userPersonalList) ? res.data.userPersonalList : [];
const organizationList = Array.isArray(res.data.organizationList) ? res.data.organizationList : [];
let list = [];
list = res.data.patientHistoryList;
list.children = [];
// 手动构造树列表;
tree.value[0] = {
id: '1',
name: '历史',
children: patientHistoryList,
children: list,
};
tree.value[1] = {
id: '2',
name: '常用',
children: doctorCommonUseList,
children: res.data.doctorCommonUseList,
};
tree.value[2] = {
id: '3',
name: '个人',
children: userPersonalList,
children: res.data.userPersonalList,
};
tree.value[3] = {
id: '4',
name: '科室',
children: organizationList,
children: res.data.organizationList,
};
console.log(tree.value);
}

View File

@@ -242,66 +242,23 @@ const rules = reactive({
});
function openDialog() {
console.log('hospitalizationDialog openDialog 被调用');
console.log('props.patientInfo:', props.patientInfo);
console.log('props.encounterId:', props.encounterId);
console.log('props.patientInfo.encounterId:', props.patientInfo?.encounterId);
console.log('orgId==========>', props.patientInfo.orgId);
getOrgList().then((res) => {
console.log('获取组织机构数据:', res);
// 确保数据结构正确
if (res && res.code === 200 && res.data && res.data.records && Array.isArray(res.data.records)) {
// 递归遍历树形结构,获取所有符合条件的节点
const flattenTree = (nodes) => {
let result = [];
nodes.forEach(node => {
// 检查当前节点是否符合条件 - 扩展筛选条件
if (node &&
node.typeEnum === 2 && // 科室类型
checkClassEnumValue(node.classEnum, 2) && // 住院类别(支持多选)
node.activeFlag !== 0) { // 活跃状态(非停用)
result.push(node);
}
// 递归处理子节点
if (node.children && Array.isArray(node.children)) {
result = result.concat(flattenTree(node.children));
}
});
return result;
};
// 从树形结构中提取所有符合条件的组织
organization.value = flattenTree(res.data.records);
console.log('筛选后的组织机构数据:', organization.value);
// 如果当前患者所属科室在筛选结果中,则默认选中
if (props.patientInfo?.orgId) {
submitForm.inHospitalOrgId =
organization.value.find((item) => item?.id === props.patientInfo.orgId)?.id || '';
}
} else {
organization.value = [];
console.warn('获取组织机构数据为空或格式不正确:', res);
}
// organization.value = res.data.records;
organization.value = res.data.records[0].children.filter(
(record) => record.typeEnum === 2 && record.classEnum === 2
);
console.log('organization==========>', organization.value);
}).catch(error => {
console.error('获取组织机构数据失败:', error);
organization.value = [];
// 显示详细的错误信息
const errorMessage = error.message || error.msg || '获取组织机构数据失败,请稍后重试';
proxy.$modal.msgError(errorMessage);
submitForm.inHospitalOrgId =
organization.value.find((item) => item.id === props.patientInfo.orgId)?.id || '';
});
// 获取初始化数据
// wardList().then((res) => {
// wardListOptions.value = res.data;
// });
getInit().then((response) => {
console.log(response, 'response');
if (response.data && response.data.priorityLevelOptionOptions) {
priorityLevelOptionOptions.value = response.data.priorityLevelOptionOptions; // 优先级
}
priorityLevelOptionOptions.value = response.data.priorityLevelOptionOptions; // 优先级
});
console.log(props.patientInfo, 'patientInfo');
getDiagnosisInfo(undefined);
@@ -311,7 +268,6 @@ function openDialog() {
diagnosisDefinitionId = props.mainDiagnosis.definitionId;
diagnosisYbNo = props.mainDiagnosis.ybNo || '';
submitForm.medTypeCode = props.mainDiagnosis.medTypeCode;
submitForm.diagnosisDesc = props.mainDiagnosis.name || ''; // 设置诊断描述
diagnosisDefinitionList.value = [props.mainDiagnosis];
}
}
@@ -328,62 +284,21 @@ function handleDiagnosisChange(item) {
}
function handleNodeClick(orgInfo) {
// 确保传入正确的科室ID
if (orgInfo && orgInfo.id) {
wardList({ orgId: orgInfo.id }).then((res) => {
if (res && res.data) {
wardListOptions.value = res.data;
// 清空之前选择的病区
submitForm.wardLocationId = undefined;
} else {
wardListOptions.value = [];
submitForm.wardLocationId = undefined;
}
}).catch(error => {
console.error('获取病区列表失败:', error);
wardListOptions.value = [];
submitForm.wardLocationId = undefined;
// 显示详细的错误信息
const errorMessage = error.message || error.msg || '获取病区列表失败,请稍后重试';
proxy.$modal.msgError(errorMessage);
});
} else {
wardListOptions.value = [];
submitForm.wardLocationId = undefined;
}
wardList({ orgId: orgInfo.id }).then((res) => {
wardListOptions.value = res.data;
});
}
function handleChange(value) {
if (!value) {
wardListOptions.value = [];
submitForm.wardLocationId = undefined;
} else {
// 当选择新科室时,清空病区选择
submitForm.wardLocationId = undefined;
}
}
function submit() {
console.log('hospitalizationDialog submit 被调用');
console.log('props.patientInfo:', props.patientInfo);
console.log('props.encounterId:', props.encounterId);
console.log('props.patientInfo.encounterId:', props.patientInfo?.encounterId);
proxy.$refs['registerRef'].validate((valid) => {
if (valid) {
// 验证必要字段
if (!props.patientInfo.patientId) {
console.log('患者信息不完整,缺少 patientId');
proxy.$modal.msgError('患者信息不完整,无法办理住院');
return;
}
if (!props.encounterId && !props.patientInfo.encounterId) {
console.log('就诊信息不完整,缺少 encounterId');
proxy.$modal.msgError('就诊信息不完整,无法办理住院');
return;
}
let saveData = {
...submitForm,
diagnosisYbNo: diagnosisYbNo,
@@ -392,14 +307,8 @@ function submit() {
ambEncounterId: props.encounterId || props.patientInfo.encounterId,
patientId: props.patientInfo.patientId,
};
console.log('提交住院数据:', saveData);
// 显示加载状态
const loading = proxy.$modal.loading('正在办理住院...');
handleHospitalization(saveData).then((res) => {
console.log('住院办理API响应:', res);
if (res.code == 200) {
proxy.$modal.msgSuccess('办理成功');
close();
@@ -409,30 +318,8 @@ function submit() {
}
}).catch(error => {
console.error('提交出错:', error);
// 构建详细的错误信息
let errorMsg = '办理住院过程中发生错误';
if (error.response) {
// 如果后端返回了具体错误信息,优先使用
if (error.response.data && error.response.data.message) {
errorMsg = error.response.data.message;
} else if (error.response.data) {
// 如果响应体中有其他可读信息
errorMsg = typeof error.response.data === 'string' ? error.response.data : `${errorMsg} (${error.response.status})`;
} else {
errorMsg = `${errorMsg} (${error.response.status}): ${error.response.statusText}`;
}
} else if (error.request) {
errorMsg = '网络请求失败,请检查网络连接';
} else {
errorMsg = error.message || errorMsg;
}
proxy.$modal.msgError(errorMsg);
}).finally(() => {
// 关闭加载状态
proxy.$modal.closeLoading();
proxy.$modal.msgError('提交请求失败');
});
} else {
console.log('表单验证失败');
}
});
}
@@ -440,20 +327,6 @@ function submit() {
function close() {
emit('close');
}
// 检查classEnum值是否包含指定值支持多选
function checkClassEnumValue(classEnum, targetValue) {
if (!classEnum) return false;
// 如果是字符串且包含逗号,说明是多选值
if (typeof classEnum === 'string' && classEnum.includes(',')) {
const values = classEnum.split(',').map(v => v.trim());
return values.some(v => v == targetValue);
}
// 单个值的情况
return classEnum == targetValue;
}
</script>
<style lang="scss" scoped>

View File

@@ -17,7 +17,7 @@
</el-button>
</div>
</div>
<!--检验信息-->
<el-table
ref="inspectionTableRef"
:data="inspectionList"
@@ -79,7 +79,7 @@
</div>
</div>
<!-- 下方申请单和检验项目选择器 -->
<!-- 下方申请单和检验项目选择器 -->
<div style="display: flex; gap: 20px">
<!-- 左侧申请单和检验信息 -->
<div style="width: 55%">
@@ -88,18 +88,18 @@
<div class="application-form" style="padding: 20px; height: 700px; overflow-y: auto; border: 1px solid #e4e7ed; border-radius: 4px; margin: 10px;">
<div style="margin-bottom: 20px">
<label style="display: block; margin-bottom: 5px; font-weight: bold">申请单号</label>
<el-input v-model="formData.applicationNo" disabled size="small" />
<el-input v-model="formData.applicationNo" readonly size="small" />
</div>
<!-- 患者信息行 -->
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px; margin-bottom: 20px">
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">姓名</label>
<el-input v-model="formData.patientName" disabled size="small" />
<el-input v-model="formData.patientName" readonly size="small" />
</div>
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">就诊卡号</label>
<el-input v-model="formData.cardNo" disabled size="small" />
<el-input v-model="formData.identifierNo" readonly size="small" />
</div>
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">费用性质</label>
@@ -115,6 +115,7 @@
<!-- 申请信息行 -->
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px; margin-bottom: 20px">
<!--申请日期-->
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">申请日期</label>
<el-date-picker
@@ -127,6 +128,7 @@
style="width: 100%"
/>
</div>
<!--申请科室-->
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">申请科室</label>
<el-select v-model="formData.departmentName" placeholder="请选择申请科室" size="small" style="width: 100%" disabled>
@@ -135,9 +137,11 @@
<el-option label="儿科" value="pediatrics" />
</el-select>
</div>
<!--申请医生-->
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">申请医生</label>
<el-select v-model="formData.doctorName" placeholder="请选择申请医生" size="small" style="width: 100%" disabled>
<el-select v-model="formData.doctorName" placeholder="请选择申请医生" size="small" style="width: 100%">
<el-option label="张医生" value="doctor_zhang" />
<el-option label="李医生" value="doctor_li" />
<el-option label="王医生" value="doctor_wang" />
@@ -164,7 +168,7 @@
<div v-if="validationErrors.executeDepartment" class="error-message">请选择执行科室</div>
</div>
<!-- 诊断相关字段 -->
<!-- 诊断描述 -->
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 20px">
<div :class="{ 'form-item-error': validationErrors.diagnosisDesc }">
<label style="display: block; margin-bottom: 5px; font-weight: bold">诊断描述 <span style="color: #f56c6c">*</span></label>
@@ -248,7 +252,6 @@
</div>
</div>
</el-tab-pane>
<el-tab-pane label="检验信息" name="inspectionInfo">
<div style="padding: 20px; height: 700px; overflow-y: auto; border: 1px solid #e4e7ed; border-radius: 4px; margin: 10px;">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 20px">
@@ -458,7 +461,7 @@ const formData = reactive({
applicationId: null,
applicationNo: '202511210001',
patientName: '',
cardNo: '',
identifierNo: '',
feeType: 'self',
applicationDate: new Date(),
departmentName: '',
@@ -579,64 +582,47 @@ const getFilteredItems = (categoryKey) => {
// 初始化数据
function initData() {
console.log('检验组件初始化patientInfo:', props.patientInfo)
// console.log('检验组件初始化patientInfo:', props.patientInfo)
if (props.patientInfo) {
// 确保 encounterId 存在且有效,优先使用 encounterId其次尝试 id最后尝试 patientId
queryParams.encounterId = props.patientInfo.encounterId || props.patientInfo.id || props.patientInfo.patientId
queryParams.encounterId = props.patientInfo.encounterId
formData.patientName = props.patientInfo.patientName || ''
formData.cardNo = props.patientInfo.cardNo || ''
formData.identifierNo = props.patientInfo.identifierNo || ''
formData.departmentName = props.patientInfo.departmentName || ''
formData.doctorName = props.patientInfo.doctorName || ''
}
// 只有在存在有效的 encounterId 时才调用接口
if (queryParams.encounterId && queryParams.encounterId !== 'undefined' && queryParams.encounterId !== 'null' && queryParams.encounterId !== '') {
// 只有在存在 encounterId 时才调用接口
if (queryParams.encounterId) {
getInspectionList()
} else {
console.warn('缺少有效的就诊ID无法获取检验申请单列表')
inspectionList.value = []
total.value = 0
}
}
// 获取检验申请单列表
function getInspectionList() {
// 先检查是否有有效的encounterId
if (!queryParams.encounterId || queryParams.encounterId === 'undefined' || queryParams.encounterId === 'null') {
console.warn('缺少有效的就诊ID无法获取检验申请单列表')
inspectionList.value = []
total.value = 0
loading.value = false
// 如果没有encounterId,不调用接口
if (!queryParams.encounterId) {
// console.warn('【检验】encounterId为空,不调用接口')
return
}
loading.value = true
// 调用真实的API只传递 encounterId 参数
// console.log('【检验】调用API,encounterId:', queryParams.encounterId)
getInspectionApplicationList({ encounterId: queryParams.encounterId }).then((res) => {
if (res.code === 200) {
inspectionList.value = res.data || []
total.value = res.data?.length || 0
// console.log('【检验】获取数据成功,数量:', inspectionList.value.length)
} else {
inspectionList.value = []
total.value = 0
console.error('获取检验申请单列表失败:', res.message || res.msg)
ElMessage.error(res.message || res.msg || '获取检验申请单列表失败')
ElMessage.error('获取检验申请单列表失败')
}
}).catch((error) => {
console.error('获取检验申请单列表异常:', error)
// console.error('获取检验申请单列表异常:', error)
inspectionList.value = []
total.value = 0
// 提供更友好的错误信息
let errorMessage = '获取检验申请单列表异常'
if (error.response) {
errorMessage += ` (${error.response.status}): ${error.response.data.message || error.response.statusText}`
} else if (error.request) {
errorMessage += ': 网络请求失败,请检查网络连接'
} else {
errorMessage += `: ${error.message}`
}
ElMessage.error(errorMessage)
ElMessage.error('获取检验申请单列表异常')
}).finally(() => {
loading.value = false
})
@@ -644,7 +630,7 @@ function getInspectionList() {
// 新增申请单
function handleNewApplication() {
console.log('点击新增按钮')
// console.log('点击新增按钮')
resetForm()
// 生成申请单号
formData.applicationNo = generateApplicationNo()
@@ -754,7 +740,7 @@ function handleSave() {
totalAmount: selectedInspectionItems.value.reduce((sum, item) => sum + item.price + (item.serviceFee || 0), 0)
}
console.log('保存检验申请单数据:', saveData)
// console.log('保存检验申请单数据:', saveData)
// 调用真实的API保存
saveInspectionApplication(saveData).then((res) => {
@@ -768,7 +754,7 @@ function handleSave() {
ElMessage.error(res.message || '保存失败')
}
}).catch((error) => {
console.error('保存检验申请单异常:', error)
// console.error('保存检验申请单异常:', error)
ElMessage.error('保存异常')
})
}
@@ -778,7 +764,7 @@ function handleFormSave() {
formRef.value?.validate((valid) => {
if (valid) {
formData.inspectionItems = selectedInspectionItems.value.map(item => item.id)
console.log('保存检验申请单表单数据:', formData)
// console.log('保存检验申请单表单数据:', formData)
ElMessage.success('保存成功')
showForm.value = false
getInspectionList()
@@ -788,7 +774,7 @@ function handleFormSave() {
// 查看详情
function handleView(row) {
console.log('点击查看按钮,数据:', row)
// console.log('点击查看按钮,数据:', row)
// 加载表单数据
Object.assign(formData, row)
@@ -828,7 +814,7 @@ function switchCategory(category) {
// 处理搜索
function handleSearch() {
// 搜索逻辑已在getFilteredItems中实现这里可以添加额外的搜索逻辑
console.log('搜索关键词:', searchKeyword.value)
// console.log('搜索关键词:', searchKeyword.value)
}
// 处理项目项点击(排除勾选框点击)
@@ -883,7 +869,7 @@ function handleSelectionChange(selection) {
// 打印申请单
function handlePrint(row) {
console.log('打印申请单:', row)
// console.log('打印申请单:', row)
// 切换到申请单TAB
leftActiveTab.value = 'application'
@@ -966,7 +952,7 @@ function handleDelete(row) {
confirmButtonClass: 'el-button--danger'
}
).then(() => {
console.log('删除申请单:', row)
// console.log('删除申请单:', row)
// 调用真实的API删除
deleteInspectionApplication(row.applicationId).then((res) => {
if (res.code === 200) {
@@ -977,7 +963,7 @@ function handleDelete(row) {
ElMessage.error(res.message || '删除失败')
}
}).catch((error) => {
console.error('删除检验申请单异常:', error)
// console.error('删除检验申请单异常:', error)
ElMessage.error('删除异常')
})
}).catch(() => {
@@ -1002,17 +988,15 @@ watch(() => props.activeTab, (newVal) => {
}
})
// 监听patientInfo变化确保在患者信息更新时也更新检验申请单列表
watch(() => props.patientInfo, (newPatientInfo) => {
if (newPatientInfo && Object.keys(newPatientInfo).length > 0) {
// 更新encounterId
queryParams.encounterId = newPatientInfo.encounterId || newPatientInfo.id || newPatientInfo.patientId
// 如果有有效的encounterId则获取检验申请单列表
if (queryParams.encounterId && queryParams.encounterId !== 'undefined' && queryParams.encounterId !== 'null' && queryParams.encounterId !== '') {
getInspectionList()
}
// 监听patientInfo变化,确保encounterId及时更新
watch(() => props.patientInfo, (newVal) => {
// console.log('【检验】patientInfo变化:', newVal)
console.log('【检验】接收到的完整patientInfo:', JSON.stringify(newVal, null, 2))
if (newVal && newVal.encounterId) {
queryParams.encounterId = newVal.encounterId
// console.log('【检验】更新encounterId:', queryParams.encounterId)
}
}, { deep: true })
}, { deep: true, immediate: true })
// 初始化
onMounted(() => {
@@ -1021,13 +1005,7 @@ onMounted(() => {
// 暴露方法
defineExpose({
getList() {
// 在调用getList时先检查是否有有效的patientInfo如果有则更新encounterId
if (props.patientInfo && Object.keys(props.patientInfo).length > 0) {
queryParams.encounterId = props.patientInfo.encounterId || props.patientInfo.id || props.patientInfo.patientId;
}
getInspectionList();
}
getList: getInspectionList
})
</script>

View File

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

View File

@@ -7,78 +7,183 @@
<el-option label="科室" :value="2"></el-option>
<el-option label="全院" :value="3"></el-option>
</el-select>
<el-input v-model="searchKeyword" placeholder="请输入名称" style="width: 240px; margin-left: 10px;"
@keyup.enter="handleSearch"></el-input>
<el-button style="margin-left: 10px;" @click="handleSearch"><el-icon>
<Search />
</el-icon> 查询</el-button>
<el-button style="margin-left: 10px;" @click="handleReset"><el-icon>
<Refresh />
</el-icon> 重置</el-button>
<el-button type="primary" style="margin-left: 10px;" @click="showAddDialog"><el-icon>
<Plus />
</el-icon> 增加</el-button>
<el-input
v-model="searchKeyword"
placeholder="请输入名称"
style="width: 240px; margin-left: 10px;"
@keyup.enter="handleSearch"
></el-input>
<el-button style="margin-left: 10px;" @click="handleSearch"><el-icon><Search /></el-icon> 查询</el-button>
<el-button style="margin-left: 10px;" @click="handleReset"><el-icon><Refresh /></el-icon> 重置</el-button>
<el-button type="primary" style="margin-left: 10px;" @click="showAddDialog"><el-icon><Plus /></el-icon> 增加</el-button>
</div>
<!-- 表格 -->
<el-table :data="tableData" style="width: 100%; margin-top: 20px;">
<el-table-column prop="sortNo" label="排序号" width="200">
<template #default="scope">
<el-input-number
v-if="editingId === scope.row.id"
v-model="editForm.sortNo"
:min="1"
:step="1"
style="width: 100%"
/>
<span v-else>{{ scope.row.sortNo }}</span>
</template>
</el-table-column>
<el-table-column prop="phraseName" label="名称" width="250">
<template #default="scope">
<el-input
v-if="editingId === scope.row.id"
v-model="editForm.phraseName"
placeholder="请输入名称(不超过50字)"
maxlength="50"
show-word-limit
style="width: 100%"
/>
<span v-else>{{ scope.row.phraseName }}</span>
</template>
</el-table-column>
<el-table-column prop="phraseContent" label="内容">
<template #default="scope">
<el-input
v-if="editingId === scope.row.id"
v-model="editForm.phraseContent"
placeholder="请输入内容"
type="textarea"
:rows="3"
style="width: 100%"
/>
<span v-else>{{ scope.row.phraseContent }}</span>
</template>
</el-table-column>
<el-table-column label="范围" width="250">
<template #default="scope">
{{ getScopeName(scope.row.phraseType) }}
<el-select
v-if="editingId === scope.row.id"
v-model="editForm.phraseType"
placeholder="请选择范围"
style="width: 100%"
>
<el-option label="个人" :value="1"></el-option>
<el-option label="科室" :value="2"></el-option>
<el-option label="全院" :value="3"></el-option>
</el-select>
<span v-else>{{ getScopeName(scope.row.phraseType) }}</span>
</template>
</el-table-column>
<!-- 业务分类列使用枚举转换中文名称 -->
<el-table-column label="业务分类" width="200">
<template #default="scope">
{{ getBusinessTypeName(scope.row.phraseCategory) }}
<el-select
v-if="editingId === scope.row.id"
v-model="editForm.phraseCategory"
placeholder="请选择业务分类"
style="width: 100%"
>
<el-option label="病愈" value="12"></el-option>
<el-option
v-for="item in businessclassification"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
<span v-else>{{ getBusinessTypeName(scope.row.phraseCategory) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="scope">
<el-button type="primary" size="small" @click="showEditDialog(scope.row)">
编辑
</el-button>
<el-button type="danger" size="small" style="margin-left: 5px;" @click="handleDelete(scope.row)">
删除
</el-button>
<!-- 编辑状态 -->
<template v-if="editingId === scope.row.id">
<el-button
type="success"
size="small"
@click="handleSave"
>
<el-icon><Check /></el-icon> 保存
</el-button>
<el-button
type="warning"
size="small"
style="margin-left: 5px;"
@click="handleCancel"
>
<el-icon><Close /></el-icon> 取消
</el-button>
</template>
<!-- 非编辑状态 -->
<template v-else>
<el-button
type="primary"
size="small"
@click="handleEdit(scope.row)"
>
编辑
</el-button>
<el-button
type="danger"
size="small"
style="margin-left: 5px;"
@click="handleDelete(scope.row)"
>
删除
</el-button>
</template>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage"
:page-sizes="[15, 30, 50]" :page-size="pageSize" layout="total, prev, pager, next, jumper, sizes"
:total="total"></el-pagination>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[15, 30, 50]"
:page-size="pageSize"
layout="total, prev, pager, next, jumper, sizes"
:total="total"
></el-pagination>
</div>
<!-- 新增模态框 -->
<el-dialog v-model="addDialogVisible" title="新增医生常用语" width="600px" center>
<el-form ref="addFormRef" :model="addForm" :rules="addRules" label-width="100px" class="add-form">
<el-form-item label="名称" prop="phraseName">
<el-input v-model="addForm.phraseName" placeholder="请输入常用语名称(不超过50字)" maxlength="50" show-word-limit
style="width: 100%;"></el-input>
<el-dialog
v-model="addDialogVisible"
title="新增医生常用语"
width="600px"
center
>
<el-form ref="addFormRef" :model="addForm" :rules="rules" label-width="100px" class="add-form">
<el-form-item label="名称" prop="phraseName" required>
<el-input v-model="addForm.phraseName" placeholder="请输入常用语名称(不超过50字)" maxlength="50" show-word-limit style="width: 100%;"></el-input>
</el-form-item>
<el-form-item label="内容" prop="phraseContent">
<el-input v-model="addForm.phraseContent" placeholder="请输入常用语内容" type="textarea" :rows="4"
style="width: 100%;"></el-input>
<el-form-item label="内容" prop="phraseContent" required>
<el-input
v-model="addForm.phraseContent"
placeholder="请输入常用语内容"
type="textarea"
:rows="4"
style="width: 100%;"
></el-input>
</el-form-item>
<el-form-item label="排序号">
<el-input-number v-model="addForm.sortNo" :min="1" :step="1" style="width: 100%;"></el-input-number>
</el-form-item>
<el-form-item label="业务分类" prop="phraseCategory">
<el-form-item label="业务分类">
<el-select v-model="addForm.phraseCategory" placeholder="请选择业务分类" style="width: 100%;">
<el-option v-for="item in businessTypeOptions" :key="item.value" :label="item.label"
:value="item.value"></el-option>
<!-- 默认添加"病愈"选项 -->
<el-option label="病愈" value="12"></el-option>
<!-- 从数据字典获取其他选项 -->
<el-option
v-for="item in businessclassification"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="范围" prop="phraseType">
<el-form-item label="范围">
<el-select v-model="addForm.phraseType" placeholder="请选择范围" style="width: 100%;">
<el-option label="个人" :value="1"></el-option>
<el-option label="科室" :value="2"></el-option>
@@ -93,48 +198,12 @@
</span>
</template>
</el-dialog>
<!-- 编辑模态框 -->
<el-dialog v-model="editDialogVisible" title="编辑医生常用语" width="600px" center>
<el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="100px" class="add-form">
<el-form-item label="名称" prop="phraseName">
<el-input v-model="editForm.phraseName" placeholder="请输入常用语名称(不超过50字)" maxlength="50" show-word-limit
style="width: 100%;"></el-input>
</el-form-item>
<el-form-item label="内容" prop="phraseContent">
<el-input v-model="editForm.phraseContent" placeholder="请输入常用语内容" type="textarea" :rows="4"
style="width: 100%;"></el-input>
</el-form-item>
<el-form-item label="排序号">
<el-input-number v-model="editForm.sortNo" :min="1" :step="1" style="width: 100%;"></el-input-number>
</el-form-item>
<el-form-item label="业务分类" prop="phraseCategory">
<el-select v-model="editForm.phraseCategory" placeholder="请选择业务分类" style="width: 100%;">
<el-option v-for="item in businessTypeOptions" :key="item.value" :label="item.label"
:value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="范围" prop="phraseType">
<el-select v-model="editForm.phraseType" placeholder="请选择范围" style="width: 100%;">
<el-option label="个人" :value="1"></el-option>
<el-option label="科室" :value="2"></el-option>
<el-option label="全院" :value="3"></el-option>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="editDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleEditSave">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { Check, Close, Plus, Refresh, Search } from '@element-plus/icons-vue'
import {onMounted, ref} from 'vue'
import {Check, Close, Plus, Refresh, Search} from '@element-plus/icons-vue'
import {
addDoctorPhrase,
deleteDoctorPhrase,
@@ -142,98 +211,71 @@ import {
searchDoctorPhraseList,
updateDoctorPhrase
} from './api'
import { ElMessage, ElMessageBox } from 'element-plus'
// 如果数据字典未对齐后端枚举暂时注释useDict改用固定枚举后续可替换为dict
// import { useDict } from '@/utils/dict'
// 核心配置定义和后端枚举对齐的业务分类选项替换原businessclassification
const businessTypeOptions = ref([
{ label: '主诉', value: 'MAIN_COMPLAINT' },
{ label: '现病史', value: 'PRESENT_HISTORY' },
{ label: '术前', value: 'PRE_OPERATION' },
{ label: '术后', value: 'POST_OPERATION' },
{ label: '既往史', value: 'PAST_HISTORY' }
])
import {ElMessage, ElMessageBox} from 'element-plus'
import {useDict} from '@/utils/dict'
// 搜索条件
const searchScope = ref(null) // null表示未选择数字类型1=个人2=科室3=全院
const searchKeyword = ref('')
const searchScope = ref(null) // null表示未选择数字类型1=个人2=科室3=全院
const searchKeyword = ref('')
// 表格数据
const tableData = ref([])
// 分页参数配置
// 分页
const currentPage = ref(1)
const pageSize = ref(15)
const total = ref(0)
// 新增模态框相关配置
// 新增模态框相关
const addDialogVisible = ref(false)
const addFormRef = ref()
// 新增表单默认值:业务分类默认空,强制用户选择
const addForm = ref({
phraseName: '',
phraseContent: '',
sortNo: 1,
phraseType: 1,
phraseCategory: ''
phraseCategory: '病愈'
})
// 完善新增表单验证规则补充业务分类必填、名称50字限制
const addRules = {
// 表单验证规则
const rules = {
phraseName: [
{ required: true, message: '请输入常用语名称', trigger: 'blur' },
{ max: 50, message: '常用语名称不能超过50字', trigger: 'blur' }
{ required: true, message: '请输入常用语名称', trigger: 'blur' }
],
phraseContent: [
{ required: true, message: '请输入常用语内容', trigger: 'blur' }
],
phraseType: [
{ required: true, message: '请选择范围', trigger: 'change' }
],
phraseCategory: [
{ required: true, message: '请选择业务分类', trigger: 'change' }
]
}
// 编辑模态框相关配置
const editDialogVisible = ref(false)
const editFormRef = ref()
// 编辑表单默认值配置
const editForm = ref({
id: '',
phraseName: '',
phraseContent: '',
sortNo: 1,
phraseType: 1,
phraseCategory: ''
})
// 完善编辑表单验证规则(和新增一致,保证校验统一)
// 编辑状态管理
const editingId = ref(null)
const editForm = ref({})
const editRules = {
phraseName: [
{ required: true, message: '请输入常用语名称', trigger: 'blur' },
{ max: 50, message: '常用语名称不能超过50字', trigger: 'blur' }
{ required: true, message: '请输入常用语名称', trigger: 'blur' }
],
phraseContent: [
{ required: true, message: '请输入常用语内容', trigger: 'blur' }
],
phraseType: [
{ required: true, message: '请选择范围', trigger: 'change' }
],
phraseCategory: [
{ required: true, message: '请选择业务分类', trigger: 'change' }
]
}
// 重构业务分类名称转换方法(适配后端编码→中文展示)
const getBusinessTypeName = (code) => {
if (!code) return '未知类型'
const item = businessTypeOptions.value.find(item => item.value === code)
return item ? item.label : '未知类型'
// 获取业务分类数据字典
const { businessclassification } = useDict('businessclassification')
// 获取业务分类名称
const getBusinessTypeName = (category) => {
// 直接返回后端返回的phrase_category字段值
// 从数据库截图看phrase_category已经是中文名称"主诉"、"现病史"、"术后"等
if (category) {
return category
}
return '未知类型'
}
// 获取范围名称转换方法
// 获取范围名称
const getScopeName = (scope) => {
const scopeMap = {
1: '个人',
@@ -243,41 +285,46 @@ const getScopeName = (scope) => {
return scopeMap[scope] || '未知范围'
}
// 名称唯一性校验函数(新增/编辑通用编辑时排除自身ID
// 名称唯一性校验函数
const validatePhraseName = (phraseName, excludeId = null) => {
if (!phraseName || !phraseName.trim()) {
return { valid: false, message: '请输入常用语名称' }
}
// 检查字数限制严格控制50字
// 检查字数限制
if (phraseName.trim().length > 50) {
return { valid: false, message: '常用语名称不能超过50字' }
}
// 检查名称是否已存在,编辑时排除当前记录
// 检查名称是否已存在
const existingPhrase = allData.value.find(item => {
// 排除自身(更新时)
if (excludeId && item.id === excludeId) {
return false
}
return item.phraseName.trim() === phraseName.trim()
return item.phraseName === phraseName.trim()
})
if (existingPhrase) {
return { valid: false, message: '常用语名称已存在,请输入不同的名称' }
}
return { valid: true, message: '' }
}
// 所有数据(用于客户端分页处理
// 所有数据(用于客户端分页)
const allData = ref([])
// 获取医生常用语列表数据
// 获取医生常用语列表
const fetchDoctorPhraseList = async () => {
try {
const response = await getDoctorPhraseList()
// 处理后端返回的数据结构data.data
if (response.code === 200 && response.data && response.data.data) {
// 按照sortNo由小到大排序,保证列表顺序正确
// 按照sortNo由小到大排序
allData.value = response.data.data.sort((a, b) => a.sortNo - b.sortNo)
total.value = allData.value.length
// 执行客户端分页逻辑
total.value = response.data.data.length
// 执行客户端分页
applyPagination()
} else {
ElMessage.error('获取数据失败: ' + (response.msg || '未知错误'))
@@ -285,54 +332,56 @@ const fetchDoctorPhraseList = async () => {
total.value = 0
}
} catch (error) {
console.error('获取列表失败:', error) // 增加控制台日志便于调试
ElMessage.error('获取数据失败: 网络请求错误')
allData.value = []
total.value = 0
}
}
// 重置功能方法
// 重置功能
const handleReset = () => {
// 重置搜索条件
searchScope.value = null
searchKeyword.value = ''
// 重置分页到第一页
currentPage.value = 1
// 重新加载所有数据
fetchDoctorPhraseList()
}
// 客户端分页处理核心方法
// 客户端分页处理
const applyPagination = () => {
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
tableData.value = allData.value.slice(start, end)
}
// 分页条数改变事件
// 分页处理
const handleSizeChange = (val) => {
pageSize.value = val
applyPagination()
}
// 分页页码改变事件
const handleCurrentChange = (val) => {
currentPage.value = val
applyPagination()
}
// 搜索功能核心方法
// 搜索功能
const handleSearch = async () => {
try {
// searchScope可能是null未选择、1=个人2=科室3=全院
const phraseType = searchScope.value === null ? undefined : searchScope.value
const searchScopeValue = searchScope.value
const phraseType = searchScopeValue === null ? undefined : searchScopeValue
// 调用搜索接口phraseName, phraseType
// 如果phraseType为undefined则后端不会按范围过滤查询所有数据
const response = await searchDoctorPhraseList(searchKeyword.value, phraseType)
// 处理后端返回的数据结构data.data
if (response.code === 200 && response.data && response.data.data) {
// 按照sortNo由小到大排序
allData.value = response.data.data.sort((a, b) => a.sortNo - b.sortNo)
total.value = allData.value.length
total.value = response.data.data.length
currentPage.value = 1 // 搜索后重置到第一页
applyPagination() // 应用分页
} else {
@@ -341,157 +390,179 @@ const handleSearch = async () => {
total.value = 0
}
} catch (error) {
console.error('搜索失败:', error)
ElMessage.error('搜索失败: 网络请求错误')
allData.value = []
total.value = 0
}
}
// 打开新增模态框方法
// 打开新增模态框
const showAddDialog = () => {
// 重置表单数据
addForm.value = {
phraseName: '',
phraseContent: '',
sortNo: 1,
phraseType: 1,
phraseCategory: ''
}
// 重置表单验证状态
if (addFormRef.value) {
addFormRef.value.clearValidate()
}
// 重置表单
addForm.value = {
phraseName: '',
phraseContent: '',
sortNo: 1,
phraseType: 1,
phraseCategory: '病愈'
}
// 打开模态框
addDialogVisible.value = true
}
// 提交新增表单方法
// 提交新增表单
const handleAdd = async () => {
try {
// 先执行表单验证
const validateResult = await addFormRef.value.validate()
if (!validateResult) return
// 验证表单
await addFormRef.value.validate()
// 名称唯一性校验
const nameValidation = validatePhraseName(addForm.value.phraseName)
if (!nameValidation.valid) {
ElMessage.error(nameValidation.message)
return
}
// 添加数据库要求的必填默认字段
const formData = {
...addForm.value,
phraseCode: 'PHR000', // 建议后端自动生成,前端可传空
phraseCode: 'PHR000',
enableFlag: 1,
staffId: 1001, // 建议从登录态获取,不要硬编码
deptCode: 'DEPT001', // 建议从登录态获取
creatorId: 1001 // 建议从登录态获取
staffId: 1001,
deptCode: 'DEPT001',
creatorId: 1001
}
// 调用新增接口
const response = await addDoctorPhrase(formData)
// 处理新增结果
if (response.code === 200) {
ElMessage.success('新增成功')
// 关闭模态框
addDialogVisible.value = false
// 重新拉取数据比手动unshift更可靠避免数据不一致
fetchDoctorPhraseList()
// 将新数据添加到数组开头,使新增的数据在表格最上方显示
const newData = {
...formData,
id: response.data.id || Date.now() // 使用后端返回的id或当前时间戳作为临时id
}
allData.value.unshift(newData)
total.value = allData.value.length
// 重新应用分页
applyPagination()
} else {
ElMessage.error('新增失败: ' + (response.msg || '未知错误'))
}
} catch (error) {
console.error('新增失败:', error)
// 表单验证失败或其他错误
ElMessage.error('新增失败: 请填写完整信息或网络异常')
if (error instanceof Error) {
ElMessage.error('请填写完整信息或网络请求错误')
} else {
// 表单验证失败的情况
ElMessage.error('请填写完整信息')
}
}
}
// 删除功能方法
// 删除功能
const handleDelete = async (row) => {
try {
// 弹出确认对话框
await ElMessageBox.confirm(
'确定要删除该常用语吗?删除后无法恢复!',
'删除确认',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
await ElMessageBox.confirm('确定要删除该常用语吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
// 调用删除接口
const response = await deleteDoctorPhrase(row.id)
// 处理删除结果
if (response.code === 200) {
ElMessage.success('删除成功')
// 重新取数据
// 重新取数据以更新界面
fetchDoctorPhraseList()
} else {
ElMessage.error('删除失败: ' + (response.msg || '未知错误'))
}
} catch (error) {
// 用户取消删除时不提示错误
if (error !== 'cancel') {
console.error('删除失败:', error)
ElMessage.error('删除操作失败: 网络异常或权限不足')
// 如果用户取消删除,不显示错误信息
if (error !== 'cancel' && error !== undefined) {
if (error.msg) {
ElMessage.error('删除操作失败: ' + error.msg)
} else if (error instanceof Error) {
ElMessage.error('删除操作失败: 网络请求错误')
} else {
ElMessage.error('删除操作失败: 未知错误')
}
}
}
}
// 打开编辑弹窗核心方法 - 点击表格编辑按钮触发
const showEditDialog = (row) => {
// 深拷贝当前行数据到编辑表单,避免原数据被修改
editForm.value = JSON.parse(JSON.stringify(row))
// 确保数字类型正确,防止表单赋值异常
editForm.value.sortNo = Number(editForm.value.sortNo) || 1
editForm.value.phraseType = Number(editForm.value.phraseType) || 1
// 重置表单验证状态
if (editFormRef.value) {
editFormRef.value.clearValidate()
// 编辑按钮点击事件
const handleEdit = (row) => {
// 进入编辑状态
editingId.value = row.id
// 复制当前行数据到编辑表单
editForm.value = {
...row,
// 确保phraseCategory和phraseType类型正确
phraseCategory: row.phraseCategory,
phraseType: row.phraseType
}
// 打开编辑弹窗
editDialogVisible.value = true
}
// 编辑表单提交保存方法
const handleEditSave = async () => {
try {
// 先执行表单验证
const validateResult = await editFormRef.value.validate()
if (!validateResult) return
// 取消编辑
const handleCancel = () => {
// 退出编辑状态
editingId.value = null
// 清空编辑表单
editForm.value = {}
}
// 名称唯一性校验排除当前编辑的这条记录ID
const nameValidation = validatePhraseName(editForm.value.phraseName, editForm.value.id)
// 保存编辑
const handleSave = async () => {
try {
// 验证表单数据
if (!editForm.value.phraseName || !editForm.value.phraseContent) {
ElMessage.error('请填写完整信息')
return
}
// 名称唯一性校验(排除当前记录)
const nameValidation = validatePhraseName(editForm.value.phraseName, editingId.value)
if (!nameValidation.valid) {
ElMessage.error(nameValidation.message)
return
}
// 准备更新数据修复时间格式为ISO字符串适配后端LocalDateTime
// 准备更新数据
const updateData = {
...editForm.value,
enableFlag: 1,
updateTime: new Date().toISOString() // 前端临时赋值,后端最终以自己的为准
enableFlag: 1 // 确保启用状态
}
// 调用更新接口
const response = await updateDoctorPhrase(updateData)
// 处理更新结果
if (response.code === 200) {
ElMessage.success('更新成功')
editDialogVisible.value = false
// 重新拉取数据,保证列表数据最新
// 退出编辑状态
editingId.value = null
// 清空编辑表单
editForm.value = {}
// 重新获取数据以更新界面
fetchDoctorPhraseList()
} else {
ElMessage.error('更新失败: ' + (response.msg || '未知错误'))
}
} catch (error) {
console.error('更新失败:', error)
ElMessage.error('更新操作失败: 网络请求错误')
}
}
// 组件挂载时初始化加载数据
// 组件挂载时获取数据
onMounted(() => {
fetchDoctorPhraseList()
})
@@ -506,10 +577,6 @@ onMounted(() => {
display: flex;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
/* 适配小屏幕,防止按钮换行溢出 */
gap: 10px;
/* 替换margin-left更优雅的间距 */
}
.pagination-container {
@@ -517,9 +584,4 @@ onMounted(() => {
display: flex;
justify-content: flex-end;
}
/* 新增表单样式适配 */
.add-form {
padding: 10px 0;
}
</style>

View File

@@ -110,10 +110,10 @@
<el-button type="primary" plain @click.stop="handleRefund(patientInfo.encounterId)">
退费
</el-button>
<el-button type="primary" plain class="top-layer-btn" @click.stop="getEnPrescription(patientInfo.encounterId)">
<el-button type="primary" plain @click.stop="getEnPrescription(patientInfo.encounterId)">
处方单
</el-button>
<el-button type="primary" plain class="top-layer-btn" :disabled="isHospitalizationButtonDisabled" @click.stop="handleHospitalizationClick()" @mouseenter="console.log('办理住院按钮状态:', { patientInfo: patientInfo?.value, hasEncounterId: patientInfo?.value?.encounterId, isDisabled: isHospitalizationButtonDisabled })"> 办理住院 </el-button>
<el-button type="primary" plain @click.stop="onHospitalization"> 办理住院 </el-button>
</el-descriptions-item>
</el-descriptions>
</div>
@@ -174,7 +174,7 @@
<ReportQuery :patientInfo="patientInfo" ref="reportQueryRef" />
</el-tab-pane>
</el-tabs>
<div class="overlay" :class="{ 'overlay-disabled': disabled }" v-if="disabled"></div>
<div class="overlay" v-if="disabled"></div>
</div>
</div>
<el-drawer v-model="drawer" title="患者队列" direction="ltr" @open="handleOpen">
@@ -225,7 +225,7 @@ import {formatDate, formatDateStr} from '@/utils/index';
import useUserStore from '@/store/modules/user';
import {nextTick} from 'vue';
import {updatePatientInfo} from './components/store/patient.js';
import {ElMessage, ElMessageBox} from 'element-plus';
import {ElMessage} from 'element-plus';
// // 监听路由离开事件
// onBeforeRouteLeave((to, from, next) => {
@@ -264,16 +264,6 @@ const patientList = ref([]);
const patientInfo = ref({});
const visitTypeDisabled = ref(false);
// 计算属性:确定办理住院按钮是否应被禁用
const isHospitalizationButtonDisabled = computed(() => {
return !patientInfo.value ||
typeof patientInfo.value !== 'object' ||
!patientInfo.value.encounterId ||
patientInfo.value.encounterId === '' ||
patientInfo.value.encounterId === null ||
patientInfo.value.encounterId === undefined;
});
const prescriptionInfo = ref([]);
const registerTime = ref(formatDate(new Date()));
const patientDrawerRef = ref();
@@ -289,8 +279,6 @@ const { proxy } = getCurrentInstance();
const visitType = ref('');
const firstVisitDate = ref('');
const disabled = computed(() => {
// 只有在有患者信息但某些条件不满足时才启用覆盖层
// 当前逻辑保持不变,但我们将在按钮级别处理禁用状态
return Object.keys(patientInfo.value).length === 0;
});
const shortcuts = [
@@ -420,10 +408,7 @@ function handleClick(tab) {
tcmRef.value.getDiagnosisInfo();
break;
case 'inspection':
// 确保检验组件获取最新的患者信息
if (patientInfo.value && patientInfo.value.encounterId) {
inspectionRef.value.getList();
}
// 检验tab点击处理逻辑可以在这里添加
break;
case 'surgery':
surgeryRef.value.getList();
@@ -473,45 +458,13 @@ function handleOpen() {
}
function handleCardClick(item, index) {
console.log('handleCardClick 被调用');
console.log('点击的患者项目:', item);
console.log('患者项目中的encounterId:', item.encounterId);
// 设置当前就诊ID确保住院功能能获取到正确的ID
currentEncounterId.value = item.encounterId;
console.log('currentEncounterId.value 设置为:', currentEncounterId.value);
currentEncounterId.value = '';
loading.value = true;
patientList.value.forEach((patient) => {
patient.active = patient.encounterId === item.encounterId;
});
patientInfo.value = item;
console.log('patientInfo.value 设置为:', patientInfo.value);
console.log('patientInfo.value.encounterId:', patientInfo.value?.encounterId);
// 确保患者信息包含必要的字段
if (!patientInfo.value.encounterId) {
console.error('患者信息缺少encounterId字段:', patientInfo.value);
// 可能需要从其他字段获取encounterId
if (item.id) {
patientInfo.value.encounterId = item.id;
console.log('使用item.id作为encounterId:', item.id);
}
}
// 添加安全检查确保item不为undefined或null
if (!item) {
console.error('handleCardClick: 传入的item为null或undefined');
return;
}
// 记录patientInfo内容到控制台
setTimeout(() => {
const patientInfoStr = JSON.stringify(patientInfo.value, null, 2);
console.log('当前patientInfo内容:', patientInfo.value);
console.log('patientInfo.encounterId值:', patientInfo.value?.encounterId);
}, 100);
// console.log('patientInfo.value.cardNo:', patientInfo.value.cardNo)
// 将患者信息保存到store中供hospitalizationEmr组件使用
updatePatientInfo(item);
activeTab.value = 'hospitalizationEmr';
@@ -557,21 +510,6 @@ function handleTimeChange(value) {
getPatientList();
}
// 处理办理住院点击事件
function handleHospitalizationClick() {
console.log('handleHospitalizationClick 被调用');
console.log('当前patientInfo:', patientInfo.value);
console.log('当前patientInfo.encounterId:', patientInfo.value?.encounterId);
if (!patientInfo.value || !patientInfo.value.encounterId) {
console.log('患者信息不完整,无法办理住院');
ElMessage.warning('请先选择患者');
} else {
console.log('调用onHospitalization函数');
onHospitalization();
}
}
// 接诊回调
function handleReceive(row) {
handleCardClick(row);
@@ -591,129 +529,31 @@ function openDrawer() {
}
// 判断是否已经入院登记
const onHospitalization = async () => {
console.log('onHospitalization 被调用');
console.log('patientInfo.value:', patientInfo.value);
console.log('patientInfo.value.encounterId:', patientInfo.value?.encounterId);
// 检查是否有有效的就诊ID
if (!patientInfo.value?.encounterId) {
console.log('缺少有效的就诊ID无法办理住院');
const diagnosisRes = await getEncounterDiagnosis(patientInfo.value.encounterId);
const hasDiagnosis = diagnosisRes.data?.length > 0;
if (!hasDiagnosis) {
ElMessage({
type: 'error',
message: '患者就诊信息不完整,无法办理住院!',
message: '患者暂无诊断信息,无法办理住院!',
});
return;
}
try {
console.log('开始获取诊断信息encounterId:', patientInfo.value.encounterId);
const diagnosisRes = await getEncounterDiagnosis(patientInfo.value.encounterId);
console.log('诊断信息获取结果:', diagnosisRes);
// 检查API调用是否成功
if (diagnosisRes.code !== 200) {
console.log('获取诊断信息失败:', diagnosisRes.msg);
ElMessage({
type: 'error',
message: diagnosisRes.msg || '获取诊断信息失败,无法办理住院!',
});
return;
}
const hasDiagnosis = diagnosisRes.data?.length > 0;
console.log('是否有诊断信息:', hasDiagnosis);
if (!hasDiagnosis) {
console.log('该患者暂无诊断信息');
const confirmResult = await ElMessageBox.confirm(
'该患者暂无诊断信息,是否前往添加诊断?',
'提示',
{
confirmButtonText: '前往添加',
cancelButtonText: '暂不办理',
type: 'warning',
}
).catch(() => {
// 用户取消操作
console.log('用户取消前往添加诊断');
return false;
});
if (confirmResult === true) {
// 自动切换到诊断标签页以便用户添加诊断
activeTab.value = 'diagnosis';
}
return;
}
const mainDiag = diagnosisRes.data.find((item) => item.maindiseFlag === 1);
console.log('主诊断信息:', mainDiag);
if (!mainDiag) {
console.log('该患者暂无主诊断信息');
const confirmResult = await ElMessageBox.confirm(
'该患者暂无主诊断信息,是否前往设置主诊断?',
'提示',
{
confirmButtonText: '前往设置',
cancelButtonText: '暂不办理',
type: 'warning',
}
).catch(() => {
// 用户取消操作
console.log('用户取消前往设置主诊断');
return false;
});
if (confirmResult === true) {
// 自动切换到诊断标签页以便用户设置主诊断
activeTab.value = 'diagnosis';
}
return;
}
mainDiagnosis.value = mainDiag;
console.log('主诊断设置成功:', mainDiagnosis.value);
console.log('检查住院状态encounterId:', patientInfo.value.encounterId);
const res = await isHospitalization({
encounterId: patientInfo.value.encounterId,
});
console.log('住院状态检查结果:', res);
// 检查API调用是否成功
if (res.code !== 200) {
console.log('检查住院状态失败:', res.msg);
ElMessage({
type: 'error',
message: res.msg || '检查住院状态失败!',
});
return;
}
console.log('住院状态检查API响应:', res);
console.log('res.data值:', res.data);
console.log('res.data类型:', typeof res.data);
if (!res.data) {
console.log('患者未办理过住院,打开住院登记对话框');
console.log('当前openDialog值:', openDialog.value);
openDialog.value = true;
console.log('设置openDialog为true后值为:', openDialog.value);
} else {
console.log('患者已办理过住院');
ElMessage({
type: 'error',
message: '该患者,已办理入院,不允许重复办理',
});
}
} catch (error) {
console.error('办理住院检查过程中发生错误:', error);
// 显示详细的错误信息
const errorMessage = error.message || error.msg || '办理住院过程中发生错误,请稍后重试!';
const mainDiag = diagnosisRes.data.find((item) => item.maindiseFlag === 1);
if (!mainDiag) {
ElMessage({ type: 'error', message: '该患者暂无主诊断信息,无法办理住院!' });
return;
}
mainDiagnosis.value = mainDiag;
const res = await isHospitalization({
encounterId: patientInfo.value.encounterId,
});
if (!res.data) {
openDialog.value = true;
} else {
ElMessage({
type: 'error',
message: errorMessage,
duration: 5000 // 增加显示时长以便用户阅读
});
message: '该患者,已办理入院,不允许重复办理',
});
}
};
</script>
@@ -906,20 +746,10 @@ const onHospitalization = async () => {
left: 0;
width: 100%;
height: calc(100% - 50px);
z-index: 998; /* 降低z-index避免覆盖按钮 */
z-index: 999;
/* 确保覆盖在内容上方,但不覆盖顶部按钮区域 */
cursor: not-allowed;
background-color: rgba(255, 255, 255, 0.01);
pointer-events: none; /* 默认不捕获鼠标事件,只在真正需要禁用时才启用 */
}
.disabled-wrapper .overlay.overlay-disabled {
pointer-events: auto; /* 当需要真正禁用时启用指针事件 */
}
/* 顶层按钮样式,确保按钮始终在最上层 */
.top-layer-btn {
position: relative !important;
z-index: 1000 !important;
pointer-events: auto;
}
</style>

View File

@@ -426,7 +426,7 @@ function getInitOptions() {
getOrgList().then((res) => {
// organization.value = res.data.records
organization.value = res.data.records[0].children.filter(
(record) => record.typeEnum === 2 && checkClassEnumValue(record.classEnum, 2)
(record) => record.typeEnum === 2 && record.classEnum === 2
);
});
// if (!props.noFile) {
@@ -537,21 +537,6 @@ const init = () => {
submitForm.wardLocationId = '';
}
};
// 检查classEnum值是否包含指定值支持多选
function checkClassEnumValue(classEnum, targetValue) {
if (!classEnum) return false;
// 如果是字符串且包含逗号,说明是多选值
if (typeof classEnum === 'string' && classEnum.includes(',')) {
const values = classEnum.split(',').map(v => v.trim());
return values.some(v => v == targetValue);
}
// 单个值的情况
return classEnum == targetValue;
}
defineExpose({ validateData, submitForm, init, medicalInsuranceTitle });
</script>
<style lang="scss" scoped>

View File

@@ -696,12 +696,6 @@ import {
listInspectionType,
updateInspectionType
} from '@/api/system/inspectionType';
import {
getDiagnosisTreatmentList,
addDiagnosisTreatment,
editDiagnosisTreatment,
stopDiseaseTreatment
} from '@/views/catalog/diagnosistreatment/components/diagnosistreatment';
import {listLisGroup} from '@/api/system/checkType';
import {
addInspectionPackage,
@@ -709,6 +703,7 @@ import {
listInspectionPackageDetails,
saveInspectionPackageDetails
} from '@/api/system/inspectionPackage';
import {getDiagnosisTreatmentList} from '@/views/catalog/diagnosistreatment/components/diagnosistreatment';
import {getLocationTree} from '@/views/charge/outpatientregistration/components/outpatientregistration';
// 获取当前登录用户信息
@@ -922,48 +917,24 @@ const testTypes = ref([
{ value: '其他检验', label: '其他检验' }
]);
// 检验项目数据 - 从后端API获取
const inspectionItems = ref([]);
// 从后端API获取检验项目数据
const loadObservationItems = async () => {
try {
const response = await getDiagnosisTreatmentList({
pageNo: 1,
pageSize: 100,
categoryCode: '检验'
});
if (response.code === 200) {
let data = [];
if (response.data && response.data.records) {
data = response.data.records;
} else if (response.data && Array.isArray(response.data)) {
data = response.data;
}
inspectionItems.value = data
// 过滤掉已停用的项目状态为3
.filter(item => item.statusEnum !== 3)
.map(item => ({
id: item.id,
code: item.busNo || '',
name: item.name || '',
testType: '',
package: '',
sampleType: item.specimenCode_dictText || '',
amount: parseFloat(item.retailPrice || 0),
sortOrder: item.sortOrder || null,
serviceRange: item.serviceRange || '全部',
sub医技Type: '',
remark: item.descriptionText || '',
status: true
}));
}
} catch (error) {
console.error('获取检验项目数据失败:', error);
}
};
// 检验项目数据
const inspectionItems = ref([
{ id: 1, code: '0101', name: '血常规五分类', testType: '生化', package: '肝功能12项', sampleType: '血液', amount: 36.00, sortOrder: 1, serviceRange: '全部', sub医技Type: '', remark: '', status: true },
{ id: 2, code: '0102', name: '肝功能12项', testType: '生化', package: '肝功能12项', sampleType: '血液', amount: 120.00, sortOrder: 2, serviceRange: '全部', sub医技Type: '', remark: '', status: true },
{ id: 3, code: '0201', name: '尿常规', testType: '常规检验', package: '', sampleType: '尿液', amount: 25.00, sortOrder: 3, serviceRange: '全部', sub医技Type: '', remark: '', status: true },
{ id: 4, code: '0202', name: '便常规+潜血', testType: '常规检验', package: '', sampleType: '粪便', amount: 30.00, sortOrder: 4, serviceRange: '门诊', sub医技Type: '', remark: '', status: true },
{ id: 5, code: '0301', name: '乙肝五项', testType: '免疫学检验', package: '乙肝套餐', sampleType: '血液', amount: 75.00, sortOrder: 5, serviceRange: '全部', sub医技Type: '', remark: '', status: true },
{ id: 6, code: '0302', name: '丙肝抗体', testType: '免疫学检验', package: '', sampleType: '血液', amount: 45.00, sortOrder: 6, serviceRange: '住院', sub医技Type: '', remark: '', status: true },
{ id: 7, code: '0401', name: '血糖', testType: '生化', package: '糖尿病套餐', sampleType: '血液', amount: 15.00, sortOrder: 7, serviceRange: '全部', sub医技Type: '', remark: '', status: true },
{ id: 8, code: '0402', name: '糖化血红蛋白', testType: '生化', package: '糖尿病套餐', sampleType: '血液', amount: 50.00, sortOrder: 8, serviceRange: '全部', sub医技Type: '', remark: '', status: true },
{ id: 9, code: '0501', name: '肌酐', testType: '生化', package: '肾功能套餐', sampleType: '血液', amount: 25.00, sortOrder: 9, serviceRange: '住院', sub医技Type: '', remark: '', status: true },
{ id: 10, code: '0502', name: '尿素氮', testType: '生化', package: '肾功能套餐', sampleType: '血液', amount: 20.00, sortOrder: 10, serviceRange: '住院', sub医技Type: '', remark: '', status: true },
{ id: 11, code: '0601', name: '白带常规', testType: '常规检验', package: '', sampleType: '分泌物', amount: 30.00, sortOrder: 11, serviceRange: '门诊', sub医技Type: '', remark: '', status: true },
{ id: 12, code: '0602', name: '前列腺液常规', testType: '常规检验', package: '', sampleType: '分泌物', amount: 35.00, sortOrder: 12, serviceRange: '门诊', sub医技Type: '', remark: '', status: true },
{ id: 13, code: '0701', name: '脑脊液常规', testType: '常规检验', package: '', sampleType: '脑脊液', amount: 60.00, sortOrder: 13, serviceRange: '住院', sub医技Type: '', remark: '', status: true },
{ id: 14, code: '0801', name: '肿瘤标志物CA125', testType: '免疫学检验', package: '肿瘤筛查套餐', sampleType: '血液', amount: 120.00, sortOrder: 14, serviceRange: '体检', sub医技Type: '', remark: '', status: true },
{ id: 15, code: '0802', name: '肿瘤标志物AFP', testType: '免疫学检验', package: '肿瘤筛查套餐', sampleType: '血液', amount: 80.00, sortOrder: 15, serviceRange: '体检', sub医技Type: '', remark: '', status: true }
]);
// 过滤条件
const testTypeFilter = ref('');
@@ -1581,7 +1552,7 @@ const updateAmountFromPackage = (item) => {
}
};
const saveItem = async (item) => {
const saveItem = (item) => {
// 验证必填字段
if (!item.code || item.code.trim() === '') {
ElMessage.error('小类编码不能为空');
@@ -1623,67 +1594,24 @@ const saveItem = async (item) => {
// 从费用套餐获取金额
updateAmountFromPackage(item);
try {
// 准备提交给后端的数据
const submitData = {
busNo: item.code.trim(),
name: item.name.trim(),
categoryCode: '检验',
specimenCode: item.sampleType,
retailPrice: item.amount,
descriptionText: item.remark,
typeEnum: 1,
statusEnum: 2,
sortOrder: item.sortOrder ? parseInt(item.sortOrder) : null,
serviceRange: item.serviceRange || '全部'
};
// 判断是新增还是更新
if (typeof item.id === 'number') { // 临时ID数字类型新增
const response = await addDiagnosisTreatment(submitData);
if (response.code === 200) {
ElMessage.success('添加成功');
await loadObservationItems();
} else {
ElMessage.error(response.msg || '添加失败');
}
} else { // 真实ID字符串类型更新
submitData.id = item.id;
const response = await editDiagnosisTreatment(submitData);
if (response.code === 200) {
ElMessage.success('更新成功');
await loadObservationItems();
} else {
ElMessage.error(response.msg || '更新失败');
}
}
editingRowId.value = null;
} catch (error) {
console.error('保存检验项目失败:', error);
ElMessage.error('保存失败,请稍后重试');
}
// 保存成功
editingRowId.value = null;
ElMessage.success('保存成功');
};
const deleteItem = async (id) => {
const deleteItem = (id) => {
ElMessageBox.confirm('确定要删除该检验项目吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const response = await stopDiseaseTreatment([id]);
if (response.code === 200) {
ElMessage.success('删除成功');
await loadObservationItems();
} else {
ElMessage.error(response.msg || '删除失败');
}
} catch (error) {
console.error('删除检验项目失败:', error);
ElMessage.error('删除失败,请稍后重试');
}).then(() => {
const index = inspectionItems.value.findIndex(item => item.id === id);
if (index !== -1) {
inspectionItems.value.splice(index, 1);
ElMessage.success('删除成功');
}
}).catch(() => {
// 取消删除
});
};
@@ -2065,8 +1993,6 @@ const refreshPage = () => {
onMounted(() => {
getInspectionTypeList();
getLisGroupList();
// 加载检验项目数据
loadObservationItems();
// 加载检验套餐明细项目
loadPackageItemsFromAPI();
// 检查URL参数如果有tab参数则切换到对应导航项

View File

@@ -195,14 +195,14 @@
<!-- 新增或修改手术对话框 -->
<el-dialog :title="title" v-model="open" width="800px" @close="cancel" append-to-body :close-on-click-modal="false">
<!-- 编辑模式下显示当前状态 -->ElMessageBox is not defined
<!-- 编辑模式下显示当前状态 -->
<el-alert v-if="isEditMode && form.statusEnum !== undefined" :title="'当前状态: ' + getStatusText(form.statusEnum)" :type="getStatusType(form.statusEnum)" :closable="false" style="margin-bottom: 20px;" />
<!-- 查看模式下显示提示 -->
<el-alert v-if="isViewMode" title="当前为查看模式,所有字段均为只读状态" type="info" :closable="false" style="margin-bottom: 20px;" />
<el-form ref="surgeryRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="id" prop="id" v-show="false">
<el-input v-model="form.id" />s
<el-input v-model="form.id" />
</el-form-item>
<el-row :gutter="20">