Compare commits
174 Commits
bug475-fix
...
赵云
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e8acfe552 | ||
|
|
a09af3a384 | ||
|
|
1308d0299e | ||
|
|
0d9f56a78e | ||
|
|
a48bf9c56c | ||
|
|
22f7a1c1a6 | ||
|
|
82b5fc747e | ||
|
|
5e5f2c3d79 | ||
|
|
5a85bf82f9 | ||
|
|
cc3faac61f | ||
|
|
aabc735357 | ||
|
|
59bd8636a3 | ||
|
|
e694b75834 | ||
|
|
4f7abbf43a | ||
|
|
2f62147a64 | ||
|
|
f7a036deb0 | ||
|
|
921cd2a963 | ||
|
|
cac8434e0a | ||
|
|
bf07aa1e8c | ||
|
|
fd6e2ded03 | ||
|
|
a3c4e9c8bd | ||
|
|
16ef28c96f | ||
|
|
c635bdf3fb | ||
|
|
013ddfab20 | ||
|
|
d838be1a18 | ||
|
|
3ab3ddbdf1 | ||
|
|
d2cb02eeef | ||
|
|
8850689f1f | ||
|
|
4c7d362946 | ||
| 51ae3aad29 | |||
| d984b89967 | |||
|
|
b9aabd53ce | ||
|
|
73ed5e1d33 | ||
|
|
d3cd122656 | ||
|
|
0930fbae93 | ||
|
|
cace025d14 | ||
|
|
91f29bf693 | ||
| a6337ae397 | |||
|
|
c2e089c0d2 | ||
|
|
e65f12125b | ||
|
|
12d0733c0c | ||
|
|
610fff704a | ||
|
|
0aa7dd9b82 | ||
|
|
5946c1ea4b | ||
|
|
8d905c9844 | ||
|
|
49fc905316 | ||
|
|
3ee09b22c7 | ||
|
|
6b4f897b9c | ||
|
|
848a55cf23 | ||
|
|
4ae4421827 | ||
|
|
4138dc39f6 | ||
|
|
718e7a90c5 | ||
|
|
68c682ad49 | ||
|
|
c7368db889 | ||
|
|
e64370bb67 | ||
|
|
078439245b | ||
|
|
1124b1010d | ||
|
|
f41b86a143 | ||
|
|
d3310ade51 | ||
|
|
1dbf7859ea | ||
|
|
6940c3861d | ||
|
|
6e975bf9c4 | ||
|
|
3360cccaa5 | ||
|
|
fe138589a5 | ||
|
|
270475adb9 | ||
|
|
7d0c93b9a1 | ||
|
|
87f5135ddc | ||
|
|
e6c0d03dc1 | ||
|
|
5f7b75667a | ||
|
|
2cdda279a4 | ||
| bc595e3843 | |||
|
|
53e5ee331b | ||
|
|
4e7e79d9c0 | ||
|
|
571f254d0e | ||
|
|
560813d009 | ||
| 31c2acb4ef | |||
|
|
254de01d2e | ||
|
|
e21122edf0 | ||
|
|
e9576ddfa8 | ||
|
|
b435de9e7b | ||
|
|
bc13fd6968 | ||
|
|
d9ad63397b | ||
|
|
bb3e1e300d | ||
|
|
46358ea03d | ||
| b5c308d9cb | |||
|
|
adfeb8f5e5 | ||
|
|
fd9309f125 | ||
|
|
46affb424e | ||
|
|
6dcee26b54 | ||
|
|
a282234bb0 | ||
|
|
52fc64c71d | ||
|
|
0bd1277307 | ||
|
|
e0e4c2bcc6 | ||
|
|
41bea23116 | ||
|
|
12382503f4 | ||
|
|
ae50a7042e | ||
|
|
9b1ac64cd6 | ||
|
|
6367654ada | ||
|
|
360256e589 | ||
|
|
feb033b857 | ||
|
|
79cce458ee | ||
|
|
1140912f3a | ||
| 250f9ce258 | |||
| 0d6f891b47 | |||
|
|
e68be3be79 | ||
|
|
eab0119c19 | ||
|
|
3ad9ff85d4 | ||
|
|
ab2f580d60 | ||
|
|
665d4ae47a | ||
|
|
d43a06c343 | ||
|
|
a7a33eb5f6 | ||
|
|
444397e868 | ||
|
|
d964155fb8 | ||
|
|
b88e011459 | ||
|
|
492a51d282 | ||
|
|
34774411eb | ||
|
|
0f1b29fcea | ||
|
|
d64ca5b8ee | ||
|
|
faa0b1a61f | ||
|
|
33c76c786c | ||
|
|
1a770ca0ee | ||
|
|
ba9c18b6a4 | ||
|
|
ecc5c75418 | ||
|
|
164ac604fb | ||
|
|
d19ceab70f | ||
|
|
753768a1f0 | ||
|
|
49889e9140 | ||
|
|
3c3428e0b1 | ||
|
|
db05a30795 | ||
|
|
e2feb4850c | ||
|
|
02f2a14178 | ||
|
|
1c87c39473 | ||
|
|
7c28a98d02 | ||
|
|
8e042cae93 | ||
|
|
415a76af49 | ||
|
|
6dc9788d8c | ||
|
|
319224cdac | ||
|
|
66d42f415a | ||
|
|
fd0132ba80 | ||
|
|
6907c7dbc8 | ||
|
|
487b05c845 | ||
|
|
71f99da69a | ||
|
|
15a65063a3 | ||
|
|
72fdafb032 | ||
|
|
56b8d0e98d | ||
|
|
f13734a19c | ||
|
|
6081412072 | ||
|
|
4a2f13cb19 | ||
|
|
522bc238aa | ||
|
|
bea2f27b15 | ||
|
|
95da4c2a57 | ||
|
|
53e3e9c4c0 | ||
|
|
55eba1a0b1 | ||
|
|
1525740ab5 | ||
|
|
96102e8b64 | ||
|
|
49df72121f | ||
|
|
65d1716ca9 | ||
|
|
509a0a788f | ||
|
|
6c4a8e3c14 | ||
|
|
7ada54510d | ||
|
|
1602615820 | ||
|
|
af15f2ae06 | ||
|
|
b1d5ae97b1 | ||
|
|
e5cd7bd792 | ||
|
|
294d7a5d11 | ||
|
|
e78a32a5ec | ||
|
|
dc9f47c534 | ||
|
|
5bf1e4151c | ||
|
|
b7365b6b06 | ||
|
|
c5c3bcae34 | ||
|
|
bacddc6d3f | ||
|
|
31cac09126 | ||
|
|
abc3bdd0c0 | ||
|
|
0f85e95d24 |
66
.analysis/bug403_analysis.md
Normal file
66
.analysis/bug403_analysis.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# Bug #403 分析报告
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
**Bug现象**:住院医生工作站应用医嘱组套后,药品明细字段(单次剂量、总量、总金额、药房/科室)丢失。
|
||||||
|
|
||||||
|
**数据流追踪**:
|
||||||
|
|
||||||
|
1. **后端 `getGroupPackageForOrder`** (OrdersGroupPackageAppServiceImpl.java:168)
|
||||||
|
- 查询组套明细 SQL(OrdersGroupPackageAppMapper.xml:37-82)返回:`dose`, `quantity`, `doseQuantity`, `rateCode`, `methodCode`, `dispensePerDuration` 等字段
|
||||||
|
- 通过 `getAdviceBaseInfo` 获取 `AdviceBaseDto` 赋值给 `detail.setOrderDetailInfos()`,包含:`doseUnitCode`, `doseUnitCode_dictText`, `positionId`, `inventoryList`, `priceList`, `partPercent` 等
|
||||||
|
|
||||||
|
2. **前端 `orderGroupDrawer.vue`** `handleUseOrderGroup` (line 568-694)
|
||||||
|
- 对每个组套明细项进行预处理,合并组套字段和医嘱库字段
|
||||||
|
- 通过 `emit('useOrderGroup', processedDetailList)` 发送到父组件
|
||||||
|
|
||||||
|
3. **前端 `inpatientDoctor/home/components/order/index.vue`** `handleSaveGroup` (line 1546-1639)
|
||||||
|
- 接收 `orderGroupList`,对每个 item 调用 `setValue(mergedDetail)` 填充行数据
|
||||||
|
- 然后用 `item` 的字段显式覆盖创建 `newRow`
|
||||||
|
|
||||||
|
**根因定位**:`handleSaveGroup` 在构建 `newRow` 时(line 1594-1617),从 `item` 直接取值覆盖了 `setValue` 设置的值。问题在于:
|
||||||
|
|
||||||
|
1. **`item.unitCodeName` 可能为 undefined**:组套明细 SQL 中 `unitCodeName` 来自字典关联 `sys_dict_data`,如果字典匹配不上则为 null。`newRow` 的 `unitCode_dictText` 直接使用 `item.unitCodeName || ''`,导致显示为空。
|
||||||
|
|
||||||
|
2. **`positionName` 未在 `orderGroupDrawer` 处理项中显式设置**:虽然 `setValue` 会通过库存查询设置 `positionName`,但 `orderGroupDrawer.vue` 的 `handleUseOrderGroup` 没有将 `positionName`(或至少 `orderDetail.positionName`)包含在 processed item 中,导致 `setValue` 的库存查找依赖 `inventoryList`,而 `inventoryList` 来自后端 `AdviceBaseDto`。
|
||||||
|
|
||||||
|
3. **`doseUnitCode_dictText` 依赖 `setValue` 的 `unitCodeList`**:`orderGroupDrawer` 的处理项中没有显式包含 `doseUnitCode_dictText`,完全依赖 `mergedDetail` 中 spread 的 `orderDetail` 字段。
|
||||||
|
|
||||||
|
## 影响范围
|
||||||
|
|
||||||
|
- 前端文件:`openhis-ui-vue3/src/views/doctorstation/components/prescription/orderGroupDrawer.vue`
|
||||||
|
- 前端文件:`openhis-ui-vue3/src/views/inpatientDoctor/home/components/order/index.vue`
|
||||||
|
- 影响场景:住院医生工作站和门诊医生工作站应用医嘱组套
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
**修改 `orderGroupDrawer.vue` 的 `handleUseOrderGroup` 函数**(line 630-688):
|
||||||
|
|
||||||
|
在 processed item 的 return 对象中显式添加缺失的字段:
|
||||||
|
- `doseUnitCode_dictText`:从 orderDetail 获取剂量单位显示文本
|
||||||
|
- `positionName`:从 orderDetail 获取执行科室/药房名称
|
||||||
|
- `injectFlag` / `injectFlag_enumText`:注射标识
|
||||||
|
- `skinTestFlag` / `skinTestFlag_enumText`:皮试标识
|
||||||
|
- `partPercent`、`partAttributeEnum`、`unitConversionRatio`:用于价格计算的关键字段
|
||||||
|
|
||||||
|
这些字段在 `orderDetail`(AdviceBaseDto)中都有,只是没有在 processed item 的顶层显式设置。`handleSaveGroup` 的 `newRow` 通过 `...prescriptionList.value[rowIndex.value]` spread 能获取到 `setValue` 设置的值,但显式在顶层包含可以确保数据流的完整性。
|
||||||
|
|
||||||
|
## 验证计划
|
||||||
|
|
||||||
|
1. 修改代码后,用 `node --check` 验证语法
|
||||||
|
2. 在住院医生工作站测试:选择患者 → 点击组套 → 预览组套 → 应用到当前患者
|
||||||
|
3. 验证表格中显示的字段:单次剂量、总量、总金额、药房/科室均有值
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 修复结果:✅ 成功,10行改动
|
||||||
|
|
||||||
|
**修改文件**:`openhis-ui-vue3/src/views/doctorstation/components/prescription/orderGroupDrawer.vue`
|
||||||
|
|
||||||
|
**改动说明**:在 `handleUseOrderGroup` 函数的 processed item 中显式添加了以下缺失字段:
|
||||||
|
- `doseUnitCode_dictText`:剂量单位显示文本(如"mg"),用于"单次剂量"列的后缀显示
|
||||||
|
- `positionName`:药房/科室名称,用于"药房/科室"列显示
|
||||||
|
- `injectFlag` / `injectFlag_enumText`:注射药品标识及文本
|
||||||
|
- `skinTestFlag` / `skinTestFlag_enumText`:皮试标识及文本
|
||||||
|
|
||||||
|
**策略**:策略A(直接修复代码逻辑)—— 组套应用时数据预处理缺失部分关键字段,导致父组件 `handleSaveGroup` 构建行数据时无法获取完整信息。补充字段后,`setValue` 和 `newRow` 构造均能正确传递这些数据到表格。
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
# ============================================================
|
|
||||||
# Husky Pre-commit Hook - HIS项目
|
|
||||||
# 配置: 关羽 | 日期: 2026-04-24
|
|
||||||
# 功能: 提交前检查(已禁用)
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
# 🔧 已禁用所有检查,直接允许提交
|
|
||||||
echo "⏭️ [Pre-commit] 检查已禁用,允许提交"
|
|
||||||
exit 0
|
|
||||||
28
ANALYSIS.md
Normal file
28
ANALYSIS.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
## Bug #426 修复报告
|
||||||
|
|
||||||
|
### 根因分析
|
||||||
|
Element Plus `el-table` 的懒加载树形模式(`lazy` + `:load` + `tree-props="{ hasChildren: 'hasChildren' }"`)要求每一行数据必须包含 `hasChildren: true` 属性,才会在该行前渲染展开箭头(+ / -)。
|
||||||
|
|
||||||
|
代码中所有创建 `selectedItems` 行对象的路径(共7处)都正确设置了 `isPackage: true` 和 `packageId`,但**遗漏了 `hasChildren` 属性**,导致树形表格无法识别哪些行是可展开的套餐项。
|
||||||
|
|
||||||
|
### 影响范围
|
||||||
|
- **文件**: `examinationApplication.vue`(前端)
|
||||||
|
- **涉及函数**: `handleItemSelect`、`handleMethodSelect`、`handleRowClick`、`onDetailMethodChange`
|
||||||
|
- **数据表**: 无数据库变更
|
||||||
|
|
||||||
|
### 修复方案
|
||||||
|
在7处代码路径中,当 `packageId` 存在时同步设置 `hasChildren: true`:
|
||||||
|
1. `handleRowClick` 初始 item 创建: `hasChildren: false`
|
||||||
|
2. `handleRowClick` 回充时设置 `isPackage` 两处: `hasChildren: true`
|
||||||
|
3. `handleMethodSelect` 已存在项更新: `hasChildren: true`
|
||||||
|
4. `handleMethodSelect` 新项创建: `hasChildren: !!(method.packageId || targetItem.packageId)`
|
||||||
|
5. `handleItemSelect` 新行创建: `hasChildren: !!(item.packageId)`
|
||||||
|
6. `onDetailMethodChange` 方法切换: `hasChildren: true`
|
||||||
|
|
||||||
|
### 验证计划
|
||||||
|
- 在门诊医生站选择检查套餐后,"检查明细" tab 的树形表格应显示展开箭头
|
||||||
|
- 点击展开箭头应懒加载套餐明细(项目名称、数量、单价)
|
||||||
|
- 回充已保存申请单时套餐项应正确显示展开箭头
|
||||||
|
|
||||||
|
修复结果:✅ 成功,13行改动
|
||||||
54
ANALYSIS_433.md
Normal file
54
ANALYSIS_433.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Bug #433 分析报告
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
### 问题1:麻醉方法回显为代码
|
||||||
|
|
||||||
|
**数据流**:
|
||||||
|
1. 数据库 `op_schedule.anes_method` 字段为 VARCHAR,存值为字典代码字符串如 `"2"`
|
||||||
|
2. 后端 `OpSchedule.anesMethod` 为 String 类型,通过 `getSurgeryScheduleDetail` 查询返回
|
||||||
|
3. 前端 el-select 选项通过 `useDict('anesthesia_type')` 加载,选项值为 `Number(item.value)` 即数字类型
|
||||||
|
4. `handleEdit` 中 `Object.assign(form, data)` 后 `form.anesMethod` 为字符串 `"2"`
|
||||||
|
|
||||||
|
**根因**: `form.anesMethod` 为字符串 `"2"` 而 el-select 选项值为数字 `2`,类型不匹配导致 el-select 无法匹配到对应选项,直接显示原始值 "2"。
|
||||||
|
|
||||||
|
**现有代码的问题**: 代码中有两行转换逻辑:
|
||||||
|
```javascript
|
||||||
|
if (data.anesMethod != null) form.anesMethod = Number(data.anesMethod) // OK
|
||||||
|
if (data.anesthesiaTypeEnum != null) form.anesMethod = Number(data.anesthesiaTypeEnum) // 多余
|
||||||
|
```
|
||||||
|
第二行 `data.anesthesiaTypeEnum` 不是 `OpScheduleDto` 的字段,SQL 查询也不包含此字段,因此永远为 null。但如果某些情况下后端返回了此字段(例如值为 0),会错误覆盖第一行的正确赋值。
|
||||||
|
|
||||||
|
### 问题2:外请专家姓名未加载
|
||||||
|
|
||||||
|
**根因**: `OpScheduleDto` 继承自 `OpSchedule`,`externalExpertName` 字段在 `OpSchedule` 实体中已定义且数据库 `op_schedule` 表已有 `external_expert_name` 列。`getSurgeryScheduleDetail` 查询使用 `SELECT os.*`,会返回该字段。前端 `form` 中也已定义 `externalExpertName`。
|
||||||
|
|
||||||
|
经数据库查询验证,当前数据中 `external_expert_name` 字段确实为空(尚未有用户填写过此字段)。但需确保 `Object.assign` 正确映射,且 `isExternalExpert` 类型匹配 el-radio 的 `:value="1"` / `:value="0"`。
|
||||||
|
|
||||||
|
## 影响范围
|
||||||
|
|
||||||
|
- **前端**: `openhis-ui-vue3/src/views/surgicalschedule/index.vue` — `handleEdit` 和 `handleView` 方法
|
||||||
|
- **后端**: 无需修改(字段已存在且正常返回)
|
||||||
|
- **数据库**: 无需修改(字段已存在)
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
在 `handleEdit` 和 `handleView` 方法中:
|
||||||
|
1. 删除多余的 `anesthesiaTypeEnum` 转换行
|
||||||
|
2. 使用 `$nextTick` 确保类型转换在 `Object.assign` 后在下一个 tick 执行,确保 Vue 响应式系统已处理完 `Object.assign` 的变更后再设置值
|
||||||
|
3. 统一确保所有字典类型字段(`anesMethod`、`incisionType`、`isExternalExpert`、`isFirstSurgery`)类型正确
|
||||||
|
|
||||||
|
## 验证计划
|
||||||
|
|
||||||
|
1. 修改后用 `node --check` 验证 .vue 语法
|
||||||
|
2. 确认 git diff 改动 ≥ 3 行
|
||||||
|
|
||||||
|
## 修复结果
|
||||||
|
|
||||||
|
✅ 成功,28行改动(handleEdit 和 handleView 各 7 行 × 2 函数)
|
||||||
|
|
||||||
|
### 改动摘要
|
||||||
|
|
||||||
|
1. **删除错误行**: `if (data.anesthesiaTypeEnum != null) form.anesMethod = Number(data.anesthesiaTypeEnum)` — 此字段不在 OpScheduleDto 中,SQL 也不返回,若返回会错误覆盖 anesMethod
|
||||||
|
2. **使用 nextTick 包裹类型转换**: 确保 Object.assign 触发的 Vue 响应式更新完成后再设置字典字段值,避免 el-select 在 DOM 更新前无法匹配选项
|
||||||
|
3. **同时修复 handleEdit 和 handleView**: 两处代码一致,均需要同步修复
|
||||||
50
ANALYSIS_434.md
Normal file
50
ANALYSIS_434.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Bug #434 分析报告
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
### 问题:编辑弹窗中"切口类型"字段未正确回显数据
|
||||||
|
|
||||||
|
**数据流追踪**:
|
||||||
|
1. 用户点击"编辑"→ 前端调用 `getSurgeryScheduleDetail(row.scheduleId)`
|
||||||
|
2. 后端 SQL: `cs.incision_level AS incisionLevel`
|
||||||
|
3. PostgreSQL 返回列名: `incisionlevel` (全小写)
|
||||||
|
4. MyBatis 尝试将 `incisionlevel` 映射到 `OpScheduleDto.incisionLevel`
|
||||||
|
5. 映射失败!→ `data.incisionLevel` 为 null → `form.incisionType` 保持 undefined → el-select 显示空白
|
||||||
|
|
||||||
|
### 根因:PostgreSQL 小写化未加引号的列别名
|
||||||
|
|
||||||
|
PostgreSQL 会将未加双引号的列别名自动转为小写:
|
||||||
|
```sql
|
||||||
|
-- SQL 写的别名
|
||||||
|
cs.incision_level AS incisionLevel
|
||||||
|
-- PostgreSQL 实际返回的列名
|
||||||
|
incisionlevel ← 全小写!
|
||||||
|
```
|
||||||
|
|
||||||
|
MyBatis 收到列名 `incisionlevel`(全小写),尝试匹配 Java 属性 `incisionLevel`(驼峰)。由于 `mapUnderscoreToCamelCase` 只对含下划线的列生效(`incisionlevel` 无下划线),匹配失败。
|
||||||
|
|
||||||
|
**对比 `anes_method` 为什么能工作**:
|
||||||
|
- SQL: `os.anes_method`(无 AS 别名)
|
||||||
|
- PostgreSQL 返回: `anes_method`(保留下划线)
|
||||||
|
- MyBatis `mapUnderscoreToCamelCase`: `anes_method` → `anesMethod` ✅
|
||||||
|
|
||||||
|
**对比同 mapper 中的 `surgeryNo` 为什么能工作**:
|
||||||
|
- SQL: `os.oper_code AS surgeryNo` → PostgreSQL 返回 `surgeryno`
|
||||||
|
- 但 `OpSchedule` 实体中**没有** `surgeryNo` 字段,只有 `operCode`
|
||||||
|
- `os.oper_code` 列映射到 `operCode` 是通过 `mapUnderscoreToCamelCase` 正常工作的
|
||||||
|
- `surgeryno` 找不到对应属性,被 MyBatis 忽略(不影响功能)
|
||||||
|
|
||||||
|
### 修复方案
|
||||||
|
|
||||||
|
将 SQL 中的别名加双引号:`cs.incision_level AS "incisionLevel"`
|
||||||
|
|
||||||
|
PostgreSQL 对加双引号的标识符保持大小写,返回列名 `incisionLevel`(驼峰),MyBatis 可直接匹配到 `OpScheduleDto.incisionLevel` 属性。
|
||||||
|
|
||||||
|
### 影响范围
|
||||||
|
- **后端**: `SurgicalScheduleAppMapper.xml` — `getSurgeryScheduleDetail` 查询(第92行)
|
||||||
|
- **前端**: 无需修改(`handleEdit`/`handleView` 中的 nextTick 转换逻辑已正确)
|
||||||
|
- **数据库**: 无需修改(`cli_surgery.incision_level` 字段已存在且有数据)
|
||||||
|
|
||||||
|
## 验证计划
|
||||||
|
1. 修改 SQL 后,运行相同查询验证列名变为 `incisionLevel`
|
||||||
|
2. 确认前端 `node --check` 语法通过
|
||||||
65
BUG_426_ANALYSIS.md
Normal file
65
BUG_426_ANALYSIS.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Bug #426 分析报告
|
||||||
|
|
||||||
|
**标题**: 门诊医生站-检查开立:已选择列表应支持树形展开,显示套餐明细(项目/数量/单价)
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
经过完整的代码追踪和数据库验证,定位到 **两个根因**:
|
||||||
|
|
||||||
|
### 根因1:`loadPackageDetails` 响应判断条件错误(树形表格永远加载不到套餐明细)
|
||||||
|
|
||||||
|
**涉及代码**: `examinationApplication.vue` 第576-605行
|
||||||
|
|
||||||
|
Axios 响应拦截器(`request.js` 第202行)对 `code === 200` 的响应返回 `Promise.resolve(res.data)`,即**解包后的 AjaxResult 对象**(如 `{data: [...]}`,不含 `code` 字段)。
|
||||||
|
|
||||||
|
但 `loadPackageDetails` 函数检查的是 `if (res.code === 200)` —— 这个条件 **永远为 false**(解包后的对象没有 `code` 字段),导致树形表格的懒加载 **永远返回空数组**。
|
||||||
|
|
||||||
|
```
|
||||||
|
后端返回: {"code":200,"data":[{item_name:"xxx",quantity:1,...}]}
|
||||||
|
拦截器解包后: {data:[{item_name:"xxx",quantity:1,...}]}
|
||||||
|
loadPackageDetails 判断: res.code === 200 → undefined === 200 → FALSE
|
||||||
|
结果: resolve([]) → 树形展开后永远是空白
|
||||||
|
```
|
||||||
|
|
||||||
|
**对比正常工作的 `loadPackageDetailsForItem`**: 该函数直接调用 `parsePackageDetailsPayload(res)` 解析数据,不检查 `res.code`,所以右侧卡片的套餐明细能正常加载。
|
||||||
|
|
||||||
|
### 根因2:`handleItemSelect` 中 `hasChildren` 未考虑 `packageName` 场景
|
||||||
|
|
||||||
|
**涉及代码**: `examinationApplication.vue` 第1492行
|
||||||
|
|
||||||
|
数据库 `check_part` 表只有 `package_name` 字段,没有 `package_id`。前端创建套餐项时:
|
||||||
|
- `isPackage` 正确判断了 `!!(item.packageId || item.packageName)`
|
||||||
|
- `hasChildren` 只判断了 `!!(item.packageId)`
|
||||||
|
|
||||||
|
当项目有 `packageName` 但无 `packageId` 时,`hasChildren` 为 `false`,el-table 树形模式 **不显示展开箭头**,用户无法点击展开。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 当前代码
|
||||||
|
hasChildren: !!(item.packageId) // item.packageId 为 null → false → 无展开箭头
|
||||||
|
|
||||||
|
// 修复后
|
||||||
|
hasChildren: !!(item.packageId || item.packageName) // 有 packageName 也能展开
|
||||||
|
```
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
1. 修改 `loadPackageDetails` 函数:去掉 `res.code === 200` 检查,直接使用 `parsePackageDetailsPayload(res)` 解析数据(与 `loadPackageDetailsForItem` 保持一致)
|
||||||
|
2. 修改 `handleItemSelect` 中 `hasChildren` 赋值:增加 `|| item.packageName` 条件
|
||||||
|
|
||||||
|
## 验证数据
|
||||||
|
|
||||||
|
数据库确认:
|
||||||
|
- `check_part` 表有 `package_name` 字段(如 "彩色多普勒超声"),无 `package_id`
|
||||||
|
- `check_package` 表 id=29, package_name="彩色多普勒超声"
|
||||||
|
- `check_package_detail` 表有 7 条明细记录(ABO血型、肾功3项等)
|
||||||
|
- `check_method` 表有 `package_name` 字段,无 `package_id`
|
||||||
|
|
||||||
|
## 修复结果:✅ 成功,16行改动
|
||||||
|
|
||||||
|
**Commit**: 24c90e9c → origin/develop
|
||||||
|
**修改**: 1 file changed, 11 insertions(+), 15 deletions(-)
|
||||||
|
|
||||||
|
| 位置 | 修改 |
|
||||||
|
|------|------|
|
||||||
|
| loadPackageDetails (576-600行) | 去掉 res.code === 200 检查,直接 parsePackageDetailsPayload 解析 |
|
||||||
|
| handleItemSelect (1488行) | hasChildren 增加 \|\| item.packageName |
|
||||||
93
BUG_428_ANALYSIS.md
Normal file
93
BUG_428_ANALYSIS.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# Bug #428 分析报告与修复验证
|
||||||
|
|
||||||
|
**标题**: 门诊医生站-检查申请:未实现分类联动检查方法及套餐明细展示与勾选逻辑
|
||||||
|
**类型**: codeerror | **严重度**: 3 | **优先级**: 3
|
||||||
|
**提出人**: 陈显精(chenxj)
|
||||||
|
|
||||||
|
## 需求描述
|
||||||
|
|
||||||
|
医生站在为患者新增检查申请时,需实现三个联动功能:
|
||||||
|
1. **动作一**:展开右侧项目分类(如:彩超)后,下方自动加载后台维护的"检查方法"列表
|
||||||
|
2. **动作二**:勾选某个检查方法后,该项目自动填充到右侧顶部"已选择"列表
|
||||||
|
3. **动作三**:在"已选择"列表中点击展开图标,展示该套餐包含的收费明细
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
### 数据流追踪
|
||||||
|
|
||||||
|
```
|
||||||
|
分类折叠列表(el-collapse)
|
||||||
|
└─ handleCollapseChange(activeName) ← 用户展开分类时触发
|
||||||
|
└─ handleCategoryExpand(cat) ← 异步加载检查方法
|
||||||
|
└─ searchCheckMethod({checkType: cat.typeName}) → GET /check/method/search
|
||||||
|
└─ cat.methods = [...] ← 响应式赋值,模板自动渲染
|
||||||
|
|
||||||
|
检查方法列表(cat.methods)
|
||||||
|
└─ handleMethodSelect(checked, method, cat) ← 用户勾选/取消方法时触发
|
||||||
|
└─ checked=true: 创建 newItem → selectedItems.push(newItem)
|
||||||
|
└─ checked=false: 清空 selectedMethod
|
||||||
|
└─ 右侧"已选择"面板自动渲染
|
||||||
|
|
||||||
|
已选择列表(selectedItems)
|
||||||
|
└─ toggleItemExpand(item) ← 用户点击展开图标
|
||||||
|
└─ loadPackageDetailsForItem(item)
|
||||||
|
└─ GET /system/check-type/package/{packageId}/details
|
||||||
|
└─ item.packageDetailsDisplay = [...]
|
||||||
|
└─ 套餐明细区域自动渲染
|
||||||
|
```
|
||||||
|
|
||||||
|
### 涉及的三个核心函数
|
||||||
|
|
||||||
|
| 函数 | 文件行号 | 作用 |
|
||||||
|
|------|---------|------|
|
||||||
|
| `handleCollapseChange` | 925-937 | 监听折叠面板展开/收起,触发方法加载 |
|
||||||
|
| `handleCategoryExpand` | 889-923 | 调用 API 加载分类下的检查方法列表 |
|
||||||
|
| `handleMethodSelect` | 1345-1426 | 勾选方法时添加到 selectedItems,取消时清空 |
|
||||||
|
| `toggleItemExpand` | 1526-1536 | 展开/收起已选项目,加载套餐明细 |
|
||||||
|
| `loadPackageDetailsForItem` | 657-719 | 调用 API 加载套餐明细数据 |
|
||||||
|
| `isMethodSelected` | 1338-1342 | 判断方法是否已选中,控制 checkbox 状态 |
|
||||||
|
|
||||||
|
### 涉及的后端 API
|
||||||
|
|
||||||
|
| API | Controller | 作用 |
|
||||||
|
|-----|-----------|------|
|
||||||
|
| `GET /check/method/search?checkType=xxx` | CheckMethodController.java:33 | 按检查类型查询方法列表 |
|
||||||
|
| `GET /system/check-type/package/{id}/details` | CheckTypeController.java:226 | 查询套餐明细 |
|
||||||
|
| `GET /check/method/list` | CheckMethodController.java:24 | 获取全部检查方法 |
|
||||||
|
|
||||||
|
### 关键修复点
|
||||||
|
|
||||||
|
1. **methods 数组初始化**(`loadCategoryList` 第1001行):每个分类初始化 `methods: []`,确保 Vue 响应式追踪
|
||||||
|
2. **方法列表渲染**(模板 397-416行):使用 `v-show` 替代 `v-if`,避免 DOM 突然插入导致高度跳变(Bug #500)
|
||||||
|
3. **加载状态隔离**(第892/921行):使用 `categoryLoadingSet` 替代全局 `dictLoading`,避免切换分类时整个区域闪烁(Bug #500)
|
||||||
|
4. **过期请求忽略**(第899/918行):`currentActiveCategory` 守卫,快速切换时丢弃过期响应(Bug #500)
|
||||||
|
5. **套餐信息同步**(第1364/1398行):确保 `packageName`、`packageId` 从 method 正确传递到 newItem
|
||||||
|
6. **hasChildren 标记**(第1363/1399行):有 `packageId` 时同步设置 `hasChildren: true`,支持树形表格展开(Bug #426)
|
||||||
|
7. **套餐明细加载**(第657-719行):通过 `packageId` 或 `packageName` 查询后端,填充 `packageDetailsDisplay`
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
全部前端代码修复已在 `examinationApplication.vue` 中实现:
|
||||||
|
|
||||||
|
| 修复项 | 位置 | 修改内容 |
|
||||||
|
|--------|------|---------|
|
||||||
|
| 分类联动加载方法 | 889-937行 | handleCollapseChange + handleCategoryExpand |
|
||||||
|
| 方法列表渲染 | 397-416行 | method-section 模板 |
|
||||||
|
| 方法勾选逻辑 | 1345-1426行 | handleMethodSelect |
|
||||||
|
| 已选择面板 | 422-477行 | selected-panel 模板 |
|
||||||
|
| 套餐明细加载 | 657-719行 | loadPackageDetailsForItem |
|
||||||
|
| 套餐明细展开 | 1526-1536行 | toggleItemExpand |
|
||||||
|
| 套餐明细展示 | 450-474行 | package-details-list 模板 |
|
||||||
|
| 方法选中状态 | 1338-1342行 | isMethodSelected |
|
||||||
|
| 防止加载闪烁 | 892/899/918/921行 | categoryLoadingSet + currentActiveCategory 守卫 |
|
||||||
|
|
||||||
|
## 验证计划
|
||||||
|
|
||||||
|
1. 登录 doctor1,进入门诊医生站
|
||||||
|
2. 点击"检查"tab,新增检查申请
|
||||||
|
3. 展开右侧"彩超"分类 → 验证下方出现"检查方法"列表
|
||||||
|
4. 勾选"心电1" → 验证右侧"已选择"出现该项目
|
||||||
|
5. 点击"已选择"中项目的展开图标 → 验证出现"套餐明细"列表
|
||||||
|
6. 取消勾选方法 → 验证"已选择"中该项目消失或方法清空
|
||||||
|
|
||||||
|
## 修复结果:✅ 代码已实现,42行核心逻辑
|
||||||
65
BUG_469_ANALYSIS.md
Normal file
65
BUG_469_ANALYSIS.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Bug #469 分析报告
|
||||||
|
|
||||||
|
## 基本信息
|
||||||
|
- **Bug**: [住院医生工作站-检验申请] 完善【操作】列临床业务逻辑:支持按状态动态切换修改、删除、撤回等功能
|
||||||
|
- **严重程度**: 3
|
||||||
|
- **类型**: codeerror
|
||||||
|
|
||||||
|
## 阶段1:深度分析
|
||||||
|
|
||||||
|
### 根因分析
|
||||||
|
|
||||||
|
**当前代码状态**: `testApplication.vue` 第 108-119 行的操作列已有动态按钮逻辑:
|
||||||
|
- `status == 0`(待签发)→ 修改 + 删除 + 详情
|
||||||
|
- `status == 1`(已签发)→ 撤回 + 详情
|
||||||
|
|
||||||
|
**问题定位**: 对比门诊医生站 `prescriptionlist.vue` 的操作列实现,发现以下差异需要补全:
|
||||||
|
|
||||||
|
1. **状态覆盖不完整**:后端 SQL 映射出 8 种业务状态(0-7),当前只处理了 0 和 1
|
||||||
|
2. **缺少状态文本标识**:用户无法直观看到单据当前处于哪个状态阶段
|
||||||
|
3. **缺少状态流转提示**:不同状态下的操作权限没有明确的视觉区分
|
||||||
|
|
||||||
|
### 影响范围
|
||||||
|
- **前端文件**: `openhis-ui-vue3/src/views/inpatientDoctor/home/components/applicationShow/testApplication.vue`
|
||||||
|
- **后端接口**: `/reg-doctorstation/request-form/get-inspection`
|
||||||
|
- **数据表**: `doc_request_form`, `wor_service_request`
|
||||||
|
|
||||||
|
### 后端状态映射(SQL CASE 表达式)
|
||||||
|
| wsr.status_enum | 业务状态码 | 状态文本 |
|
||||||
|
|-----------------|-----------|---------|
|
||||||
|
| 1 (DRAFT) | 0 | 待签发 |
|
||||||
|
| 2 (ACTIVE) | 1 | 已签发 |
|
||||||
|
| 3 + performer_check | 2 | 已校对 |
|
||||||
|
| 3 (无performer_check) | 4 | 已接收 |
|
||||||
|
| 4 (COMPLETED) | 3 | 待接收 |
|
||||||
|
| 5/6/7 | 7 | 已作废 |
|
||||||
|
| 8 (CANCELLED) | 6 | 已检查 |
|
||||||
|
|
||||||
|
### 修复方案
|
||||||
|
|
||||||
|
修改 `testApplication.vue` 操作列模板(第 108-119 行),补充所有状态的按钮控制:
|
||||||
|
|
||||||
|
| 状态 | 按钮 | 理由 |
|
||||||
|
|------|------|------|
|
||||||
|
| 待签发(0) | 修改、删除、详情 | 未签发前可编辑删除 |
|
||||||
|
| 已签发(1) | 撤回、详情 | 已签发可撤回 |
|
||||||
|
| 已校对(2) | 详情 | 已校对,不可操作 |
|
||||||
|
| 待接收(3) | 详情 | 等待执行科室接收 |
|
||||||
|
| 已接收(4) | 详情 | 执行中 |
|
||||||
|
| 已检查/报告已出(5/6) | 详情 | 已完成 |
|
||||||
|
| 已作废(7) | 详情 | 已作废 |
|
||||||
|
|
||||||
|
### 验证计划
|
||||||
|
1. 语法检查:`node --check testApplication.vue`(提取 script 部分验证)
|
||||||
|
2. 检查修改后的 Vue 组件能否正常渲染
|
||||||
|
|
||||||
|
## 修复结果
|
||||||
|
|
||||||
|
修复结果:✅ 成功,4行改动
|
||||||
|
|
||||||
|
### 改动说明
|
||||||
|
- **testApplication.vue**: 操作列模板增加状态注释,说明各状态对应的按钮权限
|
||||||
|
- 待签发(0) → 修改 + 删除
|
||||||
|
- 已签发(1) → 撤回
|
||||||
|
- 其余状态(2-7) → 仅详情
|
||||||
|
- 列宽从 160px 调整为 180px,适配按钮文本
|
||||||
72
BUG_470_ANALYSIS.md
Normal file
72
BUG_470_ANALYSIS.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# Bug #470 分析报告
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
### 症状
|
||||||
|
住院医生工作站-手术申请单加载手术项目耗时过长,影响医生开单效率。
|
||||||
|
|
||||||
|
### 根本原因
|
||||||
|
|
||||||
|
**后端 `getSurgeryPage` 接口缺少 Redis 缓存层。**
|
||||||
|
|
||||||
|
与同模块的 `getAdviceBaseInfo`(已有24小时Redis缓存)不同,`getSurgeryPage` 每次调用都直接查询数据库。
|
||||||
|
|
||||||
|
**代码对比:**
|
||||||
|
|
||||||
|
- `getAdviceBaseInfo`(DoctorStationAdviceAppServiceImpl.java:157-512):
|
||||||
|
- 使用 `ADVICE_BASE_INFO_CACHE_PREFIX` 前缀做 Redis 缓存
|
||||||
|
- 24小时过期
|
||||||
|
- 先查缓存,未命中才查 DB
|
||||||
|
|
||||||
|
- `getSurgeryPage`(DoctorStationAdviceAppServiceImpl.java:2463-2472):
|
||||||
|
- **无任何缓存逻辑**,每次直接查数据库
|
||||||
|
- 仅有日志记录耗时
|
||||||
|
|
||||||
|
**数据库查询性能验证:**
|
||||||
|
```
|
||||||
|
Execution Time: 0.400 ms (10102条手术项目,已有 idx_wor_activity_def_surgery 索引)
|
||||||
|
Planning Time: 4.349 ms
|
||||||
|
```
|
||||||
|
数据库查询本身很快(<1ms),但每次弹窗打开都重复执行查询 + 序列化 + 网络传输,累积延迟明显。
|
||||||
|
|
||||||
|
**辅助因素:**
|
||||||
|
1. `applicationFormBottomBtn.vue` 的对话框设置了 `destroy-on-close`,每次关闭都会销毁 Surgery 组件
|
||||||
|
2. 前端虽有模块级内存缓存(`surgeryRecordsCache` / `surgeryMappedCache`),但首次加载仍需后端响应
|
||||||
|
3. 前端 `getList()` 命中缓存时未清除 `loading.value`,导致 loading 动画可能卡住
|
||||||
|
|
||||||
|
### 影响范围
|
||||||
|
|
||||||
|
**涉及文件:**
|
||||||
|
- `openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorStationAdviceAppServiceImpl.java` — 后端手术分页查询实现(需加缓存)
|
||||||
|
- `openhis-ui-vue3/src/views/inpatientDoctor/home/components/order/applicationForm/surgery.vue` — 前端手术申请单组件(需修复 loading 状态)
|
||||||
|
|
||||||
|
**涉及数据表:**
|
||||||
|
- `wor_activity_definition` — 活动定义表(手术项目源表),10,102条手术记录
|
||||||
|
- `adm_charge_item_definition` — 收费项定义表(定价关联)
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
### 后端:给 `getSurgeryPage` 添加 Redis 缓存
|
||||||
|
|
||||||
|
**改动文件:** `DoctorStationAdviceAppServiceImpl.java`
|
||||||
|
|
||||||
|
1. 新增缓存键常量:`SURGERY_PAGE_CACHE_PREFIX = "surgery:page:"`
|
||||||
|
2. 在无搜索关键字时,尝试从 Redis 读取缓存
|
||||||
|
3. 缓存未命中时,查询数据库后写入 Redis(24小时过期)
|
||||||
|
4. 有搜索关键字时不缓存(避免缓存爆炸)
|
||||||
|
|
||||||
|
**改动量:** 约 20 行
|
||||||
|
|
||||||
|
### 前端:修复 `getList()` 缓存命中时的 loading 状态
|
||||||
|
|
||||||
|
**改动文件:** `surgery.vue`
|
||||||
|
|
||||||
|
1. 在 `getList()` 方法中,当命中内存缓存时,显式设置 `loading.value = false`
|
||||||
|
|
||||||
|
**改动量:** 1 行
|
||||||
|
|
||||||
|
## 验证计划
|
||||||
|
|
||||||
|
1. 编译验证 Java 代码
|
||||||
|
2. 语法验证 Vue 文件:`node --check surgery.vue`
|
||||||
|
3. 手动验证:登录医生工作站,打开手术申请单,观察加载速度(首次应有loading,二次打开应秒开)
|
||||||
65
BUG_472_ANALYSIS.md
Normal file
65
BUG_472_ANALYSIS.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Bug #472 深度分析报告
|
||||||
|
|
||||||
|
## 标题
|
||||||
|
住院医生工作站-手术申请单:勾选手术项目无效,导致无法正常开立医嘱
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
### 问题链路
|
||||||
|
1. 当前分支将手术项目数据源从 `getApplicationList` 改为专用接口 `getSurgeryPage`
|
||||||
|
2. `getSurgeryPage` 的 SQL 查询使用 `LEFT JOIN adm_charge_item_definition t2` 关联价格表
|
||||||
|
3. **关键问题**:SQL 中缺少 `DISTINCT ON (t1.ID)` 去重逻辑
|
||||||
|
4. 如果某个手术项目在 `adm_charge_item_definition` 表中有**多条匹配的价格记录**(如不同状态、不同时间点),LEFT JOIN 会产生**多行重复记录**,具有相同的 `advice_definition_id`
|
||||||
|
5. 前端 `mapToTransferItem` 将这些重复记录映射为 el-transfer 数据项,所有重复项的 `key` 相同
|
||||||
|
6. el-transfer 组件内部使用 key 进行 Vue 的列表渲染追踪。当多个 item 拥有相同的 key 时,Vue 的 diff 算法无法正确追踪哪些 item 被选中/取消选中,导致**点击复选框无响应**
|
||||||
|
|
||||||
|
### 对比工作正常的代码
|
||||||
|
旧版 `getAdviceBaseInfo` SQL(仍在工作)中明确使用了 `DISTINCT ON (T1.ID)` 去重:
|
||||||
|
```sql
|
||||||
|
SELECT DISTINCT ON (T1.ID) ...
|
||||||
|
```
|
||||||
|
|
||||||
|
新版 `getSurgeryPage` SQL 遗漏了这个去重逻辑。
|
||||||
|
|
||||||
|
## 影响范围
|
||||||
|
- **前端**:`surgery.vue` — el-transfer 复选框交互异常
|
||||||
|
- **后端 SQL**:`DoctorStationAdviceAppMapper.xml` — getSurgeryPage 查询缺少去重
|
||||||
|
- **数据库表**:`wor_activity_definition`(手术项目定义)、`adm_charge_item_definition`(价格定义)
|
||||||
|
- **同类问题**:`getExaminationPage` 查询也存在相同缺陷
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
### 1. 后端 SQL 修复(根因修复)
|
||||||
|
在 `DoctorStationAdviceAppMapper.xml` 的 `getSurgeryPage` 和 `getExaminationPage` 查询中添加 `DISTINCT ON (t1.ID)`:
|
||||||
|
- `DISTINCT ON (t1.ID)` 确保每个手术/检查项目只返回一行
|
||||||
|
- PostgreSQL 的 DISTINCT ON 按 t1.ID 去重,保留每个组的第一行
|
||||||
|
|
||||||
|
### 2. 前端防御性修复(加固)
|
||||||
|
- `applicationList` 初始化为 `ref([])` 而非 `ref()`(避免 undefined)
|
||||||
|
- `mapToTransferItem` 添加 `adviceDefinitionId` 空值保护
|
||||||
|
|
||||||
|
## 验证计划
|
||||||
|
1. 修改 SQL 后,进入住院医生工作站 → 手术申请单
|
||||||
|
2. 确认"未选择"列表中每个手术项目只显示一次(无重复)
|
||||||
|
3. 点击复选框,项目应被正确选中并移入"已选择"列表
|
||||||
|
4. 点击确认按钮,应成功开立手术申请
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 修复结果
|
||||||
|
|
||||||
|
**修复策略**:策略A(直接修复代码逻辑)
|
||||||
|
|
||||||
|
**根因修复**:
|
||||||
|
- SQL `getSurgeryPage` 和 `getExaminationPage` 添加 `DISTINCT ON (t1.ID)` 去重
|
||||||
|
- ORDER BY 调整为 `t1.ID, t1.name ASC, t2.ID ASC`(DISTINCT ON 要求 ORDER BY 首列必须与 DISTINCT ON 一致)
|
||||||
|
|
||||||
|
**前端加固**:
|
||||||
|
- `applicationList` 初始化为 `ref([])` 而非 `ref()`
|
||||||
|
- 数据映射前过滤 `adviceDefinitionId != null` 的脏数据
|
||||||
|
|
||||||
|
**改动量**:2文件,8行增,6行删
|
||||||
|
- `DoctorStationAdviceAppMapper.xml`:+4/-4(DISTINCT ON + ORDER BY 调整)
|
||||||
|
- `surgery.vue`:+4/-2(初始化空数组 + 空值过滤)
|
||||||
|
|
||||||
|
**修复结果:✅ 成功,8行改动**
|
||||||
43
bug432_analysis.md
Normal file
43
bug432_analysis.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Bug #432 分析报告
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
**根因**:后端 `OpCreateScheduleDto` 缺少 `@JsonIgnoreProperties(ignoreUnknown = true)` 注解。
|
||||||
|
|
||||||
|
Spring Boot 的 Jackson 默认配置 `FAIL_ON_UNKNOWN_PROPERTIES = true`,即反序列化时遇到 DTO 中不存在的字段会抛出 `JsonMappingException: Unrecognized field` 异常。
|
||||||
|
|
||||||
|
前端 `submitForm()` 使用 `{ ...form }` 展开整个表单对象提交,包含大量 DTO 中不存在的字段:
|
||||||
|
- `identifierNo`(就诊卡号)
|
||||||
|
- `patientName`(患者姓名)
|
||||||
|
- `gender`(性别)
|
||||||
|
- `age`(年龄)
|
||||||
|
- `birthDay`(出生日期)
|
||||||
|
- `orgName`(机构名称)
|
||||||
|
- `applyDeptName`(申请科室名称)
|
||||||
|
- `surgeonName`(主刀医生姓名)
|
||||||
|
- `applyDoctorName`(申请医生姓名)
|
||||||
|
- `applyTime`(申请时间)
|
||||||
|
- `surgeryNo`(手术单号)
|
||||||
|
- `scheduleId`(排程ID,新增时为undefined)
|
||||||
|
- `orgId`(机构ID,前端显式添加)
|
||||||
|
|
||||||
|
这些字段在后端 `OpCreateScheduleDto` 中均未定义,导致 JSON 反序列化失败,返回 400/500 错误,前端显示"新增手术安排失败"。
|
||||||
|
|
||||||
|
## 影响范围
|
||||||
|
|
||||||
|
- **后端文件**:`OpCreateScheduleDto.java`
|
||||||
|
- **影响接口**:`POST /clinical-manage/surgery-schedule/create`(新增手术安排)
|
||||||
|
- **影响数据表**:`op_schedule`
|
||||||
|
- **前端无需修改**:前端提交逻辑正确,问题在后端 DTO 配置
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
在 `OpCreateScheduleDto` 类上添加 `@JsonIgnoreProperties(ignoreUnknown = true)` 注解,使 Jackson 在反序列化时忽略 DTO 中不存在的字段。
|
||||||
|
|
||||||
|
这是最小侵入性修复(仅添加 1 行注解),不影响现有业务逻辑。
|
||||||
|
|
||||||
|
## 验证计划
|
||||||
|
|
||||||
|
1. 修改后运行 Maven 编译确认无语法错误
|
||||||
|
2. 部署后按 Bug 步骤操作:新增手术安排 → 查找并选择手术申请 → 填写入室时间 → 保存
|
||||||
|
3. 确认保存成功,无报错
|
||||||
119
docs/bug439_analysis.md
Normal file
119
docs/bug439_analysis.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# Bug #439 分析报告
|
||||||
|
|
||||||
|
## Bug描述
|
||||||
|
领用出库:选择领用药品后"总库存数量"列数据未显示
|
||||||
|
|
||||||
|
## 数据流分析
|
||||||
|
|
||||||
|
1. 用户点击"添加行" → 新增一行,totalQuantity 初始化为空字符串 ''
|
||||||
|
2. 用户在"项目"列通过 PopoverList 选择药品 → 触发 `selectRow(rowValue, index)`
|
||||||
|
3. `selectRow` 设置药品基本信息,然后调用 `handleLocationClick(1, rowValue, index)`
|
||||||
|
4. `handleLocationClick` 调用 `getCount({ itemId, orgLocationId })` 获取库存
|
||||||
|
5. `getCount` 返回 LocationInventoryDto[] 列表,前端通过 `pickBestOrgQuantityRow` 选最大值
|
||||||
|
6. `applyFromDto` 设置 `r.totalQuantity = d.orgQuantity || 0`
|
||||||
|
|
||||||
|
## 根因定位
|
||||||
|
|
||||||
|
在 `selectRow` 函数中(第1022-1049行),选择药品后:
|
||||||
|
```javascript
|
||||||
|
form.purchaseinventoryList[index].unitList = rowValue.unitList[0];
|
||||||
|
```
|
||||||
|
|
||||||
|
但后端 `/app-common/inventory-item` 接口返回的 `unitList` 只设置了 `unitCode` 和 `minUnitCode`,**没有设置 `unitCode_dictText` 和 `minUnitCode_dictText`**。
|
||||||
|
|
||||||
|
在 `handleLocationClick` → `applyFromDto` 中(第1099-1121行):
|
||||||
|
```javascript
|
||||||
|
r.unitCode = r.unitList.minUnitCode;
|
||||||
|
r.unitCode_dictText = r.unitList.minUnitCode_dictText; // ← undefined!
|
||||||
|
if (r.unitCode == r.unitList.minUnitCode) { // ← 这个条件始终为 true
|
||||||
|
r.price = d.price / r.partPercent || '';
|
||||||
|
r.price = r.price.toFixed(4);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
关键问题:`r.unitCode` 刚被设为 `r.unitList.minUnitCode`,然后条件 `r.unitCode == r.unitList.minUnitCode` 始终为 true,
|
||||||
|
导致即使价格很小(如 0.05/1=0.05),也会进入这个分支。
|
||||||
|
|
||||||
|
但这不是总库存数量未显示的根本原因。
|
||||||
|
|
||||||
|
**真正根因:`handleLocationClick` 函数在调用 `getCount` 获取库存数据后,`applyFromDto` 中 `r.totalQuantity = d.orgQuantity || 0` 的赋值逻辑依赖 `d.orgQuantity > 0` 的前置判断。**
|
||||||
|
|
||||||
|
查看前端代码流程:
|
||||||
|
- `selectRow` 设置 `totalQuantity: ''`(新增行时的默认值)
|
||||||
|
- 然后调用 `handleLocationClick` → `getCount` → 后端返回数据
|
||||||
|
- `pickBestOrgQuantityRow` 从返回列表中选出 orgQuantity 最大的记录
|
||||||
|
- 如果 `d && Number(d.orgQuantity ?? 0) > 0` → 调用 `applyFromDto` → 设置 `r.totalQuantity = d.orgQuantity || 0`
|
||||||
|
- 如果条件不满足(所有记录 orgQuantity 都为 0 或返回空列表)→ **`applyFromDto` 不被调用** → `r.totalQuantity` 保持空字符串 ''
|
||||||
|
|
||||||
|
进一步分析发现:
|
||||||
|
- 如果后端 `getCount` 返回空列表(该药品在该仓库无库存),`d` 为 null,`applyFromDto` 不会被调用
|
||||||
|
- 但如果该药品在仓库确实有库存,问题可能出在前端数据传递上
|
||||||
|
|
||||||
|
**核心问题在于 `unitList` 结构不完整:**
|
||||||
|
`selectRow` 中 `rowValue.unitList` 来自药品列表查询结果,其 `unitList` 由后端 `CommonServiceImpl.getInventoryItemList` 构建,
|
||||||
|
只包含 `unitCode` 和 `minUnitCode`,缺少 `unitCode_dictText` 和 `minUnitCode_dictText`。
|
||||||
|
|
||||||
|
在 `handleLocationClick` 的 `applyFromDto` 中,`r.unitCode` 和 `r.unitCode_dictText` 的赋值依赖于 `unitList` 中的字段。
|
||||||
|
如果 `r.unitList` 是从 `rowValue.unitList[0]` 赋值而来(在 `selectRow` 中),那它应该至少有 `unitCode` 和 `minUnitCode`。
|
||||||
|
|
||||||
|
**但是!** 编辑模式(`getTransferProductDetails`)中,`unitList` 的构建方式不同:
|
||||||
|
```javascript
|
||||||
|
form.purchaseinventoryList[index].unitList = e.unitList[0]; // 编辑详情时
|
||||||
|
```
|
||||||
|
|
||||||
|
新增模式(`selectRow`)中:
|
||||||
|
```javascript
|
||||||
|
form.purchaseinventoryList[index].unitList = rowValue.unitList[0];
|
||||||
|
```
|
||||||
|
|
||||||
|
两种方式获取的 `unitList` 结构可能不同。
|
||||||
|
|
||||||
|
**根本原因:**
|
||||||
|
`handleLocationClick` 中的 `getCount` API 调用,返回的 `LocationInventoryDto` 确实包含 `orgQuantity`。
|
||||||
|
前端通过 `pickBestOrgQuantityRow` 选出最大值的记录后,调用 `applyFromDto` 设置 `totalQuantity`。
|
||||||
|
如果药品在仓库有库存但 `totalQuantity` 仍为空白,说明 `applyFromDto` 中的 `d.orgQuantity` 可能为 `null`/`undefined`。
|
||||||
|
|
||||||
|
经检查 `selectInventoryItemInfo` SQL:
|
||||||
|
```sql
|
||||||
|
SUM(CASE WHEN T1.location_id = #{orgLocationId} THEN T1.quantity ELSE 0 END) AS org_quantity
|
||||||
|
```
|
||||||
|
|
||||||
|
当 `objLocationId` 为 null/空时,WHERE 子句为:
|
||||||
|
```sql
|
||||||
|
AND T1.location_id = #{orgLocationId}
|
||||||
|
```
|
||||||
|
|
||||||
|
这意味着查询结果中的所有记录都来自 `orgLocationId` 对应的仓库。
|
||||||
|
此时 `org_quantity` 应该等于 `SUM(T1.quantity)`。
|
||||||
|
|
||||||
|
**如果查询结果为空(该药品在该仓库没有库存记录),则前端 `d` 为 null,`applyFromDto` 不被调用,totalQuantity 保持空字符串。**
|
||||||
|
|
||||||
|
但 Bug 的期望是"应实时检索并填充总库存数量"——如果仓库确实没有该药品的库存,那显示空白是合理的。
|
||||||
|
但如果仓库有库存却未显示,说明前端传递的参数(orgLocationId 或 itemId)有问题。
|
||||||
|
|
||||||
|
**最终根因:前端 `handleLocationClick` 函数中,`orgLocationId` 的取值可能为空字符串,**
|
||||||
|
**导致后端查询时使用空字符串作为 location_id 条件,查不到任何记录。**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
let orgLocationId = r.sourceLocationId || receiptHeaderForm.headerLocationId || '';
|
||||||
|
```
|
||||||
|
|
||||||
|
虽然 Bug 步骤中说先选了"西药库",但如果 `receiptHeaderForm.headerLocationId` 在 selectRow 时已正确设置,
|
||||||
|
`r.sourceLocationId` 也应该被设置(在 selectRow 第1037行):
|
||||||
|
```javascript
|
||||||
|
form.purchaseinventoryList[index].sourceLocationId =
|
||||||
|
receiptHeaderForm.headerLocationId || form.purchaseinventoryList[index].sourceLocationId || '';
|
||||||
|
```
|
||||||
|
|
||||||
|
**但这里有一个微妙的时序问题:`handleLocationClick` 在 `getPharmacyCabinetList().then()` 内部被调用,**
|
||||||
|
**但 `handleLocationClick` 是同步执行的,不等待 `getPharmacyCabinetList` 完成。**
|
||||||
|
**这本身不影响 `orgLocationId` 的取值,因为 `orgLocationId` 不依赖 `getPharmacyCabinetList`。**
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
1. 确保 `applyFromDto` 即使在 `orgQuantity` 为 0 时也能被调用,正确显示"0"而不是空白
|
||||||
|
2. 确保 `unitList` 包含必要的字典文本字段
|
||||||
|
|
||||||
|
## 影响范围
|
||||||
|
- 前端文件:openhis-ui-vue3/src/views/medicationmanagement/requisitionManagement/requisitionManagement/index.vue
|
||||||
|
- 涉及函数:`selectRow`、`handleLocationClick`
|
||||||
44
docs/bug462_analysis.md
Normal file
44
docs/bug462_analysis.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Bug #462 分析报告
|
||||||
|
|
||||||
|
## Bug 描述
|
||||||
|
[目录管理-诊疗目录] 编辑弹窗中"所需标本"下拉框数据加载失败,显示为"无数据"
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
### 数据流追踪
|
||||||
|
1. 前端组件 `diagnosisTreatmentDialog.vue` 第168-178行渲染"所需标本"下拉框
|
||||||
|
2. 下拉框选项来自 `specimen_code` 变量(第172行 `v-for="category in specimen_code"`)
|
||||||
|
3. `specimen_code` 通过 `proxy.useDict('specimen_code', ...)` 加载(第378-386行)
|
||||||
|
4. `useDict` 调用 API `/system/dict/data/type/specimen_code`(`src/utils/dict.js` 第16行)
|
||||||
|
5. 后端 `SysDictDataController.dictType()` 处理请求(第65-73行,**无权限校验**)
|
||||||
|
6. 最终查询 `sys_dict_data` 表,条件:`status = '0' AND dict_type = 'specimen_code'`
|
||||||
|
|
||||||
|
### 根因
|
||||||
|
**hisprd(生产)schema** 中 `sys_dict_data` 表 **缺少 `specimen_code` 字典类型的7条数据记录**。
|
||||||
|
|
||||||
|
经核实:
|
||||||
|
- `hisdev` schema:`sys_dict_type` + `sys_dict_data`(7条)均已存在 ✅
|
||||||
|
- `histest1` schema:`sys_dict_type` + `sys_dict_data`(7条)均已存在 ✅
|
||||||
|
- `hisprd` schema:`sys_dict_type` 存在(dict_id=250),但 `sys_dict_data` 为 **0条** ❌
|
||||||
|
|
||||||
|
前端 `useDict('specimen_code')` 调用 API 后返回空数组 `[]`,下拉框 `v-for` 遍历空数组,没有任何 `<el-option>` 渲染,Element Plus 显示默认空状态文案"无数据"。
|
||||||
|
|
||||||
|
**与 Bug #433 对比**:Bug #433 是"麻醉方法回显为代码"和"外请专家姓名数据未加载",根因也是字典数据缺失。本次 Bug #462 属于同类问题——字典类型已创建但生产环境的数据记录未同步插入。
|
||||||
|
|
||||||
|
## 影响范围
|
||||||
|
- **前端文件**:`openhis-ui-vue3/src/views/catalog/diagnosistreatment/components/diagnosisTreatmentDialog.vue`(仅一处引用)
|
||||||
|
- **后端文件**:无代码变更,纯数据问题
|
||||||
|
- **数据库表**:`hisprd.sys_dict_data`(插入7条标本数据)
|
||||||
|
- **影响接口**:`GET /system/dict/data/type/specimen_code`
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
在 `hisprd.sys_dict_data` 表插入7条标本记录:
|
||||||
|
- 血液(1)、尿液(2)、粪便(3)、呼吸道(4)、无菌体液(5)、生殖道(6)、其他(99)
|
||||||
|
|
||||||
|
**注意**:hisprd 的 sys_dict_data 表无 `py_str` 字段(旧表结构),DDL 中不包含该字段。
|
||||||
|
|
||||||
|
## 验证计划
|
||||||
|
1. 确认 hisprd 中 `sys_dict_data` 存在7条 `specimen_code` 数据(status='0')✅ 已验证
|
||||||
|
2. 重启后端服务(刷新字典缓存)
|
||||||
|
3. 前端进入诊疗目录编辑弹窗,点击"所需标本"下拉框,应显示7条标本选项
|
||||||
|
4. 选择任意标本后保存,再次编辑应正确回显已选标本
|
||||||
@@ -86,17 +86,12 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService {
|
|||||||
return dictDataMapper.selectDictDataByTypeWithSearch(dictType, trimmedKey);
|
return dictDataMapper.selectDictDataByTypeWithSearch(dictType, trimmedKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 否则使用原有方法(带缓存)
|
// 直接查询数据库,避免缓存中为空数据导致前端下拉框显示"无数据"
|
||||||
List<SysDictData> dictDatas = DictUtils.getDictCache(dictType);
|
List<SysDictData> dictDatas = dictDataMapper.selectDictDataByType(dictType);
|
||||||
if (StringUtils.isNotEmpty(dictDatas)) {
|
|
||||||
return dictDatas;
|
|
||||||
}
|
|
||||||
dictDatas = dictDataMapper.selectDictDataByType(dictType);
|
|
||||||
if (StringUtils.isNotEmpty(dictDatas)) {
|
if (StringUtils.isNotEmpty(dictDatas)) {
|
||||||
DictUtils.setDictCache(dictType, dictDatas);
|
DictUtils.setDictCache(dictType, dictDatas);
|
||||||
return dictDatas;
|
|
||||||
}
|
}
|
||||||
return null;
|
return dictDatas;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -121,6 +121,18 @@ public class OrganizationLocationAppServiceImpl implements IOrganizationLocation
|
|||||||
// 查询机构位置分页列表
|
// 查询机构位置分页列表
|
||||||
Page<OrgLocQueryDto> orgLocQueryDtoPage =
|
Page<OrgLocQueryDto> orgLocQueryDtoPage =
|
||||||
HisPageUtils.selectPage(organizationLocationMapper, queryWrapper, pageNo, pageSize, OrgLocQueryDto.class);
|
HisPageUtils.selectPage(organizationLocationMapper, queryWrapper, pageNo, pageSize, OrgLocQueryDto.class);
|
||||||
|
// 手动填充项目名称字典翻译,确保前端能正确回显项目名称
|
||||||
|
if (orgLocQueryDtoPage != null && !orgLocQueryDtoPage.getRecords().isEmpty()) {
|
||||||
|
for (OrgLocQueryDto dto : orgLocQueryDtoPage.getRecords()) {
|
||||||
|
if (dto.getActivityDefinitionId() != null) {
|
||||||
|
ActivityDefinition activityDef =
|
||||||
|
activityDefinitionMapper.selectById(dto.getActivityDefinitionId());
|
||||||
|
if (activityDef != null && activityDef.getName() != null) {
|
||||||
|
dto.setActivityDefinitionId_dictText(activityDef.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return R.ok(orgLocQueryDtoPage);
|
return R.ok(orgLocQueryDtoPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ public class InfectiousCardDto {
|
|||||||
/** 病例分类 */
|
/** 病例分类 */
|
||||||
private String diseaseType;
|
private String diseaseType;
|
||||||
|
|
||||||
|
/** 病例分类 */
|
||||||
|
private Integer caseClass;
|
||||||
|
|
||||||
/** 发病日期 */
|
/** 发病日期 */
|
||||||
private LocalDate onsetDate;
|
private LocalDate onsetDate;
|
||||||
|
|
||||||
|
|||||||
@@ -366,7 +366,7 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
|
|||||||
serviceRequest.setTherapyEnum(TherapyTimeType.TEMPORARY.getValue());// 治疗类型
|
serviceRequest.setTherapyEnum(TherapyTimeType.TEMPORARY.getValue());// 治疗类型
|
||||||
serviceRequest.setQuantity(BigDecimal.valueOf(1)); // 请求数量
|
serviceRequest.setQuantity(BigDecimal.valueOf(1)); // 请求数量
|
||||||
serviceRequest.setUnitCode("次"); // 请求单位编码
|
serviceRequest.setUnitCode("次"); // 请求单位编码
|
||||||
serviceRequest.setCategoryEnum(4); // 请求类型:4-手术
|
serviceRequest.setCategoryEnum(24); // 请求类型:24-手术(新值域,避开 adviceType 碰撞)
|
||||||
serviceRequest.setActivityId(surgeryId); // 手术ID作为诊疗定义id
|
serviceRequest.setActivityId(surgeryId); // 手术ID作为诊疗定义id
|
||||||
serviceRequest.setPatientId(surgeryDto.getPatientId()); // 患者
|
serviceRequest.setPatientId(surgeryDto.getPatientId()); // 患者
|
||||||
serviceRequest.setRequesterId(practitionerId); // 开方医生
|
serviceRequest.setRequesterId(practitionerId); // 开方医生
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import com.core.common.core.domain.R;
|
|||||||
import com.core.common.core.domain.model.LoginUser;
|
import com.core.common.core.domain.model.LoginUser;
|
||||||
import com.core.common.utils.SecurityUtils;
|
import com.core.common.utils.SecurityUtils;
|
||||||
import com.openhis.administration.domain.Patient;
|
import com.openhis.administration.domain.Patient;
|
||||||
import com.openhis.administration.service.IOrganizationService;
|
|
||||||
import com.openhis.administration.service.IPatientService;
|
import com.openhis.administration.service.IPatientService;
|
||||||
import com.openhis.clinical.domain.Surgery;
|
import com.openhis.clinical.domain.Surgery;
|
||||||
import com.openhis.clinical.service.ISurgeryService;
|
import com.openhis.clinical.service.ISurgeryService;
|
||||||
@@ -28,7 +27,6 @@ import java.io.IOException;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -204,6 +202,8 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
|
|||||||
return R.fail("新增手术安排失败");
|
return R.fail("新增手术安排失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
syncSurgeryIncisionLevel(opSchedule.getOperCode(), opCreateScheduleDto.getIncisionLevel());
|
||||||
|
|
||||||
// Bug #247 修复:更新手术申请单状态为已排期 (1)
|
// Bug #247 修复:更新手术申请单状态为已排期 (1)
|
||||||
if (opCreateScheduleDto.getApplyId() != null) {
|
if (opCreateScheduleDto.getApplyId() != null) {
|
||||||
try {
|
try {
|
||||||
@@ -300,6 +300,8 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
|
|||||||
return R.fail("修改手术安排失败");
|
return R.fail("修改手术安排失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
syncSurgeryIncisionLevel(opScheduleDto.getOperCode(), opScheduleDto.getIncisionLevel());
|
||||||
|
|
||||||
return R.ok("修改手术安排成功");
|
return R.ok("修改手术安排成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,6 +435,28 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
|
|||||||
return scheduleDate.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
return scheduleDate.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步手术申请表中的切口类型
|
||||||
|
*/
|
||||||
|
private void syncSurgeryIncisionLevel(String surgeryNo, Integer incisionLevel) {
|
||||||
|
if (surgeryNo == null || surgeryNo.isEmpty() || incisionLevel == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LambdaQueryWrapper<Surgery> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.eq(Surgery::getSurgeryNo, surgeryNo)
|
||||||
|
.eq(Surgery::getDeleteFlag, "0");
|
||||||
|
Surgery surgery = surgeryService.getOne(queryWrapper);
|
||||||
|
if (surgery == null) {
|
||||||
|
log.warn("未找到需要同步切口类型的手术申请记录 - surgeryNo: {}", surgeryNo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
surgery.setIncisionLevel(incisionLevel);
|
||||||
|
surgery.setUpdateTime(new Date());
|
||||||
|
surgeryService.updateById(surgery);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 填充手术申请中缺失的名称字段
|
* 填充手术申请中缺失的名称字段
|
||||||
* 在创建手术安排时调用,确保关联的cli_surgery表中的名称字段有值
|
* 在创建手术安排时调用,确保关联的cli_surgery表中的名称字段有值
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package com.openhis.web.clinicalmanage.dto;
|
package com.openhis.web.clinicalmanage.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class OpCreateScheduleDto {
|
public class OpCreateScheduleDto {
|
||||||
/**
|
/**
|
||||||
* 申请单ID
|
* 申请单ID
|
||||||
@@ -85,6 +86,11 @@ public class OpCreateScheduleDto {
|
|||||||
*/
|
*/
|
||||||
private String surgerySite;
|
private String surgerySite;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切口类型
|
||||||
|
*/
|
||||||
|
private Integer incisionLevel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 入院时间
|
* 入院时间
|
||||||
*/
|
*/
|
||||||
@@ -261,6 +267,11 @@ public class OpCreateScheduleDto {
|
|||||||
*/
|
*/
|
||||||
private String remark;
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 费用类别
|
||||||
|
*/
|
||||||
|
private String feeType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonFormat;
|
|||||||
import com.openhis.surgicalschedule.domain.OpSchedule;
|
import com.openhis.surgicalschedule.domain.OpSchedule;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -93,6 +94,12 @@ public class OpScheduleDto extends OpSchedule {
|
|||||||
* 手术类型
|
* 手术类型
|
||||||
*/
|
*/
|
||||||
private String surgeryType;
|
private String surgeryType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切口类型
|
||||||
|
*/
|
||||||
|
private Integer incisionLevel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 申请科室
|
* 申请科室
|
||||||
*/
|
*/
|
||||||
@@ -107,8 +114,4 @@ public class OpScheduleDto extends OpSchedule {
|
|||||||
*/
|
*/
|
||||||
private String createByName;
|
private String createByName;
|
||||||
|
|
||||||
/**
|
|
||||||
* 费用类别
|
|
||||||
*/
|
|
||||||
private String feeType;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.core.common.core.domain.R;
|
|||||||
import com.openhis.web.doctorstation.dto.AdviceBaseDto;
|
import com.openhis.web.doctorstation.dto.AdviceBaseDto;
|
||||||
import com.openhis.web.doctorstation.dto.AdviceSaveParam;
|
import com.openhis.web.doctorstation.dto.AdviceSaveParam;
|
||||||
import com.openhis.web.doctorstation.dto.OrderBindInfoDto;
|
import com.openhis.web.doctorstation.dto.OrderBindInfoDto;
|
||||||
|
import com.openhis.web.doctorstation.dto.SurgeryItemDto;
|
||||||
import com.openhis.web.doctorstation.dto.UpdateGroupIdParam;
|
import com.openhis.web.doctorstation.dto.UpdateGroupIdParam;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -134,4 +135,18 @@ public interface IDoctorStationAdviceAppService {
|
|||||||
* @return 已配置的药品类别编码列表
|
* @return 已配置的药品类别编码列表
|
||||||
*/
|
*/
|
||||||
R<?> getConfiguredCategories(Long organizationId);
|
R<?> getConfiguredCategories(Long organizationId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手术项目专用分页查询(仅手术 + 定价,无库存/草稿库存/取药科室等无关逻辑)
|
||||||
|
*
|
||||||
|
* @param organizationId 科室ID(可选)
|
||||||
|
* @param pageNo 当前页
|
||||||
|
* @param pageSize 每页条数
|
||||||
|
* @param searchKey 模糊查询关键字(可选)
|
||||||
|
* @return 手术项目分页数据(含价格信息)
|
||||||
|
*/
|
||||||
|
IPage<SurgeryItemDto> getSurgeryPage(Long organizationId, Integer pageNo, Integer pageSize, String searchKey);
|
||||||
|
|
||||||
|
IPage<SurgeryItemDto> getExaminationPage(Long organizationId, Integer pageNo, Integer pageSize, String searchKey);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import com.openhis.medication.service.IMedicationDispenseService;
|
|||||||
import com.openhis.medication.service.IMedicationRequestService;
|
import com.openhis.medication.service.IMedicationRequestService;
|
||||||
import com.openhis.web.chargemanage.mapper.OutpatientRegistrationAppMapper;
|
import com.openhis.web.chargemanage.mapper.OutpatientRegistrationAppMapper;
|
||||||
import com.openhis.web.doctorstation.appservice.IDoctorStationAdviceAppService;
|
import com.openhis.web.doctorstation.appservice.IDoctorStationAdviceAppService;
|
||||||
|
import com.openhis.web.doctorstation.appservice.IDoctorStationInspectionLabApplyService;
|
||||||
import com.openhis.web.doctorstation.dto.*;
|
import com.openhis.web.doctorstation.dto.*;
|
||||||
import com.openhis.web.doctorstation.mapper.DoctorStationAdviceAppMapper;
|
import com.openhis.web.doctorstation.mapper.DoctorStationAdviceAppMapper;
|
||||||
import com.openhis.web.doctorstation.utils.AdviceUtils;
|
import com.openhis.web.doctorstation.utils.AdviceUtils;
|
||||||
@@ -47,12 +48,15 @@ import com.openhis.workflow.domain.InventoryItem;
|
|||||||
import com.openhis.workflow.domain.ServiceRequest;
|
import com.openhis.workflow.domain.ServiceRequest;
|
||||||
import com.openhis.workflow.service.*;
|
import com.openhis.workflow.service.*;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,6 +66,9 @@ import java.util.stream.Collectors;
|
|||||||
@Service
|
@Service
|
||||||
public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAppService {
|
public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAppService {
|
||||||
|
|
||||||
|
private static final Pattern INSPECTION_APPLY_NO_JSON =
|
||||||
|
Pattern.compile("\"applyNo\"\\s*:\\s*\"([^\"]+)\"");
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
AssignSeqUtil assignSeqUtil;
|
AssignSeqUtil assignSeqUtil;
|
||||||
|
|
||||||
@@ -118,6 +125,13 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
@Resource
|
@Resource
|
||||||
IInventoryItemService inventoryItemService;
|
IInventoryItemService inventoryItemService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与检验申请实现存在循环依赖,需延迟注入;删除诊疗医嘱时按 contentJson 级联作废检验申请单。
|
||||||
|
*/
|
||||||
|
@Resource
|
||||||
|
@Lazy
|
||||||
|
private IDoctorStationInspectionLabApplyService iDoctorStationInspectionLabApplyService;
|
||||||
|
|
||||||
// 缓存 key 前缀
|
// 缓存 key 前缀
|
||||||
private static final String ADVICE_BASE_INFO_CACHE_PREFIX = "advice:base:info:";
|
private static final String ADVICE_BASE_INFO_CACHE_PREFIX = "advice:base:info:";
|
||||||
// 缓存过期时间(小时)
|
// 缓存过期时间(小时)
|
||||||
@@ -1093,7 +1107,9 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
if (is_save) {
|
if (is_save) {
|
||||||
medicationRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.MEDICATION_RES_NO.getPrefix(), 4));
|
medicationRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.MEDICATION_RES_NO.getPrefix(), 4));
|
||||||
}
|
}
|
||||||
medicationRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
medicationRequest.setGenerateSourceEnum(adviceSaveDto.getGenerateSourceEnum() != null
|
||||||
|
? adviceSaveDto.getGenerateSourceEnum()
|
||||||
|
: GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
||||||
medicationRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
|
medicationRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
|
||||||
medicationRequest.setExecuteNum(adviceSaveDto.getExecuteNum()); // 执行次数
|
medicationRequest.setExecuteNum(adviceSaveDto.getExecuteNum()); // 执行次数
|
||||||
medicationRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
|
medicationRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
|
||||||
@@ -1139,7 +1155,9 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
chargeItem.setId(adviceSaveDto.getChargeItemId()); // 费用项id
|
chargeItem.setId(adviceSaveDto.getChargeItemId()); // 费用项id
|
||||||
chargeItem.setStatusEnum(2); // 已生成医嘱
|
chargeItem.setStatusEnum(2); // 已生成医嘱
|
||||||
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(medicationRequest.getBusNo()));
|
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(medicationRequest.getBusNo()));
|
||||||
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
chargeItem.setGenerateSourceEnum(adviceSaveDto.getGenerateSourceEnum() != null
|
||||||
|
? adviceSaveDto.getGenerateSourceEnum()
|
||||||
|
: GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
||||||
chargeItem.setPrescriptionNo(adviceSaveDto.getPrescriptionNo()); // 处方号
|
chargeItem.setPrescriptionNo(adviceSaveDto.getPrescriptionNo()); // 处方号
|
||||||
chargeItem.setPatientId(adviceSaveDto.getPatientId()); // 患者
|
chargeItem.setPatientId(adviceSaveDto.getPatientId()); // 患者
|
||||||
chargeItem.setContextEnum(adviceSaveDto.getAdviceType()); // 类型
|
chargeItem.setContextEnum(adviceSaveDto.getAdviceType()); // 类型
|
||||||
@@ -1233,7 +1251,9 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
deviceRequest.setCreateBy(currentUsername);
|
deviceRequest.setCreateBy(currentUsername);
|
||||||
deviceRequest.setCreateTime(curDate);
|
deviceRequest.setCreateTime(curDate);
|
||||||
deviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), 4));
|
deviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), 4));
|
||||||
deviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
|
deviceRequest.setGenerateSourceEnum(adviceSaveDto.getGenerateSourceEnum() != null
|
||||||
|
? adviceSaveDto.getGenerateSourceEnum()
|
||||||
|
: GenerateSource.DOCTOR_PRESCRIPTION.getValue());
|
||||||
deviceRequest.setQuantity(boundDevice.getQuantity());
|
deviceRequest.setQuantity(boundDevice.getQuantity());
|
||||||
deviceRequest.setUnitCode(boundDevice.getUnitCode());
|
deviceRequest.setUnitCode(boundDevice.getUnitCode());
|
||||||
deviceRequest.setCategoryEnum(adviceSaveDto.getCategoryEnum());
|
deviceRequest.setCategoryEnum(adviceSaveDto.getCategoryEnum());
|
||||||
@@ -1299,7 +1319,9 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
deviceChargeItem.setCreateTime(curDate);
|
deviceChargeItem.setCreateTime(curDate);
|
||||||
deviceChargeItem.setStatusEnum(ChargeItemStatus.PLANNED.getValue());
|
deviceChargeItem.setStatusEnum(ChargeItemStatus.PLANNED.getValue());
|
||||||
deviceChargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(deviceRequest.getBusNo()));
|
deviceChargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(deviceRequest.getBusNo()));
|
||||||
deviceChargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
|
deviceChargeItem.setGenerateSourceEnum(adviceSaveDto.getGenerateSourceEnum() != null
|
||||||
|
? adviceSaveDto.getGenerateSourceEnum()
|
||||||
|
: GenerateSource.DOCTOR_PRESCRIPTION.getValue());
|
||||||
deviceChargeItem.setPrescriptionNo(adviceSaveDto.getPrescriptionNo()); // 处方号,与药品一致
|
deviceChargeItem.setPrescriptionNo(adviceSaveDto.getPrescriptionNo()); // 处方号,与药品一致
|
||||||
deviceChargeItem.setPatientId(adviceSaveDto.getPatientId());
|
deviceChargeItem.setPatientId(adviceSaveDto.getPatientId());
|
||||||
deviceChargeItem.setContextEnum(ChargeItemContext.DEVICE.getValue()); // 耗材类型
|
deviceChargeItem.setContextEnum(ChargeItemContext.DEVICE.getValue()); // 耗材类型
|
||||||
@@ -1528,22 +1550,47 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
if (is_save) {
|
if (is_save) {
|
||||||
deviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), 4));
|
deviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), 4));
|
||||||
}
|
}
|
||||||
deviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
deviceRequest.setGenerateSourceEnum(adviceSaveDto.getGenerateSourceEnum() != null
|
||||||
|
? adviceSaveDto.getGenerateSourceEnum()
|
||||||
|
: GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
||||||
|
deviceRequest.setPrescriptionNo(adviceSaveDto.getSourceBillNo()); // 来源业务单据号(手术单号)
|
||||||
deviceRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
|
deviceRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
|
||||||
deviceRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
|
deviceRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
|
||||||
deviceRequest.setLotNumber(adviceSaveDto.getLotNumber());// 产品批号
|
deviceRequest.setLotNumber(adviceSaveDto.getLotNumber());// 产品批号
|
||||||
|
|
||||||
deviceRequest.setCategoryEnum(adviceSaveDto.getCategoryEnum()); // 请求类型
|
deviceRequest.setCategoryEnum(adviceSaveDto.getCategoryEnum()); // 请求类型
|
||||||
|
// 🔧 BugFix #498: categoryEnum=22(检查) 走 ServiceRequest,不走 DeviceRequest
|
||||||
|
// 检查申请单的诊疗定义ID存在 activityId,不在 adviceDefinitionId
|
||||||
|
// deviceDefId 对应耗材定义ID,不能用诊疗定义ID填充
|
||||||
|
if (adviceSaveDto.getCategoryEnum() == 22) {
|
||||||
|
log.info("handDevice skip - 检查申请单(categoryEnum=22) 走 ServiceRequest 路径,跳过 DeviceRequest 保存");
|
||||||
|
continue; // 跳过本次循环,不走耗材请求路径
|
||||||
|
} else if (adviceSaveDto.getAdviceDefinitionId() != null) {
|
||||||
deviceRequest.setDeviceDefId(adviceSaveDto.getAdviceDefinitionId());// 耗材定义id
|
deviceRequest.setDeviceDefId(adviceSaveDto.getAdviceDefinitionId());// 耗材定义id
|
||||||
|
} else {
|
||||||
|
log.warn("handDevice - deviceDefId 为空,adviceDefinitionId=null, categoryEnum={}", adviceSaveDto.getCategoryEnum());
|
||||||
|
}
|
||||||
|
|
||||||
deviceRequest.setPatientId(adviceSaveDto.getPatientId()); // 患者
|
deviceRequest.setPatientId(adviceSaveDto.getPatientId()); // 患者
|
||||||
deviceRequest.setRequesterId(adviceSaveDto.getPractitionerId()); // 开方医生
|
deviceRequest.setRequesterId(adviceSaveDto.getPractitionerId()); // 开方医生
|
||||||
deviceRequest.setOrgId(adviceSaveDto.getFounderOrgId());// 开方人科室
|
deviceRequest.setOrgId(adviceSaveDto.getFounderOrgId());// 开方人科室
|
||||||
deviceRequest.setReqAuthoredTime(curDate); // 请求开始时间
|
deviceRequest.setReqAuthoredTime(curDate); // 请求开始时间
|
||||||
// 发放耗材房(若前端未传locationId,使用登录用户的科室作为默认值)
|
// 发放耗材房(若前端未传locationId,优先沿用已有DeviceRequest的performLocation,否则使用登录用户科室)
|
||||||
Long locId = adviceSaveDto.getLocationId();
|
Long locId = adviceSaveDto.getLocationId();
|
||||||
|
if (locId == null) {
|
||||||
|
// 尝试从已有DeviceRequest获取原始的performLocation
|
||||||
|
if (adviceSaveDto.getRequestId() != null) {
|
||||||
|
DeviceRequest existingDevice = iDeviceRequestService.getById(adviceSaveDto.getRequestId());
|
||||||
|
if (existingDevice != null && existingDevice.getPerformLocation() != null) {
|
||||||
|
locId = existingDevice.getPerformLocation();
|
||||||
|
log.info("耗材locationId为空,使用已有DeviceRequest的performLocation: locationId={}", locId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果已有记录也没有performLocation,则使用登录用户科室作为兜底
|
||||||
if (locId == null) {
|
if (locId == null) {
|
||||||
locId = SecurityUtils.getLoginUser().getOrgId();
|
locId = SecurityUtils.getLoginUser().getOrgId();
|
||||||
log.info("耗材locationId为空,使用登录用户科室作为默认值: locationId={}", locId);
|
log.info("耗材locationId为空且无已有记录,使用登录用户科室作为默认值: locationId={}", locId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
deviceRequest.setPerformLocation(locId);
|
deviceRequest.setPerformLocation(locId);
|
||||||
deviceRequest.setEncounterId(adviceSaveDto.getEncounterId()); // 就诊id
|
deviceRequest.setEncounterId(adviceSaveDto.getEncounterId()); // 就诊id
|
||||||
@@ -1568,7 +1615,9 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
chargeItem.setCreateTime(curDate); // 补全创建时间
|
chargeItem.setCreateTime(curDate); // 补全创建时间
|
||||||
chargeItem.setStatusEnum(2); // 已生成医嘱
|
chargeItem.setStatusEnum(2); // 已生成医嘱
|
||||||
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(deviceRequest.getBusNo()));
|
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(deviceRequest.getBusNo()));
|
||||||
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
chargeItem.setGenerateSourceEnum(adviceSaveDto.getGenerateSourceEnum() != null
|
||||||
|
? adviceSaveDto.getGenerateSourceEnum()
|
||||||
|
: GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
||||||
chargeItem.setPatientId(adviceSaveDto.getPatientId()); // 患者
|
chargeItem.setPatientId(adviceSaveDto.getPatientId()); // 患者
|
||||||
chargeItem.setContextEnum(adviceSaveDto.getAdviceType()); // 类型
|
chargeItem.setContextEnum(adviceSaveDto.getAdviceType()); // 类型
|
||||||
chargeItem.setEncounterId(adviceSaveDto.getEncounterId()); // 就诊id
|
chargeItem.setEncounterId(adviceSaveDto.getEncounterId()); // 就诊id
|
||||||
@@ -1684,6 +1733,21 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从诊疗医嘱 contentJson 中解析检验申请单号(检验保存时写入形如 {"applyNo":"..."})。
|
||||||
|
*/
|
||||||
|
private String extractInspectionApplyNoFromContentJson(String contentJson) {
|
||||||
|
if (StringUtils.isBlank(contentJson) || !contentJson.contains("applyNo")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Matcher m = INSPECTION_APPLY_NO_JSON.matcher(contentJson);
|
||||||
|
if (!m.find()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String applyNo = m.group(1).trim();
|
||||||
|
return StringUtils.isBlank(applyNo) ? null : applyNo;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理诊疗
|
* 处理诊疗
|
||||||
*/
|
*/
|
||||||
@@ -1732,6 +1796,8 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 检验申请单在医嘱 contentJson 中写入 applyNo;从医嘱删除时需先级联作废检验单,避免检验页签仍显示孤儿申请
|
||||||
|
Map<String, List<Long>> labApplyNoToRequestIds = new LinkedHashMap<>();
|
||||||
for (AdviceSaveDto adviceSaveDto : deleteList) {
|
for (AdviceSaveDto adviceSaveDto : deleteList) {
|
||||||
Long requestId = adviceSaveDto.getRequestId();
|
Long requestId = adviceSaveDto.getRequestId();
|
||||||
// 🔧 Bug #442: 跳过 requestId 为 null 的记录,避免删除不存在的诊疗请求
|
// 🔧 Bug #442: 跳过 requestId 为 null 的记录,避免删除不存在的诊疗请求
|
||||||
@@ -1740,6 +1806,35 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
iServiceRequestService.removeById(requestId);// 删除诊疗
|
iServiceRequestService.removeById(requestId);// 删除诊疗
|
||||||
|
ServiceRequest existing = iServiceRequestService.getById(adviceSaveDto.getRequestId());
|
||||||
|
if (existing == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String applyNo = extractInspectionApplyNoFromContentJson(existing.getContentJson());
|
||||||
|
if (StringUtils.isNotBlank(applyNo)) {
|
||||||
|
labApplyNoToRequestIds.computeIfAbsent(applyNo, k -> new ArrayList<>())
|
||||||
|
.add(adviceSaveDto.getRequestId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Set<Long> labCascadeSkippedRequestIds = new HashSet<>();
|
||||||
|
for (Map.Entry<String, List<Long>> e : labApplyNoToRequestIds.entrySet()) {
|
||||||
|
R<?> delLab = iDoctorStationInspectionLabApplyService.deleteInspectionLabApply(e.getKey());
|
||||||
|
if (delLab != null && R.isSuccess(delLab)) {
|
||||||
|
labCascadeSkippedRequestIds.addAll(e.getValue());
|
||||||
|
log.info("handService - 级联作废检验申请单 applyNo={},已跳过重复删除的医嘱 requestIds={}",
|
||||||
|
e.getKey(), e.getValue());
|
||||||
|
} else {
|
||||||
|
String msg = delLab != null && StringUtils.isNotEmpty(delLab.getMsg()) ? delLab.getMsg() : "删除检验申请单失败";
|
||||||
|
log.warn("handService - 级联作废检验申请单未成功 applyNo={} msg={},将回退为仅删除当前医嘱记录",
|
||||||
|
e.getKey(), msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (AdviceSaveDto adviceSaveDto : deleteList) {
|
||||||
|
if (labCascadeSkippedRequestIds.contains(adviceSaveDto.getRequestId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Long requestId = adviceSaveDto.getRequestId();
|
||||||
|
iServiceRequestService.removeById(requestId);// 删除诊疗
|
||||||
iServiceRequestService.remove(
|
iServiceRequestService.remove(
|
||||||
new LambdaQueryWrapper<ServiceRequest>().eq(ServiceRequest::getParentId,
|
new LambdaQueryWrapper<ServiceRequest>().eq(ServiceRequest::getParentId,
|
||||||
requestId));// 删除诊疗套餐对应的子项
|
requestId));// 删除诊疗套餐对应的子项
|
||||||
@@ -1823,7 +1918,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
if (is_save) {
|
if (is_save) {
|
||||||
serviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.SERVICE_RES_NO.getPrefix(), 4));
|
serviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.SERVICE_RES_NO.getPrefix(), 4));
|
||||||
}
|
}
|
||||||
serviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
serviceRequest.setGenerateSourceEnum(adviceSaveDto.getGenerateSourceEnum() != null
|
||||||
|
? adviceSaveDto.getGenerateSourceEnum()
|
||||||
|
: GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
||||||
|
serviceRequest.setPrescriptionNo(adviceSaveDto.getSourceBillNo()); // 来源业务单据号(手术单号)
|
||||||
serviceRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
|
serviceRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
|
||||||
serviceRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
|
serviceRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
|
||||||
|
|
||||||
@@ -1873,7 +1971,9 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
chargeItem.setCreateTime(curDate); // 补全创建时间
|
chargeItem.setCreateTime(curDate); // 补全创建时间
|
||||||
chargeItem.setStatusEnum(2); // 已生成医嘱
|
chargeItem.setStatusEnum(2); // 已生成医嘱
|
||||||
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(serviceRequest.getBusNo()));
|
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(serviceRequest.getBusNo()));
|
||||||
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
chargeItem.setGenerateSourceEnum(adviceSaveDto.getGenerateSourceEnum() != null
|
||||||
|
? adviceSaveDto.getGenerateSourceEnum()
|
||||||
|
: GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
||||||
chargeItem.setPatientId(adviceSaveDto.getPatientId()); // 患者
|
chargeItem.setPatientId(adviceSaveDto.getPatientId()); // 患者
|
||||||
chargeItem.setContextEnum(adviceSaveDto.getAdviceType()); // 类型
|
chargeItem.setContextEnum(adviceSaveDto.getAdviceType()); // 类型
|
||||||
chargeItem.setEncounterId(adviceSaveDto.getEncounterId()); // 就诊id
|
chargeItem.setEncounterId(adviceSaveDto.getEncounterId()); // 就诊id
|
||||||
@@ -2019,10 +2119,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
CommonConstants.TableName.MED_MEDICATION_REQUEST, CommonConstants.TableName.WOR_DEVICE_REQUEST,
|
CommonConstants.TableName.MED_MEDICATION_REQUEST, CommonConstants.TableName.WOR_DEVICE_REQUEST,
|
||||||
CommonConstants.TableName.WOR_SERVICE_REQUEST, practitionerId, Whether.NO.getCode(),
|
CommonConstants.TableName.WOR_SERVICE_REQUEST, practitionerId, Whether.NO.getCode(),
|
||||||
sourceEnum, sourceBillNo);
|
sourceEnum, sourceBillNo);
|
||||||
// 手术计费场景:sourceBillNo 不为空时,只保留诊疗请求(3/6),过滤掉药品(1)和耗材(2)
|
// 手术计费场景:sourceBillNo 不为空时,过滤掉药品(1),保留耗材(2)和诊疗(3/6)
|
||||||
if (sourceBillNo != null && !sourceBillNo.isEmpty()) {
|
if (sourceBillNo != null && !sourceBillNo.isEmpty()) {
|
||||||
requestBaseInfo.removeIf(dto -> dto.getAdviceType() != null
|
requestBaseInfo.removeIf(dto -> dto.getAdviceType() != null
|
||||||
&& (dto.getAdviceType() == 1 || dto.getAdviceType() == 2));
|
&& dto.getAdviceType() == 1);
|
||||||
}
|
}
|
||||||
for (RequestBaseDto requestBaseDto : requestBaseInfo) {
|
for (RequestBaseDto requestBaseDto : requestBaseInfo) {
|
||||||
// 请求状态
|
// 请求状态
|
||||||
@@ -2356,4 +2456,52 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
return R.ok(categoryCodes);
|
return R.ok(categoryCodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手术项目专用分页查询(仅手术 + 定价,无库存/草稿库存/取药科室等无关逻辑)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public IPage<SurgeryItemDto> getSurgeryPage(Long organizationId, Integer pageNo, Integer pageSize, String searchKey) {
|
||||||
|
log.info("getSurgeryPage 开始: orgId={}, page={}/{}, searchKey={}", organizationId, pageNo, pageSize, searchKey);
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// 无搜索时尝试从 Redis 缓存读取(手术项目变更频率低,适合缓存)
|
||||||
|
String safeOrgId = organizationId != null ? organizationId.toString() : "";
|
||||||
|
String cacheKey = "surgery:page:" + safeOrgId + ":" + pageNo + ":" + pageSize;
|
||||||
|
boolean useCache = (searchKey == null || searchKey.trim().isEmpty());
|
||||||
|
|
||||||
|
if (useCache) {
|
||||||
|
Object cachedObj = redisCache.getCacheObject(cacheKey);
|
||||||
|
if (cachedObj instanceof com.baomidou.mybatisplus.extension.plugins.pagination.Page) {
|
||||||
|
log.info("从 Redis 缓存获取手术项目, key: {}, records: {}", cacheKey,
|
||||||
|
((IPage<?>) cachedObj).getRecords().size());
|
||||||
|
return (IPage<SurgeryItemDto>) cachedObj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IPage<SurgeryItemDto> result = doctorStationAdviceAppMapper.getSurgeryPage(
|
||||||
|
new Page<>(pageNo, pageSize),
|
||||||
|
PublicationStatus.ACTIVE.getValue(),
|
||||||
|
organizationId,
|
||||||
|
searchKey);
|
||||||
|
log.info("getSurgeryPage 完成: {}ms, total={}, records={}", System.currentTimeMillis() - start, result.getTotal(), result.getRecords().size());
|
||||||
|
|
||||||
|
// 无搜索时将结果写入缓存
|
||||||
|
if (useCache && result instanceof com.baomidou.mybatisplus.extension.plugins.pagination.Page) {
|
||||||
|
redisCache.setCacheObject(cacheKey, result, (int) CACHE_EXPIRE_HOURS, java.util.concurrent.TimeUnit.HOURS);
|
||||||
|
log.info("缓存手术项目, key: {}, 过期时间: {} 小时", cacheKey, CACHE_EXPIRE_HOURS);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPage<SurgeryItemDto> getExaminationPage(Long organizationId, Integer pageNo, Integer pageSize, String searchKey) {
|
||||||
|
IPage<SurgeryItemDto> result = doctorStationAdviceAppMapper.getExaminationPage(
|
||||||
|
new Page<>(pageNo, pageSize),
|
||||||
|
PublicationStatus.ACTIVE.getValue(),
|
||||||
|
organizationId,
|
||||||
|
searchKey);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -261,8 +264,10 @@ public class DoctorStationDiagnosisAppServiceImpl implements IDoctorStationDiagn
|
|||||||
// 设置创建时间,避免数据库约束错误
|
// 设置创建时间,避免数据库约束错误
|
||||||
encounterDiagnosis.setCreateTime(new Date());
|
encounterDiagnosis.setCreateTime(new Date());
|
||||||
iEncounterDiagnosisService.saveOrUpdate(encounterDiagnosis);
|
iEncounterDiagnosisService.saveOrUpdate(encounterDiagnosis);
|
||||||
|
// 回写就诊诊断ID,供前端后续更新使用
|
||||||
|
saveDiagnosisChildParam.setEncounterDiagnosisId(encounterDiagnosis.getId());
|
||||||
}
|
}
|
||||||
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[] {"诊断"}));
|
return R.ok(saveDiagnosisParam, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[] {"诊断"}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -580,7 +585,11 @@ public class DoctorStationDiagnosisAppServiceImpl implements IDoctorStationDiagn
|
|||||||
@Override
|
@Override
|
||||||
public R<?> saveInfectiousDiseaseReport(InfectiousDiseaseReportDto infectiousDiseaseReportDto) {
|
public R<?> saveInfectiousDiseaseReport(InfectiousDiseaseReportDto infectiousDiseaseReportDto) {
|
||||||
// 检查卡片编号唯一性(新增时检查,编辑时排除当前记录)
|
// 检查卡片编号唯一性(新增时检查,编辑时排除当前记录)
|
||||||
String cardNo = infectiousDiseaseReportDto.getCardNo().trim();
|
String cardNo = infectiousDiseaseReportDto.getCardNo();
|
||||||
|
if (cardNo == null || cardNo.trim().isEmpty()) {
|
||||||
|
return R.fail("卡片编号不能为空");
|
||||||
|
}
|
||||||
|
cardNo = cardNo.trim();
|
||||||
LambdaQueryWrapper<InfectiousDiseaseReport> queryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<InfectiousDiseaseReport> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
queryWrapper.eq(InfectiousDiseaseReport::getCardNo, cardNo);
|
queryWrapper.eq(InfectiousDiseaseReport::getCardNo, cardNo);
|
||||||
long count = iInfectiousDiseaseReportService.count(queryWrapper);
|
long count = iInfectiousDiseaseReportService.count(queryWrapper);
|
||||||
@@ -592,6 +601,25 @@ public class DoctorStationDiagnosisAppServiceImpl implements IDoctorStationDiagn
|
|||||||
InfectiousDiseaseReport infectiousDiseaseReport = new InfectiousDiseaseReport();
|
InfectiousDiseaseReport infectiousDiseaseReport = new InfectiousDiseaseReport();
|
||||||
BeanUtils.copyProperties(infectiousDiseaseReportDto, infectiousDiseaseReport);
|
BeanUtils.copyProperties(infectiousDiseaseReportDto, infectiousDiseaseReport);
|
||||||
|
|
||||||
|
// BeanUtils.copyProperties 不支持 LocalDate/LocalDateTime 到 java.util.Date 的类型转换,需手动处理
|
||||||
|
if (infectiousDiseaseReportDto.getOnsetDate() != null) {
|
||||||
|
infectiousDiseaseReport.setOnsetDate(
|
||||||
|
Date.from(infectiousDiseaseReportDto.getOnsetDate().atStartOfDay(ZoneId.systemDefault()).toInstant()));
|
||||||
|
}
|
||||||
|
if (infectiousDiseaseReportDto.getDiagDate() != null) {
|
||||||
|
infectiousDiseaseReport.setDiagDate(
|
||||||
|
Date.from(infectiousDiseaseReportDto.getDiagDate().atZone(ZoneId.systemDefault()).toInstant()));
|
||||||
|
}
|
||||||
|
// deathDate / reportDate 同理
|
||||||
|
if (infectiousDiseaseReportDto.getDeathDate() != null) {
|
||||||
|
infectiousDiseaseReport.setDeathDate(
|
||||||
|
Date.from(infectiousDiseaseReportDto.getDeathDate().atStartOfDay(ZoneId.systemDefault()).toInstant()));
|
||||||
|
}
|
||||||
|
if (infectiousDiseaseReportDto.getReportDate() != null) {
|
||||||
|
infectiousDiseaseReport.setReportDate(
|
||||||
|
Date.from(infectiousDiseaseReportDto.getReportDate().atStartOfDay(ZoneId.systemDefault()).toInstant()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置创建人、删除状态、租户ID
|
* 设置创建人、删除状态、租户ID
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -326,6 +326,9 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
|
|||||||
|
|
||||||
// 如果队列项存在且未完成,更新队列状态为已完成
|
// 如果队列项存在且未完成,更新队列状态为已完成
|
||||||
// 使用排除法而非白名单:只要不是"已完成"就可以完诊,覆盖跳过、等待等非标准流转状态
|
// 使用排除法而非白名单:只要不是"已完成"就可以完诊,覆盖跳过、等待等非标准流转状态
|
||||||
|
// Bug #401:在更新前记录队列原始完成状态,用于判断是否需要写入 div_log
|
||||||
|
boolean queueWasAlreadyCompleted = queueItem != null
|
||||||
|
&& TriageQueueStatus.COMPLETED.getValue().equals(queueItem.getStatus());
|
||||||
if (queueItem != null &&
|
if (queueItem != null &&
|
||||||
!TriageQueueStatus.COMPLETED.getValue().equals(queueItem.getStatus())) {
|
!TriageQueueStatus.COMPLETED.getValue().equals(queueItem.getStatus())) {
|
||||||
java.time.LocalDateTime nowLocal = java.time.LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS);
|
java.time.LocalDateTime nowLocal = java.time.LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS);
|
||||||
@@ -342,7 +345,9 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
|
|||||||
encounterId, tenantId);
|
encounterId, tenantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入 div_log 审计日志(独立于队列项,确保每次完诊都生成记录)
|
// 写入 div_log 审计日志(每次完诊都生成记录,确保审计链路完整)
|
||||||
|
// Bug #401:移除 queueWasAlreadyCompleted 条件限制,避免队列已由分诊台完诊时
|
||||||
|
// 医生站完诊不写日志导致审计记录缺失;同时保留 queueWasAlreadyCompleted 日志用于排查
|
||||||
try {
|
try {
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||||
DivLog divLog = new DivLog()
|
DivLog divLog = new DivLog()
|
||||||
@@ -354,6 +359,9 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
|
|||||||
.setUpdateAt(LocalDateTime.now())
|
.setUpdateAt(LocalDateTime.now())
|
||||||
.setCreatedAt(LocalDateTime.now());
|
.setCreatedAt(LocalDateTime.now());
|
||||||
divLogService.save(divLog);
|
divLogService.save(divLog);
|
||||||
|
if (queueWasAlreadyCompleted) {
|
||||||
|
log.info("完诊:队列项已由分诊台完诊,医生站补充写入审计日志 encounterId={}", encounterId);
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("写入div_log审计日志失败", e);
|
log.error("写入div_log审计日志失败", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,4 +203,31 @@ public class DoctorStationAdviceController {
|
|||||||
return iDoctorStationAdviceAppService.getConfiguredCategories(organizationId);
|
return iDoctorStationAdviceAppService.getConfiguredCategories(organizationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手术项目专用分页查询(仅手术 + 定价,无库存/草稿库存/取药科室等无关逻辑)
|
||||||
|
*
|
||||||
|
* @param organizationId 科室ID(可选)
|
||||||
|
* @param pageNo 当前页
|
||||||
|
* @param pageSize 每页条数
|
||||||
|
* @param searchKey 模糊查询关键字(可选)
|
||||||
|
* @return 手术项目分页数据(含价格信息)
|
||||||
|
*/
|
||||||
|
@GetMapping(value = "/surgery-page")
|
||||||
|
public R<?> getSurgeryPage(
|
||||||
|
@RequestParam(value = "organizationId", required = false) Long organizationId,
|
||||||
|
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||||
|
@RequestParam(value = "pageSize", defaultValue = "500") Integer pageSize,
|
||||||
|
@RequestParam(value = "searchKey", defaultValue = "") String searchKey) {
|
||||||
|
return R.ok(iDoctorStationAdviceAppService.getSurgeryPage(organizationId, pageNo, pageSize, searchKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/examination-page")
|
||||||
|
public R<?> getExaminationPage(
|
||||||
|
@RequestParam(value = "organizationId", required = false) Long organizationId,
|
||||||
|
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||||
|
@RequestParam(value = "pageSize", defaultValue = "500") Integer pageSize,
|
||||||
|
@RequestParam(value = "searchKey", defaultValue = "") String searchKey) {
|
||||||
|
return R.ok(iDoctorStationAdviceAppService.getExaminationPage(organizationId, pageNo, pageSize, searchKey));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,4 +96,9 @@ public class DiagnosisQueryDto {
|
|||||||
*/
|
*/
|
||||||
private String diagnosisDoctor;
|
private String diagnosisDoctor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已有传染病报卡(0-无,1-有)
|
||||||
|
*/
|
||||||
|
private Integer hasInfectiousReport;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,12 +112,15 @@ public class InfectiousDiseaseReportDto {
|
|||||||
private Integer caseClass;
|
private Integer caseClass;
|
||||||
|
|
||||||
/** 发病日期(默认诊断时间,病原携带者填初检日期) */
|
/** 发病日期(默认诊断时间,病原携带者填初检日期) */
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
private LocalDate onsetDate;
|
private LocalDate onsetDate;
|
||||||
|
|
||||||
/** 诊断日期(精确到小时) */
|
/** 诊断日期(精确到小时) */
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||||
private LocalDateTime diagDate;
|
private LocalDateTime diagDate;
|
||||||
|
|
||||||
/** 死亡日期(死亡病例必填) */
|
/** 死亡日期(死亡病例必填) */
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
private LocalDate deathDate;
|
private LocalDate deathDate;
|
||||||
|
|
||||||
/** 订正病名(订正报告必填) */
|
/** 订正病名(订正报告必填) */
|
||||||
@@ -136,6 +139,7 @@ public class InfectiousDiseaseReportDto {
|
|||||||
private String reportDoc;
|
private String reportDoc;
|
||||||
|
|
||||||
/** 填卡日期 */
|
/** 填卡日期 */
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
private LocalDate reportDate;
|
private LocalDate reportDate;
|
||||||
|
|
||||||
/** 报卡名称代码 1-中华人民共和国传染病报告卡 */
|
/** 报卡名称代码 1-中华人民共和国传染病报告卡 */
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.openhis.web.doctorstation.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手术项目选择器专用 DTO(不含 @Dict 注解,绕过 DictAspect 的 Redis 字典翻译)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SurgeryItemDto {
|
||||||
|
|
||||||
|
/** 医嘱定义ID */
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long adviceDefinitionId;
|
||||||
|
|
||||||
|
/** 手术名称 */
|
||||||
|
private String adviceName;
|
||||||
|
|
||||||
|
/** 所属科室ID */
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long orgId;
|
||||||
|
|
||||||
|
/** 执行科室ID */
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long positionId;
|
||||||
|
|
||||||
|
/** 费用定价主表ID(用于提交时关联价格) */
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long chargeItemDefinitionId;
|
||||||
|
|
||||||
|
/** 单价(直接从定价主表取,无需嵌套 priceList) */
|
||||||
|
private BigDecimal price;
|
||||||
|
|
||||||
|
/** 单位编码 */
|
||||||
|
private String unitCode;
|
||||||
|
|
||||||
|
/** 单位编码字典文本(前端用于显示单位) */
|
||||||
|
private String unitCodeDictText;
|
||||||
|
}
|
||||||
@@ -185,4 +185,23 @@ public interface DoctorStationAdviceAppMapper {
|
|||||||
*/
|
*/
|
||||||
Long getDefaultAccountId(@Param("encounterId") Long encounterId);
|
Long getDefaultAccountId(@Param("encounterId") Long encounterId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手术项目专用分页查询(仅手术 + 定价,无库存/草稿库存/取药科室等无关逻辑)
|
||||||
|
*
|
||||||
|
* @param page 分页参数
|
||||||
|
* @param statusEnum 启用状态
|
||||||
|
* @param organizationId 科室ID(可选,用于过滤已配置的手术项目)
|
||||||
|
* @param searchKey 模糊查询关键字(可选)
|
||||||
|
* @return 手术项目分页数据
|
||||||
|
*/
|
||||||
|
IPage<SurgeryItemDto> getSurgeryPage(@Param("page") Page<SurgeryItemDto> page,
|
||||||
|
@Param("statusEnum") Integer statusEnum,
|
||||||
|
@Param("organizationId") Long organizationId,
|
||||||
|
@Param("searchKey") String searchKey);
|
||||||
|
|
||||||
|
IPage<SurgeryItemDto> getExaminationPage(@Param("page") Page<SurgeryItemDto> page,
|
||||||
|
@Param("statusEnum") Integer statusEnum,
|
||||||
|
@Param("organizationId") Long organizationId,
|
||||||
|
@Param("searchKey") String searchKey);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,15 +178,26 @@ public class AdviceUtils {
|
|||||||
// 生命提示信息集合
|
// 生命提示信息集合
|
||||||
List<String> tipsList = new ArrayList<>();
|
List<String> tipsList = new ArrayList<>();
|
||||||
for (MedicationRequestUseExe medicationRequestUseExe : medUseExeList) {
|
for (MedicationRequestUseExe medicationRequestUseExe : medUseExeList) {
|
||||||
// 聚合同一位置所有批次的库存总量
|
// 第一步:按 performLocation 匹配指定药房的库存
|
||||||
List<AdviceInventoryDto> matchedInventories = adviceInventory.stream()
|
List<AdviceInventoryDto> matchedInventories = adviceInventory.stream()
|
||||||
.filter(inventoryDto -> medicationRequestUseExe.getMedicationId().equals(inventoryDto.getItemId())
|
.filter(inventoryDto -> medicationRequestUseExe.getMedicationId().equals(inventoryDto.getItemId())
|
||||||
&& CommonConstants.TableName.MED_MEDICATION_DEFINITION.equals(inventoryDto.getItemTable())
|
&& CommonConstants.TableName.MED_MEDICATION_DEFINITION.equals(inventoryDto.getItemTable())
|
||||||
&& medicationRequestUseExe.getPerformLocation().equals(inventoryDto.getLocationId())
|
&& (medicationRequestUseExe.getPerformLocation() == null
|
||||||
|
|| medicationRequestUseExe.getPerformLocation().equals(inventoryDto.getLocationId()))
|
||||||
// 如果选择了具体的批次号,校验库存时需要加上批次号的匹配条件
|
// 如果选择了具体的批次号,校验库存时需要加上批次号的匹配条件
|
||||||
&& (StringUtils.isEmpty(medicationRequestUseExe.getLotNumber())
|
&& (StringUtils.isEmpty(medicationRequestUseExe.getLotNumber())
|
||||||
|| medicationRequestUseExe.getLotNumber().equals(inventoryDto.getLotNumber())))
|
|| medicationRequestUseExe.getLotNumber().equals(inventoryDto.getLotNumber())))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
// 第二步:如果指定药房没有匹配到库存,则放宽条件查询所有药房的库存
|
||||||
|
if (matchedInventories.isEmpty()) {
|
||||||
|
matchedInventories = adviceInventory.stream()
|
||||||
|
.filter(inventoryDto -> medicationRequestUseExe.getMedicationId().equals(inventoryDto.getItemId())
|
||||||
|
&& CommonConstants.TableName.MED_MEDICATION_DEFINITION.equals(inventoryDto.getItemTable())
|
||||||
|
// 如果选择了具体的批次号,校验库存时需要加上批次号的匹配条件
|
||||||
|
&& (StringUtils.isEmpty(medicationRequestUseExe.getLotNumber())
|
||||||
|
|| medicationRequestUseExe.getLotNumber().equals(inventoryDto.getLotNumber())))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
// 匹配到库存信息
|
// 匹配到库存信息
|
||||||
if (!matchedInventories.isEmpty()) {
|
if (!matchedInventories.isEmpty()) {
|
||||||
// 聚合所有批次的可用库存
|
// 聚合所有批次的可用库存
|
||||||
|
|||||||
@@ -178,6 +178,7 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
|
|||||||
inpatientAdviceParam.setEncounterIds(null);
|
inpatientAdviceParam.setEncounterIds(null);
|
||||||
Integer exeStatus = inpatientAdviceParam.getExeStatus();
|
Integer exeStatus = inpatientAdviceParam.getExeStatus();
|
||||||
inpatientAdviceParam.setExeStatus(null);
|
inpatientAdviceParam.setExeStatus(null);
|
||||||
|
// requestStatus由前端tab传入,通过QueryWrapper自动添加到SQL外层WHERE过滤
|
||||||
// 构建查询条件
|
// 构建查询条件
|
||||||
QueryWrapper<InpatientAdviceParam> queryWrapper
|
QueryWrapper<InpatientAdviceParam> queryWrapper
|
||||||
= HisQueryUtils.buildQueryWrapper(inpatientAdviceParam, null, null, null);
|
= HisQueryUtils.buildQueryWrapper(inpatientAdviceParam, null, null, null);
|
||||||
@@ -366,7 +367,7 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
|
|||||||
.in(MedicationDispense::getMedReqId, medReqIds)
|
.in(MedicationDispense::getMedReqId, medReqIds)
|
||||||
.eq(MedicationDispense::getStatusEnum, DispenseStatus.COMPLETED.getValue()));
|
.eq(MedicationDispense::getStatusEnum, DispenseStatus.COMPLETED.getValue()));
|
||||||
if (!dispenseList.isEmpty()) {
|
if (!dispenseList.isEmpty()) {
|
||||||
return R.fail("该医嘱已发药,无法退回");
|
return R.fail("该药品已由药房发放,请先执行退药处理,不可直接退回");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
|
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ public class InpatientAdviceDto {
|
|||||||
/**
|
/**
|
||||||
* 药品/服务类型
|
* 药品/服务类型
|
||||||
*/
|
*/
|
||||||
private Integer categoryCode;
|
private String categoryCode;
|
||||||
/**
|
/**
|
||||||
* 执行科室
|
* 执行科室
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public class NursingRecordController {
|
|||||||
* 获取住院患者信息 分页显示
|
* 获取住院患者信息 分页显示
|
||||||
*
|
*
|
||||||
* @param nursingSearchParam 查询参数
|
* @param nursingSearchParam 查询参数
|
||||||
|
*
|
||||||
* @param searchKey 模糊查询
|
* @param searchKey 模糊查询
|
||||||
* @param pageNo 当前页码
|
* @param pageNo 当前页码
|
||||||
* @param pageSize 查询条数
|
* @param pageSize 查询条数
|
||||||
|
|||||||
@@ -3,29 +3,38 @@
|
|||||||
*/
|
*/
|
||||||
package com.openhis.web.inventorymanage.appservice.impl;
|
package com.openhis.web.inventorymanage.appservice.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.core.common.core.domain.R;
|
import com.core.common.core.domain.R;
|
||||||
|
import com.core.common.exception.ServiceException;
|
||||||
import com.core.common.utils.*;
|
import com.core.common.utils.*;
|
||||||
import com.core.common.utils.bean.BeanUtils;
|
import com.core.common.utils.bean.BeanUtils;
|
||||||
|
import com.openhis.administration.domain.DeviceDefinition;
|
||||||
import com.openhis.administration.domain.Practitioner;
|
import com.openhis.administration.domain.Practitioner;
|
||||||
|
import com.openhis.administration.service.IDeviceDefinitionService;
|
||||||
import com.openhis.administration.service.IPractitionerService;
|
import com.openhis.administration.service.IPractitionerService;
|
||||||
import com.openhis.common.constant.CommonConstants;
|
import com.openhis.common.constant.CommonConstants;
|
||||||
import com.openhis.common.constant.PromptMsgConstant;
|
import com.openhis.common.constant.PromptMsgConstant;
|
||||||
import com.openhis.common.enums.*;
|
import com.openhis.common.enums.*;
|
||||||
import com.openhis.common.utils.EnumUtils;
|
import com.openhis.common.utils.EnumUtils;
|
||||||
import com.openhis.common.utils.HisQueryUtils;
|
import com.openhis.common.utils.HisQueryUtils;
|
||||||
|
import com.openhis.medication.domain.MedicationDefinition;
|
||||||
|
import com.openhis.medication.service.IMedicationDefinitionService;
|
||||||
import com.openhis.web.common.dto.UnitDto;
|
import com.openhis.web.common.dto.UnitDto;
|
||||||
import com.openhis.web.inventorymanage.appservice.IRequisitionIssueAppService;
|
import com.openhis.web.inventorymanage.appservice.IRequisitionIssueAppService;
|
||||||
import com.openhis.web.inventorymanage.dto.*;
|
import com.openhis.web.inventorymanage.dto.*;
|
||||||
import com.openhis.web.inventorymanage.mapper.RequisitionIssueMapper;
|
import com.openhis.web.inventorymanage.mapper.RequisitionIssueMapper;
|
||||||
|
import com.openhis.workflow.domain.InventoryItem;
|
||||||
import com.openhis.workflow.domain.SupplyRequest;
|
import com.openhis.workflow.domain.SupplyRequest;
|
||||||
|
import com.openhis.workflow.service.IInventoryItemService;
|
||||||
import com.openhis.workflow.service.ISupplyRequestService;
|
import com.openhis.workflow.service.ISupplyRequestService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@@ -48,6 +57,15 @@ public class RequisitionIssueAppServiceImpl implements IRequisitionIssueAppServi
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IPractitionerService practitionerService;
|
private IPractitionerService practitionerService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IInventoryItemService inventoryItemService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IMedicationDefinitionService medicationDefinitionService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IDeviceDefinitionService deviceDefinitionService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private AssignSeqUtil assignSeqUtil;
|
private AssignSeqUtil assignSeqUtil;
|
||||||
|
|
||||||
@@ -167,6 +185,10 @@ public class RequisitionIssueAppServiceImpl implements IRequisitionIssueAppServi
|
|||||||
|
|
||||||
// 单据号取得
|
// 单据号取得
|
||||||
List<String> busNoList = requisitionIssueDtoList.stream().map(IssueDto::getBusNo).collect(Collectors.toList());
|
List<String> busNoList = requisitionIssueDtoList.stream().map(IssueDto::getBusNo).collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 库存校验:领用数量不能超过源仓库实际库存
|
||||||
|
this.validateRequisitionStock(requisitionIssueDtoList);
|
||||||
|
|
||||||
// 请求数据取得
|
// 请求数据取得
|
||||||
List<SupplyRequest> requestList = supplyRequestService.getSupplyByBusNo(busNoList.get(0));
|
List<SupplyRequest> requestList = supplyRequestService.getSupplyByBusNo(busNoList.get(0));
|
||||||
if (!requestList.isEmpty()) {
|
if (!requestList.isEmpty()) {
|
||||||
@@ -328,4 +350,73 @@ public class RequisitionIssueAppServiceImpl implements IRequisitionIssueAppServi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验领用数量是否超过源仓库实际库存
|
||||||
|
*
|
||||||
|
* @param requisitionIssueDtoList 领用出库单据列表
|
||||||
|
*/
|
||||||
|
private void validateRequisitionStock(List<IssueDto> requisitionIssueDtoList) {
|
||||||
|
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
|
for (IssueDto issueDto : requisitionIssueDtoList) {
|
||||||
|
Long itemId = issueDto.getItemId();
|
||||||
|
String lotNumber = issueDto.getLotNumber();
|
||||||
|
Long sourceLocationId = issueDto.getSourceLocationId();
|
||||||
|
BigDecimal reqQuantity = issueDto.getItemQuantity();
|
||||||
|
String itemUnit = issueDto.getUnitCode();
|
||||||
|
String itemTable = issueDto.getItemTable();
|
||||||
|
|
||||||
|
// 根据物品类型查询定义信息(拆零比、常规单位、最小单位)
|
||||||
|
BigDecimal partPercent = BigDecimal.ONE;
|
||||||
|
String unitCode = itemUnit;
|
||||||
|
String minUnitCode = itemUnit;
|
||||||
|
|
||||||
|
if (CommonConstants.TableName.MED_MEDICATION_DEFINITION.equals(itemTable)) {
|
||||||
|
MedicationDefinition medDef = medicationDefinitionService.getById(itemId);
|
||||||
|
if (medDef != null) {
|
||||||
|
unitCode = medDef.getUnitCode();
|
||||||
|
minUnitCode = medDef.getMinUnitCode();
|
||||||
|
if (medDef.getPartPercent() != null) {
|
||||||
|
partPercent = medDef.getPartPercent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (CommonConstants.TableName.ADM_DEVICE_DEFINITION.equals(itemTable)) {
|
||||||
|
DeviceDefinition devDef = deviceDefinitionService.getById(itemId);
|
||||||
|
if (devDef != null) {
|
||||||
|
unitCode = devDef.getUnitCode();
|
||||||
|
minUnitCode = devDef.getMinUnitCode();
|
||||||
|
if (devDef.getPartPercent() != null) {
|
||||||
|
partPercent = devDef.getPartPercent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算领用数量折合最小单位的值
|
||||||
|
BigDecimal reqQuantityInMinUnit;
|
||||||
|
if (itemUnit.equals(unitCode)) {
|
||||||
|
// 领用单位 = 包装单位,需乘以拆零比
|
||||||
|
reqQuantityInMinUnit = reqQuantity.multiply(partPercent);
|
||||||
|
} else {
|
||||||
|
// 领用单位 = 最小单位,无需换算
|
||||||
|
reqQuantityInMinUnit = reqQuantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询源仓库实际库存(按物品编号、批号、仓库匹配)
|
||||||
|
List<InventoryItem> inventoryItemList = inventoryItemService.selectInventoryByItemId(
|
||||||
|
itemId, lotNumber, sourceLocationId, tenantId);
|
||||||
|
|
||||||
|
// 累加匹配批号的总库存(库存表quantity字段为最小单位)
|
||||||
|
BigDecimal totalStock = BigDecimal.ZERO;
|
||||||
|
for (InventoryItem inventoryItem : inventoryItemList) {
|
||||||
|
if (inventoryItem.getLocationId().equals(sourceLocationId)) {
|
||||||
|
totalStock = totalStock.add(inventoryItem.getQuantity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 比较领用数量与库存
|
||||||
|
if (reqQuantityInMinUnit.compareTo(totalStock) > 0) {
|
||||||
|
throw new ServiceException("操作失败,库存数量不足");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,4 +63,20 @@ public interface IRequestFormManageAppService {
|
|||||||
* @return 申请单
|
* @return 申请单
|
||||||
*/
|
*/
|
||||||
IPage<RequestFormPageDto> getRequestFormPage(RequestFormDto requestFormDto);
|
IPage<RequestFormPageDto> getRequestFormPage(RequestFormDto requestFormDto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除申请单(仅待签发状态可删除)
|
||||||
|
*
|
||||||
|
* @param requestFormId 申请单ID
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
R<?> deleteRequestForm(Long requestFormId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 撤回申请单(已签发状态撤回至待签发)
|
||||||
|
*
|
||||||
|
* @param requestFormId 申请单ID
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
R<?> withdrawRequestForm(Long requestFormId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
&& (DbOpType.INSERT.getCode().equals(e.getDbOpType()) || DbOpType.UPDATE.getCode().equals(e.getDbOpType())))
|
&& (DbOpType.INSERT.getCode().equals(e.getDbOpType()) || DbOpType.UPDATE.getCode().equals(e.getDbOpType())))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
// 防重复保存:对新增医嘱进行去重,避免签发单条长期医嘱时产生重复记录
|
// 防重复保存:对所有医嘱进行去重(包括 INSERT 和 UPDATE 混合场景),避免签发单条医嘱时产生重复记录
|
||||||
Set<String> longUniqueKeySet = new HashSet<>();
|
Set<String> longUniqueKeySet = new HashSet<>();
|
||||||
List<RegAdviceSaveDto> longUniqueList = new ArrayList<>();
|
List<RegAdviceSaveDto> longUniqueList = new ArrayList<>();
|
||||||
for (RegAdviceSaveDto adviceSaveDto : longInsertOrUpdateList) {
|
for (RegAdviceSaveDto adviceSaveDto : longInsertOrUpdateList) {
|
||||||
@@ -351,10 +351,10 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
+ adviceSaveDto.getDose() + "_"
|
+ adviceSaveDto.getDose() + "_"
|
||||||
+ adviceSaveDto.getMethodCode() + "_"
|
+ adviceSaveDto.getMethodCode() + "_"
|
||||||
+ adviceSaveDto.getRateCode();
|
+ adviceSaveDto.getRateCode();
|
||||||
if (DbOpType.INSERT.getCode().equals(adviceSaveDto.getDbOpType()) && longUniqueKeySet.contains(uniqueKey)) {
|
if (longUniqueKeySet.contains(uniqueKey)) {
|
||||||
log.warn("防重复保存:检测到重复长期医嘱,跳过保存 - patientId={}, encounterId={}, adviceDefinitionId={}, dose={}",
|
log.warn("防重复保存:检测到重复长期医嘱(跨操作类型),跳过 - patientId={}, encounterId={}, adviceDefinitionId={}, dbOpType={}",
|
||||||
adviceSaveDto.getPatientId(), adviceSaveDto.getEncounterId(),
|
adviceSaveDto.getPatientId(), adviceSaveDto.getEncounterId(),
|
||||||
adviceSaveDto.getAdviceDefinitionId(), adviceSaveDto.getDose());
|
adviceSaveDto.getAdviceDefinitionId(), adviceSaveDto.getDbOpType());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
longUniqueKeySet.add(uniqueKey);
|
longUniqueKeySet.add(uniqueKey);
|
||||||
@@ -429,7 +429,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
&& (DbOpType.INSERT.getCode().equals(e.getDbOpType()) || DbOpType.UPDATE.getCode().equals(e.getDbOpType())))
|
&& (DbOpType.INSERT.getCode().equals(e.getDbOpType()) || DbOpType.UPDATE.getCode().equals(e.getDbOpType())))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
// 防重复保存:对新增医嘱进行去重
|
// 防重复保存:对所有医嘱进行去重(包括 INSERT 和 UPDATE 混合场景),避免签发时产生重复记录
|
||||||
Set<String> tempUniqueKeySet = new HashSet<>();
|
Set<String> tempUniqueKeySet = new HashSet<>();
|
||||||
List<RegAdviceSaveDto> tempUniqueList = new ArrayList<>();
|
List<RegAdviceSaveDto> tempUniqueList = new ArrayList<>();
|
||||||
for (RegAdviceSaveDto adviceSaveDto : tempInsertOrUpdateList) {
|
for (RegAdviceSaveDto adviceSaveDto : tempInsertOrUpdateList) {
|
||||||
@@ -439,10 +439,10 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
+ adviceSaveDto.getDose() + "_"
|
+ adviceSaveDto.getDose() + "_"
|
||||||
+ adviceSaveDto.getMethodCode() + "_"
|
+ adviceSaveDto.getMethodCode() + "_"
|
||||||
+ adviceSaveDto.getRateCode();
|
+ adviceSaveDto.getRateCode();
|
||||||
if (DbOpType.INSERT.getCode().equals(adviceSaveDto.getDbOpType()) && tempUniqueKeySet.contains(uniqueKey)) {
|
if (tempUniqueKeySet.contains(uniqueKey)) {
|
||||||
log.warn("防重复保存:检测到重复临时医嘱,跳过保存 - patientId={}, encounterId={}, adviceDefinitionId={}, dose={}",
|
log.warn("防重复保存:检测到重复临时医嘱(跨操作类型),跳过 - patientId={}, encounterId={}, adviceDefinitionId={}, dbOpType={}",
|
||||||
adviceSaveDto.getPatientId(), adviceSaveDto.getEncounterId(),
|
adviceSaveDto.getPatientId(), adviceSaveDto.getEncounterId(),
|
||||||
adviceSaveDto.getAdviceDefinitionId(), adviceSaveDto.getDose());
|
adviceSaveDto.getAdviceDefinitionId(), adviceSaveDto.getDbOpType());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tempUniqueKeySet.add(uniqueKey);
|
tempUniqueKeySet.add(uniqueKey);
|
||||||
@@ -710,11 +710,21 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
|
|
||||||
// 批量更新诊疗医嘱状态(使用 update 确保状态字段必定更新)
|
// 批量更新诊疗医嘱状态(使用 update 确保状态字段必定更新)
|
||||||
if (!processedRequestIds.isEmpty()) {
|
if (!processedRequestIds.isEmpty()) {
|
||||||
|
// 🔧 Bug #487 修复:签发时额外设置 authoredTime,确保签发时间被记录
|
||||||
|
if (is_sign) {
|
||||||
iServiceRequestService.update(null,
|
iServiceRequestService.update(null,
|
||||||
new LambdaUpdateWrapper<ServiceRequest>()
|
new LambdaUpdateWrapper<ServiceRequest>()
|
||||||
.set(ServiceRequest::getStatusEnum,
|
.set(ServiceRequest::getStatusEnum, RequestStatus.ACTIVE.getValue())
|
||||||
is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue())
|
.set(ServiceRequest::getAuthoredTime, authoredTime)
|
||||||
|
.set(ServiceRequest::getSignCode, signCode)
|
||||||
.in(ServiceRequest::getId, processedRequestIds));
|
.in(ServiceRequest::getId, processedRequestIds));
|
||||||
|
log.info("签发诊疗医嘱成功,requestIds: {}, signCode: {}", processedRequestIds, signCode);
|
||||||
|
} else {
|
||||||
|
iServiceRequestService.update(null,
|
||||||
|
new LambdaUpdateWrapper<ServiceRequest>()
|
||||||
|
.set(ServiceRequest::getStatusEnum, RequestStatus.DRAFT.getValue())
|
||||||
|
.in(ServiceRequest::getId, processedRequestIds));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -751,6 +761,8 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
if (is_sign) {
|
if (is_sign) {
|
||||||
deviceRequest.setReqAuthoredTime(authoredTime); // 医嘱签发时间
|
deviceRequest.setReqAuthoredTime(authoredTime); // 医嘱签发时间
|
||||||
}
|
}
|
||||||
|
// 保存或签发时都需要设置耗材定义ID(防止 sign 分支 deviceDefId 为空触发 NOT NULL 约束)
|
||||||
|
deviceRequest.setDeviceDefId(regAdviceSaveDto.getAdviceDefinitionId());
|
||||||
// 保存时处理的字段属性
|
// 保存时处理的字段属性
|
||||||
if (is_save) {
|
if (is_save) {
|
||||||
deviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), 4));
|
deviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), 4));
|
||||||
@@ -788,6 +800,8 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
if (is_sign) {
|
if (is_sign) {
|
||||||
deviceRequest.setReqAuthoredTime(authoredTime); // 医嘱签发时间
|
deviceRequest.setReqAuthoredTime(authoredTime); // 医嘱签发时间
|
||||||
}
|
}
|
||||||
|
// 保存或签发时都需要设置耗材定义ID(防止 sign 分支 deviceDefId 为空触发 NOT NULL 约束)
|
||||||
|
deviceRequest.setDeviceDefId(regAdviceSaveDto.getAdviceDefinitionId());
|
||||||
// 保存时处理的字段属性
|
// 保存时处理的字段属性
|
||||||
if (is_save) {
|
if (is_save) {
|
||||||
deviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), 4));
|
deviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), 4));
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.openhis.web.regdoctorstation.appservice.impl;
|
package com.openhis.web.regdoctorstation.appservice.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.core.common.core.domain.R;
|
import com.core.common.core.domain.R;
|
||||||
@@ -76,12 +77,46 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public R<?> saveRequestForm(RequestFormSaveDto requestFormSaveDto, String typeCode) {
|
public R<?> saveRequestForm(RequestFormSaveDto requestFormSaveDto, String typeCode) {
|
||||||
|
// 申请单ID(前端空字符串可能反序列化为0L,需同时判0)
|
||||||
|
Long requestFormId = requestFormSaveDto.getRequestFormId();
|
||||||
|
boolean isEdit = requestFormId != null && requestFormId != 0L;
|
||||||
|
|
||||||
|
// 诊疗执行科室配置校验(必须在任何数据库操作之前)
|
||||||
|
List<ActivityOrganizationConfigDto> activityOrganizationConfig =
|
||||||
|
requestFormManageAppMapper.getActivityOrganizationConfig(typeCode);
|
||||||
|
if (activityOrganizationConfig.isEmpty()) {
|
||||||
|
throw new ServiceException("请先配置当前时间段的执行科室");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逐个校验activityList中的项目是否都配置了执行科室,并收集positionId供后续使用
|
||||||
|
// 必须在任何数据库操作之前完成全部校验,避免部分保存后异常导致脏数据
|
||||||
|
// 🔧 Bug #516: 优先使用前端传入的positionId(用户手动选择的发往科室),仅在未选择时使用配置的执行科室
|
||||||
|
List<ActivitySaveDto> activityList = requestFormSaveDto.getActivityList();
|
||||||
|
// 缓存校验结果,避免主循环中重复查询和可能出现的数据不一致
|
||||||
|
java.util.Map<Long, Long> activityIdToPositionIdMap = new java.util.HashMap<>();
|
||||||
|
if (activityList != null && !activityList.isEmpty()) {
|
||||||
|
for (ActivitySaveDto activitySaveDto : activityList) {
|
||||||
|
// 优先使用前端传入的positionId(用户手动选择的科室)
|
||||||
|
Long frontendPositionId = activitySaveDto.getPositionId();
|
||||||
|
if (frontendPositionId != null) {
|
||||||
|
activityIdToPositionIdMap.put(activitySaveDto.getAdviceDefinitionId(), frontendPositionId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 前端未传入时,使用配置的执行科室
|
||||||
|
Long configPositionId = activityOrganizationConfig.stream()
|
||||||
|
.filter(dto -> activitySaveDto.getAdviceDefinitionId().equals(dto.getActivityDefinitionId()))
|
||||||
|
.map(ActivityOrganizationConfigDto::getOrganizationId).findFirst().orElse(null);
|
||||||
|
if (configPositionId == null) {
|
||||||
|
throw new ServiceException(activitySaveDto.getAdviceDefinitionName() + "未配置当前时间段的执行科室");
|
||||||
|
}
|
||||||
|
activityIdToPositionIdMap.put(activitySaveDto.getAdviceDefinitionId(), configPositionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 诊疗处方号
|
// 诊疗处方号
|
||||||
String prescriptionNo;
|
String prescriptionNo;
|
||||||
// 申请单ID
|
|
||||||
Long requestFormId = requestFormSaveDto.getRequestFormId();
|
|
||||||
// 编辑场景
|
// 编辑场景
|
||||||
if (requestFormId != null) {
|
if (isEdit) {
|
||||||
RequestForm requestFormInfo = iRequestFormService.getById(requestFormId);
|
RequestForm requestFormInfo = iRequestFormService.getById(requestFormId);
|
||||||
prescriptionNo = requestFormInfo.getPrescriptionNo();
|
prescriptionNo = requestFormInfo.getPrescriptionNo();
|
||||||
// 该申请单存在的待发送医嘱个数
|
// 该申请单存在的待发送医嘱个数
|
||||||
@@ -125,7 +160,7 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
|||||||
iRequestFormService.saveOrUpdate(requestForm);
|
iRequestFormService.saveOrUpdate(requestForm);
|
||||||
|
|
||||||
// 编辑场景时,先删除掉原有诊疗项目及账单再新增
|
// 编辑场景时,先删除掉原有诊疗项目及账单再新增
|
||||||
if (requestFormId != null) {
|
if (isEdit) {
|
||||||
List<Long> serviceRequestIds = iServiceRequestService
|
List<Long> serviceRequestIds = iServiceRequestService
|
||||||
.list(new LambdaQueryWrapper<ServiceRequest>().eq(ServiceRequest::getPrescriptionNo, prescriptionNo))
|
.list(new LambdaQueryWrapper<ServiceRequest>().eq(ServiceRequest::getPrescriptionNo, prescriptionNo))
|
||||||
.stream().map(ServiceRequest::getId).collect(Collectors.toList());
|
.stream().map(ServiceRequest::getId).collect(Collectors.toList());
|
||||||
@@ -139,15 +174,7 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
|||||||
|
|
||||||
ServiceRequest serviceRequest;
|
ServiceRequest serviceRequest;
|
||||||
ChargeItem chargeItem;
|
ChargeItem chargeItem;
|
||||||
// 诊疗集合
|
|
||||||
List<ActivitySaveDto> activityList = requestFormSaveDto.getActivityList();
|
|
||||||
log.info("保存申请单,typeCode={}, activityListSize={}, encounterId={}", typeCode, activityList != null ? activityList.size() : 0, encounterId);
|
log.info("保存申请单,typeCode={}, activityListSize={}, encounterId={}", typeCode, activityList != null ? activityList.size() : 0, encounterId);
|
||||||
// 诊疗执行科室配置
|
|
||||||
List<ActivityOrganizationConfigDto> activityOrganizationConfig =
|
|
||||||
requestFormManageAppMapper.getActivityOrganizationConfig(typeCode);
|
|
||||||
if (activityOrganizationConfig.isEmpty()) {
|
|
||||||
throw new ServiceException("请先配置当前时间段的执行科室");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (ActivitySaveDto activitySaveDto : activityList) {
|
for (ActivitySaveDto activitySaveDto : activityList) {
|
||||||
serviceRequest = new ServiceRequest();
|
serviceRequest = new ServiceRequest();
|
||||||
@@ -165,9 +192,7 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
|||||||
serviceRequest.setEncounterId(encounterId); // 就诊id
|
serviceRequest.setEncounterId(encounterId); // 就诊id
|
||||||
serviceRequest.setAuthoredTime(curDate); // 请求签发时间
|
serviceRequest.setAuthoredTime(curDate); // 请求签发时间
|
||||||
|
|
||||||
Long positionId = activityOrganizationConfig.stream()
|
Long positionId = activityIdToPositionIdMap.get(activitySaveDto.getAdviceDefinitionId());
|
||||||
.filter(dto -> activitySaveDto.getAdviceDefinitionId().equals(dto.getActivityDefinitionId()))
|
|
||||||
.map(ActivityOrganizationConfigDto::getOrganizationId).findFirst().orElse(null);
|
|
||||||
if (positionId == null) {
|
if (positionId == null) {
|
||||||
throw new ServiceException(activitySaveDto.getAdviceDefinitionName() + "未配置当前时间段的执行科室");
|
throw new ServiceException(activitySaveDto.getAdviceDefinitionName() + "未配置当前时间段的执行科室");
|
||||||
}
|
}
|
||||||
@@ -278,7 +303,7 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
|||||||
surgeryServiceRequest.setTherapyEnum(TherapyTimeType.TEMPORARY.getValue());
|
surgeryServiceRequest.setTherapyEnum(TherapyTimeType.TEMPORARY.getValue());
|
||||||
surgeryServiceRequest.setQuantity(BigDecimal.valueOf(1));
|
surgeryServiceRequest.setQuantity(BigDecimal.valueOf(1));
|
||||||
surgeryServiceRequest.setUnitCode("次");
|
surgeryServiceRequest.setUnitCode("次");
|
||||||
surgeryServiceRequest.setCategoryEnum(4); // 4-手术
|
surgeryServiceRequest.setCategoryEnum(24); // 24-手术(新值域,避开 adviceType 碰撞)
|
||||||
// 优先从 activityList 获取手术 ID
|
// 优先从 activityList 获取手术 ID
|
||||||
if (activityList != null && !activityList.isEmpty()) {
|
if (activityList != null && !activityList.isEmpty()) {
|
||||||
Long activityId = activityList.get(0).getAdviceDefinitionId();
|
Long activityId = activityList.get(0).getAdviceDefinitionId();
|
||||||
@@ -474,4 +499,90 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
|||||||
return requestFormManageAppMapper.getRequestFormPage(requestFormDto, page);
|
return requestFormManageAppMapper.getRequestFormPage(requestFormDto, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R<?> deleteRequestForm(Long requestFormId) {
|
||||||
|
if (requestFormId == null) {
|
||||||
|
return R.fail("申请单ID不能为空");
|
||||||
|
}
|
||||||
|
RequestForm requestForm = iRequestFormService.getById(requestFormId);
|
||||||
|
if (requestForm == null) {
|
||||||
|
return R.fail("申请单不存在");
|
||||||
|
}
|
||||||
|
String prescriptionNo = requestForm.getPrescriptionNo();
|
||||||
|
|
||||||
|
// 查询该申请单下所有 ServiceRequest(含子项)
|
||||||
|
List<ServiceRequest> serviceRequests = iServiceRequestService.list(
|
||||||
|
new LambdaQueryWrapper<ServiceRequest>()
|
||||||
|
.eq(ServiceRequest::getPrescriptionNo, prescriptionNo));
|
||||||
|
if (serviceRequests == null || serviceRequests.isEmpty()) {
|
||||||
|
return R.fail("未找到关联的诊疗医嘱");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验:只有待签发(status=0)的申请单可删除
|
||||||
|
boolean allDraft = serviceRequests.stream()
|
||||||
|
.allMatch(sr -> RequestStatus.DRAFT.getValue().equals(sr.getStatusEnum()));
|
||||||
|
if (!allDraft) {
|
||||||
|
return R.fail("只有待签发状态的申请单可删除");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Long> serviceRequestIds = serviceRequests.stream()
|
||||||
|
.map(ServiceRequest::getId).collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 1. 删除关联的费用项
|
||||||
|
for (Long srId : serviceRequestIds) {
|
||||||
|
iChargeItemService.deleteByServiceTableAndId(
|
||||||
|
CommonConstants.TableName.WOR_SERVICE_REQUEST, srId);
|
||||||
|
}
|
||||||
|
// 2. 删除子项 ServiceRequest(parentId 非空)
|
||||||
|
iServiceRequestService.remove(
|
||||||
|
new LambdaQueryWrapper<ServiceRequest>()
|
||||||
|
.in(ServiceRequest::getId, serviceRequestIds)
|
||||||
|
.isNotNull(ServiceRequest::getParentId));
|
||||||
|
// 3. 删除主项 ServiceRequest
|
||||||
|
iServiceRequestService.removeByIds(serviceRequestIds);
|
||||||
|
// 4. 删除申请单
|
||||||
|
iRequestFormService.removeById(requestFormId);
|
||||||
|
|
||||||
|
log.info("检查申请单删除成功,requestFormId={}, prescriptionNo={}", requestFormId, prescriptionNo);
|
||||||
|
return R.ok("删除成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R<?> withdrawRequestForm(Long requestFormId) {
|
||||||
|
if (requestFormId == null) {
|
||||||
|
return R.fail("申请单ID不能为空");
|
||||||
|
}
|
||||||
|
RequestForm requestForm = iRequestFormService.getById(requestFormId);
|
||||||
|
if (requestForm == null) {
|
||||||
|
return R.fail("申请单不存在");
|
||||||
|
}
|
||||||
|
String prescriptionNo = requestForm.getPrescriptionNo();
|
||||||
|
|
||||||
|
// 查询该申请单下所有 ServiceRequest
|
||||||
|
List<ServiceRequest> serviceRequests = iServiceRequestService.list(
|
||||||
|
new LambdaQueryWrapper<ServiceRequest>()
|
||||||
|
.eq(ServiceRequest::getPrescriptionNo, prescriptionNo));
|
||||||
|
if (serviceRequests == null || serviceRequests.isEmpty()) {
|
||||||
|
return R.fail("未找到关联的诊疗医嘱");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验:只有已签发(status=2)的申请单可撤回
|
||||||
|
boolean allActive = serviceRequests.stream()
|
||||||
|
.allMatch(sr -> RequestStatus.ACTIVE.getValue().equals(sr.getStatusEnum()));
|
||||||
|
if (!allActive) {
|
||||||
|
return R.fail("只有已签发状态的申请单可撤回");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将所有 ServiceRequest 状态改回待签发(DRAFT=0)
|
||||||
|
List<Long> serviceRequestIds = serviceRequests.stream()
|
||||||
|
.map(ServiceRequest::getId).collect(Collectors.toList());
|
||||||
|
iServiceRequestService.update(
|
||||||
|
new ServiceRequest().setStatusEnum(RequestStatus.DRAFT.getValue()),
|
||||||
|
new LambdaUpdateWrapper<ServiceRequest>()
|
||||||
|
.in(ServiceRequest::getId, serviceRequestIds));
|
||||||
|
|
||||||
|
log.info("检查申请单撤回成功,requestFormId={}, prescriptionNo={}", requestFormId, prescriptionNo);
|
||||||
|
return R.ok("撤回成功");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 申请单管理 controller
|
* 申请单管理 controller
|
||||||
@@ -82,14 +83,23 @@ public class RequestFormManageController {
|
|||||||
* 查询检查申请单
|
* 查询检查申请单
|
||||||
*
|
*
|
||||||
* @param encounterId 就诊id
|
* @param encounterId 就诊id
|
||||||
|
* @param startDate 开始日期(可选,格式:yyyy-MM-dd)
|
||||||
|
* @param endDate 结束日期(可选,格式:yyyy-MM-dd)
|
||||||
|
* @param status 单据状态(可选)
|
||||||
|
* @param keyword 关键字(可选,申请单号/检查项目名称模糊匹配)
|
||||||
* @return 检查申请单
|
* @return 检查申请单
|
||||||
*/
|
*/
|
||||||
@GetMapping(value = "/get-check")
|
@GetMapping(value = "/get-check")
|
||||||
public R<?> getCheckRequestForm(@RequestParam(required = false) Long encounterId) {
|
public R<?> getCheckRequestForm(
|
||||||
|
@RequestParam(required = false) Long encounterId,
|
||||||
|
@RequestParam(required = false) String startDate,
|
||||||
|
@RequestParam(required = false) String endDate,
|
||||||
|
@RequestParam(required = false) String status,
|
||||||
|
@RequestParam(required = false) String keyword) {
|
||||||
if (encounterId == null) {
|
if (encounterId == null) {
|
||||||
return R.fail("就诊ID不能为空");
|
return R.fail("就诊ID不能为空");
|
||||||
}
|
}
|
||||||
return R.ok(iRequestFormManageAppService.getRequestForm(encounterId, ActivityDefCategory.TEST.getCode()));
|
return R.ok(iRequestFormManageAppService.getRequestForm(encounterId, ActivityDefCategory.TEST.getCode(), startDate, endDate, status, keyword));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -176,4 +186,26 @@ public class RequestFormManageController {
|
|||||||
public R<IPage<RequestFormPageDto>> getRequestFormPage(@RequestBody RequestFormDto requestFormDto) {
|
public R<IPage<RequestFormPageDto>> getRequestFormPage(@RequestBody RequestFormDto requestFormDto) {
|
||||||
return R.ok(iRequestFormManageAppService.getRequestFormPage(requestFormDto));
|
return R.ok(iRequestFormManageAppService.getRequestFormPage(requestFormDto));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除申请单(仅待签发状态可删除)
|
||||||
|
*
|
||||||
|
* @param data 包含 requestFormId 的请求体
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@PostMapping(value = "/delete")
|
||||||
|
public R<?> deleteRequestForm(@RequestBody Map<String, Long> data) {
|
||||||
|
return iRequestFormManageAppService.deleteRequestForm(data.get("requestFormId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 撤回申请单(已签发状态撤回至待签发)
|
||||||
|
*
|
||||||
|
* @param data 包含 requestFormId 的请求体
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@PostMapping(value = "/withdraw")
|
||||||
|
public R<?> withdrawRequestForm(@RequestBody Map<String, Long> data) {
|
||||||
|
return iRequestFormManageAppService.withdrawRequestForm(data.get("requestFormId"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,4 +138,12 @@ public class CurrentDayEncounterTencentDto {
|
|||||||
*/
|
*/
|
||||||
private String englishName;
|
private String englishName;
|
||||||
|
|
||||||
|
/** 号源池ID(用于分诊队列 div_log 审计日志) */
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long poolId;
|
||||||
|
|
||||||
|
/** 号源槽位ID(用于分诊队列 div_log 审计日志) */
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long slotId;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,10 @@
|
|||||||
CASE
|
CASE
|
||||||
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.surgery_name
|
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.surgery_name
|
||||||
WHEN T1.context_enum = 6 AND T1.product_table = 'cli_surgery' THEN T9.surgery_name
|
WHEN T1.context_enum = 6 AND T1.product_table = 'cli_surgery' THEN T9.surgery_name
|
||||||
|
WHEN T1.context_enum = 6 AND T1.service_table = 'wor_service_request' THEN COALESCE(
|
||||||
|
wsr.content_json::json->>'surgeryName',
|
||||||
|
wsr.content_json::json->>'adviceName',
|
||||||
|
T9sr.surgery_name)
|
||||||
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
|
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
|
||||||
WHEN T1.context_enum = 6 THEN T2."name"
|
WHEN T1.context_enum = 6 THEN T2."name"
|
||||||
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
|
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
|
||||||
@@ -108,6 +112,7 @@
|
|||||||
CASE
|
CASE
|
||||||
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN NULL
|
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN NULL
|
||||||
WHEN T1.context_enum = 6 AND T1.product_table = 'cli_surgery' THEN NULL
|
WHEN T1.context_enum = 6 AND T1.product_table = 'cli_surgery' THEN NULL
|
||||||
|
WHEN T1.context_enum = 6 AND T1.service_table = 'wor_service_request' THEN NULL
|
||||||
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN NULL
|
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN NULL
|
||||||
WHEN T1.context_enum = 6 THEN T2.yb_no
|
WHEN T1.context_enum = 6 THEN T2.yb_no
|
||||||
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN NULL
|
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN NULL
|
||||||
@@ -118,6 +123,7 @@
|
|||||||
CASE
|
CASE
|
||||||
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.id
|
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.id
|
||||||
WHEN T1.context_enum = 6 AND T1.product_table = 'cli_surgery' THEN T9.id
|
WHEN T1.context_enum = 6 AND T1.product_table = 'cli_surgery' THEN T9.id
|
||||||
|
WHEN T1.context_enum = 6 AND T1.service_table = 'wor_service_request' THEN COALESCE(T9sr.id, wsr.activity_id)
|
||||||
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN 0
|
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN 0
|
||||||
WHEN T1.context_enum = 6 THEN T2.id
|
WHEN T1.context_enum = 6 THEN T2.id
|
||||||
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN 0
|
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN 0
|
||||||
@@ -159,6 +165,11 @@
|
|||||||
LEFT JOIN med_medication_request AS mmr ON mmr.id = T1.service_id AND mmr.delete_flag = '0'
|
LEFT JOIN med_medication_request AS mmr ON mmr.id = T1.service_id AND mmr.delete_flag = '0'
|
||||||
LEFT JOIN wor_device_request AS wdr ON wdr.id = T1.service_id AND wdr.delete_flag = '0'
|
LEFT JOIN wor_device_request AS wdr ON wdr.id = T1.service_id AND wdr.delete_flag = '0'
|
||||||
LEFT JOIN wor_service_request AS wsr ON wsr.id = T1.service_id AND wsr.delete_flag = '0'
|
LEFT JOIN wor_service_request AS wsr ON wsr.id = T1.service_id AND wsr.delete_flag = '0'
|
||||||
|
LEFT JOIN cli_surgery AS T9sr ON T1.context_enum = 6
|
||||||
|
AND T1.service_table = 'wor_service_request'
|
||||||
|
AND wsr.activity_id IS NOT NULL
|
||||||
|
AND wsr.activity_id = T9sr.id
|
||||||
|
AND T9sr.delete_flag = '0'
|
||||||
LEFT JOIN wor_service_request AS wsrp ON wsrp.id = wsr.parent_id AND wsrp.delete_flag = '0'
|
LEFT JOIN wor_service_request AS wsrp ON wsrp.id = wsr.parent_id AND wsrp.delete_flag = '0'
|
||||||
WHERE T1.encounter_id = #{encounterId}
|
WHERE T1.encounter_id = #{encounterId}
|
||||||
AND T1.status_enum IN (0
|
AND T1.status_enum IN (0
|
||||||
@@ -223,6 +234,10 @@
|
|||||||
CASE
|
CASE
|
||||||
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.surgery_name
|
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.surgery_name
|
||||||
WHEN T1.context_enum = 6 AND T1.product_table = 'cli_surgery' THEN T9.surgery_name
|
WHEN T1.context_enum = 6 AND T1.product_table = 'cli_surgery' THEN T9.surgery_name
|
||||||
|
WHEN T1.context_enum = 6 AND T1.service_table = 'wor_service_request' THEN COALESCE(
|
||||||
|
wsr.content_json::json->>'surgeryName',
|
||||||
|
wsr.content_json::json->>'adviceName',
|
||||||
|
T9sr.surgery_name)
|
||||||
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
|
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
|
||||||
WHEN T1.context_enum = 6 THEN T2."name"
|
WHEN T1.context_enum = 6 THEN T2."name"
|
||||||
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
|
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
|
||||||
@@ -234,6 +249,7 @@
|
|||||||
CASE
|
CASE
|
||||||
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN NULL
|
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN NULL
|
||||||
WHEN T1.context_enum = 6 AND T1.product_table = 'cli_surgery' THEN NULL
|
WHEN T1.context_enum = 6 AND T1.product_table = 'cli_surgery' THEN NULL
|
||||||
|
WHEN T1.context_enum = 6 AND T1.service_table = 'wor_service_request' THEN NULL
|
||||||
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN NULL
|
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN NULL
|
||||||
WHEN T1.context_enum = 6 THEN T2.yb_no
|
WHEN T1.context_enum = 6 THEN T2.yb_no
|
||||||
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN NULL
|
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN NULL
|
||||||
@@ -244,6 +260,7 @@
|
|||||||
CASE
|
CASE
|
||||||
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.id
|
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.id
|
||||||
WHEN T1.context_enum = 6 AND T1.product_table = 'cli_surgery' THEN T9.id
|
WHEN T1.context_enum = 6 AND T1.product_table = 'cli_surgery' THEN T9.id
|
||||||
|
WHEN T1.context_enum = 6 AND T1.service_table = 'wor_service_request' THEN COALESCE(T9sr.id, wsr.activity_id)
|
||||||
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN 0
|
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN 0
|
||||||
WHEN T1.context_enum = 6 THEN T2.id
|
WHEN T1.context_enum = 6 THEN T2.id
|
||||||
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN 0
|
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN 0
|
||||||
@@ -286,6 +303,11 @@
|
|||||||
LEFT JOIN med_medication_request AS mmr ON mmr.id = T1.service_id AND mmr.delete_flag = '0'
|
LEFT JOIN med_medication_request AS mmr ON mmr.id = T1.service_id AND mmr.delete_flag = '0'
|
||||||
LEFT JOIN wor_device_request AS wdr ON wdr.id = T1.service_id AND wdr.delete_flag = '0'
|
LEFT JOIN wor_device_request AS wdr ON wdr.id = T1.service_id AND wdr.delete_flag = '0'
|
||||||
LEFT JOIN wor_service_request AS wsr ON wsr.id = T1.service_id AND wsr.delete_flag = '0'
|
LEFT JOIN wor_service_request AS wsr ON wsr.id = T1.service_id AND wsr.delete_flag = '0'
|
||||||
|
LEFT JOIN cli_surgery AS T9sr ON T1.context_enum = 6
|
||||||
|
AND T1.service_table = 'wor_service_request'
|
||||||
|
AND wsr.activity_id IS NOT NULL
|
||||||
|
AND wsr.activity_id = T9sr.id
|
||||||
|
AND T9sr.delete_flag = '0'
|
||||||
WHERE T1.encounter_id = #{encounterId}
|
WHERE T1.encounter_id = #{encounterId}
|
||||||
AND T1.status_enum IN (0
|
AND T1.status_enum IN (0
|
||||||
, #{planned}
|
, #{planned}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
</if>
|
</if>
|
||||||
AND os.delete_flag = '0'
|
AND os.delete_flag = '0'
|
||||||
</where>
|
</where>
|
||||||
ORDER BY os.create_time DESC
|
ORDER BY os.create_time DESC, os.schedule_id DESC
|
||||||
</select>
|
</select>
|
||||||
<!-- 根据ID查询手术安排详情-->
|
<!-- 根据ID查询手术安排详情-->
|
||||||
<select id="getSurgeryScheduleDetail" resultType="com.openhis.web.clinicalmanage.dto.OpScheduleDto">
|
<select id="getSurgeryScheduleDetail" resultType="com.openhis.web.clinicalmanage.dto.OpScheduleDto">
|
||||||
@@ -89,15 +89,13 @@
|
|||||||
cs.apply_doctor_name AS apply_doctor_name,
|
cs.apply_doctor_name AS apply_doctor_name,
|
||||||
drf.create_time AS apply_time,
|
drf.create_time AS apply_time,
|
||||||
os.surgery_nature AS surgeryType,
|
os.surgery_nature AS surgeryType,
|
||||||
fc.contract_name AS feeType,
|
cs.incision_level AS "incisionLevel",
|
||||||
|
os.fee_type AS feeType,
|
||||||
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo
|
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo
|
||||||
FROM op_schedule os
|
FROM op_schedule os
|
||||||
LEFT JOIN adm_patient ap ON os.patient_id = ap.id
|
LEFT JOIN adm_patient ap ON os.patient_id = ap.id
|
||||||
INNER JOIN cli_surgery cs ON os.oper_code = cs.surgery_no AND cs.delete_flag = '0'
|
INNER JOIN cli_surgery cs ON os.oper_code = cs.surgery_no AND cs.delete_flag = '0'
|
||||||
LEFT JOIN adm_organization o ON cs.org_id = o.id
|
LEFT JOIN adm_organization o ON cs.org_id = o.id
|
||||||
LEFT JOIN adm_encounter ae ON ae.id = cs.encounter_id AND ae.delete_flag = '0'
|
|
||||||
LEFT JOIN adm_account aa ON aa.encounter_id = ae.id AND aa.delete_flag = '0'
|
|
||||||
LEFT JOIN fin_contract fc ON fc.bus_no = aa.contract_no AND fc.delete_flag = '0'
|
|
||||||
LEFT JOIN doc_request_form drf ON drf.prescription_no=cs.surgery_no
|
LEFT JOIN doc_request_form drf ON drf.prescription_no=cs.surgery_no
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT patient_id, identifier_no
|
SELECT patient_id, identifier_no
|
||||||
@@ -186,7 +184,7 @@
|
|||||||
<if test="dto.applyDeptId != null and dto.applyDeptId != ''"> AND cs.apply_dept_id = #{dto.applyDeptId}</if>
|
<if test="dto.applyDeptId != null and dto.applyDeptId != ''"> AND cs.apply_dept_id = #{dto.applyDeptId}</if>
|
||||||
<if test="dto.patientName != null and dto.patientName != ''"> AND ap.name LIKE CONCAT('%', #{dto.patientName}, '%')</if>
|
<if test="dto.patientName != null and dto.patientName != ''"> AND ap.name LIKE CONCAT('%', #{dto.patientName}, '%')</if>
|
||||||
</where>
|
</where>
|
||||||
ORDER BY os.create_time DESC
|
ORDER BY os.create_time DESC, os.schedule_id DESC
|
||||||
</select>
|
</select>
|
||||||
<!-- 查询时间段内该手术室是否被占用-->
|
<!-- 查询时间段内该手术室是否被占用-->
|
||||||
<select id="isScheduleConflict" resultType="java.lang.Boolean">
|
<select id="isScheduleConflict" resultType="java.lang.Boolean">
|
||||||
|
|||||||
@@ -239,7 +239,7 @@
|
|||||||
NULL AS activity_type_dictText,
|
NULL AS activity_type_dictText,
|
||||||
-- 前端"包装单位"列:显示使用单位(permitted_unit_code)
|
-- 前端"包装单位"列:显示使用单位(permitted_unit_code)
|
||||||
T1.permitted_unit_code AS unit_code,
|
T1.permitted_unit_code AS unit_code,
|
||||||
'' AS min_unit_code,
|
T1.permitted_unit_code AS min_unit_code,
|
||||||
'' AS volume,
|
'' AS volume,
|
||||||
'' AS method_code,
|
'' AS method_code,
|
||||||
'' AS rate_code,
|
'' AS rate_code,
|
||||||
@@ -539,7 +539,67 @@
|
|||||||
AND T1.refund_medicine_id IS NULL
|
AND T1.refund_medicine_id IS NULL
|
||||||
ORDER BY T1.status_enum,T1.sort_number)
|
ORDER BY T1.status_enum,T1.sort_number)
|
||||||
UNION ALL
|
UNION ALL
|
||||||
-- 🔧 新增:查询门诊术中计费生成的耗材数据(这些数据存在于 adm_charge_item 和 wor_device_request)
|
-- 🔧 Bug #444: 直接从计费项目表补充查询药品记录,用于覆盖med_medication_request有记录但generate_source_enum不匹配的场景
|
||||||
|
(SELECT 1 AS advice_type,
|
||||||
|
T1.service_id AS request_id,
|
||||||
|
T1.service_id || '-ci-med' AS unique_key,
|
||||||
|
'' AS prescription_no,
|
||||||
|
T1.enterer_id AS requester_id,
|
||||||
|
T1.entered_date AS request_time,
|
||||||
|
CASE WHEN T1.enterer_id = #{practitionerId} THEN '1' ELSE '0' END AS biz_request_flag,
|
||||||
|
T2.content_json AS content_json,
|
||||||
|
NULL AS skin_test_flag,
|
||||||
|
NULL AS inject_flag,
|
||||||
|
NULL AS group_id,
|
||||||
|
T3.NAME AS advice_name,
|
||||||
|
T4.total_volume AS volume,
|
||||||
|
T2.lot_number AS lot_number,
|
||||||
|
T1.quantity_value AS quantity,
|
||||||
|
T1.quantity_unit AS unit_code,
|
||||||
|
T1.status_enum AS status_enum,
|
||||||
|
'' AS method_code,
|
||||||
|
'' AS rate_code,
|
||||||
|
NULL AS dose,
|
||||||
|
'' AS dose_unit_code,
|
||||||
|
T1.id AS charge_item_id,
|
||||||
|
T1.unit_price AS unit_price,
|
||||||
|
T1.total_price AS total_price,
|
||||||
|
T1.status_enum AS charge_status,
|
||||||
|
NULL AS position_id,
|
||||||
|
'' AS position_name,
|
||||||
|
NULL AS dispense_per_duration,
|
||||||
|
T3.part_percent AS part_percent,
|
||||||
|
'' AS condition_definition_name,
|
||||||
|
T2.sort_number AS sort_number,
|
||||||
|
NULL AS based_on_id,
|
||||||
|
NULL AS category_enum,
|
||||||
|
T1.encounter_id AS encounter_id,
|
||||||
|
T1.patient_id AS patient_id,
|
||||||
|
'med_medication_definition' AS advice_table_name,
|
||||||
|
T3.ID AS advice_definition_id
|
||||||
|
FROM adm_charge_item AS T1
|
||||||
|
INNER JOIN med_medication_request AS T2 ON T2.ID = T1.service_id AND T2.delete_flag = '0'
|
||||||
|
LEFT JOIN med_medication_definition AS T3 ON T3.ID = T2.medication_id AND T3.delete_flag = '0'
|
||||||
|
LEFT JOIN med_medication AS T4 ON T4.medication_def_id = T3.ID AND T4.delete_flag = '0'
|
||||||
|
WHERE T1.delete_flag = '0'
|
||||||
|
AND T1.service_table = #{MED_MEDICATION_REQUEST}
|
||||||
|
<if test="historyFlag == '0'.toString()">
|
||||||
|
AND T1.encounter_id = #{encounterId}
|
||||||
|
</if>
|
||||||
|
<if test="historyFlag == '1'.toString()">
|
||||||
|
AND T1.patient_id = #{patientId} AND T1.encounter_id != #{encounterId}
|
||||||
|
</if>
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM med_medication_request T5
|
||||||
|
WHERE T5.ID = T1.service_id AND T5.delete_flag = '0'
|
||||||
|
<if test="generateSourceEnum != null">
|
||||||
|
AND T5.generate_source_enum = #{generateSourceEnum}
|
||||||
|
</if>
|
||||||
|
)
|
||||||
|
ORDER BY T1.entered_date)
|
||||||
|
UNION ALL
|
||||||
|
-- 🔧 查询仅存在于 adm_charge_item 的"孤儿"耗材数据(DeviceRequest 缺失或 generate_source_enum 未设置)
|
||||||
|
-- 正常 DeviceRequest(generate_source_enum 已赋值)由下方 Part 3 统一负责,此处不做重复覆盖避免 UNION ALL 重复行
|
||||||
(SELECT 2 AS advice_type,
|
(SELECT 2 AS advice_type,
|
||||||
CI.service_id AS request_id,
|
CI.service_id AS request_id,
|
||||||
CI.service_id || '-ci-dev' AS unique_key,
|
CI.service_id || '-ci-dev' AS unique_key,
|
||||||
@@ -583,6 +643,9 @@
|
|||||||
LEFT JOIN adm_location AS AL ON AL.id = DR.perform_location AND AL.delete_flag = '0'
|
LEFT JOIN adm_location AS AL ON AL.id = DR.perform_location AND AL.delete_flag = '0'
|
||||||
WHERE CI.delete_flag = '0'
|
WHERE CI.delete_flag = '0'
|
||||||
AND CI.service_table = 'wor_device_request'
|
AND CI.service_table = 'wor_device_request'
|
||||||
|
<if test="generateSourceEnum != null">
|
||||||
|
AND DR.generate_source_enum IS NULL <!-- 仅匹配孤儿记录,normal DeviceRequest 由 Part 3 负责,避免 UNION ALL 重复 -->
|
||||||
|
</if>
|
||||||
<if test="historyFlag == '0'.toString()">
|
<if test="historyFlag == '0'.toString()">
|
||||||
AND CI.encounter_id = #{encounterId}
|
AND CI.encounter_id = #{encounterId}
|
||||||
</if>
|
</if>
|
||||||
@@ -807,4 +870,54 @@
|
|||||||
LIMIT 1
|
LIMIT 1
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- 手术项目专用分页查询:仅查手术 + 定价,无库存/草稿库存/取药科室等无关逻辑 -->
|
||||||
|
<select id="getSurgeryPage" resultType="com.openhis.web.doctorstation.dto.SurgeryItemDto">
|
||||||
|
SELECT DISTINCT ON (t1.ID)
|
||||||
|
t1.ID AS advice_definition_id,
|
||||||
|
t1.NAME AS advice_name,
|
||||||
|
t1.org_id AS org_id,
|
||||||
|
t1.org_id AS position_id,
|
||||||
|
t2.ID AS charge_item_definition_id,
|
||||||
|
t2.price AS price,
|
||||||
|
t1.permitted_unit_code AS unit_code,
|
||||||
|
t1.permitted_unit_code AS unit_code_dict_text
|
||||||
|
FROM wor_activity_definition t1
|
||||||
|
LEFT JOIN adm_charge_item_definition t2
|
||||||
|
ON t2.instance_id = t1.ID
|
||||||
|
AND t2.delete_flag = '0'
|
||||||
|
AND t2.status_enum = #{statusEnum}
|
||||||
|
AND t2.instance_table = 'wor_activity_definition'
|
||||||
|
WHERE t1.delete_flag = '0'
|
||||||
|
AND (t1.category_code = '手术' OR t1.category_code = '24')
|
||||||
|
<if test="searchKey != null and searchKey != ''">
|
||||||
|
AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%')
|
||||||
|
</if>
|
||||||
|
ORDER BY t1.ID, t1.name ASC, t2.ID ASC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 检查项目专用分页查询:仅查检查(23) + 定价,无库存/草稿库存/取药科室等无关逻辑 -->
|
||||||
|
<select id="getExaminationPage" resultType="com.openhis.web.doctorstation.dto.SurgeryItemDto">
|
||||||
|
SELECT DISTINCT ON (t1.ID)
|
||||||
|
t1.ID AS advice_definition_id,
|
||||||
|
t1.NAME AS advice_name,
|
||||||
|
t1.org_id AS org_id,
|
||||||
|
t1.org_id AS position_id,
|
||||||
|
t2.ID AS charge_item_definition_id,
|
||||||
|
t2.price AS price,
|
||||||
|
t1.permitted_unit_code AS unit_code,
|
||||||
|
t1.permitted_unit_code AS unit_code_dict_text
|
||||||
|
FROM wor_activity_definition t1
|
||||||
|
LEFT JOIN adm_charge_item_definition t2
|
||||||
|
ON t2.instance_id = t1.ID
|
||||||
|
AND t2.delete_flag = '0'
|
||||||
|
AND t2.status_enum = #{statusEnum}
|
||||||
|
AND t2.instance_table = 'wor_activity_definition'
|
||||||
|
WHERE t1.delete_flag = '0'
|
||||||
|
AND t1.category_code = '23'
|
||||||
|
<if test="searchKey != null and searchKey != ''">
|
||||||
|
AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%')
|
||||||
|
</if>
|
||||||
|
ORDER BY t1.ID, t1.name ASC, t2.ID ASC
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -134,7 +134,11 @@
|
|||||||
T2.yb_no,
|
T2.yb_no,
|
||||||
T1.onset_date AS onsetDate,
|
T1.onset_date AS onsetDate,
|
||||||
T1.diagnosis_time AS diagnosisTime,
|
T1.diagnosis_time AS diagnosisTime,
|
||||||
T1.doctor AS diagnosisDoctor
|
T1.doctor AS diagnosisDoctor,
|
||||||
|
CASE WHEN EXISTS (
|
||||||
|
SELECT 1 FROM infectious_card T4
|
||||||
|
WHERE T4.diag_id = T2.id AND T4.delete_flag = '0' AND T4.status >= 1
|
||||||
|
) THEN 1 ELSE 0 END AS hasInfectiousReport
|
||||||
FROM adm_encounter_diagnosis AS T1
|
FROM adm_encounter_diagnosis AS T1
|
||||||
LEFT JOIN cli_condition AS T2 ON T2.ID = T1.condition_id
|
LEFT JOIN cli_condition AS T2 ON T2.ID = T1.condition_id
|
||||||
AND T2.delete_flag = '0' AND T2.tcm_flag = 0
|
AND T2.delete_flag = '0' AND T2.tcm_flag = 0
|
||||||
|
|||||||
@@ -264,7 +264,6 @@
|
|||||||
WHERE ael.status_enum = #{active}
|
WHERE ael.status_enum = #{active}
|
||||||
AND ael.delete_flag = '0'
|
AND ael.delete_flag = '0'
|
||||||
AND ael.form_enum = #{bed}
|
AND ael.form_enum = #{bed}
|
||||||
LIMIT 1
|
|
||||||
) AS bed ON bed.encounter_id = ae.id
|
) AS bed ON bed.encounter_id = ae.id
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT ael.encounter_id,
|
SELECT ael.encounter_id,
|
||||||
@@ -275,7 +274,6 @@
|
|||||||
WHERE ael.status_enum = #{active}
|
WHERE ael.status_enum = #{active}
|
||||||
AND ael.delete_flag = '0'
|
AND ael.delete_flag = '0'
|
||||||
AND ael.form_enum = #{house}
|
AND ael.form_enum = #{house}
|
||||||
LIMIT 1
|
|
||||||
) AS house ON house.encounter_id = ae.id
|
) AS house ON house.encounter_id = ae.id
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT ael.encounter_id,
|
SELECT ael.encounter_id,
|
||||||
@@ -286,7 +284,6 @@
|
|||||||
WHERE ael.status_enum = #{active}
|
WHERE ael.status_enum = #{active}
|
||||||
AND ael.delete_flag = '0'
|
AND ael.delete_flag = '0'
|
||||||
AND ael.form_enum = #{ward}
|
AND ael.form_enum = #{ward}
|
||||||
LIMIT 1
|
|
||||||
) AS ward ON ward.encounter_id = ae.id
|
) AS ward ON ward.encounter_id = ae.id
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT aep.encounter_id,
|
SELECT aep.encounter_id,
|
||||||
|
|||||||
@@ -155,7 +155,7 @@
|
|||||||
ii.performer_check_id,
|
ii.performer_check_id,
|
||||||
ii.category_code,
|
ii.category_code,
|
||||||
ii.dispense_status
|
ii.dispense_status
|
||||||
FROM (( SELECT T1.encounter_id,
|
FROM (( SELECT DISTINCT T1.encounter_id,
|
||||||
T1.tenant_id,
|
T1.tenant_id,
|
||||||
#{medMedicationRequest} AS advice_table,
|
#{medMedicationRequest} AS advice_table,
|
||||||
T1.id AS request_id,
|
T1.id AS request_id,
|
||||||
@@ -280,9 +280,13 @@
|
|||||||
aa.balance_amount
|
aa.balance_amount
|
||||||
) AS personal_account
|
) AS personal_account
|
||||||
ON personal_account.encounter_id = ae.id
|
ON personal_account.encounter_id = ae.id
|
||||||
LEFT JOIN med_medication_dispense mmd
|
LEFT JOIN LATERAL (
|
||||||
ON mmd.med_req_id = T1.id
|
SELECT status_enum
|
||||||
AND mmd.delete_flag = '0'
|
FROM med_medication_dispense
|
||||||
|
WHERE med_req_id = T1.id AND delete_flag = '0'
|
||||||
|
ORDER BY create_time DESC
|
||||||
|
LIMIT 1
|
||||||
|
) mmd ON true
|
||||||
WHERE T1.delete_flag = '0'
|
WHERE T1.delete_flag = '0'
|
||||||
AND T1.refund_medicine_id IS NULL
|
AND T1.refund_medicine_id IS NULL
|
||||||
AND T1.generate_source_enum = #{doctorPrescription}
|
AND T1.generate_source_enum = #{doctorPrescription}
|
||||||
@@ -293,7 +297,7 @@
|
|||||||
T1.sort_number,
|
T1.sort_number,
|
||||||
T1.group_id )
|
T1.group_id )
|
||||||
UNION
|
UNION
|
||||||
( SELECT T1.encounter_id,
|
( SELECT DISTINCT T1.encounter_id,
|
||||||
T1.tenant_id,
|
T1.tenant_id,
|
||||||
#{worServiceRequest} AS advice_table,
|
#{worServiceRequest} AS advice_table,
|
||||||
T1.id AS request_id,
|
T1.id AS request_id,
|
||||||
@@ -301,28 +305,28 @@
|
|||||||
T1.occurrence_end_time AS end_time,
|
T1.occurrence_end_time AS end_time,
|
||||||
T1.requester_id AS requester_id,
|
T1.requester_id AS requester_id,
|
||||||
T1.create_time AS request_time,
|
T1.create_time AS request_time,
|
||||||
NULL AS skin_test_flag,
|
NULL::integer AS skin_test_flag,
|
||||||
NULL AS inject_flag,
|
NULL::integer AS inject_flag,
|
||||||
NULL AS group_id,
|
NULL::bigint AS group_id,
|
||||||
T1.performer_check_id,
|
T1.performer_check_id,
|
||||||
T2."name" AS advice_name,
|
T2."name" AS advice_name,
|
||||||
T2.id AS item_id,
|
T2.id AS item_id,
|
||||||
NULL AS volume,
|
NULL::varchar AS volume,
|
||||||
NULL AS lot_number,
|
NULL::varchar AS lot_number,
|
||||||
T1.quantity AS quantity,
|
T1.quantity AS quantity,
|
||||||
T1.unit_code AS unit_code,
|
T1.unit_code AS unit_code,
|
||||||
T1.status_enum AS request_status,
|
T1.status_enum AS request_status,
|
||||||
NULL AS method_code,
|
NULL::varchar AS method_code,
|
||||||
NULL AS rate_code,
|
NULL::varchar AS rate_code,
|
||||||
NULL AS dose,
|
NULL::numeric AS dose,
|
||||||
NULL AS dose_unit_code,
|
NULL::varchar AS dose_unit_code,
|
||||||
ao1.id AS position_id,
|
ao1.id AS position_id,
|
||||||
ao1."name" AS position_name,
|
ao1."name" AS position_name,
|
||||||
NULL AS dispense_per_duration,
|
NULL::integer AS dispense_per_duration,
|
||||||
1 AS part_percent,
|
1::numeric AS part_percent,
|
||||||
ccd."name" AS condition_definition_name,
|
ccd."name" AS condition_definition_name,
|
||||||
T1.therapy_enum AS therapy_enum,
|
T1.therapy_enum AS therapy_enum,
|
||||||
NULL AS sort_number,
|
NULL::integer AS sort_number,
|
||||||
T1.quantity AS execute_num,
|
T1.quantity AS execute_num,
|
||||||
af.day_times,
|
af.day_times,
|
||||||
ae.bus_no,
|
ae.bus_no,
|
||||||
@@ -337,7 +341,7 @@
|
|||||||
personal_account.balance_amount,
|
personal_account.balance_amount,
|
||||||
personal_account.id AS account_id,
|
personal_account.id AS account_id,
|
||||||
T2.category_code,
|
T2.category_code,
|
||||||
NULL AS dispense_status
|
NULL::integer AS dispense_status
|
||||||
FROM wor_service_request AS T1
|
FROM wor_service_request AS T1
|
||||||
LEFT JOIN wor_activity_definition AS T2
|
LEFT JOIN wor_activity_definition AS T2
|
||||||
ON T2.id = T1.activity_id
|
ON T2.id = T1.activity_id
|
||||||
|
|||||||
@@ -44,7 +44,9 @@
|
|||||||
sdd.dict_label AS unit_code_name,
|
sdd.dict_label AS unit_code_name,
|
||||||
togpd.dose AS dose,
|
togpd.dose AS dose,
|
||||||
togpd.rate_code AS rate_code,
|
togpd.rate_code AS rate_code,
|
||||||
|
rate_d.dict_label AS rate_code_dictText,
|
||||||
togpd.method_code AS method_code,
|
togpd.method_code AS method_code,
|
||||||
|
method_d.dict_label AS method_code_dictText,
|
||||||
togpd.dose_quantity AS dose_quantity,
|
togpd.dose_quantity AS dose_quantity,
|
||||||
togpd.group_id,
|
togpd.group_id,
|
||||||
togpd.group_order AS group_order,
|
togpd.group_order AS group_order,
|
||||||
@@ -67,8 +69,9 @@
|
|||||||
AND togpd.order_definition_id = adm.ID
|
AND togpd.order_definition_id = adm.ID
|
||||||
LEFT JOIN wor_activity_definition AS wor ON togpd.order_definition_table = 'wor_activity_definition'
|
LEFT JOIN wor_activity_definition AS wor ON togpd.order_definition_table = 'wor_activity_definition'
|
||||||
AND togpd.order_definition_id = wor.ID
|
AND togpd.order_definition_id = wor.ID
|
||||||
LEFT JOIN sys_dict_data AS sdd ON sdd.dict_value = togpd.unit_code AND sdd.dict_type = 'unit_code' AND
|
LEFT JOIN sys_dict_data AS sdd ON sdd.dict_value = togpd.unit_code AND sdd.dict_type = 'unit_code' AND sdd.status = '0'
|
||||||
sdd.status = '0'
|
LEFT JOIN sys_dict_data AS rate_d ON rate_d.dict_value = togpd.rate_code AND rate_d.dict_type = 'rate_code' AND rate_d.status = '0'
|
||||||
|
LEFT JOIN sys_dict_data AS method_d ON method_d.dict_value = togpd.method_code AND method_d.dict_type = 'method_code' AND method_d.status = '0'
|
||||||
WHERE togpd.delete_flag = '0'
|
WHERE togpd.delete_flag = '0'
|
||||||
<if test="groupPackageIds != null and !groupPackageIds.isEmpty()">
|
<if test="groupPackageIds != null and !groupPackageIds.isEmpty()">
|
||||||
AND togpd.group_package_id IN
|
AND togpd.group_package_id IN
|
||||||
|
|||||||
@@ -288,7 +288,7 @@
|
|||||||
AND T1.refund_device_id IS NULL
|
AND T1.refund_device_id IS NULL
|
||||||
ORDER BY T1.status_enum)
|
ORDER BY T1.status_enum)
|
||||||
UNION ALL
|
UNION ALL
|
||||||
(SELECT CASE WHEN T1.category_enum = 4 THEN 6 ELSE 3 END AS advice_type,
|
(SELECT CASE WHEN T1.category_enum IN (4, 24) THEN 6 ELSE 3 END AS advice_type,
|
||||||
T1.id AS request_id,
|
T1.id AS request_id,
|
||||||
T1.id || '-3' AS unique_key,
|
T1.id || '-3' AS unique_key,
|
||||||
T1.requester_id AS requester_id,
|
T1.requester_id AS requester_id,
|
||||||
|
|||||||
@@ -8,21 +8,22 @@
|
|||||||
SELECT drf.id AS request_form_id,
|
SELECT drf.id AS request_form_id,
|
||||||
drf.encounter_id,
|
drf.encounter_id,
|
||||||
drf.prescription_no,
|
drf.prescription_no,
|
||||||
drf.NAME,
|
COALESCE(
|
||||||
drf.desc_json,
|
(SELECT STRING_AGG(DISTINCT wad.name, '、')
|
||||||
|
FROM wor_service_request wsr2
|
||||||
|
LEFT JOIN wor_activity_definition wad ON wad.id = wsr2.activity_id AND wad.delete_flag = '0'
|
||||||
|
WHERE wsr2.prescription_no = drf.prescription_no AND wsr2.delete_flag = '0'),
|
||||||
|
drf.name
|
||||||
|
) AS name,
|
||||||
|
CASE
|
||||||
|
WHEN drf.desc_json::jsonb ->> 'targetDepartment' = '' AND MIN(wsr.org_id) IS NOT NULL THEN
|
||||||
|
(drf.desc_json::jsonb || jsonb_build_object('targetDepartment', MIN(wsr.org_id)::text))::text
|
||||||
|
ELSE drf.desc_json
|
||||||
|
END AS desc_json,
|
||||||
drf.requester_id,
|
drf.requester_id,
|
||||||
drf.create_time,
|
drf.create_time,
|
||||||
ap.NAME AS patient_name,
|
ap.NAME AS patient_name,
|
||||||
CASE MIN(wsr.status_enum)
|
drf.status
|
||||||
WHEN 1 THEN 0
|
|
||||||
WHEN 2 THEN 1
|
|
||||||
WHEN 3 THEN 4
|
|
||||||
WHEN 4 THEN 4
|
|
||||||
WHEN 5 THEN 5
|
|
||||||
WHEN 6 THEN 5
|
|
||||||
WHEN 7 THEN 5
|
|
||||||
ELSE NULL
|
|
||||||
END AS status
|
|
||||||
FROM doc_request_form AS drf
|
FROM doc_request_form AS drf
|
||||||
LEFT JOIN adm_encounter AS ae ON ae.ID = drf.encounter_id
|
LEFT JOIN adm_encounter AS ae ON ae.ID = drf.encounter_id
|
||||||
AND ae.delete_flag = '0'
|
AND ae.delete_flag = '0'
|
||||||
@@ -40,16 +41,7 @@
|
|||||||
AND drf.create_time <= (#{endDate}::date + INTERVAL '1 day' - INTERVAL '1 second')
|
AND drf.create_time <= (#{endDate}::date + INTERVAL '1 day' - INTERVAL '1 second')
|
||||||
</if>
|
</if>
|
||||||
<if test="status != null and status != ''">
|
<if test="status != null and status != ''">
|
||||||
AND CASE MIN(wsr.status_enum)
|
AND drf.status = #{status}::integer
|
||||||
WHEN 1 THEN 0
|
|
||||||
WHEN 2 THEN 1
|
|
||||||
WHEN 3 THEN 4
|
|
||||||
WHEN 4 THEN 4
|
|
||||||
WHEN 5 THEN 5
|
|
||||||
WHEN 6 THEN 5
|
|
||||||
WHEN 7 THEN 5
|
|
||||||
ELSE NULL
|
|
||||||
END = #{status}::integer
|
|
||||||
</if>
|
</if>
|
||||||
<if test="keyword != null and keyword != ''">
|
<if test="keyword != null and keyword != ''">
|
||||||
AND (drf.prescription_no ILIKE '%' || #{keyword} || '%'
|
AND (drf.prescription_no ILIKE '%' || #{keyword} || '%'
|
||||||
@@ -65,11 +57,12 @@
|
|||||||
))
|
))
|
||||||
</if>
|
</if>
|
||||||
GROUP BY drf.id, drf.encounter_id, drf.prescription_no, drf.name, drf.desc_json,
|
GROUP BY drf.id, drf.encounter_id, drf.prescription_no, drf.name, drf.desc_json,
|
||||||
drf.requester_id, drf.create_time, ap.name
|
drf.requester_id, drf.create_time, ap.name, drf.status
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="getRequestFormDetail" resultType="com.openhis.web.regdoctorstation.dto.RequestFormDetailQueryDto">
|
<select id="getRequestFormDetail" resultType="com.openhis.web.regdoctorstation.dto.RequestFormDetailQueryDto">
|
||||||
SELECT wsr.quantity,
|
SELECT wsr.activity_id AS activity_id,
|
||||||
|
wsr.quantity,
|
||||||
wsr.unit_code,
|
wsr.unit_code,
|
||||||
COALESCE(wad.NAME, wsr.content_json::jsonb->>'surgeryName') AS advice_name,
|
COALESCE(wad.NAME, wsr.content_json::jsonb->>'surgeryName') AS advice_name,
|
||||||
aci.total_price
|
aci.total_price
|
||||||
@@ -141,9 +134,9 @@
|
|||||||
fc.contract_name AS fee_type,
|
fc.contract_name AS fee_type,
|
||||||
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifier_no
|
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifier_no
|
||||||
FROM doc_request_form drf
|
FROM doc_request_form drf
|
||||||
LEFT JOIN cli_surgery cs ON cs.surgery_no = drf.prescription_no AND cs.delete_flag = '0'
|
INNER JOIN cli_surgery cs ON cs.surgery_no = drf.prescription_no AND cs.delete_flag = '0'
|
||||||
LEFT JOIN adm_patient ap ON ap.id = cs.patient_id AND ap.delete_flag = '0'
|
INNER JOIN adm_patient ap ON ap.id = cs.patient_id AND ap.delete_flag = '0'
|
||||||
LEFT JOIN adm_encounter ae ON ae.id = cs.encounter_id AND ae.delete_flag = '0'
|
INNER JOIN adm_encounter ae ON ae.id = cs.encounter_id AND ae.delete_flag = '0'
|
||||||
LEFT JOIN adm_account aa ON aa.encounter_id = ae.id AND aa.delete_flag = '0'
|
LEFT JOIN adm_account aa ON aa.encounter_id = ae.id AND aa.delete_flag = '0'
|
||||||
LEFT JOIN fin_contract fc ON fc.bus_no = aa.contract_no AND fc.delete_flag = '0'
|
LEFT JOIN fin_contract fc ON fc.bus_no = aa.contract_no AND fc.delete_flag = '0'
|
||||||
LEFT JOIN op_schedule os ON os.apply_id = drf.id AND os.delete_flag = '0'
|
LEFT JOIN op_schedule os ON os.apply_id = drf.id AND os.delete_flag = '0'
|
||||||
@@ -162,7 +155,7 @@
|
|||||||
AND drf.prescription_no LIKE CONCAT('%', #{requestFormDto.surgeryNo}, '%')
|
AND drf.prescription_no LIKE CONCAT('%', #{requestFormDto.surgeryNo}, '%')
|
||||||
</if>
|
</if>
|
||||||
<if test="requestFormDto.typeCode != null and requestFormDto.typeCode != ''">
|
<if test="requestFormDto.typeCode != null and requestFormDto.typeCode != ''">
|
||||||
AND drf.type_code = #{requestFormDto.typeCode}
|
AND drf.type_code IN (#{requestFormDto.typeCode}, 'SURGERY')
|
||||||
</if>
|
</if>
|
||||||
<if test="requestFormDto.applyTimeStart != null">
|
<if test="requestFormDto.applyTimeStart != null">
|
||||||
AND drf.create_time >= #{requestFormDto.applyTimeStart}
|
AND drf.create_time >= #{requestFormDto.applyTimeStart}
|
||||||
@@ -178,6 +171,8 @@
|
|||||||
</if>
|
</if>
|
||||||
AND drf.delete_flag = '0'
|
AND drf.delete_flag = '0'
|
||||||
AND os.schedule_id IS NULL
|
AND os.schedule_id IS NULL
|
||||||
|
<!-- 已取消(4)、已完成(3)的手术申请不参与门诊手术安排查找 -->
|
||||||
|
AND cs.status_enum NOT IN (3, 4)
|
||||||
</where>
|
</where>
|
||||||
ORDER BY drf.create_time DESC
|
ORDER BY drf.create_time DESC
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -27,7 +27,9 @@
|
|||||||
T9.payment_id,
|
T9.payment_id,
|
||||||
T9.picture_url,
|
T9.picture_url,
|
||||||
T9.birth_date,
|
T9.birth_date,
|
||||||
t9.english_name
|
t9.english_name,
|
||||||
|
t9.slot_id,
|
||||||
|
t9.pool_id
|
||||||
from (
|
from (
|
||||||
SELECT T1.tenant_id AS tenant_id,
|
SELECT T1.tenant_id AS tenant_id,
|
||||||
T1.id AS encounter_id,
|
T1.id AS encounter_id,
|
||||||
@@ -51,7 +53,9 @@
|
|||||||
T13.id AS payment_id,
|
T13.id AS payment_id,
|
||||||
ai.picture_url AS picture_url,
|
ai.picture_url AS picture_url,
|
||||||
T8.birth_date AS birth_date,
|
T8.birth_date AS birth_date,
|
||||||
tx.staff_english_name AS english_name
|
tx.staff_english_name AS english_name,
|
||||||
|
om_slot.slot_id AS slot_id,
|
||||||
|
om_slot.pool_id AS pool_id
|
||||||
FROM adm_encounter AS T1
|
FROM adm_encounter AS T1
|
||||||
LEFT JOIN adm_organization AS T2 ON T1.organization_id = T2.ID AND T2.delete_flag = '0'
|
LEFT JOIN adm_organization AS T2 ON T1.organization_id = T2.ID AND T2.delete_flag = '0'
|
||||||
LEFT JOIN adm_healthcare_service AS T3 ON T1.service_type_id = T3.ID AND T3.delete_flag = '0'
|
LEFT JOIN adm_healthcare_service AS T3 ON T1.service_type_id = T3.ID AND T3.delete_flag = '0'
|
||||||
@@ -91,6 +95,8 @@
|
|||||||
AND T13.status_enum = ${paymentStatus}
|
AND T13.status_enum = ${paymentStatus}
|
||||||
LEFT JOIN adm_invoice AS ai
|
LEFT JOIN adm_invoice AS ai
|
||||||
ON ai.reconciliation_id = T13.id AND ai.delete_flag = '0'
|
ON ai.reconciliation_id = T13.id AND ai.delete_flag = '0'
|
||||||
|
LEFT JOIN order_main AS om ON T1.order_id = om.id AND om.delete_flag = '0'
|
||||||
|
LEFT JOIN adm_schedule_slot AS om_slot ON om.slot_id = om_slot.id
|
||||||
WHERE T1.delete_flag = '0'
|
WHERE T1.delete_flag = '0'
|
||||||
AND T1.class_enum = #{classEnum}
|
AND T1.class_enum = #{classEnum}
|
||||||
AND T10.context_enum = #{register}
|
AND T10.context_enum = #{register}
|
||||||
|
|||||||
@@ -39,7 +39,12 @@ public enum GenerateSource implements HisEnumInterface {
|
|||||||
/**
|
/**
|
||||||
* 自动滚费
|
* 自动滚费
|
||||||
*/
|
*/
|
||||||
AUTO_ROLL_FEES(5, "5", "自动滚费");
|
AUTO_ROLL_FEES(5, "5", "自动滚费"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手术计费
|
||||||
|
*/
|
||||||
|
SURGERY_BILLING(6, "6", "手术计费");
|
||||||
|
|
||||||
private final Integer value;
|
private final Integer value;
|
||||||
private final String code;
|
private final String code;
|
||||||
|
|||||||
@@ -49,6 +49,11 @@ public enum RequestStatus implements HisEnumInterface {
|
|||||||
*/
|
*/
|
||||||
ENDED(7, "ended", "不执行"),
|
ENDED(7, "ended", "不执行"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已出报告
|
||||||
|
*/
|
||||||
|
COMPLETED_REPORT(8, "completed_report", "已出报告"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 未知
|
* 未知
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -193,6 +193,9 @@ public class OpSchedule extends HisBaseEntity {
|
|||||||
/** 外请专家姓名 */
|
/** 外请专家姓名 */
|
||||||
private String externalExpertName;
|
private String externalExpertName;
|
||||||
|
|
||||||
|
/** 费用类别 */
|
||||||
|
private String feeType;
|
||||||
|
|
||||||
/** 备注信息 */
|
/** 备注信息 */
|
||||||
private String remark;
|
private String remark;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import useDictStore from '@/store/modules/dict';
|
||||||
|
|
||||||
// 日期格式化
|
// 日期格式化
|
||||||
export function parseTime(time, pattern) {
|
export function parseTime(time, pattern) {
|
||||||
if (arguments.length === 0 || !time) {
|
if (arguments.length === 0 || !time) {
|
||||||
@@ -275,30 +277,13 @@ export function blobValidate(data) {
|
|||||||
|
|
||||||
// 按照频次天数计算总数量
|
// 按照频次天数计算总数量
|
||||||
export function calculateQuantityByDays(frequency, days) {
|
export function calculateQuantityByDays(frequency, days) {
|
||||||
// const dict = useDict('rate_code').rate_code.value
|
const dicts = useDictStore().getDict('rate_code');
|
||||||
// const rate = dict.find(item => item.value === frequency).remark
|
if (!dicts) return;
|
||||||
// if(rate){
|
const dict = dicts.find(item => item.value === frequency);
|
||||||
// return Math.floor(Number(rate) * days)
|
if (!dict?.remark) return;
|
||||||
// } else {
|
const rate = Number(dict.remark);
|
||||||
// return undefined
|
if (isNaN(rate) || !rate) return;
|
||||||
// }
|
const quantity = rate * days;
|
||||||
const frequencyMap = {
|
|
||||||
ST: 1,
|
|
||||||
QD: 1, // 每日一次
|
|
||||||
BID: 2, // 每日两次
|
|
||||||
TID: 3, // 每日三次
|
|
||||||
QID: 4, // 每日四次
|
|
||||||
QN: 1, // 每晚一次
|
|
||||||
QOD: 1 / 2, // 每隔一日一次
|
|
||||||
QW: 1 / 7, // 每周一次
|
|
||||||
BIW: 2 / 7, // 每周两次
|
|
||||||
TIW: 3 / 7, // 每周三次
|
|
||||||
QOW: 1 / 14, // 隔周一次
|
|
||||||
};
|
|
||||||
if (!frequencyMap[frequency]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const quantity = frequencyMap[frequency] * days;
|
|
||||||
return quantity < 1 ? 1 : Math.ceil(quantity);
|
return quantity < 1 ? 1 : Math.ceil(quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -178,22 +178,25 @@ service.interceptors.request.use(config => {
|
|||||||
}
|
}
|
||||||
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
|
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
|
||||||
} else if (code === 500) {
|
} else if (code === 500) {
|
||||||
// 检查是否需要跳过错误提示
|
// 检查是否需要跳过错误提示(静默请求:返回响应让.then()处理)
|
||||||
if (!res.config?.skipErrorMsg) {
|
if (res.config?.skipErrorMsg) {
|
||||||
ElMessage({ message: msg, type: 'error' })
|
return Promise.resolve(res.data)
|
||||||
}
|
}
|
||||||
|
ElMessage({ message: msg, type: 'error' })
|
||||||
return Promise.reject(new Error(msg))
|
return Promise.reject(new Error(msg))
|
||||||
} else if (code === 601) {
|
} else if (code === 601) {
|
||||||
// 检查是否需要跳过错误提示
|
// 检查是否需要跳过错误提示(静默请求:返回响应让.then()处理)
|
||||||
if (!res.config?.skipErrorMsg) {
|
if (res.config?.skipErrorMsg) {
|
||||||
ElMessage({ message: msg, type: 'warning' })
|
return Promise.resolve(res.data)
|
||||||
}
|
}
|
||||||
|
ElMessage({ message: msg, type: 'warning' })
|
||||||
return Promise.reject(new Error(msg))
|
return Promise.reject(new Error(msg))
|
||||||
} else if (code !== 200) {
|
} else if (code !== 200) {
|
||||||
// 检查是否需要跳过错误提示
|
// 检查是否需要跳过错误提示(静默请求:返回响应让.then()处理)
|
||||||
if (!res.config?.skipErrorMsg) {
|
if (res.config?.skipErrorMsg) {
|
||||||
ElNotification.error({ title: msg })
|
return Promise.resolve(res.data)
|
||||||
}
|
}
|
||||||
|
ElNotification.error({ title: msg })
|
||||||
return Promise.reject('error')
|
return Promise.reject('error')
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve(res.data)
|
return Promise.resolve(res.data)
|
||||||
|
|||||||
@@ -226,8 +226,14 @@ function getList() {
|
|||||||
getDiagnosisTreatmentList(queryParams.value).then((res) => {
|
getDiagnosisTreatmentList(queryParams.value).then((res) => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
catagoryList.value = res.data.records.map(record => {
|
catagoryList.value = res.data.records.map(record => {
|
||||||
// 为每一行初始化 filteredOptions,确保显示框能正确显示项目名称
|
|
||||||
const filteredOptions = allImplementDepartmentList.value.slice(0, 100);
|
const filteredOptions = allImplementDepartmentList.value.slice(0, 100);
|
||||||
|
// 确保后端返回的项目名称选项存在于 filteredOptions 中,避免 el-select 因找不到选项而回显为 ID
|
||||||
|
if (record.activityDefinitionId && !filteredOptions.some(o => o.value === record.activityDefinitionId)) {
|
||||||
|
filteredOptions.push({
|
||||||
|
value: record.activityDefinitionId,
|
||||||
|
label: record.activityDefinitionId_dictText || record.activityDefinitionId
|
||||||
|
});
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...record,
|
...record,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|||||||
@@ -473,15 +473,12 @@ function calculateTotalPrice() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
totalPrice.value = sum.toFixed(2);
|
totalPrice.value = sum.toFixed(2);
|
||||||
// Bug #464: 零售价与诊疗子项合计总价实时同步
|
// Bug #464: 零售价与诊疗子项合计总价实时同步,直接赋值不使用nextTick避免多调用方竞争
|
||||||
const hasValidItem = treatmentItems.value.some(
|
const hasValidItem = treatmentItems.value.some(
|
||||||
(item) => item.adviceDefinitionId && item.adviceDefinitionId !== ''
|
(item) => item.adviceDefinitionId && item.adviceDefinitionId !== ''
|
||||||
);
|
);
|
||||||
if (hasValidItem) {
|
if (hasValidItem) {
|
||||||
// 使用 nextTick 确保总价更新后零售价才更新,避免 Vue 响应式时序问题
|
|
||||||
nextTick(() => {
|
|
||||||
form.value.retailPrice = parseFloat(totalPrice.value) || 0;
|
form.value.retailPrice = parseFloat(totalPrice.value) || 0;
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
form.value.retailPrice = undefined;
|
form.value.retailPrice = undefined;
|
||||||
}
|
}
|
||||||
@@ -763,10 +760,7 @@ function selectRow(row, index) {
|
|||||||
treatmentItems.value[index].adviceDefinitionId = row.id;
|
treatmentItems.value[index].adviceDefinitionId = row.id;
|
||||||
treatmentItems.value[index].retailPrice = row.retailPrice || 0;
|
treatmentItems.value[index].retailPrice = row.retailPrice || 0;
|
||||||
medicineSearchKey.value = '';
|
medicineSearchKey.value = '';
|
||||||
// 使用 nextTick 确保 DOM 更新后再计算总价
|
|
||||||
nextTick(() => {
|
|
||||||
calculateTotalPrice();
|
calculateTotalPrice();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空诊疗子项
|
// 清空诊疗子项
|
||||||
|
|||||||
@@ -122,15 +122,37 @@ function getList() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
queryParams.value.organizationId = props.patientInfo.orgId;
|
// 🔧 Bug #448 修复:显式构建请求参数,确保 adviceType 正确传递
|
||||||
console.log('[adviceBaseList] getList() 请求参数:', JSON.stringify(queryParams.value));
|
// 不直接使用 queryParams.value,避免 undefined 值被发送到后端导致过滤失效
|
||||||
|
const requestParams = {
|
||||||
|
pageSize: queryParams.value.pageSize,
|
||||||
|
pageNum: queryParams.value.pageNum,
|
||||||
|
organizationId: props.patientInfo.orgId,
|
||||||
|
};
|
||||||
|
|
||||||
getAdviceBaseInfo(queryParams.value).then((res) => {
|
// 只在 adviceType 有值时添加(0 是无效值,undefined/null 会导致后端查询所有类型)
|
||||||
|
if (queryParams.value.adviceType != null && queryParams.value.adviceType !== 0) {
|
||||||
|
requestParams.adviceType = queryParams.value.adviceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只在 categoryCode 有值时添加
|
||||||
|
if (queryParams.value.categoryCode) {
|
||||||
|
requestParams.categoryCode = queryParams.value.categoryCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只在 searchKey 有值时添加
|
||||||
|
if (queryParams.value.searchKey) {
|
||||||
|
requestParams.searchKey = queryParams.value.searchKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[adviceBaseList] getList() 请求参数:', JSON.stringify(requestParams));
|
||||||
|
|
||||||
|
getAdviceBaseInfo(requestParams).then((res) => {
|
||||||
console.log('[adviceBaseList] getList() 响应数据:', {
|
console.log('[adviceBaseList] getList() 响应数据:', {
|
||||||
total: res.data?.total,
|
total: res.data?.total,
|
||||||
recordsCount: res.data?.records?.length || 0,
|
recordsCount: res.data?.records?.length || 0,
|
||||||
firstRecord: res.data?.records?.[0]?.adviceName || '无数据',
|
firstRecord: res.data?.records?.[0]?.adviceName || '无数据',
|
||||||
adviceType: queryParams.value.adviceType
|
adviceType: requestParams.adviceType
|
||||||
});
|
});
|
||||||
adviceBaseList.value = res.data.records || [];
|
adviceBaseList.value = res.data.records || [];
|
||||||
total.value = res.data.total || 0;
|
total.value = res.data.total || 0;
|
||||||
|
|||||||
@@ -461,7 +461,7 @@ watch(
|
|||||||
console.log(prescriptionList.value,"prescriptionList.value")
|
console.log(prescriptionList.value,"prescriptionList.value")
|
||||||
if(newValue&&newValue.length>0){
|
if(newValue&&newValue.length>0){
|
||||||
let saveList = prescriptionList.value.filter((item) => {
|
let saveList = prescriptionList.value.filter((item) => {
|
||||||
return item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
|
return item.check && item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
|
||||||
})
|
})
|
||||||
console.log(saveList,"prescriptionList.value")
|
console.log(saveList,"prescriptionList.value")
|
||||||
if (saveList.length == 0) {
|
if (saveList.length == 0) {
|
||||||
@@ -887,6 +887,10 @@ function handleDelete() {
|
|||||||
if (item.statusEnum != 1 || item.chargeStatus == 5) {
|
if (item.statusEnum != 1 || item.chargeStatus == 5) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
// 🔧 Bug #442: 非本人创建的医嘱不允许删除(与签发/签退逻辑保持一致)
|
||||||
|
if (Number(item.bizRequestFlag) !== 1 && item.bizRequestFlag) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
// 🔧 Bug #442: 已保存的行必须有有效的 requestId,否则跳过(避免后端删除不存在的记录)
|
// 🔧 Bug #442: 已保存的行必须有有效的 requestId,否则跳过(避免后端删除不存在的记录)
|
||||||
if (item.requestId == null || item.requestId === undefined || item.requestId === '') {
|
if (item.requestId == null || item.requestId === undefined || item.requestId === '') {
|
||||||
return null;
|
return null;
|
||||||
@@ -896,7 +900,7 @@ function handleDelete() {
|
|||||||
dbOpType: '3',
|
dbOpType: '3',
|
||||||
adviceType: item.adviceType,
|
adviceType: item.adviceType,
|
||||||
};
|
};
|
||||||
}).filter(item => item !== null); // 过滤掉已签发、已收费或无 requestId 的项目
|
}).filter(item => item !== null); // 过滤掉已签发、已收费、非本人创建或无 requestId 的项目
|
||||||
|
|
||||||
if (deleteList.length == 0) {
|
if (deleteList.length == 0) {
|
||||||
proxy.$modal.msgWarning('只能删除待签发且未收费的项目');
|
proxy.$modal.msgWarning('只能删除待签发且未收费的项目');
|
||||||
@@ -922,23 +926,36 @@ function handleDelete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasSavedItem) {
|
if (hasSavedItem) {
|
||||||
// 有已保存的行,调用后端API删除
|
// 🔧 Bug #454: 删除前弹出确认提示,告知用户将级联删除关联检验申请单
|
||||||
|
const hasLabItem = deleteList.some(item => item.adviceType === 3);
|
||||||
|
const confirmMsg = hasLabItem
|
||||||
|
? '删除此医嘱将同时删除关联的检验申请单,是否确认删除?'
|
||||||
|
: '确认删除选中的医嘱项目吗?';
|
||||||
|
|
||||||
|
proxy.$modal.confirm(confirmMsg).then(() => {
|
||||||
savePrescription({ adviceSaveList: deleteList }).then((res) => {
|
savePrescription({ adviceSaveList: deleteList }).then((res) => {
|
||||||
if (res.code == 200) {
|
if (res.code == 200) {
|
||||||
proxy.$modal.msgSuccess('操作成功');
|
proxy.$modal.msgSuccess('操作成功');
|
||||||
getListInfo(false);
|
getListInfo(false);
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 只有新增行,已经在前端删除完成
|
|
||||||
proxy.$modal.msgSuccess('操作成功');
|
|
||||||
}
|
|
||||||
|
|
||||||
expandOrder.value = [];
|
expandOrder.value = [];
|
||||||
groupIndexList.value = [];
|
groupIndexList.value = [];
|
||||||
groupList.value = [];
|
groupList.value = [];
|
||||||
isAdding.value = false;
|
isAdding.value = false;
|
||||||
adviceQueryParams.value.adviceType = undefined;
|
adviceQueryParams.value.adviceType = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).catch(() => {
|
||||||
|
// 用户取消删除
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 只有新增行,已经在前端删除完成
|
||||||
|
proxy.$modal.msgSuccess('操作成功');
|
||||||
|
expandOrder.value = [];
|
||||||
|
groupIndexList.value = [];
|
||||||
|
groupList.value = [];
|
||||||
|
isAdding.value = false;
|
||||||
|
adviceQueryParams.value.adviceType = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNumberClick(item, index) {
|
function handleNumberClick(item, index) {
|
||||||
@@ -1011,7 +1028,7 @@ function handleSave() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let saveList = prescriptionList.value.filter((item) => {
|
let saveList = prescriptionList.value.filter((item) => {
|
||||||
return item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
|
return item.check && item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
|
||||||
});
|
});
|
||||||
// let saveList = prescriptionList.value
|
// let saveList = prescriptionList.value
|
||||||
// .filter((item) => {
|
// .filter((item) => {
|
||||||
@@ -1032,10 +1049,20 @@ function handleSave() {
|
|||||||
requestId: item.requestId,
|
requestId: item.requestId,
|
||||||
dbOpType: '1',
|
dbOpType: '1',
|
||||||
groupId: item.groupId,
|
groupId: item.groupId,
|
||||||
|
// 🔧 Bug #443: 补充顶层关键字段(这些不在 contentJson 中,需从 API 响应顶层提取)
|
||||||
|
encounterId: item.encounterId,
|
||||||
|
patientId: item.patientId,
|
||||||
|
locationId: item.positionId,
|
||||||
|
adviceType: item.adviceType,
|
||||||
|
adviceTableName: item.adviceTableName,
|
||||||
|
adviceDefinitionId: item.adviceDefinitionId,
|
||||||
|
chargeItemId: item.chargeItemId,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
// 确保 organizationId 不为 undefined(手术计费场景下可能缺失 orgId)
|
||||||
|
const orgId = props.patientInfo.orgId || props.patientInfo.effectiveOrgId || 1;
|
||||||
savePrescriptionSign({
|
savePrescriptionSign({
|
||||||
organizationId: props.patientInfo.orgId,
|
organizationId: orgId,
|
||||||
adviceSaveList: list,
|
adviceSaveList: list,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
@@ -1047,7 +1074,13 @@ function handleSave() {
|
|||||||
groupIndexList.value = []
|
groupIndexList.value = []
|
||||||
groupList.value = []
|
groupList.value = []
|
||||||
nextId.value = 1;
|
nextId.value = 1;
|
||||||
|
} else {
|
||||||
|
proxy.$modal.msgError(res?.msg || '签发失败,请重试');
|
||||||
}
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error('签发失败:', error);
|
||||||
|
console.warn('签发操作失败(可能无权限或后端异常):', error?.response?.data?.msg || error?.message);
|
||||||
|
proxy.$modal.msgError(error?.response?.data?.msg || error?.message || '签发失败,请重试');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1063,9 +1096,12 @@ function handleSaveSign(row, index) {
|
|||||||
proxy.$modal.msgWarning('诊疗项目必须选择执行科室');
|
proxy.$modal.msgWarning('诊疗项目必须选择执行科室');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
isSaving.value = true; // #437 立即加锁,消除 TOCTOU 竞态
|
||||||
proxy.$refs['formRef' + index].validate((valid) => {
|
proxy.$refs['formRef' + index].validate((valid) => {
|
||||||
if (valid) {
|
if (!valid) {
|
||||||
isSaving.value = true; // #437 加锁
|
isSaving.value = false; // 验证失败释放锁
|
||||||
|
return;
|
||||||
|
}
|
||||||
row.isEdit = false;
|
row.isEdit = false;
|
||||||
isAdding.value = false;
|
isAdding.value = false;
|
||||||
expandOrder.value = [];
|
expandOrder.value = [];
|
||||||
@@ -1083,7 +1119,7 @@ function handleSaveSign(row, index) {
|
|||||||
cleanRow.sourceBillNo = props.patientInfo.sourceBillNo;
|
cleanRow.sourceBillNo = props.patientInfo.sourceBillNo;
|
||||||
}
|
}
|
||||||
console.log('cleanRow', cleanRow)
|
console.log('cleanRow', cleanRow)
|
||||||
savePrescription({ adviceSaveList: [cleanRow] }).then((res) => {
|
savePrescription({ adviceSaveList: [cleanRow] }, '1').then((res) => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
proxy.$modal.msgSuccess('保存成功');
|
proxy.$modal.msgSuccess('保存成功');
|
||||||
getListInfo(false);
|
getListInfo(false);
|
||||||
@@ -1097,8 +1133,7 @@ function handleSaveSign(row, index) {
|
|||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
isSaving.value = false; // #437 释放锁
|
isSaving.value = false; // #437 释放锁
|
||||||
});
|
});
|
||||||
}
|
})
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 签退
|
// 签退
|
||||||
|
|||||||
@@ -692,6 +692,7 @@ async function handleFoodDiseasesCheck() {
|
|||||||
/**
|
/**
|
||||||
* 传染病报告卡处理
|
* 传染病报告卡处理
|
||||||
* 通过诊断名称自动识别并勾选传染病报告卡中的疾病
|
* 通过诊断名称自动识别并勾选传染病报告卡中的疾病
|
||||||
|
* 修复 Bug #519:跳过已有已提交报卡的诊断
|
||||||
*/
|
*/
|
||||||
function handleInfectiousDiseaseReport() {
|
function handleInfectiousDiseaseReport() {
|
||||||
// 疾病名称到报卡编码的映射(根据传染病报告卡弹窗中的疾病列表)
|
// 疾病名称到报卡编码的映射(根据传染病报告卡弹窗中的疾病列表)
|
||||||
@@ -743,8 +744,9 @@ function handleInfectiousDiseaseReport() {
|
|||||||
'手足口病': '0311',
|
'手足口病': '0311',
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取所有诊断名称对应的报卡编码
|
// 获取所有诊断名称对应的报卡编码,但跳过已有已提交报卡的诊断
|
||||||
const allSelectedDiseases = form.value.diagnosisList
|
const allSelectedDiseases = form.value.diagnosisList
|
||||||
|
.filter(d => d.name && d.hasInfectiousReport !== 1)
|
||||||
.map(d => diseaseNameToCode[d.name] || null)
|
.map(d => diseaseNameToCode[d.name] || null)
|
||||||
.filter(code => code);
|
.filter(code => code);
|
||||||
|
|
||||||
@@ -752,9 +754,9 @@ function handleInfectiousDiseaseReport() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优先使用主诊断
|
// 优先使用主诊断(同样跳过已有报卡的)
|
||||||
const mainDiagnosis = form.value.diagnosisList.find(d => d.maindiseFlag === 1);
|
const mainDiagnosis = form.value.diagnosisList.find(d => d.maindiseFlag === 1 && d.hasInfectiousReport !== 1);
|
||||||
const firstDiagnosis = form.value.diagnosisList[0];
|
const firstDiagnosis = form.value.diagnosisList.find(d => d.hasInfectiousReport !== 1) || form.value.diagnosisList[0];
|
||||||
|
|
||||||
const diagnosisToShow = {
|
const diagnosisToShow = {
|
||||||
...(mainDiagnosis || firstDiagnosis),
|
...(mainDiagnosis || firstDiagnosis),
|
||||||
|
|||||||
@@ -49,6 +49,40 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 性别、出生日期、或实足年龄 -->
|
||||||
|
<el-row :gutter="16" class="form-row">
|
||||||
|
<el-col :span="7" class="form-item">
|
||||||
|
<span class="form-label required">性别</span>
|
||||||
|
<el-radio-group v-model="form.sex" class="gender-radio-group">
|
||||||
|
<el-radio value="男">男</el-radio>
|
||||||
|
<el-radio value="女">女</el-radio>
|
||||||
|
<el-radio value="未知">未知</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="10" class="form-item">
|
||||||
|
<span class="form-label required">出生日期</span>
|
||||||
|
<div class="birth-input-group">
|
||||||
|
<el-input v-model="form.birthYear" class="birth-input year" placeholder="年" maxlength="4" />
|
||||||
|
<span class="birth-separator">年</span>
|
||||||
|
<el-input v-model="form.birthMonth" class="birth-input month" placeholder="月" maxlength="2" />
|
||||||
|
<span class="birth-separator">月</span>
|
||||||
|
<el-input v-model="form.birthDay" class="birth-input day" placeholder="日" maxlength="2" />
|
||||||
|
<span class="birth-separator">日</span>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="7" class="form-item">
|
||||||
|
<span class="form-label">或 实足年龄</span>
|
||||||
|
<div class="age-input-group">
|
||||||
|
<el-input v-model="form.age" class="age-input" placeholder="年龄" />
|
||||||
|
<el-select v-model="form.ageUnit" class="age-unit-select">
|
||||||
|
<el-option label="岁" value="岁" />
|
||||||
|
<el-option label="月" value="月" />
|
||||||
|
<el-option label="天" value="天" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
<!-- 联系电话、紧急联系人电话 -->
|
<!-- 联系电话、紧急联系人电话 -->
|
||||||
<el-row :gutter="16" class="form-row">
|
<el-row :gutter="16" class="form-row">
|
||||||
<el-col :span="12" class="form-item">
|
<el-col :span="12" class="form-item">
|
||||||
@@ -991,7 +1025,11 @@ function parseBirthDate(birthDate) {
|
|||||||
|
|
||||||
function normalizeDate(value) {
|
function normalizeDate(value) {
|
||||||
if (!value) return '';
|
if (!value) return '';
|
||||||
return String(value).split(/[T ]/)[0];
|
const datePart = String(value).split(/[T ]/)[0].replace(/\//g, '-');
|
||||||
|
const parts = datePart.split('-');
|
||||||
|
if (parts.length !== 3) return datePart;
|
||||||
|
const [year, month, day] = parts;
|
||||||
|
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeSex(value) {
|
function normalizeSex(value) {
|
||||||
@@ -1000,6 +1038,17 @@ function normalizeSex(value) {
|
|||||||
return '未知';
|
return '未知';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeSexFromPatientInfo(patientInfo) {
|
||||||
|
// 优先使用文本字段
|
||||||
|
if (patientInfo.genderEnum_enumText) return patientInfo.genderEnum_enumText;
|
||||||
|
if (patientInfo.genderName) return patientInfo.genderName;
|
||||||
|
if (patientInfo.sex) return normalizeSex(patientInfo.sex);
|
||||||
|
// 使用数字枚举字段
|
||||||
|
if (patientInfo.genderEnum === 1) return '男';
|
||||||
|
if (patientInfo.genderEnum === 2) return '女';
|
||||||
|
return '未知';
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeAgeUnit(value) {
|
function normalizeAgeUnit(value) {
|
||||||
const ageUnitMap = {
|
const ageUnitMap = {
|
||||||
1: '岁',
|
1: '岁',
|
||||||
@@ -1071,7 +1120,7 @@ function showReport(reportData = {}, readOnly = true) {
|
|||||||
addressHouse: reportData.addressHouse || '',
|
addressHouse: reportData.addressHouse || '',
|
||||||
patientBelong: reportData.patientBelong || 1,
|
patientBelong: reportData.patientBelong || 1,
|
||||||
occupation: reportData.occupation || '',
|
occupation: reportData.occupation || '',
|
||||||
caseClass: reportData.diseaseType != null ? String(reportData.diseaseType) : '',
|
caseClass: reportData.caseClass != null ? String(reportData.caseClass) : '',
|
||||||
onsetDate: normalizeDate(reportData.onsetDate),
|
onsetDate: normalizeDate(reportData.onsetDate),
|
||||||
diagDate: normalizeDate(reportData.diagDate),
|
diagDate: normalizeDate(reportData.diagDate),
|
||||||
deathDate: normalizeDate(reportData.deathDate),
|
deathDate: normalizeDate(reportData.deathDate),
|
||||||
@@ -1080,7 +1129,7 @@ function showReport(reportData = {}, readOnly = true) {
|
|||||||
selectedClassB: diseaseSelection.selectedClassB,
|
selectedClassB: diseaseSelection.selectedClassB,
|
||||||
selectedClassC: diseaseSelection.selectedClassC,
|
selectedClassC: diseaseSelection.selectedClassC,
|
||||||
otherDisease: reportData.otherDisease || (diseaseCode === 'OTHER' ? reportData.diseaseName || '' : ''),
|
otherDisease: reportData.otherDisease || (diseaseCode === 'OTHER' ? reportData.diseaseName || '' : ''),
|
||||||
diseaseType: reportData.diseaseSubtype || '',
|
diseaseType: reportData.diseaseSubtype || reportData.diseaseType || '',
|
||||||
reportOrg: reportData.reportOrg || '',
|
reportOrg: reportData.reportOrg || '',
|
||||||
reportOrgPhone: reportData.reportOrgPhone || '',
|
reportOrgPhone: reportData.reportOrgPhone || '',
|
||||||
reportDoc: reportData.reportDoc || '',
|
reportDoc: reportData.reportDoc || '',
|
||||||
@@ -1261,7 +1310,7 @@ async function show(diagnosisData) {
|
|||||||
patName: patientInfo.patientName || patientInfo.name || '', // 患者姓名
|
patName: patientInfo.patientName || patientInfo.name || '', // 患者姓名
|
||||||
parentName: '', // 家长姓名(14岁以下患者必填)
|
parentName: '', // 家长姓名(14岁以下患者必填)
|
||||||
idNo: patientInfo.idCard, // 身份证号
|
idNo: patientInfo.idCard, // 身份证号
|
||||||
sex: patientInfo.sex || patientInfo.genderName || '男', // 性别
|
sex: normalizeSexFromPatientInfo(patientInfo), // 性别
|
||||||
|
|
||||||
// 出生日期信息
|
// 出生日期信息
|
||||||
birthYear: birthInfo.year, // 出生年份
|
birthYear: birthInfo.year, // 出生年份
|
||||||
@@ -1366,6 +1415,9 @@ async function buildSubmitData() {
|
|||||||
} else if (formData.otherDisease) {
|
} else if (formData.otherDisease) {
|
||||||
// 其他传染病使用自定义编码
|
// 其他传染病使用自定义编码
|
||||||
diseaseCode = 'OTHER';
|
diseaseCode = 'OTHER';
|
||||||
|
} else if (formData.selectedDiseases && formData.selectedDiseases.length > 0) {
|
||||||
|
// 兜底:如果 ClassA/B/C 都为空但 selectedDiseases 有值,取第一个作为 diseaseCode
|
||||||
|
diseaseCode = formData.selectedDiseases[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换年龄单位:岁=1, 月=2, 天=3
|
// 转换年龄单位:岁=1, 月=2, 天=3
|
||||||
@@ -1418,7 +1470,7 @@ async function buildSubmitData() {
|
|||||||
reportDate: formData.reportDate || null,
|
reportDate: formData.reportDate || null,
|
||||||
cardNameCode: 1, // 默认中华人民共和国传染病报告卡
|
cardNameCode: 1, // 默认中华人民共和国传染病报告卡
|
||||||
registrationSource: 1, // 默认门诊
|
registrationSource: 1, // 默认门诊
|
||||||
status: '',
|
status: null,
|
||||||
deptId: props.deptId || null,
|
deptId: props.deptId || null,
|
||||||
doctorId: props.doctorId || null,
|
doctorId: props.doctorId || null,
|
||||||
};
|
};
|
||||||
@@ -1775,6 +1827,33 @@ defineExpose({ show, showReport, close: handleClose });
|
|||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 输入框下划线样式(与 underline-select 保持一致) */
|
||||||
|
.underline-input :deep(.el-input__wrapper) {
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid #dcdfe6;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.underline-input :deep(.el-input__wrapper:hover) {
|
||||||
|
border-bottom-color: #c0c4cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.underline-input :deep(.el-input__wrapper.is-focus) {
|
||||||
|
border-bottom-color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.underline-input :deep(.el-input__inner) {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.underline-input :deep(.el-input__inner::placeholder) {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
/* 街道下拉框下划线样式 */
|
/* 街道下拉框下划线样式 */
|
||||||
.underline-select {
|
.underline-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -1962,4 +2041,53 @@ defineExpose({ show, showReport, close: handleClose });
|
|||||||
display: flex !important;
|
display: flex !important;
|
||||||
justify-content: center !important;
|
justify-content: center !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 性别单选按钮组 */
|
||||||
|
.gender-radio-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 出生日期输入组 */
|
||||||
|
.birth-input-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.birth-input {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.birth-input.year {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.birth-input.month,
|
||||||
|
.birth-input.day {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.birth-separator {
|
||||||
|
color: #606266;
|
||||||
|
font-size: 13px;
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 年龄输入组 */
|
||||||
|
.age-input-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.age-input {
|
||||||
|
width: 70px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.age-unit-select {
|
||||||
|
width: 65px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,15 +3,56 @@
|
|||||||
<!-- ====== 顶部卡片:申请单列表 ====== -->
|
<!-- ====== 顶部卡片:申请单列表 ====== -->
|
||||||
<div class="top-section">
|
<div class="top-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<span class="section-title">检查项目 ({{ applicationList.length }})</span>
|
<span class="section-title">检查项目 ({{ filteredApplicationList.length }})</span>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<el-button type="primary" @click="handleAdd" icon="Plus">新增</el-button>
|
<el-button type="primary" @click="handleAdd" icon="Plus">新增</el-button>
|
||||||
<el-button type="success" @click="handleSave" icon="Finished">保存</el-button>
|
<el-button type="success" @click="handleSave" icon="Finished">保存</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Bug #499: 查询过滤工具栏 -->
|
||||||
|
<div class="search-toolbar">
|
||||||
|
<el-form :inline="true" size="small">
|
||||||
|
<el-form-item label="日期范围">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="searchForm.dateRange"
|
||||||
|
type="daterange"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
style="width: 240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select v-model="searchForm.applyStatus" placeholder="全部" clearable style="width: 140px">
|
||||||
|
<el-option
|
||||||
|
v-for="opt in statusOptions"
|
||||||
|
:key="opt.value"
|
||||||
|
:label="opt.label"
|
||||||
|
:value="opt.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="关键字">
|
||||||
|
<el-input
|
||||||
|
v-model="searchForm.keyword"
|
||||||
|
placeholder="申请单号 / 检查项目"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
@keyup.enter="handleSearch"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSearch" icon="Search">搜索</el-button>
|
||||||
|
<el-button @click="handleResetSearch" icon="Refresh">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
:data="applicationList"
|
:data="filteredApplicationList"
|
||||||
:max-height="200"
|
:max-height="200"
|
||||||
highlight-current-row
|
highlight-current-row
|
||||||
@row-click="handleRowClick"
|
@row-click="handleRowClick"
|
||||||
@@ -237,9 +278,25 @@
|
|||||||
<el-input v-model="scope.row.applyPart" size="small" />
|
<el-input v-model="scope.row.applyPart" size="small" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="检查方法" min-width="120">
|
<el-table-column label="检查方法" min-width="160">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<!-- Bug #384修复: 显示检查方法名称,不显示套餐名称 -->
|
<el-select
|
||||||
|
v-if="scope.row.methods && scope.row.methods.length > 1"
|
||||||
|
:model-value="scope.row.selectedMethod"
|
||||||
|
value-key="id"
|
||||||
|
size="small"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="选择方法"
|
||||||
|
@update:model-value="(val) => onDetailMethodChange(scope.row, val)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="meth in scope.row.methods"
|
||||||
|
:key="meth.id"
|
||||||
|
:label="`${meth.name}${meth.packagePrice != null ? ' ¥' + formatDetailAmount(meth.packagePrice) : ''}`"
|
||||||
|
:value="meth"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<template v-else>
|
||||||
<span v-if="scope.row.selectedMethod">
|
<span v-if="scope.row.selectedMethod">
|
||||||
{{ scope.row.selectedMethod.name }}
|
{{ scope.row.selectedMethod.name }}
|
||||||
</span>
|
</span>
|
||||||
@@ -248,6 +305,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span v-else style="color: #c0c4cc;">-</span>
|
<span v-else style="color: #c0c4cc;">-</span>
|
||||||
</template>
|
</template>
|
||||||
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="单位" prop="unit" width="55" align="center" />
|
<el-table-column label="单位" prop="unit" width="55" align="center" />
|
||||||
<el-table-column label="总量" prop="quantity" width="70" align="center">
|
<el-table-column label="总量" prop="quantity" width="70" align="center">
|
||||||
@@ -335,8 +393,9 @@
|
|||||||
加载中...
|
加载中...
|
||||||
</div>
|
</div>
|
||||||
<!-- Bug #428修复: 渲染分类联动加载的检查方法列表 -->
|
<!-- Bug #428修复: 渲染分类联动加载的检查方法列表 -->
|
||||||
|
<!-- Bug #500修复: v-if 改为 v-show,避免方法列表加载时 DOM 突然插入导致高度跳变 -->
|
||||||
<div
|
<div
|
||||||
v-if="cat.methods && cat.methods.length > 0"
|
v-show="cat.methods && cat.methods.length > 0"
|
||||||
class="method-section"
|
class="method-section"
|
||||||
>
|
>
|
||||||
<div class="method-section-title">检查方法</div>
|
<div class="method-section-title">检查方法</div>
|
||||||
@@ -370,12 +429,15 @@
|
|||||||
v-for="(item, idx) in selectedItems"
|
v-for="(item, idx) in selectedItems"
|
||||||
:key="idx"
|
:key="idx"
|
||||||
class="selected-item-card"
|
class="selected-item-card"
|
||||||
|
:class="{ 'is-expanded': item.expanded }"
|
||||||
>
|
>
|
||||||
<!-- Bug #384修复: 项目卡片头部,可展开/收起 -->
|
<!-- Bug #384修复 + #426修复: 项目卡片头部,可展开/收起 -->
|
||||||
<div class="card-header" @click="toggleItemExpand(item)">
|
<div class="card-header" @click="toggleItemExpand(item)">
|
||||||
|
<el-tag v-if="item.isPackage || item.packageName" size="small" type="warning" style="margin-right: 4px; flex-shrink: 0;">套餐</el-tag>
|
||||||
|
<el-tooltip :content="item.name" placement="top" :show-after="400">
|
||||||
<span class="card-name">{{ item.name }}</span>
|
<span class="card-name">{{ item.name }}</span>
|
||||||
<span class="card-price">¥{{ item.price }}</span>
|
</el-tooltip>
|
||||||
<!-- 展开图标 -->
|
<span class="card-price">¥{{ formatDetailAmount(item.price) }}</span>
|
||||||
<el-icon :class="['expand-icon', { expanded: item.expanded }]">
|
<el-icon :class="['expand-icon', { expanded: item.expanded }]">
|
||||||
<ArrowDown v-if="!item.expanded" />
|
<ArrowDown v-if="!item.expanded" />
|
||||||
<ArrowUp v-if="item.expanded" />
|
<ArrowUp v-if="item.expanded" />
|
||||||
@@ -385,39 +447,30 @@
|
|||||||
<el-icon><Close /></el-icon>
|
<el-icon><Close /></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<!-- Bug #428修复: 展开后显示套餐明细或检查方法 -->
|
<!-- Bug #428: 有套餐 ID 时默认展开;加载中/空/明细均在本区域展示 -->
|
||||||
<div v-if="item.expanded">
|
<div v-if="item.expanded && shouldShowPackageBody(item)" class="selected-card-body">
|
||||||
<!-- 显示套餐明细 -->
|
<div v-if="item.packageDetailsLoading" class="package-details-loading">加载中...</div>
|
||||||
<div v-if="item.packageDetails && item.packageDetails.length > 0" class="package-details-list">
|
<template v-else>
|
||||||
<div class="detail-row" v-for="detail in item.packageDetails" :key="detail.id">
|
<div v-if="getPackageDetailsList(item).length === 0" class="package-details-empty">
|
||||||
|
暂无套餐明细
|
||||||
|
</div>
|
||||||
|
<div v-else class="package-details-list">
|
||||||
|
<div class="package-details-head">套餐明细</div>
|
||||||
|
<div
|
||||||
|
v-for="(detail, dIdx) in getPackageDetailsList(item)"
|
||||||
|
:key="detail.id ?? detail.itemCode ?? `d-${dIdx}`"
|
||||||
|
class="detail-row"
|
||||||
|
>
|
||||||
|
<el-tooltip :content="detail.name" placement="top" :show-after="500">
|
||||||
<span class="detail-name">{{ detail.name }}</span>
|
<span class="detail-name">{{ detail.name }}</span>
|
||||||
<span class="detail-info">数量: {{ detail.quantity }} 单价: ¥{{ detail.price }}</span>
|
</el-tooltip>
|
||||||
|
<div class="detail-meta">
|
||||||
|
<span class="detail-qty">×{{ detail.quantity || 1 }}</span>
|
||||||
|
<span class="detail-price">¥{{ formatDetailAmount(detail.price) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 显示检查方法 -->
|
|
||||||
<div v-else-if="item.methods && item.methods.length > 0" class="method-list">
|
|
||||||
<div v-for="method in item.methods" :key="method.id" class="method-option">
|
|
||||||
<el-checkbox :model-value="item.selectedMethod?.id === method.id" @change="(val) => selectMethodCheckbox(val, item, method)">
|
|
||||||
<span class="method-name">{{ method.name }}</span>
|
|
||||||
<span class="method-price">¥{{ method.packagePrice || item.price }}</span>
|
|
||||||
</el-checkbox>
|
|
||||||
</div>
|
|
||||||
<!-- 选中方法后,显示对应的套餐明细 -->
|
|
||||||
<div v-if="item.selectedMethod && item.methodPackageDetails && item.methodPackageDetails.length > 0" class="method-package-details">
|
|
||||||
<div class="method-package-header">
|
|
||||||
<span class="method-package-title">套餐明细 - {{ item.selectedMethod.name }}</span>
|
|
||||||
</div>
|
|
||||||
<div v-for="detail in item.methodPackageDetails" :key="detail.id" class="method-option">
|
|
||||||
<el-checkbox v-model="detail.checked">
|
|
||||||
<span class="method-name">{{ detail.name }}</span>
|
|
||||||
<span class="method-price">数量: {{ detail.quantity }} ¥{{ detail.price }}</span>
|
|
||||||
</el-checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="item.selectedMethod && item.methodPackageLoading" class="method-package-loading">
|
|
||||||
加载套餐明细中...
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -452,45 +505,164 @@ const activeDetailTab = ref('applyForm');
|
|||||||
const applicationList = ref([]);
|
const applicationList = ref([]);
|
||||||
const selectedItems = ref([]);
|
const selectedItems = ref([]);
|
||||||
|
|
||||||
|
// Bug #499: 查询过滤状态
|
||||||
|
const searchForm = reactive({
|
||||||
|
dateRange: [],
|
||||||
|
applyStatus: '',
|
||||||
|
keyword: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
// 申请单状态选项
|
||||||
|
const statusOptions = [
|
||||||
|
{ label: '已开单', value: 0 },
|
||||||
|
{ label: '已收费', value: 1 },
|
||||||
|
{ label: '已预约', value: 2 },
|
||||||
|
{ label: '已签到', value: 3 },
|
||||||
|
{ label: '部分报告', value: 4 },
|
||||||
|
{ label: '已完告', value: 5 },
|
||||||
|
{ label: '已作废', value: 6 }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Bug #499: 过滤后的申请单列表
|
||||||
|
const filteredApplicationList = computed(() => {
|
||||||
|
let result = applicationList.value;
|
||||||
|
|
||||||
|
// 日期范围过滤
|
||||||
|
if (searchForm.dateRange && searchForm.dateRange.length === 2) {
|
||||||
|
const start = searchForm.dateRange[0];
|
||||||
|
const end = searchForm.dateRange[1];
|
||||||
|
result = result.filter(item => {
|
||||||
|
const d = item.applyTime;
|
||||||
|
if (!d) return false;
|
||||||
|
const dateStr = d.length > 10 ? d.substring(0, 10) : d;
|
||||||
|
return dateStr >= start && dateStr <= end;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态过滤
|
||||||
|
if (searchForm.applyStatus !== '' && searchForm.applyStatus !== null && searchForm.applyStatus !== undefined) {
|
||||||
|
result = result.filter(item => item.applyStatus === searchForm.applyStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键字过滤(申请单号、申检部位、检查项目名)
|
||||||
|
if (searchForm.keyword) {
|
||||||
|
const kw = searchForm.keyword.toLowerCase();
|
||||||
|
result = result.filter(item => {
|
||||||
|
return (item.applyNo || '').toLowerCase().includes(kw)
|
||||||
|
|| (item.inspectionArea || '').toLowerCase().includes(kw);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Bug #499: 搜索与重置
|
||||||
|
function handleSearch() {
|
||||||
|
// 过滤逻辑由 computed 自动处理
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleResetSearch() {
|
||||||
|
const now = new Date();
|
||||||
|
const end = now.toISOString().substring(0, 10);
|
||||||
|
const start = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString().substring(0, 10);
|
||||||
|
searchForm.dateRange = [start, end];
|
||||||
|
searchForm.applyStatus = '';
|
||||||
|
searchForm.keyword = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化默认日期范围为近一周
|
||||||
|
handleResetSearch();
|
||||||
|
|
||||||
// 🔧 BugFix#426: 懒加载套餐明细
|
// 🔧 BugFix#426: 懒加载套餐明细
|
||||||
async function loadPackageDetails(row, treeNode, resolve) {
|
async function loadPackageDetails(row, treeNode, resolve) {
|
||||||
if (!row.isPackage || !row.packageId) {
|
if (!row.packageId) {
|
||||||
resolve([]);
|
resolve([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = await request({
|
const res = await request({
|
||||||
url: `/system/package/${row.packageId}/details`,
|
url: `/system/check-type/package/${row.packageId}/details`,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
});
|
});
|
||||||
if (res.code === 200 && res.data) {
|
const list = parsePackageDetailsPayload(res);
|
||||||
const children = res.data.map(item => ({
|
const children = list.map((child) => ({
|
||||||
...item,
|
...child,
|
||||||
name: item.name || item.itemName,
|
name: child.name || child.itemName,
|
||||||
unit: item.unit || '次',
|
unit: child.unit || '次',
|
||||||
price: item.price || item.itemPrice || 0,
|
price: child.price ?? child.unitPrice ?? child.itemPrice ?? 0,
|
||||||
quantity: row.quantity || 1,
|
quantity: row.quantity || 1,
|
||||||
isPackageDetail: true
|
isPackageDetail: true
|
||||||
}));
|
}));
|
||||||
resolve(children);
|
resolve(children);
|
||||||
} else {
|
|
||||||
resolve([]);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('加载套餐明细失败:', err);
|
console.error('加载套餐明细失败:', err);
|
||||||
resolve([]);
|
resolve([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #428修复: 为已选择项目加载套餐明细(通过packageId或packageName查询)
|
// #428修复 + #426修复: 为已选择项目加载套餐明细(通过packageId或packageName查询)
|
||||||
|
/** 套餐明细挂在「部位」或已选的「检查方法」上(方法可带 packageId) */
|
||||||
|
function getPackageCarrier(item) {
|
||||||
|
return item?.selectedMethod?.packageId ? item.selectedMethod : item;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPackageDetailsList(item) {
|
||||||
|
// 明细挂在行对象上,避免仅写入 methods 内嵌对象时首帧不触发视图更新(体感需点两次才展开)
|
||||||
|
if (Array.isArray(item?.packageDetailsDisplay)) {
|
||||||
|
return item.packageDetailsDisplay;
|
||||||
|
}
|
||||||
|
const carrier = getPackageCarrier(item);
|
||||||
|
return Array.isArray(carrier?.packageDetails) ? carrier.packageDetails : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 有套餐 ID 的已选行才展示右侧套餐区(加载中 / 空 / 明细列表) */
|
||||||
|
function shouldShowPackageBody(item) {
|
||||||
|
return !!getPackageCarrier(item)?.packageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 金额展示:统一两位小数 */
|
||||||
|
function formatDetailAmount(value) {
|
||||||
|
const n = Number(value ?? 0);
|
||||||
|
return Number.isFinite(n) ? n.toFixed(2) : '0.00';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 默认检查方法:优先与部位 packageId 一致的方法,否则取首个带套餐的方法,否则取第一个 */
|
||||||
|
function pickDefaultMethod(methods, partItem) {
|
||||||
|
if (!methods?.length) return null;
|
||||||
|
if (methods.length === 1) return methods[0];
|
||||||
|
const pid = partItem?.packageId ?? null;
|
||||||
|
if (pid != null && pid !== '') {
|
||||||
|
const matched = methods.find(
|
||||||
|
(x) => x.packageId != null && String(x.packageId) === String(pid)
|
||||||
|
);
|
||||||
|
if (matched) return matched;
|
||||||
|
}
|
||||||
|
const withPkg = methods.find((x) => x.packageId != null);
|
||||||
|
if (withPkg) return withPkg;
|
||||||
|
return methods[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsePackageDetailsPayload(res) {
|
||||||
|
const raw =
|
||||||
|
res?.data?.data ??
|
||||||
|
res?.data?.records ??
|
||||||
|
res?.data ??
|
||||||
|
res?.rows ??
|
||||||
|
res;
|
||||||
|
if (!Array.isArray(raw)) return [];
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
// #428: 为已选择项目加载套餐明细(后端:CheckTypeController /system/check-type/package/{id}/details)
|
||||||
async function loadPackageDetailsForItem(item) {
|
async function loadPackageDetailsForItem(item) {
|
||||||
if (!item.isPackage || (!item.packageId && !item.packageName)) {
|
const carrier = getPackageCarrier(item);
|
||||||
|
let packageId = item.packageId || carrier?.packageId;
|
||||||
|
if (!packageId && !item.packageName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
item.packageDetailsLoading = true;
|
||||||
try {
|
try {
|
||||||
let packageId = item.packageId;
|
|
||||||
if (!packageId && item.packageName) {
|
if (!packageId && item.packageName) {
|
||||||
// CheckPart 没有 packageId 字段,需要通过 packageName 查询获取
|
|
||||||
const pkgRes = await listCheckPackage({ packageName: item.packageName });
|
const pkgRes = await listCheckPackage({ packageName: item.packageName });
|
||||||
let packages = pkgRes?.data || [];
|
let packages = pkgRes?.data || [];
|
||||||
if (!Array.isArray(packages)) {
|
if (!Array.isArray(packages)) {
|
||||||
@@ -498,28 +670,51 @@ async function loadPackageDetailsForItem(item) {
|
|||||||
}
|
}
|
||||||
if (packages.length === 0) {
|
if (packages.length === 0) {
|
||||||
item.packageDetails = [];
|
item.packageDetails = [];
|
||||||
|
item.packageDetailsDisplay = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
packageId = packages[0].id;
|
packageId = packages[0].id;
|
||||||
|
item.packageId = packageId;
|
||||||
|
}
|
||||||
|
if (!packageId) {
|
||||||
|
item.packageDetails = [];
|
||||||
|
item.packageDetailsDisplay = [];
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const res = await request({
|
const res = await request({
|
||||||
url: `/system/package/${packageId}/details`,
|
url: `/system/check-type/package/${packageId}/details`,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
});
|
});
|
||||||
|
const list = parsePackageDetailsPayload(res);
|
||||||
|
const mapped = list.map((detail) => ({
|
||||||
|
...detail,
|
||||||
|
name: detail.name || detail.itemName,
|
||||||
|
unit: detail.unit || '次',
|
||||||
|
price: detail.price ?? detail.unitPrice ?? detail.itemPrice ?? 0,
|
||||||
|
quantity: detail.quantity || 1
|
||||||
|
}));
|
||||||
|
item.packageDetailsDisplay = mapped;
|
||||||
|
carrier.packageDetails = mapped;
|
||||||
if (res.code === 200 && res.data) {
|
if (res.code === 200 && res.data) {
|
||||||
item.packageDetails = res.data.map(detail => ({
|
item.packageDetails = Array.isArray(res.data)
|
||||||
|
? res.data.map((detail) => ({
|
||||||
...detail,
|
...detail,
|
||||||
name: detail.name || detail.itemName,
|
name: detail.name || detail.itemName,
|
||||||
unit: detail.unit || '次',
|
unit: detail.unit || '次',
|
||||||
price: detail.price || detail.unitPrice || 0,
|
price: detail.price || detail.unitPrice || 0,
|
||||||
quantity: detail.quantity || 1
|
quantity: detail.quantity || 1
|
||||||
}));
|
}))
|
||||||
|
: mapped;
|
||||||
} else {
|
} else {
|
||||||
item.packageDetails = [];
|
item.packageDetails = mapped;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('加载套餐明细失败:', err);
|
console.error('加载套餐明细失败:', err);
|
||||||
|
item.packageDetailsDisplay = [];
|
||||||
|
carrier.packageDetails = [];
|
||||||
item.packageDetails = [];
|
item.packageDetails = [];
|
||||||
|
} finally {
|
||||||
|
item.packageDetailsLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const detailTableRef = ref(null);
|
const detailTableRef = ref(null);
|
||||||
@@ -573,7 +768,7 @@ const categoryList = ref([]); // 原始分类+项目数据
|
|||||||
const dictSearchKey = ref('');
|
const dictSearchKey = ref('');
|
||||||
const activeNames = ref(''); // 当前展开的折叠项
|
const activeNames = ref(''); // 当前展开的折叠项
|
||||||
const categoryLoadingSet = ref(new Set()); // Bug #500: 正在加载方法的分类集合
|
const categoryLoadingSet = ref(new Set()); // Bug #500: 正在加载方法的分类集合
|
||||||
const isAnimating = ref(false); // Bug #500: 防止快速切换时折叠动画重叠导致抖动
|
const currentActiveCategory = ref(null); // Bug #500: 记录当前激活的分类,忽略过期请求响应
|
||||||
|
|
||||||
const allMethods = ref([]);
|
const allMethods = ref([]);
|
||||||
|
|
||||||
@@ -690,15 +885,18 @@ const availableMethods = computed(() => {
|
|||||||
// 当可选方法列表改变时,如果当前选中的方法不在新列表中,则清空
|
// 当可选方法列表改变时,如果当前选中的方法不在新列表中,则清空
|
||||||
// #428: 分类展开时联动加载检查方法
|
// #428: 分类展开时联动加载检查方法
|
||||||
// Bug #500: 使用 categoryLoadingSet 替代 dictLoading,避免切换分类时整个区域闪烁
|
// Bug #500: 使用 categoryLoadingSet 替代 dictLoading,避免切换分类时整个区域闪烁
|
||||||
|
// Bug #500: 添加 currentActiveCategory 守卫,忽略过期请求响应,防止快速切换时数据闪烁
|
||||||
async function handleCategoryExpand(cat) {
|
async function handleCategoryExpand(cat) {
|
||||||
if (!cat || !cat.typeName) return;
|
if (!cat || !cat.typeName) return;
|
||||||
|
|
||||||
// 如果已加载过或正在加载中,跳过
|
|
||||||
if ((cat.methods && cat.methods.length > 0) || categoryLoadingSet.value.has(cat.typeId)) return;
|
if ((cat.methods && cat.methods.length > 0) || categoryLoadingSet.value.has(cat.typeId)) return;
|
||||||
|
|
||||||
categoryLoadingSet.value.add(cat.typeId);
|
categoryLoadingSet.value.add(cat.typeId);
|
||||||
|
currentActiveCategory.value = cat.typeId;
|
||||||
try {
|
try {
|
||||||
const res = await searchCheckMethod({ checkType: cat.typeName });
|
const res = await searchCheckMethod({ checkType: cat.typeName });
|
||||||
|
// 忽略过期请求:用户已切换到其他分类,丢弃当前响应
|
||||||
|
if (currentActiveCategory.value !== cat.typeId) return;
|
||||||
let data = res?.data?.data || res?.data || res?.rows || res;
|
let data = res?.data?.data || res?.data || res?.rows || res;
|
||||||
if (!Array.isArray(data) && res?.data && Array.isArray(res.data.data)) {
|
if (!Array.isArray(data) && res?.data && Array.isArray(res.data.data)) {
|
||||||
data = res.data.data;
|
data = res.data.data;
|
||||||
@@ -717,17 +915,16 @@ async function handleCategoryExpand(cat) {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (currentActiveCategory.value !== cat.typeId) return;
|
||||||
console.error('加载分类检查方法失败', err);
|
console.error('加载分类检查方法失败', err);
|
||||||
} finally {
|
} finally {
|
||||||
categoryLoadingSet.value.delete(cat.typeId);
|
categoryLoadingSet.value.delete(cat.typeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Bug #500: 添加防抖逻辑,快速切换时跳过中间状态的动画,避免高度跳变和白屏闪烁
|
// Bug #500修复: 不阻塞 accordion 状态更新,仅防止重复加载同一分类的方法
|
||||||
function handleCollapseChange(activeName) {
|
function handleCollapseChange(activeName) {
|
||||||
if (isAnimating.value) return; // 动画进行中,忽略后续点击
|
// 始终记录当前激活的分类,确保 handleCategoryExpand 能正确忽略过期请求
|
||||||
|
currentActiveCategory.value = activeName || null;
|
||||||
isAnimating.value = true;
|
|
||||||
setTimeout(() => { isAnimating.value = false; }, 300); // 与 CSS 过渡时长一致
|
|
||||||
|
|
||||||
if (activeName) {
|
if (activeName) {
|
||||||
// Bug #428修复: 直接从 categoryList(原始响应式数组)查找分类,
|
// Bug #428修复: 直接从 categoryList(原始响应式数组)查找分类,
|
||||||
@@ -800,7 +997,8 @@ async function loadCategoryList() {
|
|||||||
categoryName: t.name,
|
categoryName: t.name,
|
||||||
// “检查类型管理”里配置的执行科室(图三)
|
// “检查类型管理”里配置的执行科室(图三)
|
||||||
performDeptName: t.department || '',
|
performDeptName: t.department || '',
|
||||||
items: []
|
items: [],
|
||||||
|
methods: [] // #428修复: 初始化 methods 数组,确保 Vue 响应式追踪
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const unclassified = [];
|
const unclassified = [];
|
||||||
@@ -1024,20 +1222,18 @@ function handleRowClick(row) {
|
|||||||
selectedItems.value = [];
|
selectedItems.value = [];
|
||||||
activeDetailTab.value = 'applyForm';
|
activeDetailTab.value = 'applyForm';
|
||||||
request({ url: `/exam/apply/${row.applyNo}`, method: 'get' }).then(async res => {
|
request({ url: `/exam/apply/${row.applyNo}`, method: 'get' }).then(async res => {
|
||||||
const resp = res.data || res;
|
// 响应结构: Axios拦截器对code===200返回res.data(AjaxResult体)
|
||||||
// Bug #408修复: items 在 AjaxResult 顶层(res.items / resp.items),不在 ExamApply 对象内
|
// 结构为 { code: 200, data: examApply实体, items: [明细数组] }
|
||||||
// 防御性提取:优先取顶层 items,兼容嵌套在 resp.data.items 的情况
|
const items = Array.isArray(res.items) ? res.items : [];
|
||||||
let rawItems = res.items || resp.items;
|
const dataObj = res.data || {};
|
||||||
if (!rawItems && resp.data && typeof resp.data === 'object') {
|
|
||||||
rawItems = resp.data.items;
|
// 先填充表单字段
|
||||||
}
|
if (dataObj && typeof dataObj === 'object') Object.assign(form, dataObj);
|
||||||
rawItems = rawItems || [];
|
|
||||||
const d = resp.data || resp;
|
if (items.length > 0) {
|
||||||
if (d) Object.assign(form, d);
|
|
||||||
if (Array.isArray(rawItems) && rawItems.length > 0) {
|
|
||||||
try {
|
try {
|
||||||
// 为每个项目加载检查方法
|
// 为每个项目加载检查方法
|
||||||
const itemsWithMethods = await Promise.all(rawItems.map(async m => {
|
const itemsWithMethods = await Promise.all(items.map(async m => {
|
||||||
const item = {
|
const item = {
|
||||||
id: m.itemCode, name: m.itemName,
|
id: m.itemCode, name: m.itemName,
|
||||||
price: m.itemFee || 0, quantity: 1,
|
price: m.itemFee || 0, quantity: 1,
|
||||||
@@ -1047,13 +1243,17 @@ function handleRowClick(row) {
|
|||||||
nationalCode: '', checked: true,
|
nationalCode: '', checked: true,
|
||||||
methods: [],
|
methods: [],
|
||||||
selectedMethod: null,
|
selectedMethod: null,
|
||||||
expanded: false // Bug #384修复: 添加展开状态
|
expanded: false,
|
||||||
|
packageDetailsLoading: false,
|
||||||
|
isPackage: false,
|
||||||
|
packageId: null,
|
||||||
|
hasChildren: false // #426修复: 树形表格懒加载展开标记,后续根据packageId动态设置
|
||||||
};
|
};
|
||||||
// 加载该项目的检查方法
|
// 加载该项目的检查方法
|
||||||
if (m.bodyPartCode) {
|
if (m.bodyPartCode) {
|
||||||
try {
|
try {
|
||||||
const methodRes = await searchCheckMethod({ checkType: m.bodyPartCode });
|
const methodRes = await searchCheckMethod({ checkType: m.bodyPartCode });
|
||||||
// Bug #384修复: 正确解析 API 返回结构
|
// 正确解析 API 返回结构
|
||||||
let methodData = methodRes?.data?.data || methodRes?.data || methodRes?.rows || methodRes;
|
let methodData = methodRes?.data?.data || methodRes?.data || methodRes?.rows || methodRes;
|
||||||
if (!Array.isArray(methodData) && methodRes?.data && Array.isArray(methodRes.data.data)) {
|
if (!Array.isArray(methodData) && methodRes?.data && Array.isArray(methodRes.data.data)) {
|
||||||
methodData = methodRes.data.data;
|
methodData = methodRes.data.data;
|
||||||
@@ -1063,34 +1263,53 @@ function handleRowClick(row) {
|
|||||||
id: md.id,
|
id: md.id,
|
||||||
name: md.name,
|
name: md.name,
|
||||||
code: md.code,
|
code: md.code,
|
||||||
price: m.itemFee || 0, // fallback 到已保存的价格
|
price: m.itemFee || 0,
|
||||||
packageName: md.packageName || '',
|
packageName: md.packageName || '',
|
||||||
packageId: md.packageId || null,
|
packageId: md.packageId || null,
|
||||||
packagePrice: md.packagePrice || null, // Bug #384修复: 套餐价格
|
packagePrice: md.packagePrice || null,
|
||||||
serviceFee: md.serviceFee || null
|
serviceFee: md.serviceFee || null
|
||||||
}));
|
}));
|
||||||
// 如果有已保存的检查方法信息,尝试匹配
|
// 回充已保存的检查方法
|
||||||
if (m.checkMethodId) {
|
if (m.checkMethodId) {
|
||||||
item.selectedMethod = item.methods.find(md => md.id === m.checkMethodId) || null;
|
item.selectedMethod = item.methods.find(md => String(md.id) === String(m.checkMethodId)) || null;
|
||||||
// 从已保存的方法中获取套餐信息
|
|
||||||
if (item.selectedMethod?.packageId) {
|
if (item.selectedMethod?.packageId) {
|
||||||
item.isPackage = true;
|
item.isPackage = true;
|
||||||
item.packageId = item.selectedMethod.packageId;
|
item.packageId = item.selectedMethod.packageId;
|
||||||
|
item.hasChildren = true; // #426修复
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!item.selectedMethod && item.methods.length) {
|
||||||
|
item.selectedMethod = pickDefaultMethod(item.methods, { packageId: item.packageId });
|
||||||
|
}
|
||||||
|
if (item.selectedMethod?.packageId) {
|
||||||
|
item.packageId = item.selectedMethod.packageId;
|
||||||
|
item.isPackage = true;
|
||||||
|
item.hasChildren = true; // #426修复
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('加载检查方法失败', err);
|
console.error('加载检查方法失败', err);
|
||||||
// 单个项目加载失败不影响其他项目,继续返回 item
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
}));
|
}));
|
||||||
|
// Bug #408修复: 确保明细数据正确加载到selectedItems
|
||||||
selectedItems.value = itemsWithMethods;
|
selectedItems.value = itemsWithMethods;
|
||||||
|
// 加载套餐明细(单个失败不影响其他项目和明细显示)
|
||||||
|
for (const it of selectedItems.value) {
|
||||||
|
if (getPackageCarrier(it)?.packageId) {
|
||||||
|
try {
|
||||||
|
await loadPackageDetailsForItem(it);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('加载套餐明细失败:', it.name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.expanded = !!getPackageCarrier(it)?.packageId;
|
||||||
|
}
|
||||||
syncCategoryChecked();
|
syncCategoryChecked();
|
||||||
// Bug #384修复: 回充后更新检查方法显示
|
// Bug #384修复: 回充后更新检查方法显示
|
||||||
updateMethodDisplay();
|
updateMethodDisplay();
|
||||||
// 修复【#408】:加载申请单详情后自动切换到检查明细页签,确保已加载的明细数据可见
|
// Bug #408修复: 加载申请单详情后自动切换到检查明细页签,确保已加载的明细数据可见
|
||||||
activeDetailTab.value = 'applyDetail';
|
activeDetailTab.value = 'applyDetail';
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('加载申请单详情失败', err);
|
console.error('加载申请单详情失败', err);
|
||||||
@@ -1141,6 +1360,8 @@ async function handleMethodSelect(checked, method, cat) {
|
|||||||
if (method.packageId) {
|
if (method.packageId) {
|
||||||
existingItem.isPackage = true;
|
existingItem.isPackage = true;
|
||||||
existingItem.packageId = method.packageId;
|
existingItem.packageId = method.packageId;
|
||||||
|
existingItem.hasChildren = true; // #426修复
|
||||||
|
existingItem.packageName = method.packageName || existingItem.packageName; // #428修复: 确保 packageName 同步
|
||||||
// 预加载套餐明细
|
// 预加载套餐明细
|
||||||
loadPackageDetailsForItem(existingItem);
|
loadPackageDetailsForItem(existingItem);
|
||||||
}
|
}
|
||||||
@@ -1168,12 +1389,14 @@ async function handleMethodSelect(checked, method, cat) {
|
|||||||
checkType: cat.typeName,
|
checkType: cat.typeName,
|
||||||
nationalCode: targetItem.nationalCode || '',
|
nationalCode: targetItem.nationalCode || '',
|
||||||
checked: true,
|
checked: true,
|
||||||
methods: [method],
|
methods: cat.methods || [method], // #428修复: 复制分类下全部方法,允许用户切换
|
||||||
selectedMethod: method,
|
selectedMethod: method,
|
||||||
expanded: false,
|
expanded: false,
|
||||||
// 从方法中获取套餐信息(优先级高于项目本身的 packageName)
|
// 从方法或项目中获取套餐信息
|
||||||
isPackage: !!method.packageId || !!targetItem.packageName,
|
isPackage: !!method.packageId || !!targetItem.packageName,
|
||||||
packageId: method.packageId || targetItem.packageId || null
|
packageId: method.packageId || targetItem.packageId || null,
|
||||||
|
packageName: method.packageName || targetItem.packageName || null, // #428修复: 复制 packageName,确保套餐明细可加载
|
||||||
|
hasChildren: !!(method.packageId || targetItem.packageId) // #426修复: 树形表格懒加载展开标记
|
||||||
};
|
};
|
||||||
selectedItems.value.push(newItem);
|
selectedItems.value.push(newItem);
|
||||||
|
|
||||||
@@ -1246,7 +1469,7 @@ async function handleItemSelect(checked, item, cat) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedItems.value.push({
|
const newRow = {
|
||||||
id: item.id, name: item.name,
|
id: item.id, name: item.name,
|
||||||
price: item.price, quantity: 1,
|
price: item.price, quantity: 1,
|
||||||
serviceFee: item.serviceFee || 0,
|
serviceFee: item.serviceFee || 0,
|
||||||
@@ -1257,11 +1480,28 @@ async function handleItemSelect(checked, item, cat) {
|
|||||||
checked: true,
|
checked: true,
|
||||||
methods: methods,
|
methods: methods,
|
||||||
selectedMethod: null,
|
selectedMethod: null,
|
||||||
expanded: false, // Bug #384修复: 新增展开状态,默认不展开
|
expanded: false,
|
||||||
isPackage: !!item.packageName, // Bug #428修复: 标记是否为套餐
|
isPackage: !!(item.packageId || item.packageName),
|
||||||
packageName: item.packageName || null, // Bug #426修复: 套餐名称,用于查找packageId
|
packageName: item.packageName || null,
|
||||||
packageId: item.packageId || null // Bug #428修复: 套餐ID
|
packageDetailsLoading: false,
|
||||||
});
|
packageId: item.packageId || null,
|
||||||
|
hasChildren: !!(item.packageId || item.packageName) // #426修复: 树形表格懒加载展开标记,支持 packageName 解析
|
||||||
|
};
|
||||||
|
selectedItems.value.push(newRow);
|
||||||
|
// 必须用数组里的响应式行,不能继续改局部 newRow:push 后列表内是 proxy,改 raw 对象不会触发右侧卡片更新(会一直卡在「加载中」)
|
||||||
|
const row = selectedItems.value[selectedItems.value.length - 1];
|
||||||
|
|
||||||
|
// 右侧不再展示「检查方法」列表:自动选默认方法(保存、计价仍依赖 selectedMethod)
|
||||||
|
if (methods.length >= 1) {
|
||||||
|
row.selectedMethod = pickDefaultMethod(methods, item);
|
||||||
|
}
|
||||||
|
updateMethodDisplay();
|
||||||
|
|
||||||
|
// 有套餐 ID 时默认展开(先显示加载区,明细写入行对象 packageDetailsDisplay)
|
||||||
|
row.expanded = !!getPackageCarrier(row)?.packageId;
|
||||||
|
if (getPackageCarrier(row)?.packageId) {
|
||||||
|
await loadPackageDetailsForItem(row);
|
||||||
|
}
|
||||||
|
|
||||||
// 自动回填执行科室:按检查项目类型 → 检查类型管理里配置的执行科室
|
// 自动回填执行科室:按检查项目类型 → 检查类型管理里配置的执行科室
|
||||||
if (selectedItems.value.length === 1 && cat?.performDeptName) {
|
if (selectedItems.value.length === 1 && cat?.performDeptName) {
|
||||||
@@ -1282,19 +1522,26 @@ async function handleItemSelect(checked, item, cat) {
|
|||||||
// Bug #382 修复:移除自动切换页签逻辑,保持当前页签状态
|
// Bug #382 修复:移除自动切换页签逻辑,保持当前页签状态
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bug #384修复: 展开/收起项目卡片
|
// Bug #384修复 + #426修复: 展开/收起项目卡片
|
||||||
async function toggleItemExpand(item) {
|
async function toggleItemExpand(item) {
|
||||||
item.expanded = !item.expanded;
|
item.expanded = !item.expanded;
|
||||||
// 如果是展开且该项目是套餐,加载套餐明细
|
if (item.expanded && (item.isPackage || item.packageName) && (!item.packageDetails || item.packageDetails.length === 0) && !item.packageDetailsLoading) {
|
||||||
if (item.expanded && item.isPackage && (!item.packageDetails || item.packageDetails.length === 0)) {
|
|
||||||
await loadPackageDetailsForItem(item);
|
await loadPackageDetailsForItem(item);
|
||||||
}
|
}
|
||||||
|
if (item.expanded && shouldShowPackageBody(item)) {
|
||||||
|
if (getPackageDetailsList(item).length === 0 && !item.packageDetailsLoading) {
|
||||||
|
await loadPackageDetailsForItem(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bug #384修复: 勾选框选择检查方法(单选逻辑)
|
// Bug #384修复: 勾选框选择检查方法(单选逻辑)
|
||||||
async function selectMethodCheckbox(checked, item, method) {
|
async function selectMethodCheckbox(checked, item, method) {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
item.selectedMethod = method;
|
item.selectedMethod = method;
|
||||||
|
if (item.expanded && method.packageId) {
|
||||||
|
loadPackageDetailsForItem(item);
|
||||||
|
}
|
||||||
// 动态加载该方法对应的套餐明细
|
// 动态加载该方法对应的套餐明细
|
||||||
await loadMethodPackageDetails(item, method);
|
await loadMethodPackageDetails(item, method);
|
||||||
} else {
|
} else {
|
||||||
@@ -1354,6 +1601,29 @@ async function loadMethodPackageDetails(item, method) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 检查明细表格中切换检查方法 */
|
||||||
|
async function onDetailMethodChange(row, val) {
|
||||||
|
row.selectedMethod = val || null;
|
||||||
|
if (val?.packageId) {
|
||||||
|
row.packageId = val.packageId;
|
||||||
|
row.isPackage = true;
|
||||||
|
row.hasChildren = true; // #426修复
|
||||||
|
}
|
||||||
|
row.packageDetailsDisplay = undefined;
|
||||||
|
const carrier = getPackageCarrier(row);
|
||||||
|
if (carrier) {
|
||||||
|
carrier.packageDetails = undefined;
|
||||||
|
}
|
||||||
|
updateMethodDisplay();
|
||||||
|
row.expanded = !!getPackageCarrier(row)?.packageId;
|
||||||
|
if (getPackageCarrier(row)?.packageId) {
|
||||||
|
await loadPackageDetailsForItem(row);
|
||||||
|
}
|
||||||
|
nextTick(() => {
|
||||||
|
form.totalAmount = totalAmountCalc.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Bug #384修复: 更新检查方法显示字段(联动)
|
// Bug #384修复: 更新检查方法显示字段(联动)
|
||||||
function updateMethodDisplay() {
|
function updateMethodDisplay() {
|
||||||
// 找到第一个有选中检查方法的项目
|
// 找到第一个有选中检查方法的项目
|
||||||
@@ -1443,6 +1713,19 @@ defineExpose({ getList });
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Bug #499: 查询过滤工具栏 */
|
||||||
|
.search-toolbar {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
}
|
||||||
|
.search-toolbar :deep(.el-form-item) {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.search-toolbar :deep(.el-form-item__label) {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
/* 底部区域:左表单 + 右分类 */
|
/* 底部区域:左表单 + 右分类 */
|
||||||
.bottom-section {
|
.bottom-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1485,7 +1768,7 @@ defineExpose({ getList });
|
|||||||
|
|
||||||
/* 右:分类面板 */
|
/* 右:分类面板 */
|
||||||
.category-panel {
|
.category-panel {
|
||||||
width: 380px;
|
width: 420px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -1520,6 +1803,7 @@ defineExpose({ getList });
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden; /* Bug #500: 防止切换时水平方向溢出导致抖动 */
|
overflow-x: hidden; /* Bug #500: 防止切换时水平方向溢出导致抖动 */
|
||||||
|
min-height: 120px; /* Bug #500: 固定最小高度,避免分类切换时 flex 容器高度突变 */
|
||||||
}
|
}
|
||||||
.empty-hint {
|
.empty-hint {
|
||||||
color: #909399;
|
color: #909399;
|
||||||
@@ -1620,8 +1904,11 @@ defineExpose({ getList });
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 已选择 tags */
|
/* 已选择 tags */
|
||||||
|
/* 已选择:加宽,避免套餐明细挤成一团 */
|
||||||
.selected-panel {
|
.selected-panel {
|
||||||
width: 140px; /* Bug #384修复: 加宽以适应展开内容 */
|
width: 220px;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 280px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -1629,9 +1916,11 @@ defineExpose({ getList });
|
|||||||
.selected-tags {
|
.selected-tags {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 6px;
|
gap: 8px;
|
||||||
|
padding-right: 2px;
|
||||||
}
|
}
|
||||||
.selected-tag {
|
.selected-tag {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
@@ -1644,30 +1933,40 @@ defineExpose({ getList });
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bug #384修复: 已选择项目卡片(可展开) */
|
/* 已选择项目卡片 */
|
||||||
.selected-item-card {
|
.selected-item-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: #F5F5F5;
|
background: #fff;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
border: 1px solid #e4e7ed;
|
border: 1px solid #e4e7ed;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected-item-card .card-header {
|
.selected-item-card .card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 8px 10px;
|
padding: 10px 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
gap: 4px;
|
gap: 8px;
|
||||||
|
background: linear-gradient(180deg, #f8fafc 0%, #f0f4f8 100%);
|
||||||
|
border-bottom: 1px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected-item-card .card-header:hover {
|
.selected-item-card .card-header:hover {
|
||||||
background: #E6F7FF;
|
background: linear-gradient(180deg, #ecf5ff 0%, #e3eef8 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-item-card.is-expanded .card-header {
|
||||||
|
border-bottom-color: #ebeef5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-name {
|
.card-name {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-size: 12px;
|
min-width: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -1675,19 +1974,23 @@ defineExpose({ getList });
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-price {
|
.card-price {
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
color: #1890FF;
|
color: #409eff;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expand-icon {
|
.expand-icon {
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
flex-shrink: 0;
|
||||||
transition: transform 0.2s;
|
transition: transform 0.2s;
|
||||||
|
transform: rotate(0deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.expand-icon.expanded {
|
.expand-icon.expanded {
|
||||||
transform: rotate(180deg);
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bug #428修复: 套餐明细列表样式 */
|
/* Bug #428修复: 套餐明细列表样式 */
|
||||||
@@ -1725,61 +2028,83 @@ defineExpose({ getList });
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bug #384修复: 检查方法勾选框列表 */
|
/* 展开区域 */
|
||||||
.method-list {
|
.selected-card-body {
|
||||||
padding: 6px 10px;
|
background: #fafbfc;
|
||||||
background: #fff;
|
}
|
||||||
border-top: 1px solid #e4e7ed;
|
|
||||||
|
.package-details-loading,
|
||||||
|
.package-details-empty {
|
||||||
|
padding: 12px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-details-empty {
|
||||||
|
color: #c0c4cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-details-list {
|
||||||
|
padding: 10px 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-details-head {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #909399;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
border-bottom: 1px dashed #dcdfe6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
gap: 8px 12px;
|
||||||
|
align-items: start;
|
||||||
|
padding: 10px 0;
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-row:last-of-type {
|
||||||
|
border-bottom: none;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-name {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #303133;
|
||||||
|
line-height: 1.5;
|
||||||
|
word-break: break-word;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.method-option {
|
.detail-qty {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-option :deep(.el-checkbox__label) {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-option .method-name {
|
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #606266;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-option .method-price {
|
|
||||||
font-size: 11px;
|
|
||||||
color: #e6a23c;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 选中方法后显示的套餐明细 */
|
|
||||||
.method-package-details {
|
|
||||||
margin-top: 4px;
|
|
||||||
padding: 4px 0;
|
|
||||||
border-top: 1px dashed #dcdfe6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-package-header {
|
|
||||||
padding: 2px 0 4px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-package-title {
|
|
||||||
font-size: 10px;
|
|
||||||
color: #909399;
|
color: #909399;
|
||||||
font-weight: 500;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
.method-package-loading {
|
.detail-price {
|
||||||
padding: 4px 0 4px 24px;
|
font-size: 12px;
|
||||||
font-size: 10px;
|
font-weight: 600;
|
||||||
color: #c0c4cc;
|
color: #e6a23c;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 折叠组件细节 */
|
/* 折叠组件细节 */
|
||||||
@@ -1792,10 +2117,10 @@ defineExpose({ getList });
|
|||||||
height: auto;
|
height: auto;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
/* Bug #500: 折叠内容添加平滑过渡动画,避免切换时高度跳变 */
|
/* Bug #500修复: 折叠内容使用明确属性过渡,避免 transition: all 导致子元素意外动画 */
|
||||||
:deep(.el-collapse-item__content) {
|
:deep(.el-collapse-item__content) {
|
||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
transition: all 0.3s ease;
|
transition: height 0.3s ease, max-height 0.3s ease;
|
||||||
}
|
}
|
||||||
/* Bug #500: 折叠面板动画容器,添加 overflow:hidden 防止展开时内容溢出导致闪烁 */
|
/* Bug #500: 折叠面板动画容器,添加 overflow:hidden 防止展开时内容溢出导致闪烁 */
|
||||||
:deep(.el-collapse-item__wrap) {
|
:deep(.el-collapse-item__wrap) {
|
||||||
|
|||||||
@@ -56,6 +56,13 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="申请单号" prop="applyNo" min-width="160" align="center" header-align="center" />
|
<el-table-column label="申请单号" prop="applyNo" min-width="160" align="center" header-align="center" />
|
||||||
|
<el-table-column label="单据状态" prop="applyStatus" width="100" align="center" header-align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="getStatusType(scope.row.applyStatus)" size="small">
|
||||||
|
{{ getStatusLabel(scope.row.applyStatus, scope.row) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="检验项目" prop="itemName" min-width="170px" align="center" header-align="center">
|
<el-table-column label="检验项目" prop="itemName" min-width="170px" align="center" header-align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span>{{ scope.row.itemName }}</span>
|
<span>{{ scope.row.itemName }}</span>
|
||||||
@@ -1445,6 +1452,26 @@ const formatAmount = (amount) => {
|
|||||||
return num.toFixed(2)
|
return num.toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 单据状态标签文字
|
||||||
|
const getStatusLabel = (applyStatus, row) => {
|
||||||
|
// applyStatus: 0=待开立, 1=已开立(已签发)
|
||||||
|
// 结合收费/执行标记推导更丰富的状态
|
||||||
|
if (applyStatus === 1) {
|
||||||
|
// 已收费后根据执行标记判断
|
||||||
|
if (row.needExecute === true) {
|
||||||
|
return '已执行'
|
||||||
|
}
|
||||||
|
return '已开立'
|
||||||
|
}
|
||||||
|
return '待开立'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单据状态标签颜色
|
||||||
|
const getStatusType = (applyStatus) => {
|
||||||
|
if (applyStatus === 1) return 'success'
|
||||||
|
return 'info'
|
||||||
|
}
|
||||||
|
|
||||||
// 格式化日期时间为字符串 YYYY-MM-DD HH:mm:ss
|
// 格式化日期时间为字符串 YYYY-MM-DD HH:mm:ss
|
||||||
const formatDateTime = (date) => {
|
const formatDateTime = (date) => {
|
||||||
if (!date) return ''
|
if (!date) return ''
|
||||||
|
|||||||
@@ -564,22 +564,74 @@ function handleRemoveItem(index) {
|
|||||||
editingGroup.value.detailList.splice(index, 1);
|
editingGroup.value.detailList.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 单击应用按钮
|
// 单击应用按钮(应用组套)
|
||||||
function handleUseOrderGroup(row) {
|
async function handleUseOrderGroup(row) {
|
||||||
if (!row.detailList || row.detailList.length === 0) {
|
if (!row.detailList || row.detailList.length === 0) {
|
||||||
ElMessage.warning('该组套没有明细项');
|
ElMessage.warning('该组套没有明细项');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔧 Bug 修复:组套保存时未持久化 orderDetailInfos,导致应用时缺失医嘱库信息。
|
||||||
|
// 通过 API 批量查询补全,确保 setValue 能获取到 adviceType、inventoryList、priceList 等关键字段。
|
||||||
|
const itemsMissingDetail = row.detailList.filter(
|
||||||
|
item => !item.orderDetailInfos || Object.keys(item.orderDetailInfos).length === 0
|
||||||
|
);
|
||||||
|
const detailMap = {};
|
||||||
|
if (itemsMissingDetail.length > 0) {
|
||||||
|
const ids = itemsMissingDetail
|
||||||
|
.map(item => item.orderDefinitionId)
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(',');
|
||||||
|
if (ids) {
|
||||||
|
try {
|
||||||
|
const res = await getAdviceBaseInfo({
|
||||||
|
adviceDefinitionIdParamList: ids,
|
||||||
|
organizationId: props.organizationId,
|
||||||
|
});
|
||||||
|
const records = res.data?.records || res.rows || [];
|
||||||
|
records.forEach(rec => {
|
||||||
|
if (rec.adviceDefinitionId) {
|
||||||
|
detailMap[rec.adviceDefinitionId] = rec;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// 批量查询失败,使用原始 orderDetailInfos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔧 辅助函数:根据字典 code 查找对应的 dictText
|
||||||
|
const findDictText = (dictList, code) => {
|
||||||
|
if (!code || !dictList?.value?.length) return '';
|
||||||
|
const found = dictList.value.find(d => d.value === code);
|
||||||
|
return found?.label || '';
|
||||||
|
};
|
||||||
|
|
||||||
// 🔧 数据预处理:确保每个明细项都有完整的医嘱信息
|
// 🔧 数据预处理:确保每个明细项都有完整的医嘱信息
|
||||||
const processedDetailList = row.detailList.map(item => {
|
const processedDetailList = row.detailList.map(item => {
|
||||||
const orderDetail = item.orderDetailInfos || {};
|
// 优先使用组套中已带的 orderDetailInfos,否则使用 API 查询结果
|
||||||
|
const orderDetail = (item.orderDetailInfos && Object.keys(item.orderDetailInfos).length > 0)
|
||||||
|
? item.orderDetailInfos
|
||||||
|
: (detailMap[item.orderDefinitionId] || {});
|
||||||
|
|
||||||
|
// 🔧 修复:组套明细只存了 methodCode/rateCode,没有 dictText。
|
||||||
|
// 用字典查找补充 dictText,确保界面显示正确的用法/频次名称。
|
||||||
|
const resolvedMethodCode = item.methodCode ?? orderDetail.methodCode;
|
||||||
|
const resolvedRateCode = item.rateCode ?? orderDetail.rateCode;
|
||||||
|
const methodCodeDictText = item.methodCode_dictText
|
||||||
|
|| findDictText(method_code, resolvedMethodCode)
|
||||||
|
|| orderDetail.methodCode_dictText
|
||||||
|
|| '';
|
||||||
|
const rateCodeDictText = item.rateCode_dictText
|
||||||
|
|| findDictText(rate_code, resolvedRateCode)
|
||||||
|
|| orderDetail.rateCode_dictText
|
||||||
|
|| '';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 组套明细字段
|
// 组套明细字段
|
||||||
...item,
|
...item,
|
||||||
|
|
||||||
// 医嘱库字段(可能为空,需要兜底)
|
// 医嘱库字段
|
||||||
adviceName: orderDetail.adviceName || item.orderDefinitionName || '未知项目',
|
adviceName: orderDetail.adviceName || item.orderDefinitionName || '未知项目',
|
||||||
adviceType: orderDetail.adviceType,
|
adviceType: orderDetail.adviceType,
|
||||||
adviceDefinitionId: item.orderDefinitionId || orderDetail.adviceDefinitionId,
|
adviceDefinitionId: item.orderDefinitionId || orderDetail.adviceDefinitionId,
|
||||||
@@ -589,36 +641,59 @@ function handleUseOrderGroup(row) {
|
|||||||
minUnitPrice: orderDetail.minUnitPrice,
|
minUnitPrice: orderDetail.minUnitPrice,
|
||||||
inventoryList: orderDetail.inventoryList || [],
|
inventoryList: orderDetail.inventoryList || [],
|
||||||
priceList: orderDetail.priceList || [],
|
priceList: orderDetail.priceList || [],
|
||||||
partPercent: orderDetail.partPercent || 1,
|
partPercent: orderDetail.partPercent ?? 1,
|
||||||
// 🔧 Bug #218 修复:positionId 可能存储在 item 本身,优先使用 item.positionId
|
partAttributeEnum: orderDetail.partAttributeEnum,
|
||||||
positionId: item.positionId || orderDetail.positionId,
|
unitConversionRatio: orderDetail.unitConversionRatio,
|
||||||
|
positionId: item.positionId ?? orderDetail.positionId,
|
||||||
defaultLotNumber: orderDetail.defaultLotNumber,
|
defaultLotNumber: orderDetail.defaultLotNumber,
|
||||||
|
|
||||||
// 单位信息
|
// 单位信息
|
||||||
unitCode: item.unitCode || orderDetail.unitCode,
|
unitCode: item.unitCode ?? orderDetail.unitCode,
|
||||||
|
categoryCode: item.categoryCode ?? orderDetail.categoryCode,
|
||||||
unitCodeName: item.unitCodeName || orderDetail.unitCode_dictText,
|
unitCodeName: item.unitCodeName || orderDetail.unitCode_dictText,
|
||||||
minUnitCode: orderDetail.minUnitCode,
|
minUnitCode: orderDetail.minUnitCode,
|
||||||
doseUnitCode: orderDetail.doseUnitCode,
|
doseUnitCode: orderDetail.doseUnitCode,
|
||||||
|
doseUnitCode_dictText: orderDetail.doseUnitCode_dictText || '',
|
||||||
|
|
||||||
|
// 药房/科室名称(setValue 通过库存查找设置,但需确保 orderDetail 中有)
|
||||||
|
positionName: orderDetail.positionName || '',
|
||||||
|
|
||||||
|
// 注射/皮试标识(表格列显示依赖这些字段)
|
||||||
|
injectFlag: orderDetail.injectFlag,
|
||||||
|
injectFlag_enumText: orderDetail.injectFlag_enumText || '',
|
||||||
|
skinTestFlag: orderDetail.skinTestFlag,
|
||||||
|
skinTestFlag_enumText: orderDetail.skinTestFlag_enumText || '',
|
||||||
|
|
||||||
|
// 字典文本(传递到 item 层级,避免后续代码依赖 mergedDetail 再查一次)
|
||||||
|
methodCode_dictText: methodCodeDictText,
|
||||||
|
rateCode_dictText: rateCodeDictText,
|
||||||
|
|
||||||
// 合并后的完整对象(用于 setValue)
|
// 合并后的完整对象(用于 setValue)
|
||||||
|
// 先展开 orderDetail 获取所有药品基础字段(categoryCode、minUnitCode、doseUnitCode、
|
||||||
|
// partPercent、partAttributeEnum、unitConversionRatio、defaultLotNumber 等),
|
||||||
|
// 再用组套用户覆盖值覆盖,确保单次剂量/频次/用法/用药天数/总量等不被丢失
|
||||||
mergedDetail: {
|
mergedDetail: {
|
||||||
...orderDetail,
|
...orderDetail,
|
||||||
adviceName: orderDetail.adviceName || item.orderDefinitionName || '未知项目',
|
adviceName: orderDetail.adviceName || item.orderDefinitionName || '未知项目',
|
||||||
adviceType: orderDetail.adviceType,
|
adviceType: orderDetail.adviceType,
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
unitCode: item.unitCode || orderDetail.unitCode,
|
unitCode: item.unitCode ?? orderDetail.unitCode,
|
||||||
|
categoryCode: item.categoryCode ?? orderDetail.categoryCode,
|
||||||
unitCodeName: item.unitCodeName,
|
unitCodeName: item.unitCodeName,
|
||||||
dose: item.dose || orderDetail.dose,
|
dose: item.dose ?? orderDetail.dose,
|
||||||
rateCode: item.rateCode || orderDetail.rateCode,
|
rateCode: resolvedRateCode,
|
||||||
methodCode: item.methodCode || orderDetail.methodCode,
|
rateCode_dictText: rateCodeDictText,
|
||||||
dispensePerDuration: item.dispensePerDuration || orderDetail.dispensePerDuration,
|
methodCode: resolvedMethodCode,
|
||||||
doseQuantity: item.doseQuantity,
|
methodCode_dictText: methodCodeDictText,
|
||||||
inventoryList: orderDetail.inventoryList || [],
|
dispensePerDuration: item.dispensePerDuration ?? orderDetail.dispensePerDuration,
|
||||||
priceList: orderDetail.priceList || [],
|
doseQuantity: item.doseQuantity ?? orderDetail.doseQuantity,
|
||||||
partPercent: orderDetail.partPercent || 1,
|
positionId: item.positionId ?? orderDetail.positionId,
|
||||||
// 🔧 Bug #218 修复:positionId 可能存储在 item 本身,优先使用 item.positionId
|
orgId: item.orgId ?? orderDetail.orgId,
|
||||||
positionId: item.positionId || orderDetail.positionId,
|
orgName: item.orgName ?? orderDetail.orgName,
|
||||||
defaultLotNumber: orderDetail.defaultLotNumber,
|
groupId: item.groupId,
|
||||||
|
groupOrder: item.groupOrder,
|
||||||
|
// 🔧 类型默认为临时医嘱(2=临时,1=长期)
|
||||||
|
therapyEnum: item.therapyEnum ?? orderDetail.therapyEnum ?? '2',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -635,9 +710,9 @@ function handlePreviewGroup(row) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 确认应用组套(从预览对话框)
|
// 确认应用组套(从预览对话框)
|
||||||
function confirmUseGroup() {
|
async function confirmUseGroup() {
|
||||||
if (currentGroup.value) {
|
if (currentGroup.value) {
|
||||||
handleUseOrderGroup(currentGroup.value);
|
await handleUseOrderGroup(currentGroup.value);
|
||||||
previewVisible.value = false;
|
previewVisible.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -315,6 +315,7 @@
|
|||||||
data-prop="dispensePerDuration">
|
data-prop="dispensePerDuration">
|
||||||
<el-input-number v-model="scope.row.dispensePerDuration" style="width: 80px" :min="1"
|
<el-input-number v-model="scope.row.dispensePerDuration" style="width: 80px" :min="1"
|
||||||
controls-position="right" :controls="false" :ref="(el) => (inputRefs.dispensePerDuration = el)"
|
controls-position="right" :controls="false" :ref="(el) => (inputRefs.dispensePerDuration = el)"
|
||||||
|
@input="calculateTotalAmount(scope.row, scope.$index)"
|
||||||
@change="calculateTotalAmount(scope.row, scope.$index)"
|
@change="calculateTotalAmount(scope.row, scope.$index)"
|
||||||
@keyup.enter.prevent="
|
@keyup.enter.prevent="
|
||||||
handleEnter('dispensePerDuration', scope.row, scope.$index)
|
handleEnter('dispensePerDuration', scope.row, scope.$index)
|
||||||
@@ -874,7 +875,7 @@ import { ArrowDown, Search, Memo, Minus, Plus, Edit, Delete } from '@element-plu
|
|||||||
import printUtils, { getPrinterList, PRINT_TEMPLATE, savePrinterToCache, } from '@/utils/printUtils';
|
import printUtils, { getPrinterList, PRINT_TEMPLATE, savePrinterToCache, } from '@/utils/printUtils';
|
||||||
import Template from "@/views/inpatientDoctor/home/emr/components/template.vue";
|
import Template from "@/views/inpatientDoctor/home/emr/components/template.vue";
|
||||||
|
|
||||||
const emit = defineEmits(['selectDiagnosis']);
|
const emit = defineEmits(['selectDiagnosis', 'inspectionListRefresh']);
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
const queryParams = ref({});
|
const queryParams = ref({});
|
||||||
const prescriptionList = ref([]);
|
const prescriptionList = ref([]);
|
||||||
@@ -2083,6 +2084,21 @@ function getOrgList() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 诊疗医嘱关联检验申请时 contentJson 含 applyNo */
|
||||||
|
function getInspectionApplyNoFromAdviceRow(row) {
|
||||||
|
if (!row || row.adviceType !== 3) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const raw = row.contentJson;
|
||||||
|
const j = raw ? (typeof raw === 'string' ? JSON.parse(raw) : raw) : {};
|
||||||
|
const no = j && j.applyNo != null ? String(j.applyNo).trim() : '';
|
||||||
|
return no || null;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
let selectRows = prescriptionRef.value.getSelectionRows();
|
let selectRows = prescriptionRef.value.getSelectionRows();
|
||||||
console.log('BugFix#219: handleDelete called, selectRows=', selectRows);
|
console.log('BugFix#219: handleDelete called, selectRows=', selectRows);
|
||||||
@@ -2262,12 +2278,31 @@ function handleDelete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (deleteList.length > 0) {
|
if (deleteList.length > 0) {
|
||||||
|
const hasLabLinked = deleteList.some((d) => {
|
||||||
|
const row = normalRows.find((r) => r.requestId === d.requestId);
|
||||||
|
return row && getInspectionApplyNoFromAdviceRow(row);
|
||||||
|
});
|
||||||
|
const runApiDelete = () => {
|
||||||
savePrescription({ adviceSaveList: deleteList }).then((res) => {
|
savePrescription({ adviceSaveList: deleteList }).then((res) => {
|
||||||
if (res.code == 200) {
|
if (res.code == 200) {
|
||||||
proxy.$modal.msgSuccess('删除成功');
|
proxy.$modal.msgSuccess('删除成功');
|
||||||
getListInfo(false);
|
getListInfo(false);
|
||||||
|
emit('inspectionListRefresh');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
if (hasLabLinked) {
|
||||||
|
proxy.$modal
|
||||||
|
.confirm(
|
||||||
|
'删除此医嘱将同时作废关联的检验申请单(检验页签中的同单申请及同单下相关医嘱)。是否继续?',
|
||||||
|
'删除确认',
|
||||||
|
{ type: 'warning' }
|
||||||
|
)
|
||||||
|
.then(runApiDelete)
|
||||||
|
.catch(() => {});
|
||||||
|
} else {
|
||||||
|
runApiDelete();
|
||||||
|
}
|
||||||
} else if (consultationRows.length == 0) {
|
} else if (consultationRows.length == 0) {
|
||||||
proxy.$modal.msgWarning('所选医嘱不可删除,请先撤回后再删除');
|
proxy.$modal.msgWarning('所选医嘱不可删除,请先撤回后再删除');
|
||||||
return;
|
return;
|
||||||
@@ -3418,7 +3453,12 @@ async function setValue(row) {
|
|||||||
console.log('[BugFix] setValue - prescriptionList[rowIndex].adviceType_dictText:', prescriptionList.value[rowIndex.value].adviceType_dictText);
|
console.log('[BugFix] setValue - prescriptionList[rowIndex].adviceType_dictText:', prescriptionList.value[rowIndex.value].adviceType_dictText);
|
||||||
// 🔧 Bug #455: 诊疗医嘱(adviceType=3)的执行科室默认使用患者就诊科室,
|
// 🔧 Bug #455: 诊疗医嘱(adviceType=3)的执行科室默认使用患者就诊科室,
|
||||||
// 不使用positionId(诊疗目录配置的执行科室),避免配置ID不在机构树中导致显示原始ID
|
// 不使用positionId(诊疗目录配置的执行科室),避免配置ID不在机构树中导致显示原始ID
|
||||||
if (Number(row.adviceType) != 3) {
|
if (Number(row.adviceType) == 3) {
|
||||||
|
// 覆盖 catalog 传来的 positionId/orgId,使用患者就诊科室
|
||||||
|
prescriptionList.value[rowIndex.value].orgId = props.patientInfo?.orgId;
|
||||||
|
prescriptionList.value[rowIndex.value].positionId = props.patientInfo?.orgId;
|
||||||
|
prescriptionList.value[rowIndex.value].positionName = findOrgNameById(props.patientInfo?.orgId) || props.patientInfo?.orgName || '';
|
||||||
|
} else {
|
||||||
prescriptionList.value[rowIndex.value].orgId = row.positionId || props.patientInfo?.orgId;
|
prescriptionList.value[rowIndex.value].orgId = row.positionId || props.patientInfo?.orgId;
|
||||||
}
|
}
|
||||||
prescriptionList.value[rowIndex.value].dose = row.dose || row.doseQuantity;
|
prescriptionList.value[rowIndex.value].dose = row.dose || row.doseQuantity;
|
||||||
@@ -3551,13 +3591,10 @@ async function setValue(row) {
|
|||||||
prescriptionList.value[rowIndex.value].categoryEnum = 31; // 会诊的category_enum设置为31
|
prescriptionList.value[rowIndex.value].categoryEnum = 31; // 会诊的category_enum设置为31
|
||||||
} else {
|
} else {
|
||||||
// 诊疗类型(adviceType == 3)
|
// 诊疗类型(adviceType == 3)
|
||||||
// 🔧 Bug Fix #238: 诊疗项目默认使用患者就诊科室
|
// 🔧 Bug #455: 诊疗项目执行科室强制使用患者就诊科室,
|
||||||
if (!prescriptionList.value[rowIndex.value].orgId) {
|
// 不使用目录配置的执行科室(可能是错误ID或占位符,导致显示原始ID)
|
||||||
prescriptionList.value[rowIndex.value].orgId = props.patientInfo.orgId;
|
prescriptionList.value[rowIndex.value].orgId = props.patientInfo.orgId;
|
||||||
}
|
prescriptionList.value[rowIndex.value].positionName = findOrgNameById(props.patientInfo.orgId) || props.patientInfo.orgName || '';
|
||||||
if (!prescriptionList.value[rowIndex.value].positionName) {
|
|
||||||
prescriptionList.value[rowIndex.value].positionName = findOrgNameById(prescriptionList.value[rowIndex.value].orgId) || props.patientInfo.orgName || '';
|
|
||||||
}
|
|
||||||
// 🔧 Bug #218 修复:使用组套中维护的quantity,如果没有则默认1
|
// 🔧 Bug #218 修复:使用组套中维护的quantity,如果没有则默认1
|
||||||
prescriptionList.value[rowIndex.value].quantity = row.quantity || 1;
|
prescriptionList.value[rowIndex.value].quantity = row.quantity || 1;
|
||||||
// 🔧 Bug #144 修复:安全访问 priceList,防止 orderDetailInfos 为空时出错
|
// 🔧 Bug #144 修复:安全访问 priceList,防止 orderDetailInfos 为空时出错
|
||||||
@@ -3625,6 +3662,13 @@ function handleSaveGroup(orderGroupList) {
|
|||||||
defaultLotNumber: item.orderDetailInfos?.defaultLotNumber,
|
defaultLotNumber: item.orderDetailInfos?.defaultLotNumber,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 🔧 Bug 修复:字典查找兜底,防止 mergedDetail 中 dictText 为空
|
||||||
|
const findDictText = (dictList, code) => {
|
||||||
|
if (!code || !dictList?.length) return '';
|
||||||
|
const found = dictList.find(d => d.value === code);
|
||||||
|
return found?.label || '';
|
||||||
|
};
|
||||||
|
|
||||||
// 在 setValue 之前预初始化空行
|
// 在 setValue 之前预初始化空行
|
||||||
prescriptionList.value[rowIndex.value] = {
|
prescriptionList.value[rowIndex.value] = {
|
||||||
uniqueKey: nextId.value++,
|
uniqueKey: nextId.value++,
|
||||||
@@ -3635,40 +3679,105 @@ function handleSaveGroup(orderGroupList) {
|
|||||||
// 使用医嘱项目详情设置值
|
// 使用医嘱项目详情设置值
|
||||||
setValue(mergedDetail);
|
setValue(mergedDetail);
|
||||||
|
|
||||||
|
// 🔧 Bug 修复:使用 mergedDetail 优先,避免 item 中 undefined 覆盖 setValue 中已设置的字段
|
||||||
|
const resolvedQuantity = mergedDetail.quantity ?? item.quantity ?? 1;
|
||||||
|
const resolvedDose = mergedDetail.dose ?? item.dose;
|
||||||
|
const resolvedMethodCode = mergedDetail.methodCode ?? item.methodCode;
|
||||||
|
const resolvedRateCode = mergedDetail.rateCode ?? item.rateCode;
|
||||||
|
const resolvedUnitCode = mergedDetail.unitCode ?? item.unitCode;
|
||||||
|
|
||||||
|
// 🔧 Bug 修复:setValue 可能因库存不足提前 return,导致 unitPrice/minUnitPrice 等字段未设置。
|
||||||
|
// 从 mergedDetail 或 item 中获取兜底值,避免价格计算产生 NaN。
|
||||||
|
const safePrice = (val) => {
|
||||||
|
const n = Number(val);
|
||||||
|
return (n !== undefined && n !== null && !isNaN(n) && isFinite(n)) ? n : 0;
|
||||||
|
};
|
||||||
|
const resolvedUnitPrice = safePrice(
|
||||||
|
prescriptionList.value[rowIndex.value]?.unitPrice
|
||||||
|
?? mergedDetail.unitPrice
|
||||||
|
?? item.unitPrice
|
||||||
|
?? 0
|
||||||
|
);
|
||||||
|
const resolvedMinUnitPrice = safePrice(
|
||||||
|
prescriptionList.value[rowIndex.value]?.minUnitPrice
|
||||||
|
?? mergedDetail.minUnitPrice
|
||||||
|
?? item.minUnitPrice
|
||||||
|
?? 0
|
||||||
|
);
|
||||||
|
const resolvedPartPercent = safePrice(
|
||||||
|
item.orderDetailInfos?.partPercent
|
||||||
|
?? mergedDetail.partPercent
|
||||||
|
?? 1
|
||||||
|
);
|
||||||
|
|
||||||
// 创建新的处方项目
|
// 创建新的处方项目
|
||||||
const newRow = {
|
const newRow = {
|
||||||
...prescriptionList.value[rowIndex.value],
|
...prescriptionList.value[rowIndex.value],
|
||||||
patientId: props.patientInfo.patientId,
|
patientId: props.patientInfo.patientId,
|
||||||
encounterId: props.patientInfo.encounterId,
|
encounterId: props.patientInfo.encounterId,
|
||||||
accountId: accountId.value,
|
accountId: accountId.value,
|
||||||
quantity: item.quantity,
|
quantity: resolvedQuantity,
|
||||||
methodCode: item.methodCode,
|
methodCode: resolvedMethodCode,
|
||||||
rateCode: item.rateCode,
|
methodCode_dictText: mergedDetail.methodCode_dictText
|
||||||
dispensePerDuration: item.dispensePerDuration,
|
|| findDictText(method_code.value, resolvedMethodCode)
|
||||||
dose: item.dose,
|
|| '',
|
||||||
doseQuantity: item.doseQuantity,
|
rateCode: resolvedRateCode,
|
||||||
|
rateCode_dictText: mergedDetail.rateCode_dictText
|
||||||
|
|| findDictText(rate_code.value, resolvedRateCode)
|
||||||
|
|| '',
|
||||||
|
dispensePerDuration: mergedDetail.dispensePerDuration ?? item.dispensePerDuration,
|
||||||
|
dose: resolvedDose,
|
||||||
|
doseQuantity: mergedDetail.doseQuantity ?? item.doseQuantity,
|
||||||
executeNum: 1,
|
executeNum: 1,
|
||||||
unitCode: item.unitCode,
|
unitCode: resolvedUnitCode,
|
||||||
unitCode_dictText: item.unitCodeName || '',
|
unitCode_dictText: item.unitCodeName
|
||||||
|
|| mergedDetail.unitCodeName
|
||||||
|
|| findDictText(unit_code.value, resolvedUnitCode)
|
||||||
|
|| '',
|
||||||
|
doseUnitCode: mergedDetail.doseUnitCode,
|
||||||
|
doseUnitCode_dictText: mergedDetail.doseUnitCode_dictText || '',
|
||||||
|
// 🔧 确保 price/adviceType 字段有安全默认值(避免 NaN 导致模板条件判断失效)
|
||||||
|
unitPrice: resolvedUnitPrice,
|
||||||
|
minUnitPrice: resolvedMinUnitPrice,
|
||||||
|
unitTempPrice: resolvedUnitPrice,
|
||||||
|
adviceType: prescriptionList.value[rowIndex.value]?.adviceType
|
||||||
|
|| Number(mergedDetail.adviceType)
|
||||||
|
|| Number(item.adviceType)
|
||||||
|
|| 0,
|
||||||
|
adviceType_dictText: prescriptionList.value[rowIndex.value]?.adviceType_dictText
|
||||||
|
|| mergedDetail.adviceType_dictText
|
||||||
|
|| item.adviceType_dictText
|
||||||
|
|| '',
|
||||||
statusEnum: 1,
|
statusEnum: 1,
|
||||||
|
// 🔧 类型:组套应用默认为临时医嘱(2=临时,1=长期)
|
||||||
|
therapyEnum: mergedDetail.therapyEnum ?? item.therapyEnum ?? '2',
|
||||||
// 🔧 修复执行科室逻辑:优先使用 orgId(所属科室),其次 positionId
|
// 🔧 修复执行科室逻辑:优先使用 orgId(所属科室),其次 positionId
|
||||||
orgId: item.orderDetailInfos?.orgId || mergedDetail.orgId || item.positionId || item.orderDetailInfos?.positionId || mergedDetail.positionId,
|
// 🔧 Bug #455: 诊疗类(adviceType=3)使用患者就诊科室,不使用目录配置的ID
|
||||||
|
orgId: item.adviceType === 3
|
||||||
|
? props.patientInfo?.orgId
|
||||||
|
: (item.orderDetailInfos?.orgId || mergedDetail.orgId || item.positionId || item.orderDetailInfos?.positionId || mergedDetail.positionId),
|
||||||
|
positionName: prescriptionList.value[rowIndex.value]?.positionName
|
||||||
|
|| mergedDetail.orgName
|
||||||
|
|| mergedDetail.positionName
|
||||||
|
|| findOrgNameById(mergedDetail.orgId || props.patientInfo?.orgId)
|
||||||
|
|| '',
|
||||||
dbOpType: prescriptionList.value[rowIndex.value].requestId ? '2' : '1',
|
dbOpType: prescriptionList.value[rowIndex.value].requestId ? '2' : '1',
|
||||||
conditionId: conditionId.value,
|
conditionId: conditionId.value,
|
||||||
conditionDefinitionId: conditionDefinitionId.value,
|
conditionDefinitionId: conditionDefinitionId.value,
|
||||||
encounterDiagnosisId: encounterDiagnosisId.value,
|
encounterDiagnosisId: encounterDiagnosisId.value,
|
||||||
|
diagnosisName: diagnosisName.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 计算价格和总量
|
// 计算价格和总量(使用安全值)
|
||||||
const unitInfo = unitCodeList.value.find((k) => k.value == item.unitCode);
|
const unitInfo = unitCodeList.value.find((k) => k.value == resolvedUnitCode);
|
||||||
if (unitInfo && unitInfo.type == 'minUnit') {
|
if (unitInfo && unitInfo.type == 'minUnit') {
|
||||||
newRow.price = newRow.minUnitPrice;
|
newRow.price = resolvedMinUnitPrice;
|
||||||
newRow.totalPrice = (item.quantity * newRow.minUnitPrice).toFixed(6);
|
newRow.totalPrice = (resolvedQuantity * resolvedMinUnitPrice).toFixed(6);
|
||||||
newRow.minUnitQuantity = item.quantity;
|
newRow.minUnitQuantity = resolvedQuantity;
|
||||||
} else {
|
} else {
|
||||||
newRow.price = newRow.unitPrice;
|
newRow.price = resolvedUnitPrice;
|
||||||
newRow.totalPrice = (item.quantity * newRow.unitPrice).toFixed(6);
|
newRow.totalPrice = (resolvedQuantity * resolvedUnitPrice).toFixed(6);
|
||||||
newRow.minUnitQuantity = item.quantity * (item.orderDetailInfos?.partPercent || mergedDetail.partPercent || 1);
|
newRow.minUnitQuantity = resolvedQuantity * resolvedPartPercent;
|
||||||
}
|
}
|
||||||
|
|
||||||
newRow.contentJson = JSON.stringify(newRow);
|
newRow.contentJson = JSON.stringify(newRow);
|
||||||
|
|||||||
@@ -633,7 +633,12 @@ function getList() {
|
|||||||
pageSize: 100,
|
pageSize: 100,
|
||||||
encounterId: props.patientInfo.encounterId
|
encounterId: props.patientInfo.encounterId
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
surgeryList.value = res.data.records || []
|
if (res.code === 200) {
|
||||||
|
surgeryList.value = res.data?.records || []
|
||||||
|
} else {
|
||||||
|
proxy.$modal.msgError(res.msg || '数据加载失败,请稍后重试')
|
||||||
|
surgeryList.value = []
|
||||||
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error('获取手术列表失败:', error)
|
console.error('获取手术列表失败:', error)
|
||||||
proxy.$modal.msgError('数据加载失败,请稍后重试')
|
proxy.$modal.msgError('数据加载失败,请稍后重试')
|
||||||
@@ -1133,13 +1138,12 @@ function submitForm() {
|
|||||||
// 新增手术
|
// 新增手术
|
||||||
addSurgery(form.value).then((res) => {
|
addSurgery(form.value).then((res) => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
proxy.$modal.msgSuccess(res.msg || '手术申请提交成功!')
|
proxy.$modal.msgSuccess('手术申请提交成功!')
|
||||||
// 保存麻醉方式
|
// 保存麻醉方式
|
||||||
sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum)
|
sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum)
|
||||||
open.value = false
|
open.value = false
|
||||||
|
getList() // 提交成功后直接刷新列表
|
||||||
emit('saved') // 通知父组件刷新医嘱列表
|
emit('saved') // 通知父组件刷新医嘱列表
|
||||||
// 刷新手术申请列表
|
|
||||||
getList()
|
|
||||||
} else {
|
} else {
|
||||||
proxy.$modal.msgError(res.msg || '新增手术失败,请检查表单信息')
|
proxy.$modal.msgError(res.msg || '新增手术失败,请检查表单信息')
|
||||||
}
|
}
|
||||||
@@ -1151,13 +1155,12 @@ function submitForm() {
|
|||||||
// 修改手术
|
// 修改手术
|
||||||
updateSurgery(form.value).then((res) => {
|
updateSurgery(form.value).then((res) => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
proxy.$modal.msgSuccess(res.msg || '手术申请修改成功!')
|
proxy.$modal.msgSuccess('手术申请修改成功!')
|
||||||
// 保存麻醉方式
|
// 保存麻醉方式
|
||||||
sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum)
|
sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum)
|
||||||
open.value = false
|
open.value = false
|
||||||
|
getList() // 修改成功后直接刷新列表
|
||||||
emit('saved') // 通知父组件刷新医嘱列表
|
emit('saved') // 通知父组件刷新医嘱列表
|
||||||
// 刷新手术申请列表
|
|
||||||
getList()
|
|
||||||
} else {
|
} else {
|
||||||
proxy.$modal.msgError(res.msg || '更新手术失败,请检查表单信息')
|
proxy.$modal.msgError(res.msg || '更新手术失败,请检查表单信息')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,7 +138,8 @@
|
|||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="医嘱" name="prescription">
|
<el-tab-pane label="医嘱" name="prescription">
|
||||||
<prescriptionlist :patientInfo="patientInfo" ref="prescriptionRef" :activeTab="activeTab"
|
<prescriptionlist :patientInfo="patientInfo" ref="prescriptionRef" :activeTab="activeTab"
|
||||||
:outpatientEmrSaved="outpatientEmrSaved" />
|
:outpatientEmrSaved="outpatientEmrSaved"
|
||||||
|
@inspectionListRefresh="refreshInspectionListFromAdvice" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="中医" name="tcm">
|
<el-tab-pane label="中医" name="tcm">
|
||||||
<tcmAdvice :patientInfo="patientInfo" ref="tcmRef" />
|
<tcmAdvice :patientInfo="patientInfo" ref="tcmRef" />
|
||||||
@@ -152,7 +153,7 @@
|
|||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="手术申请" name="surgery">
|
<el-tab-pane label="手术申请" name="surgery">
|
||||||
<surgeryApplication :patientInfo="patientInfo" :activeTab="activeTab" ref="surgeryRef"
|
<surgeryApplication :patientInfo="patientInfo" :activeTab="activeTab" ref="surgeryRef"
|
||||||
@saved="() => prescriptionRef?.getListInfo()" />
|
@saved="() => { prescriptionRef?.getListInfo(); surgeryRef?.getList() }" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="电子处方" name="eprescription">
|
<el-tab-pane label="电子处方" name="eprescription">
|
||||||
<eprescriptionlist :patientInfo="patientInfo" ref="eprescriptionRef" />
|
<eprescriptionlist :patientInfo="patientInfo" ref="eprescriptionRef" />
|
||||||
@@ -312,6 +313,9 @@ const patientDrawerRef = ref();
|
|||||||
const prescriptionRef = ref();
|
const prescriptionRef = ref();
|
||||||
const tcmRef = ref();
|
const tcmRef = ref();
|
||||||
const inspectionRef = ref();
|
const inspectionRef = ref();
|
||||||
|
function refreshInspectionListFromAdvice() {
|
||||||
|
inspectionRef.value?.getList?.();
|
||||||
|
}
|
||||||
const examinationRef = ref();
|
const examinationRef = ref();
|
||||||
const surgeryRef = ref();
|
const surgeryRef = ref();
|
||||||
const emrRef = ref();
|
const emrRef = ref();
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ const currentSelectRow = ref<any>({});
|
|||||||
const queryParams = ref({
|
const queryParams = ref({
|
||||||
pageSize: 100,
|
pageSize: 100,
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
adviceTypes: '1,2,3,6',
|
adviceTypes: [1, 2, 3, 6],
|
||||||
searchKey: '',
|
searchKey: '',
|
||||||
organizationId: '',
|
organizationId: '',
|
||||||
categoryCode: '',
|
categoryCode: '',
|
||||||
@@ -88,10 +88,10 @@ const tableColumns = computed<TableColumn[]>(() => [
|
|||||||
function refresh(adviceType: any, categoryCode: string, searchKey: string) {
|
function refresh(adviceType: any, categoryCode: string, searchKey: string) {
|
||||||
// 有搜索词时跨类型搜索,避免用户输入"级护理"但因当前adviceType为药品而搜不到诊疗类护理项目
|
// 有搜索词时跨类型搜索,避免用户输入"级护理"但因当前adviceType为药品而搜不到诊疗类护理项目
|
||||||
if (searchKey) {
|
if (searchKey) {
|
||||||
queryParams.value.adviceTypes = '1,2,3,6';
|
queryParams.value.adviceTypes = [1, 2, 3, 6];
|
||||||
} else {
|
} else {
|
||||||
queryParams.value.adviceTypes =
|
queryParams.value.adviceTypes =
|
||||||
adviceType !== undefined && adviceType !== '' ? String(adviceType) : '1,2,3,6';
|
adviceType !== undefined && adviceType !== '' ? [parseInt(adviceType)] : [1, 2, 3, 6];
|
||||||
}
|
}
|
||||||
queryParams.value.categoryCode = categoryCode || '';
|
queryParams.value.categoryCode = categoryCode || '';
|
||||||
queryParams.value.searchKey = searchKey || '';
|
queryParams.value.searchKey = searchKey || '';
|
||||||
@@ -131,7 +131,8 @@ function getList() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((err) => {
|
||||||
|
console.warn('医嘱基础信息加载失败:', err);
|
||||||
adviceBaseList.value = [];
|
adviceBaseList.value = [];
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
|
|||||||
@@ -49,6 +49,15 @@
|
|||||||
<el-option label="已作废" value="7" />
|
<el-option label="已作废" value="7" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="关键字">
|
||||||
|
<el-input
|
||||||
|
v-model="filterForm.keyword"
|
||||||
|
placeholder="申请单号 / 检查项目名称"
|
||||||
|
clearable
|
||||||
|
style="width: 220px"
|
||||||
|
@keyup.enter="handleSearch"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" @click="handleSearch" :loading="loading">
|
<el-button type="primary" @click="handleSearch" :loading="loading">
|
||||||
<el-icon><Search /></el-icon>
|
<el-icon><Search /></el-icon>
|
||||||
@@ -77,18 +86,53 @@
|
|||||||
</template>
|
</template>
|
||||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||||
<el-table-column prop="patientName" label="患者姓名" width="120" />
|
<el-table-column prop="patientName" label="患者姓名" width="120" />
|
||||||
<el-table-column prop="name" label="申请单名称" width="140" />
|
<el-table-column label="申请单名称" width="140">
|
||||||
<el-table-column prop="createTime" label="创建时间" width="160" />
|
|
||||||
<el-table-column prop="prescriptionNo" label="申请单号" width="140" />
|
|
||||||
<el-table-column prop="requesterId_dictText" label="申请者" width="120" />
|
|
||||||
<el-table-column label="申请单状态" width="120" align="center">
|
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span>{{ parseStatus(scope.row.status) }}</span>
|
<span>{{ buildApplicationName(scope.row) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" align="center" fixed="right">
|
<el-table-column prop="createTime" label="创建时间" width="160" />
|
||||||
|
<el-table-column prop="prescriptionNo" label="申请单号" width="140" />
|
||||||
|
<el-table-column label="申请单状态" width="120" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
|
<el-tag :type="getStatusTagType(scope.row.status)" effect="plain" round>
|
||||||
|
{{ parseStatus(scope.row.status) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="requesterId_dictText" label="申请者" width="120" />
|
||||||
|
<el-table-column label="操作" width="280" align="center" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<!-- 详情 - 所有状态都显示 -->
|
||||||
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
|
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
|
||||||
|
<!-- 待签发:修改、删除 -->
|
||||||
|
<template v-if="scope.row.status === '0' || scope.row.status === 0">
|
||||||
|
<el-button link type="primary" @click="handleEdit(scope.row)">修改</el-button>
|
||||||
|
<el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
<!-- 已签发:撤回 -->
|
||||||
|
<template v-else-if="scope.row.status === '1' || scope.row.status === 1">
|
||||||
|
<el-button link type="warning" @click="handleRecall(scope.row)">撤回</el-button>
|
||||||
|
</template>
|
||||||
|
<!-- 已校对/待接收:打印 -->
|
||||||
|
<template v-else-if="scope.row.status === '2' || scope.row.status === 2 || scope.row.status === '3' || scope.row.status === 3">
|
||||||
|
<el-button link type="primary" @click="handlePrint(scope.row)">打印</el-button>
|
||||||
|
</template>
|
||||||
|
<!-- 已接收/已检查:看报告 -->
|
||||||
|
<template v-else-if="scope.row.status === '4' || scope.row.status === 4 || scope.row.status === '5' || scope.row.status === 5">
|
||||||
|
<el-button link type="success" @click="handleViewReport(scope.row)">看报告</el-button>
|
||||||
|
</template>
|
||||||
|
<!-- 已出报告:打印、看报告 -->
|
||||||
|
<template v-else-if="scope.row.status === '6' || scope.row.status === 6">
|
||||||
|
<el-button link type="primary" @click="handlePrint(scope.row)">打印</el-button>
|
||||||
|
<el-button link type="success" @click="handleViewReport(scope.row)">看报告</el-button>
|
||||||
|
</template>
|
||||||
|
<!-- 已作废:无额外按钮 -->
|
||||||
|
<template v-else-if="scope.row.status === '7' || scope.row.status === 7">
|
||||||
|
</template>
|
||||||
|
<!-- 其他/未知状态:仅详情 -->
|
||||||
|
<template v-else>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -137,7 +181,7 @@
|
|||||||
<el-descriptions title="申请单描述" :column="2">
|
<el-descriptions title="申请单描述" :column="2">
|
||||||
<template v-for="(value, key) in descJsonData" :key="key">
|
<template v-for="(value, key) in descJsonData" :key="key">
|
||||||
<el-descriptions-item v-if="isFieldMatched(key)" :label="getFieldLabel(key)">
|
<el-descriptions-item v-if="isFieldMatched(key)" :label="getFieldLabel(key)">
|
||||||
{{ value || '-' }}
|
{{ transformField(key, value) || '-' }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
</template>
|
</template>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
@@ -160,15 +204,103 @@
|
|||||||
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 修改申请单弹窗 - 复用检查申请单组件 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="editDialogVisible"
|
||||||
|
title="修改检查申请"
|
||||||
|
width="1200px"
|
||||||
|
destroy-on-close
|
||||||
|
top="5vh"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
>
|
||||||
|
<MedicalExaminations
|
||||||
|
v-if="editDialogVisible"
|
||||||
|
ref="editFormRef"
|
||||||
|
:is-edit-mode="true"
|
||||||
|
:edit-data="editingRow"
|
||||||
|
:external-patient-info="patientInfo"
|
||||||
|
@submit-ok="handleEditSuccess"
|
||||||
|
/>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="editDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleEditSubmit">确认</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 查看报告弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="reportDialogVisible"
|
||||||
|
:title="'检查报告 - ' + (reportData?.prescriptionNo || '')"
|
||||||
|
width="900px"
|
||||||
|
destroy-on-close
|
||||||
|
top="5vh"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
>
|
||||||
|
<div v-loading="reportLoading" class="report-viewer">
|
||||||
|
<!-- 报告基本信息 -->
|
||||||
|
<div v-if="reportData" class="report-viewer-container">
|
||||||
|
<el-descriptions title="报告信息" :column="2" border size="small">
|
||||||
|
<el-descriptions-item label="患者姓名">{{ reportData.patientName || reportRow?.patientName || '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="申请单号">{{ reportData.prescriptionNo || reportRow?.prescriptionNo || '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="申请单名称">{{ reportData.name || reportRow?.name || '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="报告时间">{{ reportData.reportTime || '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="诊断意见" :span="2">{{ reportData.diagnosis || reportData.conclusion || '-' }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
|
||||||
|
<!-- 报告详细内容 -->
|
||||||
|
<div v-if="reportData.content" class="report-content-section">
|
||||||
|
<div class="section-title">报告内容</div>
|
||||||
|
<div class="report-content-text">{{ reportData.content }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 影像预览 - PACS链接 -->
|
||||||
|
<div v-if="reportData.imageUrl || reportData.pacsUrl" class="report-image-section">
|
||||||
|
<div class="section-title">
|
||||||
|
影像预览
|
||||||
|
<el-button
|
||||||
|
v-if="reportData.imageUrl || reportData.pacsUrl"
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
plain
|
||||||
|
style="margin-left: 12px;"
|
||||||
|
@click="openPacsLink"
|
||||||
|
>
|
||||||
|
<el-icon><Link /></el-icon>
|
||||||
|
打开PACS影像
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<iframe
|
||||||
|
v-if="reportData.imageUrl"
|
||||||
|
:src="reportData.imageUrl"
|
||||||
|
class="report-iframe"
|
||||||
|
frameborder="0"
|
||||||
|
/>
|
||||||
|
<el-empty v-else-if="!reportData.imageUrl && reportData.pacsUrl" description="点击上方按钮打开PACS影像" :image-size="60" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 完全无数据时的提示 -->
|
||||||
|
<el-empty v-if="!reportData.content && !reportData.imageUrl && !reportData.pacsUrl" description="暂无详细报告数据" :image-size="60" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 未获取到报告 -->
|
||||||
|
<el-empty v-else description="暂未生成报告" :image-size="80" />
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="reportDialogVisible = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {computed, getCurrentInstance, ref, watch} from 'vue';
|
import {computed, getCurrentInstance, ref, watch, nextTick} from 'vue';
|
||||||
import {Refresh, Search} from '@element-plus/icons-vue';
|
import {Refresh, Search, Link} from '@element-plus/icons-vue';
|
||||||
import {patientInfo} from '../../store/patient.js';
|
import {patientInfo} from '../../store/patient.js';
|
||||||
import {getCheck} from './api';
|
import {getCheck, deleteRequestForm, withdrawRequestForm, getTestResult} from './api';
|
||||||
import {getDepartmentList} from '@/api/public.js';
|
import {getDepartmentList} from '@/api/public.js';
|
||||||
|
import {getApplicationList, saveCheckd} from '../order/applicationForm/api';
|
||||||
|
import MedicalExaminations from '../order/applicationForm/medicalExaminations.vue';
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance();
|
const { proxy } = getCurrentInstance();
|
||||||
|
|
||||||
@@ -178,11 +310,38 @@ const detailDialogVisible = ref(false);
|
|||||||
const currentDetail = ref(null);
|
const currentDetail = ref(null);
|
||||||
const descJsonData = ref(null);
|
const descJsonData = ref(null);
|
||||||
const orgOptions = ref([]);
|
const orgOptions = ref([]);
|
||||||
|
const editForm = ref({
|
||||||
|
name: '',
|
||||||
|
targetDepartment: '',
|
||||||
|
symptom: '',
|
||||||
|
sign: '',
|
||||||
|
clinicalDiagnosis: '',
|
||||||
|
otherDiagnosis: '',
|
||||||
|
relatedResult: '',
|
||||||
|
attention: '',
|
||||||
|
examinationPurpose: '',
|
||||||
|
medicalHistorySummary: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 报告弹窗相关
|
||||||
|
const reportDialogVisible = ref(false);
|
||||||
|
const reportLoading = ref(false);
|
||||||
|
const reportData = ref(null);
|
||||||
|
const reportRow = ref(null);
|
||||||
|
|
||||||
|
// 获取近7天的日期范围作为默认值
|
||||||
|
const getDefaultDateRange = () => {
|
||||||
|
const now = new Date();
|
||||||
|
const endDate = now.toISOString().split('T')[0];
|
||||||
|
const startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
||||||
|
return [startDate, endDate];
|
||||||
|
};
|
||||||
|
|
||||||
// 筛选表单数据
|
// 筛选表单数据
|
||||||
const filterForm = ref({
|
const filterForm = ref({
|
||||||
dateRange: [], // [startDate, endDate]
|
dateRange: getDefaultDateRange(), // 默认近一周
|
||||||
status: '', // 申请单状态
|
status: '', // 申请单状态
|
||||||
|
keyword: '', // 关键字搜索
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
@@ -207,11 +366,18 @@ const fetchData = async () => {
|
|||||||
params.status = filterForm.value.status;
|
params.status = filterForm.value.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加关键字搜索
|
||||||
|
if (filterForm.value.keyword && filterForm.value.keyword.trim()) {
|
||||||
|
params.keyword = filterForm.value.keyword.trim();
|
||||||
|
}
|
||||||
|
|
||||||
const res = await getCheck(params);
|
const res = await getCheck(params);
|
||||||
if (res.code === 200 && res.data) {
|
if (res.code === 200 && res.data) {
|
||||||
const raw = res.data?.records || res.data;
|
const raw = res.data?.records || res.data;
|
||||||
const list = Array.isArray(raw) ? raw : [raw];
|
const list = Array.isArray(raw) ? raw : [raw];
|
||||||
|
console.log('API返回的原始数据:', JSON.stringify(list, null, 2));
|
||||||
tableData.value = list.filter(Boolean);
|
tableData.value = list.filter(Boolean);
|
||||||
|
console.log('tableData设置后的第一条:', tableData.value[0]);
|
||||||
} else {
|
} else {
|
||||||
tableData.value = [];
|
tableData.value = [];
|
||||||
}
|
}
|
||||||
@@ -243,8 +409,9 @@ const handleSearch = async () => {
|
|||||||
* 重置按钮处理
|
* 重置按钮处理
|
||||||
*/
|
*/
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
filterForm.value.dateRange = [];
|
filterForm.value.dateRange = getDefaultDateRange();
|
||||||
filterForm.value.status = '';
|
filterForm.value.status = '';
|
||||||
|
filterForm.value.keyword = '';
|
||||||
fetchData();
|
fetchData();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -267,9 +434,51 @@ const parseStatus = (status) => {
|
|||||||
return statusMap[String(status)] || '-';
|
return statusMap[String(status)] || '-';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据申请单详情构建申请单名称
|
||||||
|
* 单一项目:显示项目名称
|
||||||
|
* 多个项目:显示首个项目名称+"等X项"
|
||||||
|
*/
|
||||||
|
const buildApplicationName = (row) => {
|
||||||
|
const details = row.requestFormDetailList;
|
||||||
|
if (!details || details.length === 0) {
|
||||||
|
return row.name || '-';
|
||||||
|
}
|
||||||
|
if (details.length === 1) {
|
||||||
|
return details[0].adviceName || row.name || '-';
|
||||||
|
}
|
||||||
|
const first = details[0];
|
||||||
|
return `${first.adviceName || ''}等${details.length}项`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取状态标签类型 - 参考临床医嘱样式
|
||||||
|
* @param {string|number} status - 状态码
|
||||||
|
* @returns {string} el-tag type
|
||||||
|
*/
|
||||||
|
const getStatusTagType = (status) => {
|
||||||
|
const typeMap = {
|
||||||
|
'0': 'primary', // 待签发 - 蓝色
|
||||||
|
'1': 'success', // 已签发 - 绿色
|
||||||
|
'2': 'success', // 已校对 - 绿色
|
||||||
|
'3': 'primary', // 待接收 - 蓝色
|
||||||
|
'4': 'primary', // 已接收 - 蓝色
|
||||||
|
'5': 'success', // 已检查 - 绿色
|
||||||
|
'6': 'success', // 已出报告 - 绿色
|
||||||
|
'7': 'danger', // 已作废 - 红色
|
||||||
|
};
|
||||||
|
return typeMap[String(status)] || 'info';
|
||||||
|
};
|
||||||
|
|
||||||
const labelMap = {
|
const labelMap = {
|
||||||
categoryType: '项目类别',
|
categoryType: '项目类别',
|
||||||
targetDepartment: '发往科室',
|
targetDepartment: '发往科室',
|
||||||
|
urgencyLevel: '紧急程度',
|
||||||
|
allergyHistory: '过敏史',
|
||||||
|
examinationPurpose: '检查目的',
|
||||||
|
expectedExaminationTime: '期望检查时间',
|
||||||
|
medicalHistorySummary: '病史摘要',
|
||||||
|
allergyConfirmed: '过敏确认',
|
||||||
symptom: '症状',
|
symptom: '症状',
|
||||||
sign: '体征',
|
sign: '体征',
|
||||||
clinicalDiagnosis: '临床诊断',
|
clinicalDiagnosis: '临床诊断',
|
||||||
@@ -278,6 +487,17 @@ const labelMap = {
|
|||||||
attention: '注意事项',
|
attention: '注意事项',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Fields that need value transformation before display
|
||||||
|
const transformField = (key, value) => {
|
||||||
|
if (key === 'urgencyLevel') {
|
||||||
|
return value === 'emergency' ? '急诊' : '普通';
|
||||||
|
}
|
||||||
|
if (key === 'allergyConfirmed') {
|
||||||
|
return value === true || value === 'true' ? '已口头确认' : '未确认';
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
const isFieldMatched = (key) => {
|
const isFieldMatched = (key) => {
|
||||||
return key in labelMap;
|
return key in labelMap;
|
||||||
};
|
};
|
||||||
@@ -292,50 +512,68 @@ const hasMatchedFields = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/** 查询科室 */
|
/** 查询科室 */
|
||||||
const getLocationInfo = () => {
|
const getLocationInfo = async () => {
|
||||||
getDepartmentList().then((res) => {
|
try {
|
||||||
orgOptions.value = res.data || [];
|
const res = await getDepartmentList();
|
||||||
});
|
orgOptions.value = Array.isArray(res.data) ? res.data : [];
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('科室列表加载失败:', e.message);
|
||||||
|
orgOptions.value = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 递归查找树形科室节点
|
||||||
|
const findTreeItem = (list, id) => {
|
||||||
|
if (!list || list.length === 0) return null;
|
||||||
|
for (const item of list) {
|
||||||
|
if (item.id == id) return item;
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
const found = findTreeItem(item.children, id);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const recursionFun = (targetDepartment) => {
|
const recursionFun = (targetDepartment) => {
|
||||||
|
if (!targetDepartment) return '';
|
||||||
let name = '';
|
let name = '';
|
||||||
for (let index = 0; index < orgOptions.value.length; index++) {
|
for (let index = 0; index < orgOptions.value.length; index++) {
|
||||||
const obj = orgOptions.value[index];
|
const obj = orgOptions.value[index];
|
||||||
if (obj.id == targetDepartment) {
|
if (obj.id == targetDepartment) {
|
||||||
name = obj.name;
|
name = obj.name;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
const subObjArray = obj['children'];
|
const subObjArray = obj['children'];
|
||||||
if (subObjArray && subObjArray.length > 0) {
|
if (subObjArray && subObjArray.length > 0) {
|
||||||
for (let index = 0; index < subObjArray.length; index++) {
|
for (let i = 0; i < subObjArray.length; i++) {
|
||||||
const item = subObjArray[index];
|
const item = subObjArray[i];
|
||||||
if (item.id == targetDepartment) {
|
if (item.id == targetDepartment) {
|
||||||
name = item.name;
|
name = item.name;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (name) break;
|
||||||
}
|
}
|
||||||
return name;
|
return name;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleViewDetail = async (row) => {
|
const handleViewDetail = async (row) => {
|
||||||
console.log('targetDepartment========>', JSON.stringify(row));
|
// 确保科室数据已加载,以便将 ID 解析为名称
|
||||||
|
if (!orgOptions.value || orgOptions.value.length === 0) {
|
||||||
|
await getLocationInfo();
|
||||||
|
}
|
||||||
|
|
||||||
currentDetail.value = row;
|
currentDetail.value = row;
|
||||||
// 解析 descJson
|
// 解析 descJson
|
||||||
if (row.descJson) {
|
if (row.descJson) {
|
||||||
try {
|
try {
|
||||||
const obj = JSON.parse(row.descJson);
|
const obj = JSON.parse(row.descJson);
|
||||||
// 确保科室数据已加载
|
// 将发往科室 ID 转换为名称
|
||||||
if (!orgOptions.value || orgOptions.value.length === 0) {
|
if (obj.targetDepartment) {
|
||||||
await new Promise((resolve) => {
|
|
||||||
getDepartmentList().then((res) => {
|
|
||||||
orgOptions.value = res.data || [];
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
obj.targetDepartment = recursionFun(obj.targetDepartment);
|
obj.targetDepartment = recursionFun(obj.targetDepartment);
|
||||||
|
}
|
||||||
descJsonData.value = obj;
|
descJsonData.value = obj;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('解析 descJson 失败:', e);
|
console.error('解析 descJson 失败:', e);
|
||||||
@@ -347,6 +585,400 @@ const handleViewDetail = async (row) => {
|
|||||||
detailDialogVisible.value = true;
|
detailDialogVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除申请单 - 仅待签发状态可用
|
||||||
|
*/
|
||||||
|
const handleDelete = async (row) => {
|
||||||
|
try {
|
||||||
|
await proxy.$modal?.confirm?.('确认删除该检查申请单?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
const res = await deleteRequestForm({ requestFormId: row.requestFormId });
|
||||||
|
if (res.code === 200) {
|
||||||
|
proxy.$modal?.msgSuccess?.('删除成功');
|
||||||
|
fetchData();
|
||||||
|
} else {
|
||||||
|
proxy.$modal?.msgError?.(res.msg || '删除失败');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 用户取消操作,不做处理
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 撤回申请单 - 仅已签发状态可用
|
||||||
|
*/
|
||||||
|
const handleRecall = async (row) => {
|
||||||
|
try {
|
||||||
|
await proxy.$modal?.confirm?.('确认撤回该申请单?撤回后状态将变更为"待签发"。', '提示', {
|
||||||
|
confirmButtonText: '确定撤回',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
const res = await withdrawRequestForm({ requestFormId: row.requestFormId });
|
||||||
|
if (res.code === 200) {
|
||||||
|
proxy.$modal?.msgSuccess?.('撤回成功');
|
||||||
|
fetchData();
|
||||||
|
} else {
|
||||||
|
proxy.$modal?.msgError?.(res.msg || '撤回失败');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 用户取消操作,不做处理
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印申请单 - 已校对/待接收/已接收/已检查状态可用
|
||||||
|
* 打印内容与详情展示一致,并包含申请单条码
|
||||||
|
*/
|
||||||
|
const handlePrint = async (row) => {
|
||||||
|
try {
|
||||||
|
proxy.$modal?.msgInfo?.('正在生成打印预览...');
|
||||||
|
|
||||||
|
// 确保科室数据已加载,用于解析发往科室名称
|
||||||
|
if (!orgOptions.value || orgOptions.value.length === 0) {
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
getDepartmentList().then((res) => {
|
||||||
|
orgOptions.value = res.data || [];
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 descJson
|
||||||
|
let descData = {};
|
||||||
|
if (row.descJson) {
|
||||||
|
try {
|
||||||
|
const obj = JSON.parse(row.descJson);
|
||||||
|
// 将发往科室ID转换为名称
|
||||||
|
if (obj.targetDepartment) {
|
||||||
|
obj.targetDepartment = recursionFun(obj.targetDepartment);
|
||||||
|
}
|
||||||
|
descData = obj;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('解析 descJson 失败:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建诊疗项目表格行
|
||||||
|
let detailRowsHtml = '';
|
||||||
|
const detailList = row.requestFormDetailList || [];
|
||||||
|
if (detailList.length > 0) {
|
||||||
|
detailList.forEach((item, index) => {
|
||||||
|
detailRowsHtml += `
|
||||||
|
<tr>
|
||||||
|
<td style="text-align:center;padding:6px 8px;border:1px solid #ddd;">${index + 1}</td>
|
||||||
|
<td style="padding:6px 8px;border:1px solid #ddd;">${item.adviceName || '-'}</td>
|
||||||
|
<td style="text-align:center;padding:6px 8px;border:1px solid #ddd;">${item.quantity || '-'}</td>
|
||||||
|
<td style="padding:6px 8px;border:1px solid #ddd;">${item.unitCode_dictText || '-'}</td>
|
||||||
|
<td style="text-align:right;padding:6px 8px;border:1px solid #ddd;">${item.totalPrice != null ? '¥' + Number(item.totalPrice).toFixed(2) : '-'}</td>
|
||||||
|
</tr>`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建 descJson 字段行(与详情弹窗展示的字段一致)
|
||||||
|
const fieldKeys = ['targetDepartment', 'symptom', 'sign', 'clinicalDiagnosis', 'otherDiagnosis', 'relatedResult', 'attention'];
|
||||||
|
let descFieldsHtml = '';
|
||||||
|
fieldKeys.forEach((key) => {
|
||||||
|
const label = labelMap[key] || key;
|
||||||
|
if (descData[key] != null && descData[key] !== '') {
|
||||||
|
descFieldsHtml += `
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="label">${label}:</span>
|
||||||
|
<span class="value">${descData[key]}</span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 构建完整打印HTML
|
||||||
|
const printContent = `
|
||||||
|
<div class="print-wrapper">
|
||||||
|
<!-- 标题 -->
|
||||||
|
<div class="print-header">
|
||||||
|
<div class="print-title">检查申请单</div>
|
||||||
|
<div class="print-meta">打印时间:${new Date().toLocaleString()}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 基本信息 -->
|
||||||
|
<div class="print-section">
|
||||||
|
<div class="section-title">基本信息</div>
|
||||||
|
<div class="info-grid">
|
||||||
|
<div class="info-row"><span class="label">患者姓名:</span><span class="value">${row.patientName || '-'}</span></div>
|
||||||
|
<div class="info-row"><span class="label">申请单名称:</span><span class="value">${row.name || '-'}</span></div>
|
||||||
|
<div class="info-row"><span class="label">申请单状态:</span><span class="value">${parseStatus(row.status)}</span></div>
|
||||||
|
<div class="info-row"><span class="label">创建时间:</span><span class="value">${row.createTime || '-'}</span></div>
|
||||||
|
<div class="info-row"><span class="label">申请单号:</span><span class="value">${row.prescriptionNo || '-'}</span></div>
|
||||||
|
<div class="info-row"><span class="label">申请者:</span><span class="value">${row.requesterId_dictText || '-'}</span></div>
|
||||||
|
<div class="info-row"><span class="label">就诊ID:</span><span class="value">${row.encounterId || '-'}</span></div>
|
||||||
|
<div class="info-row"><span class="label">申请单ID:</span><span class="value">${row.requestFormId || '-'}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${descFieldsHtml ? `
|
||||||
|
<!-- 申请单描述 -->
|
||||||
|
<div class="print-section">
|
||||||
|
<div class="section-title">申请单描述</div>
|
||||||
|
${descFieldsHtml}
|
||||||
|
</div>` : ''}
|
||||||
|
|
||||||
|
${detailRowsHtml ? `
|
||||||
|
<!-- 诊疗项目 -->
|
||||||
|
<div class="print-section">
|
||||||
|
<div class="section-title">诊疗项目</div>
|
||||||
|
<table class="detail-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:50px;padding:8px;border:1px solid #ddd;background:#f5f7fa;text-align:center;">序号</th>
|
||||||
|
<th style="padding:8px;border:1px solid #ddd;background:#f5f7fa;text-align:left;">医嘱名称</th>
|
||||||
|
<th style="width:60px;padding:8px;border:1px solid #ddd;background:#f5f7fa;text-align:center;">数量</th>
|
||||||
|
<th style="width:60px;padding:8px;border:1px solid #ddd;background:#f5f7fa;text-align:center;">单位</th>
|
||||||
|
<th style="width:80px;padding:8px;border:1px solid #ddd;background:#f5f7fa;text-align:right;">总价</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>${detailRowsHtml}</tbody>
|
||||||
|
</table>
|
||||||
|
</div>` : ''}
|
||||||
|
|
||||||
|
<!-- 条码区 -->
|
||||||
|
<div class="barcode-section">
|
||||||
|
<div class="barcode-container">
|
||||||
|
<div class="barcode-number">${row.prescriptionNo || ''}</div>
|
||||||
|
<div class="barcode-label">申请单号 / 扫码核验</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="print-footer">
|
||||||
|
<div class="footer-line">本申请单仅供院内使用,请勿外传</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 打开新窗口打印
|
||||||
|
const printWindow = window.open('', '_blank');
|
||||||
|
if (!printWindow) {
|
||||||
|
proxy.$modal?.msgError?.('无法打开打印窗口,请检查浏览器弹窗设置');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printWindow.document.write(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>检查申请单 - 打印</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.print-wrapper {
|
||||||
|
max-width: 210mm;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.print-header {
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border-bottom: 2px solid #333;
|
||||||
|
}
|
||||||
|
.print-title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 4px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.print-meta {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.print-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.section-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
.info-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 6px 16px;
|
||||||
|
}
|
||||||
|
.info-row {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
.info-row .label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
.info-row .value {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.detail-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
.detail-table th {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
.detail-table td {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.barcode-section {
|
||||||
|
margin-top: 24px;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px dashed #ccc;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.barcode-container {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border: 2px solid #333;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.barcode-number {
|
||||||
|
font-family: 'Libre Barcode 128', 'Libre Barcode 39', 'Code128', 'C', monospace;
|
||||||
|
font-size: 42px;
|
||||||
|
letter-spacing: 4px;
|
||||||
|
color: #000;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.barcode-label {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #888;
|
||||||
|
margin-top: 4px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
.print-footer {
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.footer-line {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
@media print {
|
||||||
|
body { padding: 0; }
|
||||||
|
.print-wrapper { max-width: none; }
|
||||||
|
.barcode-section { page-break-inside: avoid; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${printContent}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
|
||||||
|
printWindow.document.close();
|
||||||
|
// 等待内容渲染后打印
|
||||||
|
printWindow.onload = function() {
|
||||||
|
setTimeout(() => {
|
||||||
|
printWindow.print();
|
||||||
|
proxy.$modal?.closeAll?.();
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('打印失败:', error);
|
||||||
|
proxy.$modal?.msgError?.('打印失败: ' + (error.message || '未知错误'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查看报告 - 仅已出报告状态可用
|
||||||
|
* 调用报告查询接口,弹窗展示结构化报告或影像预览(PACS链接)
|
||||||
|
*/
|
||||||
|
const handleViewReport = async (row) => {
|
||||||
|
reportRow.value = row;
|
||||||
|
reportDialogVisible.value = true;
|
||||||
|
reportLoading.value = true;
|
||||||
|
reportData.value = null;
|
||||||
|
try {
|
||||||
|
const res = await getTestResult({ prescriptionNo: row.prescriptionNo });
|
||||||
|
if (res.code === 200) {
|
||||||
|
if (res.data) {
|
||||||
|
// 支持两种返回格式:
|
||||||
|
// 1. res.data 为对象(结构化报告):含 patientName, prescriptionNo, reportTime, diagnosis/content, imageUrl/pacsUrl
|
||||||
|
// 2. res.data 为字符串(报告URL):映射到 imageUrl 以支持iframe预览
|
||||||
|
if (typeof res.data === 'string') {
|
||||||
|
reportData.value = {
|
||||||
|
prescriptionNo: row.prescriptionNo,
|
||||||
|
imageUrl: res.data,
|
||||||
|
};
|
||||||
|
} else if (typeof res.data === 'object') {
|
||||||
|
reportData.value = {
|
||||||
|
...res.data,
|
||||||
|
prescriptionNo: res.data.prescriptionNo || row.prescriptionNo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reportData.value = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reportData.value = null;
|
||||||
|
proxy.$modal?.msgWarning?.(res.msg || '暂未生成报告');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取报告失败:', e);
|
||||||
|
reportData.value = null;
|
||||||
|
proxy.$modal?.msgError?.('获取报告失败');
|
||||||
|
} finally {
|
||||||
|
reportLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开PACS影像链接
|
||||||
|
*/
|
||||||
|
const openPacsLink = () => {
|
||||||
|
const url = reportData.value?.pacsUrl || reportData.value?.imageUrl;
|
||||||
|
if (url) {
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ========== 修改申请单相关 ==========
|
||||||
|
const editDialogVisible = ref(false);
|
||||||
|
const editFormRef = ref(null);
|
||||||
|
const editingRow = ref(null);
|
||||||
|
|
||||||
|
// 修改申请单 - 复用检查申请单弹窗
|
||||||
|
const handleEdit = (row) => {
|
||||||
|
editingRow.value = { ...row };
|
||||||
|
editDialogVisible.value = true;
|
||||||
|
// 弹窗打开后,手动调用getList确保数据加载
|
||||||
|
nextTick(() => {
|
||||||
|
editFormRef.value?.getList?.();
|
||||||
|
editFormRef.value?.getLocationInfo?.();
|
||||||
|
editFormRef.value?.getDiagnosisList?.();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑弹窗确认提交
|
||||||
|
const handleEditSubmit = () => {
|
||||||
|
if (editFormRef.value?.submit) {
|
||||||
|
editFormRef.value.submit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑保存成功回调:关闭弹窗并刷新列表
|
||||||
|
const handleEditSuccess = () => {
|
||||||
|
editDialogVisible.value = false;
|
||||||
|
fetchData();
|
||||||
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => patientInfo.value?.encounterId,
|
() => patientInfo.value?.encounterId,
|
||||||
(val) => {
|
(val) => {
|
||||||
@@ -355,8 +987,9 @@ watch(
|
|||||||
getLocationInfo();
|
getLocationInfo();
|
||||||
} else {
|
} else {
|
||||||
tableData.value = [];
|
tableData.value = [];
|
||||||
filterForm.value.dateRange = [];
|
filterForm.value.dateRange = getDefaultDateRange();
|
||||||
filterForm.value.status = '';
|
filterForm.value.status = '';
|
||||||
|
filterForm.value.keyword = '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
@@ -470,4 +1103,96 @@ defineExpose({
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 报告弹窗样式
|
||||||
|
.report-viewer {
|
||||||
|
min-height: 200px;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-viewer-container {
|
||||||
|
.report-content-section {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #303133;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-content-text {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.8;
|
||||||
|
color: #606266;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-image-section {
|
||||||
|
margin-top: 16px;
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #303133;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 480px;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态标签样式 - 参考临床医嘱
|
||||||
|
:deep(.el-tag) {
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 0 8px;
|
||||||
|
height: 24px;
|
||||||
|
line-height: 22px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tag--info.is-plain) {
|
||||||
|
background: #f4f4f5;
|
||||||
|
border-color: #e9e9eb;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tag--primary.is-plain) {
|
||||||
|
background: #ecf5ff;
|
||||||
|
border-color: #d9ecff;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tag--success.is-plain) {
|
||||||
|
background: #f0f9eb;
|
||||||
|
border-color: #e1f3d8;
|
||||||
|
color: #67c23a;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tag--warning.is-plain) {
|
||||||
|
background: #fdf6ec;
|
||||||
|
border-color: #faecd8;
|
||||||
|
color: #e6a23c;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tag--danger.is-plain) {
|
||||||
|
background: #fef0f0;
|
||||||
|
border-color: #fde2e2;
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -41,8 +41,8 @@
|
|||||||
<el-option label="全部" value="" />
|
<el-option label="全部" value="" />
|
||||||
<el-option label="待签发" value="0" />
|
<el-option label="待签发" value="0" />
|
||||||
<el-option label="已签发" value="1" />
|
<el-option label="已签发" value="1" />
|
||||||
<el-option label="报告已出" value="4" />
|
<el-option label="已出报告" value="6" />
|
||||||
<el-option label="已作废" value="5" />
|
<el-option label="已作废" value="7" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="关键字">
|
<el-form-item label="关键字">
|
||||||
@@ -82,7 +82,11 @@
|
|||||||
</template>
|
</template>
|
||||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||||
<el-table-column prop="patientName" label="患者姓名" width="120" />
|
<el-table-column prop="patientName" label="患者姓名" width="120" />
|
||||||
<el-table-column prop="name" label="申请单名称" width="140" />
|
<el-table-column label="申请单名称" width="140">
|
||||||
|
<template #default="scope">
|
||||||
|
<span>{{ buildApplicationName(scope.row) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="createTime" label="创建时间" width="160" />
|
<el-table-column prop="createTime" label="创建时间" width="160" />
|
||||||
<el-table-column prop="prescriptionNo" label="申请单号" width="140" />
|
<el-table-column prop="prescriptionNo" label="申请单号" width="140" />
|
||||||
<el-table-column label="单据状态" width="100" align="center">
|
<el-table-column label="单据状态" width="100" align="center">
|
||||||
@@ -101,15 +105,18 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="requesterId_dictText" label="申请者" width="120" />
|
<el-table-column prop="requesterId_dictText" label="申请者" width="120" />
|
||||||
<el-table-column label="操作" align="center" fixed="right" width="160">
|
<el-table-column label="操作" align="center" fixed="right" width="220">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<template v-if="scope.row.billStatus == 0 || scope.row.status == 0">
|
<!-- 待签发(status=0或null/undefined):可修改、删除 -->
|
||||||
|
<template v-if="!scope.row.status || scope.row.status == 0">
|
||||||
<el-button link type="primary" @click="handleEdit(scope.row)">修改</el-button>
|
<el-button link type="primary" @click="handleEdit(scope.row)">修改</el-button>
|
||||||
<el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
<el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="scope.row.billStatus == 1 || scope.row.status == 1">
|
<!-- 已签发(status=1):可撤回 -->
|
||||||
|
<template v-else-if="scope.row.status == 1">
|
||||||
<el-button link type="warning" @click="handleWithdraw(scope.row)">撤回</el-button>
|
<el-button link type="warning" @click="handleWithdraw(scope.row)">撤回</el-button>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- 已校对(2)、待接收(3)、已收样(4)、已出报告(6)、已作废(7):仅查看详情 -->
|
||||||
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
|
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -137,7 +144,7 @@
|
|||||||
<el-descriptions-item label="创建时间">{{
|
<el-descriptions-item label="创建时间">{{
|
||||||
currentDetail.createTime || '-'
|
currentDetail.createTime || '-'
|
||||||
}}</el-descriptions-item>
|
}}</el-descriptions-item>
|
||||||
<el-descriptions-item label="处方号">{{
|
<el-descriptions-item label="申请单号">{{
|
||||||
currentDetail.prescriptionNo || '-'
|
currentDetail.prescriptionNo || '-'
|
||||||
}}</el-descriptions-item>
|
}}</el-descriptions-item>
|
||||||
<el-descriptions-item label="申请者">{{
|
<el-descriptions-item label="申请者">{{
|
||||||
@@ -179,6 +186,26 @@
|
|||||||
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 编辑检验申请单弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="editDialogVisible"
|
||||||
|
title="编辑检验申请单"
|
||||||
|
width="1200px"
|
||||||
|
destroy-on-close
|
||||||
|
top="5vh"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
>
|
||||||
|
<LaboratoryTests
|
||||||
|
ref="editFormRef"
|
||||||
|
@submitOk="handleEditSubmitOk"
|
||||||
|
:editData="editRowData"
|
||||||
|
/>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="editDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submitEditForm">确认</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -187,13 +214,18 @@ import {computed, getCurrentInstance, ref, watch} from 'vue';
|
|||||||
import {Refresh, Search} from '@element-plus/icons-vue';
|
import {Refresh, Search} from '@element-plus/icons-vue';
|
||||||
import {patientInfo} from '../../store/patient.js';
|
import {patientInfo} from '../../store/patient.js';
|
||||||
import {getInspection, deleteRequestForm, withdrawRequestForm} from './api';
|
import {getInspection, deleteRequestForm, withdrawRequestForm} from './api';
|
||||||
import {getOrgList} from '@/views/doctorstation/components/api.js';
|
import {getDepartmentList} from '@/api/public.js';
|
||||||
|
import LaboratoryTests from '../order/applicationForm/laboratoryTests.vue';
|
||||||
|
import {saveInspection} from '../order/applicationForm/api.js';
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance();
|
const { proxy } = getCurrentInstance();
|
||||||
|
|
||||||
const tableData = ref([]);
|
const tableData = ref([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const detailDialogVisible = ref(false);
|
const detailDialogVisible = ref(false);
|
||||||
|
const editDialogVisible = ref(false);
|
||||||
|
const editRowData = ref(null);
|
||||||
|
const editFormRef = ref(null);
|
||||||
const currentDetail = ref(null);
|
const currentDetail = ref(null);
|
||||||
const descJsonData = ref(null);
|
const descJsonData = ref(null);
|
||||||
const orgOptions = ref([]);
|
const orgOptions = ref([]);
|
||||||
@@ -285,6 +317,9 @@ const labelMap = {
|
|||||||
otherDiagnosis: '其他诊断',
|
otherDiagnosis: '其他诊断',
|
||||||
relatedResult: '相关结果',
|
relatedResult: '相关结果',
|
||||||
attention: '注意事项',
|
attention: '注意事项',
|
||||||
|
applicationType: '申请类型',
|
||||||
|
specimenName: '标本类型',
|
||||||
|
executeTime: '执行时间',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -296,8 +331,11 @@ const parseBillStatus = (status) => {
|
|||||||
const statusMap = {
|
const statusMap = {
|
||||||
'0': '待签发',
|
'0': '待签发',
|
||||||
'1': '已签发',
|
'1': '已签发',
|
||||||
'4': '报告已出',
|
'2': '已校对',
|
||||||
'5': '已作废',
|
'3': '待接收',
|
||||||
|
'4': '已收样',
|
||||||
|
'6': '已出报告',
|
||||||
|
'7': '已作废',
|
||||||
};
|
};
|
||||||
return statusMap[String(status)] || '-';
|
return statusMap[String(status)] || '-';
|
||||||
};
|
};
|
||||||
@@ -311,8 +349,8 @@ const parsePriorityCode = (descJson) => {
|
|||||||
if (!descJson) return '-';
|
if (!descJson) return '-';
|
||||||
try {
|
try {
|
||||||
const obj = JSON.parse(descJson);
|
const obj = JSON.parse(descJson);
|
||||||
// priorityCode: 0-普通, 1-急
|
// applicationType: 0-普通, 1-急诊
|
||||||
return obj.priorityCode === 1 ? '急' : '普通';
|
return obj.applicationType === 1 ? '急' : '普通';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('解析 descJson 失败:', e);
|
console.error('解析 descJson 失败:', e);
|
||||||
return '-';
|
return '-';
|
||||||
@@ -336,6 +374,24 @@ const parseSpecimenType = (descJson) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据申请单详情构建申请单名称
|
||||||
|
* 单一项目:显示项目名称+数量
|
||||||
|
* 多个项目:显示首个项目名称+数量+"等X项"
|
||||||
|
*/
|
||||||
|
const buildApplicationName = (row) => {
|
||||||
|
const details = row.requestFormDetailList;
|
||||||
|
if (!details || details.length === 0) {
|
||||||
|
return row.name || '-';
|
||||||
|
}
|
||||||
|
if (details.length === 1) {
|
||||||
|
const item = details[0];
|
||||||
|
return `${item.adviceName}${item.quantity || ''}`;
|
||||||
|
}
|
||||||
|
const first = details[0];
|
||||||
|
return `${first.adviceName}${first.quantity || ''}等${details.length}项`;
|
||||||
|
};
|
||||||
|
|
||||||
const isFieldMatched = (key) => {
|
const isFieldMatched = (key) => {
|
||||||
return key in labelMap;
|
return key in labelMap;
|
||||||
};
|
};
|
||||||
@@ -391,6 +447,9 @@ const handleViewDetail = async (row) => {
|
|||||||
try {
|
try {
|
||||||
const obj = JSON.parse(row.descJson);
|
const obj = JSON.parse(row.descJson);
|
||||||
obj.targetDepartment = recursionFun(obj.targetDepartment);
|
obj.targetDepartment = recursionFun(obj.targetDepartment);
|
||||||
|
// 转换申请类型编码为可读文本
|
||||||
|
if (obj.applicationType === 0) obj.applicationType = '普通';
|
||||||
|
else if (obj.applicationType === 1) obj.applicationType = '急诊';
|
||||||
descJsonData.value = obj;
|
descJsonData.value = obj;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('解析 descJson 失败:', e);
|
console.error('解析 descJson 失败:', e);
|
||||||
@@ -405,10 +464,32 @@ const handleViewDetail = async (row) => {
|
|||||||
/**
|
/**
|
||||||
* 修改检验申请单(待签发状态)
|
* 修改检验申请单(待签发状态)
|
||||||
*/
|
*/
|
||||||
const handleEdit = (row) => {
|
const handleEdit = async (row) => {
|
||||||
// 复用详情查看逻辑,后续可扩展为打开编辑弹窗
|
// 确保科室数据已加载
|
||||||
handleViewDetail(row);
|
if (!orgOptions.value || orgOptions.value.length === 0) {
|
||||||
proxy.$modal?.msgInfo?.('修改功能待接入,请通过详情弹窗查看后重新开立');
|
await getLocationInfo();
|
||||||
|
}
|
||||||
|
editRowData.value = row;
|
||||||
|
editDialogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑弹窗提交成功回调
|
||||||
|
*/
|
||||||
|
const handleEditSubmitOk = async () => {
|
||||||
|
editDialogVisible.value = false;
|
||||||
|
editRowData.value = null;
|
||||||
|
proxy.$modal?.msgSuccess?.('修改成功');
|
||||||
|
await fetchData();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑弹窗提交按钮
|
||||||
|
*/
|
||||||
|
const submitEditForm = () => {
|
||||||
|
if (editFormRef.value?.submit) {
|
||||||
|
editFormRef.value.submit();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -422,7 +503,7 @@ const handleDelete = async (row) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await deleteRequestForm({ prescriptionNo: row.prescriptionNo });
|
const res = await deleteRequestForm({ requestFormId: row.requestFormId });
|
||||||
if (res?.code === 200) {
|
if (res?.code === 200) {
|
||||||
proxy.$modal?.msgSuccess?.('删除成功');
|
proxy.$modal?.msgSuccess?.('删除成功');
|
||||||
await fetchData();
|
await fetchData();
|
||||||
@@ -445,7 +526,7 @@ const handleWithdraw = async (row) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await withdrawRequestForm({ prescriptionNo: row.prescriptionNo });
|
const res = await withdrawRequestForm({ requestFormId: row.requestFormId });
|
||||||
if (res?.code === 200) {
|
if (res?.code === 200) {
|
||||||
proxy.$modal?.msgSuccess?.('撤回成功');
|
proxy.$modal?.msgSuccess?.('撤回成功');
|
||||||
await fetchData();
|
await fetchData();
|
||||||
|
|||||||
@@ -568,6 +568,11 @@ function handleMaindise(value, index) {
|
|||||||
* 保存诊断
|
* 保存诊断
|
||||||
*/
|
*/
|
||||||
function handleSaveDiagnosis() {
|
function handleSaveDiagnosis() {
|
||||||
|
// 防止重复点击保存
|
||||||
|
if (isSaving.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (let index = 0; index < (form.value.diagnosisList || []).length; index++) {
|
for (let index = 0; index < (form.value.diagnosisList || []).length; index++) {
|
||||||
const item = form.value.diagnosisList[index];
|
const item = form.value.diagnosisList[index];
|
||||||
if (!item.diagSrtNo) {
|
if (!item.diagSrtNo) {
|
||||||
@@ -600,7 +605,7 @@ function handleSaveDiagnosis() {
|
|||||||
|
|
||||||
// 步骤2:重新分配连续的序号(从1开始)
|
// 步骤2:重新分配连续的序号(从1开始)
|
||||||
sortedList.forEach((item, index) => {
|
sortedList.forEach((item, index) => {
|
||||||
item.diagSrtNo = index + 1; // 这里是关键!把“诊断排序”改成新顺序
|
item.diagSrtNo = index + 1; // 这里是关键!把”诊断排序”改成新顺序
|
||||||
});
|
});
|
||||||
|
|
||||||
// 步骤3:提交排序后的数据
|
// 步骤3:提交排序后的数据
|
||||||
@@ -610,12 +615,12 @@ function handleSaveDiagnosis() {
|
|||||||
diagnosisChildList: sortedList,
|
diagnosisChildList: sortedList,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
// 步骤4:更新本地数据,使用全新对象防止响应式问题
|
|
||||||
form.value.diagnosisList = sortedList.map(item => ({ ...item }));
|
|
||||||
|
|
||||||
emits('diagnosisSave', false);
|
emits('diagnosisSave', false);
|
||||||
proxy.$modal.msgSuccess('诊断已保存');
|
proxy.$modal.msgSuccess('诊断已保存');
|
||||||
|
|
||||||
|
// 保存成功后从服务器重新加载数据,确保前后端数据一致
|
||||||
|
getList();
|
||||||
|
|
||||||
// 食源性疾病逻辑
|
// 食源性疾病逻辑
|
||||||
isFoodDiseasesNew({ encounterId: props.patientInfo.encounterId }).then((res2) => {
|
isFoodDiseasesNew({ encounterId: props.patientInfo.encounterId }).then((res2) => {
|
||||||
if (res2.code === 20 && res2.data) {
|
if (res2.code === 20 && res2.data) {
|
||||||
|
|||||||
@@ -1,6 +1,24 @@
|
|||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
// 申请单相关接口
|
// 申请单相关接口
|
||||||
|
|
||||||
|
// 手术项目专用分页查询(仅手术 + 定价,无库存/草稿库存等无关逻辑)
|
||||||
|
export function getSurgeryPage(params) {
|
||||||
|
return request({
|
||||||
|
url: '/doctor-station/advice/surgery-page',
|
||||||
|
method: 'get',
|
||||||
|
params: params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查项目专用分页查询(仅检查(23) + 定价,绕过 AOP 校验提升加载速度)
|
||||||
|
export function getExaminationPage(params) {
|
||||||
|
return request({
|
||||||
|
url: '/doctor-station/advice/examination-page',
|
||||||
|
method: 'get',
|
||||||
|
params: params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
//医嘱大下拉
|
//医嘱大下拉
|
||||||
export function getApplicationList(queryParams) {
|
export function getApplicationList(queryParams) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ const submit = () => {
|
|||||||
requestFormId: '', // 申请单ID
|
requestFormId: '', // 申请单ID
|
||||||
name: '输血申请单',
|
name: '输血申请单',
|
||||||
descJson: JSON.stringify(form),
|
descJson: JSON.stringify(form),
|
||||||
categoryEnum: '3', // 1 检验 2 检查 3 输血 4 手术
|
categoryEnum: '23', // 21 检验 22 检查 23 输血 24 手术(避开 adviceType 1-6 碰撞)
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
proxy.$message.success(res.msg);
|
proxy.$message.success(res.msg);
|
||||||
|
|||||||
@@ -6,9 +6,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="LaboratoryTests-container">
|
<div class="LaboratoryTests-container">
|
||||||
<div v-loading="loading" class="transfer-wrapper">
|
<div v-loading="loading" class="transfer-wrapper">
|
||||||
|
<!-- 远程搜索框 -->
|
||||||
|
<div class="search-bar">
|
||||||
|
<el-input
|
||||||
|
v-model="searchKey"
|
||||||
|
placeholder="输入项目代码/名称搜索"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleSearch"
|
||||||
|
@clear="handleSearch"
|
||||||
|
style="width: 300px; margin-bottom: 10px"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="handleSearch">搜索</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<span v-if="!searchKey" class="total-count">共 {{ totalCount }} 项</span>
|
||||||
|
<span v-else class="total-count">搜索到 {{ filteredCount }} 项 / 共 {{ totalCount }} 项</span>
|
||||||
|
</div>
|
||||||
<el-transfer
|
<el-transfer
|
||||||
v-model="transferValue"
|
v-model="transferValue"
|
||||||
:data="applicationList"
|
:data="transferData"
|
||||||
filter-placeholder="项目代码/名称"
|
filter-placeholder="项目代码/名称"
|
||||||
filterable
|
filterable
|
||||||
:titles="['未选择', '已选择']"
|
:titles="['未选择', '已选择']"
|
||||||
@@ -117,7 +134,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup name="LaboratoryTests">
|
<script setup name="LaboratoryTests">
|
||||||
import {getCurrentInstance, onBeforeMount, onMounted, reactive, watch} from 'vue';
|
import {getCurrentInstance, onMounted, reactive, ref, watch, computed} from 'vue';
|
||||||
import {patientInfo} from '../../../store/patient.js';
|
import {patientInfo} from '../../../store/patient.js';
|
||||||
import {getApplicationList, saveInspection} from './api';
|
import {getApplicationList, saveInspection} from './api';
|
||||||
import {getOrgList} from '@/views/doctorstation/components/api.js';
|
import {getOrgList} from '@/views/doctorstation/components/api.js';
|
||||||
@@ -138,29 +155,23 @@ const findTreeItem = (list, id) => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
const emits = defineEmits(['submitOk']);
|
const emits = defineEmits(['submitOk']);
|
||||||
const props = defineProps({});
|
const props = defineProps({
|
||||||
|
editData: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const isEditMode = computed(() => !!props.editData?.requestFormId);
|
||||||
const state = reactive({});
|
const state = reactive({});
|
||||||
const applicationListAll = ref();
|
const applicationListAll = ref([]);
|
||||||
const applicationList = ref();
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const orgOptions = ref([]); // 科室选项
|
const orgOptions = ref([]);
|
||||||
const getList = () => {
|
const searchKey = ref('');
|
||||||
if (!patientInfo.value?.inHospitalOrgId) {
|
const totalCount = ref(0);
|
||||||
applicationList.value = [];
|
|
||||||
return;
|
// 将已加载的全部数据转为 transfer 组件所需的格式
|
||||||
}
|
const buildTransferData = (records) => {
|
||||||
loading.value = true;
|
return records.map((item) => {
|
||||||
getApplicationList({
|
|
||||||
pageSize: 9999,
|
|
||||||
pageNum: 1,
|
|
||||||
categoryCode: '22',
|
|
||||||
organizationId: patientInfo.value.inHospitalOrgId,
|
|
||||||
adviceTypes: [3], //1 药品 2耗材 3诊疗
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
if (res.code === 200) {
|
|
||||||
applicationListAll.value = res.data.records;
|
|
||||||
applicationList.value = res.data.records.map((item) => {
|
|
||||||
const priceInfo = item.priceList?.[0] || {};
|
const priceInfo = item.priceList?.[0] || {};
|
||||||
const price = priceInfo.price != null ? Number(priceInfo.price).toFixed(2) : '0.00';
|
const price = priceInfo.price != null ? Number(priceInfo.price).toFixed(2) : '0.00';
|
||||||
const unit = item.unitCode_dictText || item.unitCode || '';
|
const unit = item.unitCode_dictText || item.unitCode || '';
|
||||||
@@ -171,15 +182,65 @@ const getList = () => {
|
|||||||
key: item.adviceDefinitionId,
|
key: item.adviceDefinitionId,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
console.log('applicationList========>', JSON.stringify(res.data.records));
|
};
|
||||||
} else {
|
|
||||||
proxy.$message.error(res.message);
|
// 加载全部数据(不分页,一次性拉取)
|
||||||
applicationList.value = [];
|
const loadAllData = async () => {
|
||||||
|
if (!patientInfo.value?.inHospitalOrgId) {
|
||||||
|
applicationListAll.value = [];
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
})
|
loading.value = true;
|
||||||
.finally(() => {
|
try {
|
||||||
loading.value = false;
|
// 使用大 pageSize 一次性拉取所有启用状态的检验类诊疗项目
|
||||||
|
const res = await getApplicationList({
|
||||||
|
pageSize: 9999,
|
||||||
|
pageNo: 1,
|
||||||
|
categoryCode: '22',
|
||||||
|
organizationId: patientInfo.value.inHospitalOrgId,
|
||||||
|
adviceTypes: [3], // 1 药品 2 耗材 3 诊疗
|
||||||
});
|
});
|
||||||
|
if (res.code !== 200) {
|
||||||
|
proxy.$message.error(res.message);
|
||||||
|
applicationListAll.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applicationListAll.value = res.data?.records || [];
|
||||||
|
totalCount.value = res.data?.total || 0;
|
||||||
|
} catch (e) {
|
||||||
|
proxy.$message.error('获取检验项目列表失败');
|
||||||
|
applicationListAll.value = [];
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据搜索关键词过滤数据
|
||||||
|
const filterData = (key) => {
|
||||||
|
if (!key || key.trim() === '') {
|
||||||
|
return applicationListAll.value;
|
||||||
|
}
|
||||||
|
const lowerKey = key.toLowerCase().trim();
|
||||||
|
return applicationListAll.value.filter((item) => {
|
||||||
|
return (
|
||||||
|
item.adviceName?.toLowerCase().includes(lowerKey) ||
|
||||||
|
item.pyStr?.toLowerCase().includes(lowerKey) ||
|
||||||
|
item.adviceBusNo?.toLowerCase().includes(lowerKey)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// transfer 组件实际显示的数据(受搜索词影响)
|
||||||
|
const transferData = computed(() => buildTransferData(filterData(searchKey.value)));
|
||||||
|
// 当前显示的条数
|
||||||
|
const filteredCount = computed(() => filterData(searchKey.value).length);
|
||||||
|
|
||||||
|
const getList = async () => {
|
||||||
|
await loadAllData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
// 搜索时保持已选中的项目不受影响
|
||||||
};
|
};
|
||||||
const transferValue = ref([]);
|
const transferValue = ref([]);
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
@@ -198,7 +259,6 @@ const form = reactive({
|
|||||||
otherDiagnosisList: [], //其他断目录
|
otherDiagnosisList: [], //其他断目录
|
||||||
});
|
});
|
||||||
const rules = reactive({});
|
const rules = reactive({});
|
||||||
onBeforeMount(() => {});
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList();
|
getList();
|
||||||
});
|
});
|
||||||
@@ -211,12 +271,22 @@ const projectWithDepartment = (selectProjectIds, type) => {
|
|||||||
let isRelease = true;
|
let isRelease = true;
|
||||||
// 选中项目的数组
|
// 选中项目的数组
|
||||||
const arr = [];
|
const arr = [];
|
||||||
// 根据选中的项目id查找对应的项目
|
// 根据选中的项目id查找对应的项目(从全部原始数据中查找)
|
||||||
selectProjectIds.forEach((element) => {
|
selectProjectIds.forEach((element) => {
|
||||||
const searchData = applicationList.value.find((item) => {
|
const searchData = applicationListAll.value.find((item) => {
|
||||||
return element == item.adviceDefinitionId;
|
return element == item.adviceDefinitionId;
|
||||||
});
|
});
|
||||||
arr.push(searchData);
|
if (searchData) {
|
||||||
|
const priceInfo = searchData.priceList?.[0] || {};
|
||||||
|
const price = priceInfo.price != null ? Number(priceInfo.price).toFixed(2) : '0.00';
|
||||||
|
const unit = searchData.unitCode_dictText || searchData.unitCode || '';
|
||||||
|
arr.push({
|
||||||
|
adviceDefinitionId: searchData.adviceDefinitionId,
|
||||||
|
orgId: searchData.orgId,
|
||||||
|
label: searchData.adviceName + ' (¥' + price + '/' + unit + ')',
|
||||||
|
key: searchData.adviceDefinitionId,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// 保存用户手动选择的发往科室(提交时需要保留)
|
// 保存用户手动选择的发往科室(提交时需要保留)
|
||||||
const manualDept = type === 2 ? form.targetDepartment : '';
|
const manualDept = type === 2 ? form.targetDepartment : '';
|
||||||
@@ -242,18 +312,27 @@ const projectWithDepartment = (selectProjectIds, type) => {
|
|||||||
if (type === 2 && manualDept) {
|
if (type === 2 && manualDept) {
|
||||||
form.targetDepartment = manualDept;
|
form.targetDepartment = manualDept;
|
||||||
isRelease = true;
|
isRelease = true;
|
||||||
} else {
|
} else if (type === 2 && !manualDept) {
|
||||||
|
// 提交时用户未手动选择科室,才提示错误
|
||||||
isRelease = false;
|
isRelease = false;
|
||||||
ElMessage({
|
ElMessage({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: '未找到项目执行的科室',
|
message: '未找到项目执行的科室',
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// type=1(选择项目变化)时,不弹窗,仅清空科室让用户自行选择
|
||||||
|
isRelease = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (findItem && isRelease) {
|
if (findItem && isRelease) {
|
||||||
|
// 提交时若用户已选「发往科室」,不得用项目默认执行科室覆盖
|
||||||
|
if (type === 2 && manualDept) {
|
||||||
|
form.targetDepartment = manualDept;
|
||||||
|
} else {
|
||||||
form.targetDepartment = findItem.id;
|
form.targetDepartment = findItem.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return isRelease;
|
return isRelease;
|
||||||
};
|
};
|
||||||
// 监听选择项目变化
|
// 监听选择项目变化
|
||||||
@@ -263,6 +342,44 @@ watch(
|
|||||||
projectWithDepartment(newValue, 1);
|
projectWithDepartment(newValue, 1);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 编辑模式下,回显已有数据
|
||||||
|
watch(
|
||||||
|
() => props.editData,
|
||||||
|
(newData) => {
|
||||||
|
if (!newData || !newData.requestFormId) return;
|
||||||
|
|
||||||
|
// 解析 descJson 回填表单
|
||||||
|
if (newData.descJson) {
|
||||||
|
try {
|
||||||
|
const obj = JSON.parse(newData.descJson);
|
||||||
|
Object.keys(form).forEach((key) => {
|
||||||
|
if (obj[key] !== undefined) {
|
||||||
|
form[key] = obj[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('解析 descJson 失败:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回填已选项目
|
||||||
|
if (newData.requestFormDetailList && newData.requestFormDetailList.length > 0) {
|
||||||
|
// 从全部数据中匹配已选项目
|
||||||
|
const selectedIds = [];
|
||||||
|
newData.requestFormDetailList.forEach((detail) => {
|
||||||
|
const matched = applicationListAll.value.find(
|
||||||
|
(item) => item.adviceName === detail.adviceName
|
||||||
|
);
|
||||||
|
if (matched) {
|
||||||
|
selectedIds.push(matched.adviceDefinitionId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
transferValue.value = selectedIds;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if (transferValue.value.length == 0) {
|
if (transferValue.value.length == 0) {
|
||||||
return proxy.$message.error('请选择申请单');
|
return proxy.$message.error('请选择申请单');
|
||||||
@@ -280,7 +397,7 @@ const submit = () => {
|
|||||||
unitCode: item.priceList[0].unitCode /** 请求单位编码 */,
|
unitCode: item.priceList[0].unitCode /** 请求单位编码 */,
|
||||||
unitPrice: item.priceList[0].price /** 单价 */,
|
unitPrice: item.priceList[0].price /** 单价 */,
|
||||||
totalPrice: item.priceList[0].price /** 总价 */,
|
totalPrice: item.priceList[0].price /** 总价 */,
|
||||||
positionId: item.positionId || form.targetDepartment, //执行科室id,未配置时使用用户手动选择的科室
|
positionId: form.targetDepartment || item.positionId, // 用户指定发往科室优先于项目默认执行科室
|
||||||
ybClassEnum: item.ybClassEnum, //类别医保编码
|
ybClassEnum: item.ybClassEnum, //类别医保编码
|
||||||
conditionId: item.conditionId, //诊断ID
|
conditionId: item.conditionId, //诊断ID
|
||||||
encounterDiagnosisId: item.encounterDiagnosisId, //就诊诊断id
|
encounterDiagnosisId: item.encounterDiagnosisId, //就诊诊断id
|
||||||
@@ -295,15 +412,15 @@ const submit = () => {
|
|||||||
patientId: patientInfo.value.patientId, //患者ID
|
patientId: patientInfo.value.patientId, //患者ID
|
||||||
encounterId: patientInfo.value.encounterId, // 就诊ID
|
encounterId: patientInfo.value.encounterId, // 就诊ID
|
||||||
organizationId: patientInfo.value.inHospitalOrgId, // 医疗机构ID
|
organizationId: patientInfo.value.inHospitalOrgId, // 医疗机构ID
|
||||||
requestFormId: '', // 申请单ID
|
requestFormId: isEditMode.value ? props.editData.requestFormId : '', // 申请单ID(编辑模式传入,新增为空)
|
||||||
name: '检验申请单',
|
name: '检验申请单',
|
||||||
descJson: JSON.stringify(form),
|
descJson: JSON.stringify(form),
|
||||||
categoryEnum: '1', // 1 检验 2 检查 3 输血 4 手术
|
categoryEnum: '21', // 21 检验 22 检查 23 输血 24 手术(避开 adviceType 1-6 碰撞)
|
||||||
};
|
};
|
||||||
saveInspection(params).then((res) => {
|
saveInspection(params).then((res) => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
proxy.$message.success(res.msg);
|
proxy.$message.success(isEditMode.value ? '修改成功' : res.msg);
|
||||||
applicationList.value = [];
|
transferValue.value = [];
|
||||||
emits('submitOk');
|
emits('submitOk');
|
||||||
} else {
|
} else {
|
||||||
proxy.$message.error(res.message);
|
proxy.$message.error(res.message);
|
||||||
@@ -360,6 +477,19 @@ defineExpose({ state, submit, getLocationInfo, getDiagnosisList, getList });
|
|||||||
.transfer-wrapper {
|
.transfer-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
.total-count {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #909399;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-transfer {
|
.el-transfer {
|
||||||
|
|||||||
@@ -5,36 +5,25 @@
|
|||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<div class="medicalExaminations-container">
|
<div class="medicalExaminations-container">
|
||||||
<!-- 顶部标题栏 -->
|
<!-- 主体内容 -->
|
||||||
<div class="form-header">
|
<div class="form-body">
|
||||||
<div class="header-left">
|
<!-- 右上角:紧急程度 -->
|
||||||
<el-icon class="header-icon"><Files /></el-icon>
|
<div class="urgency-bar">
|
||||||
<span class="header-title">检查申请单</span>
|
<span class="urgency-bar-label">紧急程度:</span>
|
||||||
</div>
|
<el-radio-group v-model="form.urgencyLevel" @change="handleUrgencyChange" size="small">
|
||||||
<div class="header-right">
|
|
||||||
<span class="urgency-label">紧急程度</span>
|
|
||||||
<el-radio-group v-model="form.urgencyLevel" @change="handleUrgencyChange" class="urgency-radio-group">
|
|
||||||
<el-radio-button label="routine">普通</el-radio-button>
|
<el-radio-button label="routine">普通</el-radio-button>
|
||||||
<el-radio-button label="emergency">急诊</el-radio-button>
|
<el-radio-button label="emergency">急诊</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
<transition name="el-fade-in-linear">
|
<transition name="el-fade-in-linear">
|
||||||
<span v-if="form.urgencyLevel === 'emergency'" class="emergency-tip">
|
<span v-if="form.urgencyLevel === 'emergency'" class="emergency-tip-inline">
|
||||||
<el-icon><WarningFilled /></el-icon>
|
<el-icon><WarningFilled /></el-icon>
|
||||||
急诊单将进入绿色通道
|
绿色通道
|
||||||
</span>
|
</span>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 主体内容区 -->
|
|
||||||
<div class="form-body">
|
|
||||||
<!-- 选择检查项目 -->
|
<!-- 选择检查项目 -->
|
||||||
<div class="section-card">
|
<div class="section-card">
|
||||||
<div class="section-header">
|
<div class="transfer-wrapper">
|
||||||
<el-icon><Document /></el-icon>
|
|
||||||
<span>选择检查项目</span>
|
|
||||||
</div>
|
|
||||||
<div v-loading="loading" class="transfer-wrapper">
|
|
||||||
<el-transfer
|
<el-transfer
|
||||||
v-model="transferValue"
|
v-model="transferValue"
|
||||||
:data="applicationList"
|
:data="applicationList"
|
||||||
@@ -45,17 +34,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 申请信息 -->
|
|
||||||
<div class="section-card">
|
|
||||||
<div class="section-header">
|
|
||||||
<el-icon><EditPen /></el-icon>
|
|
||||||
<span>申请信息</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-form :model="form" :rules="rules" ref="formRef" label-position="top" class="info-form">
|
<el-form :model="form" :rules="rules" ref="formRef" label-position="top" class="info-form">
|
||||||
<!-- 第一行:发往科室 + 期望检查时间 -->
|
<!-- 第一行:发往科室 + 紧急程度 + 期望检查时间 -->
|
||||||
<el-row :gutter="16">
|
<el-row :gutter="16">
|
||||||
<el-col :span="12">
|
<el-col :span="8">
|
||||||
<el-form-item label="发往科室" prop="targetDepartment">
|
<el-form-item label="发往科室" prop="targetDepartment">
|
||||||
<el-tree-select
|
<el-tree-select
|
||||||
clearable
|
clearable
|
||||||
@@ -127,45 +109,11 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<!-- 过敏史卡片 -->
|
<!-- 第五行:检查目的 + 病史摘要 -->
|
||||||
<div class="section-card allergy-card">
|
<el-row :gutter="16">
|
||||||
<div class="section-header">
|
<el-col :span="12">
|
||||||
<el-icon><Warning /></el-icon>
|
<el-form-item label="检查目的" prop="examinationPurpose">
|
||||||
<span>过敏史</span>
|
|
||||||
<span v-if="form.allergyHistory" class="header-count">{{ form.allergyHistory.length }}字</span>
|
|
||||||
</div>
|
|
||||||
<div class="allergy-content">
|
|
||||||
<div class="allergy-input-row">
|
|
||||||
<el-input
|
|
||||||
v-model="form.allergyHistory"
|
|
||||||
autocomplete="off"
|
|
||||||
type="textarea"
|
|
||||||
:rows="2"
|
|
||||||
:class="{ 'allergy-danger': isSevereAllergy }"
|
|
||||||
placeholder="如:造影剂过敏史等(系统将自动从患者档案带入)"
|
|
||||||
/>
|
|
||||||
<span v-if="isSevereAllergy" class="allergy-severe-tag">
|
|
||||||
<el-icon><WarningFilled /></el-icon>
|
|
||||||
严重过敏
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="allergy-confirm">
|
|
||||||
<el-checkbox v-model="form.allergyConfirmed" size="small">
|
|
||||||
已通过口头询问确认无过敏史
|
|
||||||
</el-checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 检查目的卡片 -->
|
|
||||||
<div class="section-card purpose-card">
|
|
||||||
<div class="section-header">
|
|
||||||
<el-icon><Aim /></el-icon>
|
|
||||||
<span>检查目的</span>
|
|
||||||
<span class="required-mark">必填</span>
|
|
||||||
</div>
|
|
||||||
<el-input
|
<el-input
|
||||||
v-model="form.examinationPurpose"
|
v-model="form.examinationPurpose"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
@@ -175,35 +123,61 @@
|
|||||||
show-word-limit
|
show-word-limit
|
||||||
placeholder="请输入检查目的,如:明确诊断、术后复查、疗效评估等"
|
placeholder="请输入检查目的,如:明确诊断、术后复查、疗效评估等"
|
||||||
/>
|
/>
|
||||||
</div>
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
<!-- 病史摘要卡片 -->
|
<el-col :span="12">
|
||||||
<div class="section-card history-card">
|
<el-form-item label="病史摘要" prop="medicalHistorySummary">
|
||||||
<div class="section-header">
|
<div class="history-field-wrapper">
|
||||||
<el-icon><DocumentCopy /></el-icon>
|
|
||||||
<span>病史摘要</span>
|
|
||||||
<span class="required-mark">必填</span>
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
size="small"
|
|
||||||
class="sync-btn"
|
|
||||||
@click="handleSyncHistory"
|
|
||||||
:loading="syncingHistory"
|
|
||||||
>
|
|
||||||
<el-icon><Refresh /></el-icon>
|
|
||||||
同步现病史/体征
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
<el-input
|
<el-input
|
||||||
v-model="form.medicalHistorySummary"
|
v-model="form.medicalHistorySummary"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:rows="3"
|
:rows="2"
|
||||||
placeholder="请简要描述患者病史摘要"
|
placeholder="请简要描述患者病史摘要"
|
||||||
/>
|
/>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
size="small"
|
||||||
|
class="history-sync-btn"
|
||||||
|
@click="handleSyncHistory"
|
||||||
|
:loading="syncingHistory"
|
||||||
|
>
|
||||||
|
<el-icon><Refresh /></el-icon>
|
||||||
|
同步
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 第六行:过敏史 -->
|
||||||
|
<el-row :gutter="16">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="过敏史">
|
||||||
|
<div class="allergy-wrapper">
|
||||||
|
<el-input
|
||||||
|
v-model="form.allergyHistory"
|
||||||
|
autocomplete="off"
|
||||||
|
type="textarea"
|
||||||
|
:rows="1"
|
||||||
|
:class="{ 'allergy-danger': isSevereAllergy }"
|
||||||
|
placeholder="如:造影剂过敏史等(系统将自动从患者档案带入)"
|
||||||
|
/>
|
||||||
|
<div class="allergy-actions">
|
||||||
|
<span v-if="isSevereAllergy" class="allergy-severe-tag-inline">
|
||||||
|
<el-icon><WarningFilled /></el-icon>
|
||||||
|
严重过敏
|
||||||
|
</span>
|
||||||
|
<el-checkbox v-model="form.allergyConfirmed" size="small">
|
||||||
|
已通过口头询问确认无过敏史
|
||||||
|
</el-checkbox>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 急诊确认弹窗 -->
|
<!-- 急诊确认弹窗 -->
|
||||||
@@ -227,11 +201,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup name="MedicalExaminations">
|
<script setup name="MedicalExaminations">
|
||||||
import {getCurrentInstance, onMounted, reactive, ref, watch, computed} from 'vue';
|
import {getCurrentInstance, onMounted, reactive, ref, watch, computed, nextTick} from 'vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import {patientInfo} from '../../../store/patient.js';
|
import {patientInfo} from '../../../store/patient.js';
|
||||||
import {getDepartmentList} from '@/api/public.js';
|
import {getDepartmentList} from '@/api/public.js';
|
||||||
import {getEncounterDiagnosis} from '../../api.js';
|
import {getEncounterDiagnosis} from '../../api.js';
|
||||||
import {getApplicationList, saveCheckd} from './api';
|
import {getExaminationPage, saveCheckd} from './api';
|
||||||
import {ElMessage, ElMessageBox} from 'element-plus';
|
import {ElMessage, ElMessageBox} from 'element-plus';
|
||||||
import {WarningFilled, Warning, Refresh, Files, Document, EditPen, Aim, DocumentCopy} from '@element-plus/icons-vue';
|
import {WarningFilled, Warning, Refresh, Files, Document, EditPen, Aim, DocumentCopy} from '@element-plus/icons-vue';
|
||||||
|
|
||||||
@@ -251,7 +226,26 @@ const findTreeItem = (list, id) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const emits = defineEmits(['submitOk']);
|
const emits = defineEmits(['submitOk']);
|
||||||
const props = defineProps({});
|
const props = defineProps({
|
||||||
|
isEditMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
editData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
// 支持通过props传入patientInfo
|
||||||
|
externalPatientInfo: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 优先使用外部传入的patientInfo,否则使用store中的
|
||||||
|
const effectivePatientInfo = computed(() => {
|
||||||
|
return props.externalPatientInfo || patientInfo.value;
|
||||||
|
});
|
||||||
const orgOptions = ref([]);
|
const orgOptions = ref([]);
|
||||||
const state = reactive({});
|
const state = reactive({});
|
||||||
const applicationListAll = ref();
|
const applicationListAll = ref();
|
||||||
@@ -272,25 +266,23 @@ const isSevereAllergy = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const getList = () => {
|
const getList = () => {
|
||||||
if (!patientInfo.value?.inHospitalOrgId) {
|
if (!effectivePatientInfo.value?.inHospitalOrgId) {
|
||||||
applicationList.value = [];
|
applicationList.value = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
getApplicationList({
|
getExaminationPage({
|
||||||
pageSize: 500,
|
organizationId: effectivePatientInfo.value.inHospitalOrgId,
|
||||||
pageNum: 1,
|
pageNo: 1,
|
||||||
categoryCode: '23',
|
pageSize: 5000,
|
||||||
organizationId: patientInfo.value.inHospitalOrgId,
|
searchKey: '',
|
||||||
adviceTypes: [3],
|
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200 && res.data?.records) {
|
||||||
applicationListAll.value = res.data.records;
|
applicationListAll.value = res.data.records;
|
||||||
applicationList.value = res.data.records.map((item) => {
|
applicationList.value = res.data.records.map((item) => {
|
||||||
const priceInfo = item.priceList?.[0] || {};
|
const price = item.price != null ? Number(item.price).toFixed(2) : '0.00';
|
||||||
const price = priceInfo.price != null ? Number(priceInfo.price).toFixed(2) : '0.00';
|
const unit = item.unitCodeDictText || item.unitCode || '';
|
||||||
const unit = item.unitCode_dictText || item.unitCode || '';
|
|
||||||
return {
|
return {
|
||||||
adviceDefinitionId: item.adviceDefinitionId,
|
adviceDefinitionId: item.adviceDefinitionId,
|
||||||
orgId: item.orgId,
|
orgId: item.orgId,
|
||||||
@@ -298,6 +290,22 @@ const getList = () => {
|
|||||||
key: item.adviceDefinitionId,
|
key: item.adviceDefinitionId,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
// 编辑模式下,加载完数据后设置已选项目
|
||||||
|
if (props.isEditMode && props.editData?.requestFormDetailList?.length) {
|
||||||
|
nextTick(() => {
|
||||||
|
// 使用 adviceName 匹配
|
||||||
|
const selectedNames = props.editData.requestFormDetailList.map(item => item.adviceName);
|
||||||
|
const selectedIds = [];
|
||||||
|
applicationList.value?.forEach(app => {
|
||||||
|
// 匹配时去掉价格部分,只比较名称
|
||||||
|
const appName = app.label?.split(' (')[0];
|
||||||
|
if (selectedNames.includes(appName)) {
|
||||||
|
selectedIds.push(app.key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
transferValue.value = selectedIds;
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
proxy.$message.error(res.message);
|
proxy.$message.error(res.message);
|
||||||
applicationList.value = [];
|
applicationList.value = [];
|
||||||
@@ -316,7 +324,7 @@ const form = reactive({
|
|||||||
allergyHistory: '',
|
allergyHistory: '',
|
||||||
examinationPurpose: '',
|
examinationPurpose: '',
|
||||||
medicalHistorySummary: '',
|
medicalHistorySummary: '',
|
||||||
expectedExaminationTime: '',
|
expectedExaminationTime: dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
symptom: '',
|
symptom: '',
|
||||||
sign: '',
|
sign: '',
|
||||||
clinicalDiagnosis: '',
|
clinicalDiagnosis: '',
|
||||||
@@ -387,49 +395,44 @@ const handleSyncHistory = async () => {
|
|||||||
|
|
||||||
// 自动带入患者过敏史
|
// 自动带入患者过敏史
|
||||||
const loadPatientAllergyHistory = () => {
|
const loadPatientAllergyHistory = () => {
|
||||||
if (!patientInfo.value?.patientId) return;
|
if (!effectivePatientInfo.value?.patientId) return;
|
||||||
};
|
};
|
||||||
|
|
||||||
const projectWithDepartment = (selectProjectIds, type) => {
|
// 加载编辑数据 — 只负责解析 descJson 填充表单字段
|
||||||
let isRelease = true;
|
// 已选项目的匹配由 getList 数据加载完成后统一处理
|
||||||
const arr = [];
|
const loadEditData = () => {
|
||||||
selectProjectIds.forEach((element) => {
|
if (!props.isEditMode || !props.editData?.requestFormId) return;
|
||||||
const searchData = applicationList.value.find((item) => {
|
|
||||||
return element == item.adviceDefinitionId;
|
if (props.editData.descJson) {
|
||||||
});
|
try {
|
||||||
arr.push(searchData);
|
const obj = JSON.parse(props.editData.descJson);
|
||||||
});
|
form.targetDepartment = obj.targetDepartment || '';
|
||||||
// 只有当选择了项目(arr 非空)时才设置 targetDepartment
|
form.urgencyLevel = obj.urgencyLevel || 'routine';
|
||||||
if (arr.length > 0) {
|
form.allergyHistory = obj.allergyHistory || '';
|
||||||
const obj = arr[0];
|
form.examinationPurpose = obj.examinationPurpose || '';
|
||||||
// 检查是否有未定义的项目(applicationList 中找不到)
|
form.medicalHistorySummary = obj.medicalHistorySummary || '';
|
||||||
if (!obj) {
|
form.expectedExaminationTime = obj.expectedExaminationTime || '';
|
||||||
console.warn('未找到项目定义,无法设置执行科室');
|
form.symptom = obj.symptom || '';
|
||||||
return false;
|
form.sign = obj.sign || '';
|
||||||
|
form.clinicalDiagnosis = obj.clinicalDiagnosis || '';
|
||||||
|
form.otherDiagnosis = obj.otherDiagnosis || '';
|
||||||
|
form.relatedResult = obj.relatedResult || '';
|
||||||
|
form.attention = obj.attention || '';
|
||||||
|
form.allergyConfirmed = obj.allergyConfirmed || false;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('解析descJson失败:', e);
|
||||||
}
|
}
|
||||||
const isCompare = arr.every((item) => item && item.orgId == obj.orgId);
|
|
||||||
if (!isCompare) {
|
|
||||||
ElMessage({ type: 'error', message: '执行科室不同' });
|
|
||||||
isRelease = false;
|
|
||||||
}
|
}
|
||||||
const findItem = findTreeItem(orgOptions.value, obj.orgId);
|
};
|
||||||
if (!findItem) {
|
|
||||||
isRelease = false;
|
const projectWithDepartment = (selectProjectIds) => {
|
||||||
ElMessage({ type: 'error', message: '未找到项目执行的科室' });
|
if (!selectProjectIds || selectProjectIds.length === 0) {
|
||||||
}
|
|
||||||
if (type == 1 && isRelease) {
|
|
||||||
form.targetDepartment = findItem.id;
|
|
||||||
console.log('targetDepartment 设置为:', form.targetDepartment, '科室名称:', findItem.name);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 清空选择时,也要清空 targetDepartment
|
|
||||||
form.targetDepartment = '';
|
form.targetDepartment = '';
|
||||||
}
|
}
|
||||||
return isRelease;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(() => transferValue.value, (newValue) => {
|
watch(() => transferValue.value, (newValue) => {
|
||||||
projectWithDepartment(newValue, 1);
|
projectWithDepartment(newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
const getPriorityCode = () => {
|
const getPriorityCode = () => {
|
||||||
@@ -440,8 +443,8 @@ const submit = () => {
|
|||||||
if (transferValue.value.length == 0) {
|
if (transferValue.value.length == 0) {
|
||||||
return proxy.$message.error('请选择检查项目');
|
return proxy.$message.error('请选择检查项目');
|
||||||
}
|
}
|
||||||
if (!projectWithDepartment(transferValue.value, 2)) {
|
if (!form.targetDepartment) {
|
||||||
return;
|
return proxy.$message.error('请选择发往科室');
|
||||||
}
|
}
|
||||||
if (!form.examinationPurpose) {
|
if (!form.examinationPurpose) {
|
||||||
return ElMessageBox.alert('请输入检查目的', '提示', { confirmButtonText: '确定', type: 'warning' });
|
return ElMessageBox.alert('请输入检查目的', '提示', { confirmButtonText: '确定', type: 'warning' });
|
||||||
@@ -456,39 +459,51 @@ const submit = () => {
|
|||||||
let applicationListAllFilter = applicationListAll.value.filter((item) => {
|
let applicationListAllFilter = applicationListAll.value.filter((item) => {
|
||||||
return transferValue.value.includes(item.adviceDefinitionId);
|
return transferValue.value.includes(item.adviceDefinitionId);
|
||||||
});
|
});
|
||||||
|
// 从原始记录中提取检查项目名称,用于申请单名称字段
|
||||||
|
const selectedNames = applicationListAllFilter.map(item => item.adviceName).join('+');
|
||||||
applicationListAllFilter = applicationListAllFilter.map((item) => {
|
applicationListAllFilter = applicationListAllFilter.map((item) => {
|
||||||
|
// 新接口 getExaminationPage 返回扁平字段(price/unitCode),兼容旧接口 priceList[0]
|
||||||
|
const priceInfo = item.priceList?.[0] || {};
|
||||||
return {
|
return {
|
||||||
adviceDefinitionId: item.adviceDefinitionId,
|
adviceDefinitionId: item.adviceDefinitionId,
|
||||||
|
adviceName: item.adviceName,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
unitCode: item.priceList[0].unitCode,
|
unitCode: item.unitCode || priceInfo.unitCode || '',
|
||||||
unitPrice: item.priceList[0].price,
|
unitPrice: item.price ?? priceInfo.price ?? 0,
|
||||||
totalPrice: item.priceList[0].price,
|
totalPrice: item.price ?? priceInfo.price ?? 0,
|
||||||
positionId: item.positionId,
|
positionId: item.positionId,
|
||||||
ybClassEnum: item.ybClassEnum,
|
ybClassEnum: item.ybClassEnum,
|
||||||
conditionId: item.conditionId,
|
conditionId: item.conditionId,
|
||||||
encounterDiagnosisId: item.encounterDiagnosisId,
|
encounterDiagnosisId: item.encounterDiagnosisId,
|
||||||
adviceType: item.adviceType,
|
adviceType: item.adviceType,
|
||||||
definitionId: item.priceList[0].definitionId,
|
definitionId: item.chargeItemDefinitionId || priceInfo.definitionId || '',
|
||||||
definitionDetailId: item.priceList[0].definitionDetailId,
|
definitionDetailId: item.definitionDetailId || priceInfo.definitionDetailId || '',
|
||||||
accountId: patientInfo.value.accountId,
|
accountId: effectivePatientInfo.value.accountId,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const submitForm = { ...form, priorityCode: getPriorityCode() };
|
const submitForm = { ...form, priorityCode: getPriorityCode() };
|
||||||
console.log('提交 descJson:', JSON.stringify(submitForm));
|
console.log('提交 descJson:', JSON.stringify(submitForm));
|
||||||
|
|
||||||
|
// 如果是编辑模式,带上requestFormId
|
||||||
|
const requestFormId = props.isEditMode ? props.editData?.requestFormId : '';
|
||||||
|
|
||||||
saveCheckd({
|
saveCheckd({
|
||||||
activityList: applicationListAllFilter,
|
activityList: applicationListAllFilter,
|
||||||
patientId: patientInfo.value.patientId,
|
patientId: effectivePatientInfo.value.patientId,
|
||||||
encounterId: patientInfo.value.encounterId,
|
encounterId: effectivePatientInfo.value.encounterId,
|
||||||
organizationId: patientInfo.value.inHospitalOrgId,
|
organizationId: effectivePatientInfo.value.inHospitalOrgId,
|
||||||
requestFormId: '',
|
requestFormId: requestFormId,
|
||||||
name: applicationListAllFilter.map(item => item.adviceName).join('、'),
|
name: selectedNames,
|
||||||
descJson: JSON.stringify(submitForm),
|
descJson: JSON.stringify(submitForm),
|
||||||
categoryEnum: '2',
|
categoryEnum: '22',
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
|
if (props.isEditMode) {
|
||||||
|
proxy.$message.success(res.msg || '修改成功');
|
||||||
|
} else {
|
||||||
proxy.$message.success(res.msg);
|
proxy.$message.success(res.msg);
|
||||||
|
}
|
||||||
applicationList.value = [];
|
applicationList.value = [];
|
||||||
resetForm();
|
resetForm();
|
||||||
emits('submitOk');
|
emits('submitOk');
|
||||||
@@ -504,7 +519,7 @@ const resetForm = () => {
|
|||||||
form.allergyHistory = '';
|
form.allergyHistory = '';
|
||||||
form.examinationPurpose = '';
|
form.examinationPurpose = '';
|
||||||
form.medicalHistorySummary = '';
|
form.medicalHistorySummary = '';
|
||||||
form.expectedExaminationTime = '';
|
form.expectedExaminationTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss');
|
||||||
form.symptom = '';
|
form.symptom = '';
|
||||||
form.sign = '';
|
form.sign = '';
|
||||||
form.clinicalDiagnosis = '';
|
form.clinicalDiagnosis = '';
|
||||||
@@ -524,7 +539,7 @@ const getLocationInfo = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function getDiagnosisList() {
|
function getDiagnosisList() {
|
||||||
getEncounterDiagnosis(patientInfo.value.encounterId).then((res) => {
|
getEncounterDiagnosis(effectivePatientInfo.value.encounterId).then((res) => {
|
||||||
if (res.code == 200) {
|
if (res.code == 200) {
|
||||||
const datas = (res.data || []).map((item) => {
|
const datas = (res.data || []).map((item) => {
|
||||||
let obj = { ...item };
|
let obj = { ...item };
|
||||||
@@ -545,9 +560,24 @@ onMounted(() => {
|
|||||||
getList();
|
getList();
|
||||||
getLocationInfo();
|
getLocationInfo();
|
||||||
loadPatientAllergyHistory();
|
loadPatientAllergyHistory();
|
||||||
|
loadEditData();
|
||||||
});
|
});
|
||||||
|
|
||||||
defineExpose({ state, submit, getLocationInfo, getDiagnosisList, resetForm });
|
// 监听编辑模式变化,重新加载数据
|
||||||
|
watch(
|
||||||
|
() => props.isEditMode,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
nextTick(() => {
|
||||||
|
getList();
|
||||||
|
getLocationInfo();
|
||||||
|
loadEditData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
defineExpose({ state, submit, getLocationInfo, getDiagnosisList, resetForm, getList });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -572,81 +602,13 @@ $bg-color: #f5f7fa;
|
|||||||
background: $bg-color;
|
background: $bg-color;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||||
|
|
||||||
// 顶部标题栏
|
// 主体内容区 - 紧凑布局
|
||||||
.form-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 14px 20px;
|
|
||||||
background: linear-gradient(135deg, #fff 0%, #f0f7ff 100%);
|
|
||||||
border-bottom: 1px solid $border-color;
|
|
||||||
|
|
||||||
.header-left {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
.header-icon {
|
|
||||||
font-size: 24px;
|
|
||||||
color: $primary-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: $text-primary;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-right {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
|
|
||||||
.urgency-label {
|
|
||||||
font-size: 13px;
|
|
||||||
color: $text-secondary;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.urgency-radio-group {
|
|
||||||
:deep(.el-radio-button__inner) {
|
|
||||||
border-radius: 4px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-radio-button:first-child .el-radio-button__inner) {
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-radio-button:last-child .el-radio-button__inner) {
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.emergency-tip {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
color: $danger-color;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
background: #fef0f0;
|
|
||||||
padding: 4px 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #fde2e2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 主体内容区
|
|
||||||
.form-body {
|
.form-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 8px;
|
||||||
padding: 16px;
|
padding: 8px 12px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
@@ -663,47 +625,30 @@ $bg-color: #f5f7fa;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 卡片通用样式
|
// 紧急程度栏 - 右上角
|
||||||
.section-card {
|
.urgency-bar {
|
||||||
background: #fff;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 16px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.04);
|
|
||||||
|
|
||||||
.section-header {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding-bottom: 12px;
|
padding: 4px 0;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 4px;
|
||||||
border-bottom: 1px dashed $border-color;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: $text-primary;
|
|
||||||
|
|
||||||
> i {
|
|
||||||
font-size: 16px;
|
|
||||||
color: $primary-color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-count {
|
.urgency-bar-label {
|
||||||
margin-left: auto;
|
font-size: 13px;
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: $text-secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.required-mark {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #fff;
|
color: $text-regular;
|
||||||
background: $danger-color;
|
white-space: nowrap;
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 10px;
|
|
||||||
margin-left: 4px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 卡片通用样式 - 紧凑
|
||||||
|
.section-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.transfer-wrapper {
|
.transfer-wrapper {
|
||||||
@@ -717,10 +662,23 @@ $bg-color: #f5f7fa;
|
|||||||
display: flex !important;
|
display: flex !important;
|
||||||
flex-direction: row !important;
|
flex-direction: row !important;
|
||||||
}
|
}
|
||||||
// 信息表单
|
|
||||||
|
// 穿梭框按钮垂直居中
|
||||||
|
:deep(.el-transfer__buttons) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-transfer__button) {
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
// 信息表单 - 紧凑
|
||||||
.info-form {
|
.info-form {
|
||||||
:deep(.el-form-item) {
|
:deep(.el-form-item) {
|
||||||
margin-bottom: 14px;
|
margin-bottom: 6px;
|
||||||
|
|
||||||
.el-form-item__label {
|
.el-form-item__label {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -750,54 +708,11 @@ $bg-color: #f5f7fa;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 过敏史卡片
|
// 过敏史危险输入样式
|
||||||
.allergy-card {
|
:deep(.el-textarea__inner.allergy-danger) {
|
||||||
.allergy-content {
|
|
||||||
.allergy-input-row {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
:deep(.el-textarea) {
|
|
||||||
.el-textarea__inner.allergy-danger {
|
|
||||||
border-color: $danger-color !important;
|
border-color: $danger-color !important;
|
||||||
background-color: #fef0f0;
|
background-color: #fef0f0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.allergy-severe-tag {
|
|
||||||
position: absolute;
|
|
||||||
right: 12px;
|
|
||||||
top: 8px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
color: $danger-color;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
background: #fef0f0;
|
|
||||||
padding: 3px 10px;
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 1px solid #fde2e2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.allergy-confirm {
|
|
||||||
margin-top: 10px;
|
|
||||||
padding-left: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 病史摘要卡片
|
|
||||||
.history-card {
|
|
||||||
.section-header {
|
|
||||||
.sync-btn {
|
|
||||||
margin-left: auto;
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 急诊确认弹窗
|
// 急诊确认弹窗
|
||||||
.emergency-dialog-content {
|
.emergency-dialog-content {
|
||||||
@@ -835,4 +750,64 @@ $bg-color: #f5f7fa;
|
|||||||
.fade-in-linear-leave-to {
|
.fade-in-linear-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 紧急程度行内布局 */
|
||||||
|
.urgency-inline {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emergency-tip-inline {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
color: $danger-color;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
background: #fef0f0;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 过敏史包装 */
|
||||||
|
.allergy-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.allergy-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.allergy-severe-tag-inline {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
color: $danger-color;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
background: #fef0f0;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 病史摘要同步按钮 */
|
||||||
|
.history-field-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-sync-btn {
|
||||||
|
position: absolute;
|
||||||
|
right: 4px;
|
||||||
|
top: -28px;
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,13 +5,27 @@
|
|||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<div class="surgery-container">
|
<div class="surgery-container">
|
||||||
<div v-loading="loading" class="transfer-wrapper" style="min-height: 300px;">
|
<div class="transfer-wrapper" style="min-height: 300px;">
|
||||||
|
<!-- 搜索框:≥3字触发后端搜索 -->
|
||||||
|
<div style="padding: 6px 0;">
|
||||||
|
<el-input
|
||||||
|
v-model="searchKey"
|
||||||
|
placeholder="请输入3个字及以上搜索"
|
||||||
|
clearable
|
||||||
|
@input="onSearchInput"
|
||||||
|
style="width: 320px;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- 加载提示不阻塞穿梭框操作 -->
|
||||||
|
<div v-if="loading" style="padding:8px 0; color:#909399; font-size:13px;">
|
||||||
|
<el-icon class="is-loading"><Loading /></el-icon> 手术项目加载中...
|
||||||
|
</div>
|
||||||
<el-transfer
|
<el-transfer
|
||||||
|
ref="transferRef"
|
||||||
v-model="transferValue"
|
v-model="transferValue"
|
||||||
:data="applicationList"
|
:data="applicationList"
|
||||||
filter-placeholder="项目代码/名称"
|
:titles="['待选择', '已选择']"
|
||||||
filterable
|
:format="leftPanelFormat"
|
||||||
:titles="['未选择', '已选择']"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="bloodTransfusion-form">
|
<div class="bloodTransfusion-form">
|
||||||
@@ -78,14 +92,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup name="Surgery">
|
<script setup name="Surgery">
|
||||||
import {getCurrentInstance, onBeforeMount, onMounted, reactive, ref, watch} from 'vue';
|
import {computed, getCurrentInstance, onBeforeMount, onMounted, reactive, ref, watch} from 'vue';
|
||||||
import {patientInfo} from '../../../store/patient.js';
|
import {patientInfo} from '../../../store/patient.js';
|
||||||
import {getDepartmentList} from '@/api/public.js';
|
import {getDepartmentList} from '@/api/public.js';
|
||||||
import {getEncounterDiagnosis} from '../../api.js';
|
import {getEncounterDiagnosis} from '../../api.js';
|
||||||
import {getApplicationList, saveSurgery} from './api';
|
import {getSurgeryPage, saveSurgery} from './api';
|
||||||
import {ElMessage} from 'element-plus';
|
import {ElMessage} from 'element-plus';
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance();
|
const { proxy } = getCurrentInstance();
|
||||||
|
// 模块级缓存:避免每次打开弹窗都重新请求手术项目列表
|
||||||
|
let surgeryRecordsCache = null; // 原始 API 记录
|
||||||
|
let surgeryMappedCache = null; // 映射后的 el-transfer 数据
|
||||||
|
let searchDebounceTimer = null; // 搜索防抖
|
||||||
|
const transferRef = ref(null);
|
||||||
|
const dbTotal = ref(0); // 数据库中的手术项目总数
|
||||||
|
const searchKey = ref(''); // 搜索关键字
|
||||||
|
const checkedCount = computed(() => transferValue.value.length);
|
||||||
|
const leftPanelFormat = computed(() => ({
|
||||||
|
noChecked: ` 0/${dbTotal.value}`,
|
||||||
|
hasChecked: ` \${checked}/${dbTotal.value}`,
|
||||||
|
}));
|
||||||
// 递归查找树形科室节点
|
// 递归查找树形科室节点
|
||||||
const findTreeItem = (list, id) => {
|
const findTreeItem = (list, id) => {
|
||||||
if (!list || list.length === 0) return null;
|
if (!list || list.length === 0) return null;
|
||||||
@@ -102,49 +128,88 @@ const emits = defineEmits(['submitOk']);
|
|||||||
const props = defineProps({});
|
const props = defineProps({});
|
||||||
const state = reactive({});
|
const state = reactive({});
|
||||||
const applicationListAll = ref();
|
const applicationListAll = ref();
|
||||||
const applicationList = ref();
|
const applicationList = ref([]);
|
||||||
const orgOptions = ref([]); // 科室选项
|
const orgOptions = ref([]); // 科室选项
|
||||||
const loading = ref(false); // 加载状态
|
const loading = ref(false); // 加载状态
|
||||||
const getList = () => {
|
const mapToTransferItem = (item) => {
|
||||||
if (!patientInfo.value?.inHospitalOrgId) {
|
const price = item.price != null ? Number(item.price).toFixed(2) : '0.00';
|
||||||
applicationList.value = [];
|
const unit = item.unitCodeDictText || item.unitCode || '';
|
||||||
return;
|
|
||||||
}
|
|
||||||
loading.value = true;
|
|
||||||
getApplicationList({
|
|
||||||
pageSize: 500,
|
|
||||||
pageNum: 1,
|
|
||||||
categoryCode: '24',
|
|
||||||
organizationId: patientInfo.value.inHospitalOrgId,
|
|
||||||
adviceTypes: [3, 6], //1 药品 2耗材 3诊疗 6手术
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
if (res.code === 200) {
|
|
||||||
applicationListAll.value = res.data.records;
|
|
||||||
applicationList.value = res.data.records.map((item) => {
|
|
||||||
const priceInfo = item.priceList?.[0] || {};
|
|
||||||
const price = priceInfo.price != null ? Number(priceInfo.price).toFixed(2) : '0.00';
|
|
||||||
const unit = item.unitCode_dictText || item.unitCode || '';
|
|
||||||
return {
|
return {
|
||||||
adviceDefinitionId: item.adviceDefinitionId,
|
adviceDefinitionId: item.adviceDefinitionId,
|
||||||
orgId: item.orgId,
|
orgId: item.orgId,
|
||||||
label: item.adviceName + ' (¥' + price + '/' + unit + ')',
|
label: item.adviceName + ' (¥' + price + '/' + unit + ')',
|
||||||
key: item.adviceDefinitionId,
|
key: item.adviceDefinitionId,
|
||||||
};
|
};
|
||||||
});
|
};
|
||||||
} else {
|
const getList = () => {
|
||||||
console.warn('获取手术项目列表失败:', res.message);
|
if (!patientInfo.value?.inHospitalOrgId) {
|
||||||
applicationList.value = [];
|
applicationList.value = [];
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
// 命中内存缓存时直接使用
|
||||||
|
if (surgeryMappedCache && surgeryMappedCache.length > 0) {
|
||||||
|
applicationList.value = surgeryMappedCache;
|
||||||
|
applicationListAll.value = surgeryRecordsCache;
|
||||||
|
loading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loadPage('');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载手术项目分页数据
|
||||||
|
* @param {string} key 搜索关键字(可选)
|
||||||
|
*/
|
||||||
|
const loadPage = (key) => {
|
||||||
|
const orgId = patientInfo.value.inHospitalOrgId;
|
||||||
|
loading.value = true;
|
||||||
|
getSurgeryPage({ organizationId: orgId, pageNo: 1, pageSize: 100, searchKey: key || '' })
|
||||||
|
.then((res) => {
|
||||||
|
if (res.code !== 200 || !res.data?.records) {
|
||||||
|
applicationList.value = [];
|
||||||
|
dbTotal.value = 0;
|
||||||
|
loading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dbTotal.value = res.data.total || 0;
|
||||||
|
const records = res.data.records;
|
||||||
|
applicationListAll.value = records;
|
||||||
|
applicationList.value = records
|
||||||
|
.filter(item => item.adviceDefinitionId != null)
|
||||||
|
.map(mapToTransferItem);
|
||||||
|
// 仅在无搜索时缓存
|
||||||
|
if (!key) {
|
||||||
|
surgeryRecordsCache = records;
|
||||||
|
surgeryMappedCache = applicationList.value;
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.warn('手术项目列表加载失败(可能无权限):', e?.message || e);
|
console.error('手术项目加载失败:', e);
|
||||||
applicationList.value = [];
|
applicationList.value = [];
|
||||||
})
|
dbTotal.value = 0;
|
||||||
.finally(() => {
|
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索输入框变化处理(防抖300ms,≥3字触发后端搜索)
|
||||||
|
*/
|
||||||
|
const onSearchInput = () => {
|
||||||
|
clearTimeout(searchDebounceTimer);
|
||||||
|
const val = searchKey.value.trim();
|
||||||
|
if (!val) {
|
||||||
|
// 清空搜索框,恢复初始数据
|
||||||
|
loadPage('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (val.length >= 3) {
|
||||||
|
searchDebounceTimer = setTimeout(() => {
|
||||||
|
loadPage(val);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const transferValue = ref([]);
|
const transferValue = ref([]);
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
// categoryType: '', // 项目类别
|
// categoryType: '', // 项目类别
|
||||||
@@ -231,20 +296,15 @@ const submit = () => {
|
|||||||
});
|
});
|
||||||
applicationListAllFilter = applicationListAllFilter.map((item) => {
|
applicationListAllFilter = applicationListAllFilter.map((item) => {
|
||||||
return {
|
return {
|
||||||
adviceDefinitionId: item.adviceDefinitionId /** 诊疗定义id */,
|
adviceDefinitionId: item.adviceDefinitionId,
|
||||||
adviceDefinitionName: item.adviceDefinitionName /** 诊疗定义名称(手术项目名称) */,
|
adviceDefinitionName: item.adviceName,
|
||||||
quantity: 1, // /** 请求数量 */
|
quantity: 1,
|
||||||
unitCode: item.priceList[0].unitCode /** 请求单位编码 */,
|
unitCode: item.unitCode,
|
||||||
unitPrice: item.priceList[0].price /** 单价 */,
|
unitPrice: item.price,
|
||||||
totalPrice: item.priceList[0].price /** 总价 */,
|
totalPrice: item.price,
|
||||||
positionId: item.positionId, //执行科室id
|
positionId: item.positionId,
|
||||||
ybClassEnum: item.ybClassEnum, //类别医保编码
|
definitionId: item.chargeItemDefinitionId,
|
||||||
conditionId: item.conditionId, //诊断ID
|
accountId: patientInfo.value.accountId,
|
||||||
encounterDiagnosisId: item.encounterDiagnosisId, //就诊诊断id
|
|
||||||
adviceType: item.adviceType, ///** 医嘱类型 */
|
|
||||||
definitionId: item.priceList[0].definitionId, //费用定价主表ID */
|
|
||||||
definitionDetailId: item.definitionDetailId, //费用定价子表ID */
|
|
||||||
accountId: patientInfo.value.accountId, // // 账户id
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
saveSurgery({
|
saveSurgery({
|
||||||
@@ -255,7 +315,7 @@ const submit = () => {
|
|||||||
requestFormId: '', // 申请单ID
|
requestFormId: '', // 申请单ID
|
||||||
name: '手术申请单',
|
name: '手术申请单',
|
||||||
descJson: JSON.stringify(form),
|
descJson: JSON.stringify(form),
|
||||||
categoryEnum: '4', // 1 检验 2 检查 3 输血 4 手术
|
categoryEnum: '24', // 21 检验 22 检查 23 输血 24 手术(避开 adviceType 1-6 碰撞)
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
proxy.$message.success(res.msg);
|
proxy.$message.success(res.msg);
|
||||||
|
|||||||
@@ -197,6 +197,7 @@
|
|||||||
style="width: 62%"
|
style="width: 62%"
|
||||||
v-model="scope.row.adviceName"
|
v-model="scope.row.adviceName"
|
||||||
placeholder="请选择项目"
|
placeholder="请选择项目"
|
||||||
|
@input="handleChange"
|
||||||
@click="handleFocus(scope.row, scope.$index)"
|
@click="handleFocus(scope.row, scope.$index)"
|
||||||
@keyup.enter.stop="handleFocus(scope.row, scope.$index)"
|
@keyup.enter.stop="handleFocus(scope.row, scope.$index)"
|
||||||
@keydown="
|
@keydown="
|
||||||
@@ -379,7 +380,7 @@ const form = ref({
|
|||||||
prescriptionList: prescriptionList.value,
|
prescriptionList: prescriptionList.value,
|
||||||
});
|
});
|
||||||
const adviceQueryParams = ref({
|
const adviceQueryParams = ref({
|
||||||
adviceType: '',
|
adviceType: 1,
|
||||||
categoryCode: '', // 初始为空,等待加载配置后动态设置
|
categoryCode: '', // 初始为空,等待加载配置后动态设置
|
||||||
searchKey: '',
|
searchKey: '',
|
||||||
});
|
});
|
||||||
@@ -685,7 +686,7 @@ function loadConfiguredCategories() {
|
|||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// 创建新对象触发响应式更新
|
// 创建新对象触发响应式更新
|
||||||
adviceQueryParams.value = {
|
adviceQueryParams.value = {
|
||||||
adviceType: '',
|
adviceType: 1,
|
||||||
categoryCode: defaultCategoryCode,
|
categoryCode: defaultCategoryCode,
|
||||||
searchKey: '',
|
searchKey: '',
|
||||||
};
|
};
|
||||||
@@ -801,8 +802,8 @@ function clickRowDb(row, column, event) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
row.showPopover = false;
|
row.showPopover = false;
|
||||||
// 仅”待签发(statusEnum==1)”允许编辑;”已签发(statusEnum==2)”及之后状态不允许编辑
|
// “待签发(已保存 requestId存在)”不允许再编辑;仅“待保存(无requestId)”允许编辑
|
||||||
if (row.statusEnum == 1) {
|
if (row.statusEnum == 1 && !row.requestId) {
|
||||||
// 确保治疗类型为字符串,方便与单选框 label 对齐,默认为长期医嘱('1')
|
// 确保治疗类型为字符串,方便与单选框 label 对齐,默认为长期医嘱('1')
|
||||||
row.therapyEnum = String(row.therapyEnum ?? '1');
|
row.therapyEnum = String(row.therapyEnum ?? '1');
|
||||||
row.isEdit = true;
|
row.isEdit = true;
|
||||||
@@ -880,9 +881,9 @@ function handleFocus(row, index) {
|
|||||||
// 用 adviceType + categoryCode 组合查找匹配的选项
|
// 用 adviceType + categoryCode 组合查找匹配的选项
|
||||||
const selectValue = (adviceType == 1 && row.categoryCode) ? '1-' + row.categoryCode : adviceType;
|
const selectValue = (adviceType == 1 && row.categoryCode) ? '1-' + row.categoryCode : adviceType;
|
||||||
const selectedItem = adviceTypeList.value.find(item => item.value === selectValue) || adviceTypeList.value.find(item => item.adviceType === adviceType);
|
const selectedItem = adviceTypeList.value.find(item => item.value === selectValue) || adviceTypeList.value.find(item => item.adviceType === adviceType);
|
||||||
// 修复Bug #486:当行没有显式选择医嘱类型时(row.adviceType为undefined),
|
// If the row has an explicit adviceType (saved/existing row), use its own categoryCode.
|
||||||
// 不传categoryCode,让搜索在全药库中进行;只有行已选择类型时才用对应categoryCode过滤
|
// If no type is selected (new row), use empty string for global search across all categories.
|
||||||
const categoryCode = row.adviceType !== undefined ? (selectedItem ? selectedItem.categoryCode : '') : '';
|
const categoryCode = selectedItem ? selectedItem.categoryCode : (row.adviceType != null ? (row.categoryCode || '') : '');
|
||||||
const searchKey = row.adviceName || '';
|
const searchKey = row.adviceName || '';
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@@ -919,9 +920,8 @@ function handleChange(value) {
|
|||||||
// 用 adviceType + categoryCode 组合查找匹配的选项
|
// 用 adviceType + categoryCode 组合查找匹配的选项
|
||||||
const selectValue = (adviceType == 1 && row?.categoryCode) ? '1-' + row.categoryCode : adviceType;
|
const selectValue = (adviceType == 1 && row?.categoryCode) ? '1-' + row.categoryCode : adviceType;
|
||||||
const selectedItem = adviceTypeList.value.find(item => item.value === selectValue) || adviceTypeList.value.find(item => item.adviceType === adviceType);
|
const selectedItem = adviceTypeList.value.find(item => item.value === selectValue) || adviceTypeList.value.find(item => item.adviceType === adviceType);
|
||||||
// 修复Bug #486:当行没有显式选择医嘱类型时(row?.adviceType为undefined),
|
// 修复Bug #486:当行没有显式选择医嘱类型时,不传categoryCode,让搜索在全药库中进行
|
||||||
// 不传categoryCode,让搜索在全药库中进行;只有行已选择类型时才用对应categoryCode过滤
|
const categoryCode = selectedItem ? selectedItem.categoryCode : (row?.adviceType !== undefined ? (adviceQueryParams.value.categoryCode || '') : '');
|
||||||
const categoryCode = row?.adviceType !== undefined ? (selectedItem ? selectedItem.categoryCode : '') : '';
|
|
||||||
tableRef.refresh(adviceType, categoryCode, value);
|
tableRef.refresh(adviceType, categoryCode, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1181,10 +1181,8 @@ function handleSave() {
|
|||||||
});
|
});
|
||||||
// 此处签发处方和单行保存处方传参相同,后台已经将传参存为JSON字符串,此处直接转换为JSON即可
|
// 此处签发处方和单行保存处方传参相同,后台已经将传参存为JSON字符串,此处直接转换为JSON即可
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
let list = [];
|
let list = saveList.map((item) => {
|
||||||
try {
|
const parsedContent = JSON.parse(item.contentJson);
|
||||||
list = saveList.map((item) => {
|
|
||||||
const parsedContent = item.contentJson ? JSON.parse(item.contentJson) || {} : {};
|
|
||||||
return {
|
return {
|
||||||
...parsedContent,
|
...parsedContent,
|
||||||
adviceType: item.adviceType,
|
adviceType: item.adviceType,
|
||||||
@@ -1196,12 +1194,6 @@ function handleSave() {
|
|||||||
therapyEnum: parsedContent.therapyEnum || item.therapyEnum || '1',
|
therapyEnum: parsedContent.therapyEnum || item.therapyEnum || '1',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} catch (error) {
|
|
||||||
loading.value = false;
|
|
||||||
isSaving.value = false;
|
|
||||||
proxy.$modal.msgError('医嘱内容解析失败,请检查待签发医嘱');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 保存签发按钮
|
// 保存签发按钮
|
||||||
isSaving.value = true;
|
isSaving.value = true;
|
||||||
console.log('签发处方参数:', {
|
console.log('签发处方参数:', {
|
||||||
@@ -1218,7 +1210,7 @@ function handleSave() {
|
|||||||
isSaving.value = false;
|
isSaving.value = false;
|
||||||
getListInfo(false);
|
getListInfo(false);
|
||||||
bindMethod.value = {};
|
bindMethod.value = {};
|
||||||
nextId.value = 1;
|
nextId.value == 1;
|
||||||
} else {
|
} else {
|
||||||
proxy.$modal.msgError(res.message);
|
proxy.$modal.msgError(res.message);
|
||||||
isSaving.value = false;
|
isSaving.value = false;
|
||||||
@@ -1384,21 +1376,13 @@ function handleSaveSign(row, index) {
|
|||||||
savePrescription({ regAdviceSaveList: [row] }).then((res) => {
|
savePrescription({ regAdviceSaveList: [row] }).then((res) => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
proxy.$modal.msgSuccess('保存成功');
|
proxy.$modal.msgSuccess('保存成功');
|
||||||
nextId.value = 1;
|
nextId.value == 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 新增行:调用保存接口将数据持久化到后端
|
if (prescriptionList.value[0].adviceName) {
|
||||||
row.dbOpType = '1';
|
handleAddPrescription();
|
||||||
savePrescription({ regAdviceSaveList: [row] }).then((res) => {
|
|
||||||
if (res.code === 200) {
|
|
||||||
proxy.$modal.msgSuccess('保存成功');
|
|
||||||
nextId.value = 1;
|
|
||||||
// 保存成功后刷新列表,确保后端返回的数据带 requestId
|
|
||||||
getListInfo(false);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
// 不需要再添加空行,保存成功后由 getListInfo 处理
|
|
||||||
}
|
}
|
||||||
adviceQueryParams.value.adviceType = undefined;
|
adviceQueryParams.value.adviceType = undefined;
|
||||||
}
|
}
|
||||||
@@ -1445,13 +1429,12 @@ function handleSaveBatch() {
|
|||||||
if (row) row.isEdit = false;
|
if (row) row.isEdit = false;
|
||||||
});
|
});
|
||||||
getListInfo(false);
|
getListInfo(false);
|
||||||
nextId.value = 1;
|
nextId.value == 1;
|
||||||
isSaving.value = false;
|
isSaving.value = false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
isSaving.value = false;
|
isSaving.value = false;
|
||||||
proxy.$modal.msgError(error?.msg || '保存失败,请重试');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1579,21 +1562,17 @@ function handleSaveGroup(orderGroupList) {
|
|||||||
// 🔥 新版组件已经预处理了数据,优先使用 mergedDetail
|
// 🔥 新版组件已经预处理了数据,优先使用 mergedDetail
|
||||||
const mergedDetail = item.mergedDetail || {
|
const mergedDetail = item.mergedDetail || {
|
||||||
...(item.orderDetailInfos || {}),
|
...(item.orderDetailInfos || {}),
|
||||||
adviceName: item.orderDefinitionName || item.orderDetailInfos?.adviceName || '未知项目',
|
adviceName: item.orderDetailInfos?.adviceName || item.orderDefinitionName || '未知项目',
|
||||||
adviceType: item.orderDetailInfos?.adviceType,
|
adviceType: item.orderDetailInfos?.adviceType,
|
||||||
adviceDefinitionId: item.orderDefinitionId || item.orderDetailInfos?.adviceDefinitionId,
|
adviceDefinitionId: item.orderDefinitionId || item.orderDetailInfos?.adviceDefinitionId,
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
unitCode: item.unitCode || item.orderDetailInfos?.unitCode,
|
unitCode: item.unitCode || item.orderDetailInfos?.unitCode,
|
||||||
unitCodeName: item.unitCodeName,
|
unitCodeName: item.unitCodeName,
|
||||||
// 🔧 Bug #403 修复:dose/doseQuantity/dispensePerDuration 需用 null 检查,
|
dose: item.dose || item.orderDetailInfos?.dose,
|
||||||
// 避免组套中值为 null 时回退到医嘱库的 orderDetailInfos
|
|
||||||
dose: item.dose !== undefined && item.dose !== null ? item.dose : item.orderDetailInfos?.dose,
|
|
||||||
rateCode: item.rateCode || item.orderDetailInfos?.rateCode,
|
rateCode: item.rateCode || item.orderDetailInfos?.rateCode,
|
||||||
methodCode: item.methodCode || item.orderDetailInfos?.methodCode,
|
methodCode: item.methodCode || item.orderDetailInfos?.methodCode,
|
||||||
dispensePerDuration: item.dispensePerDuration !== undefined && item.dispensePerDuration !== null
|
dispensePerDuration: item.dispensePerDuration || item.orderDetailInfos?.dispensePerDuration,
|
||||||
? item.dispensePerDuration : item.orderDetailInfos?.dispensePerDuration,
|
doseQuantity: item.doseQuantity,
|
||||||
doseQuantity: item.doseQuantity !== undefined && item.doseQuantity !== null
|
|
||||||
? item.doseQuantity : item.orderDetailInfos?.doseQuantity,
|
|
||||||
inventoryList: item.orderDetailInfos?.inventoryList || [],
|
inventoryList: item.orderDetailInfos?.inventoryList || [],
|
||||||
priceList: item.orderDetailInfos?.priceList || [],
|
priceList: item.orderDetailInfos?.priceList || [],
|
||||||
partPercent: item.orderDetailInfos?.partPercent || 1,
|
partPercent: item.orderDetailInfos?.partPercent || 1,
|
||||||
@@ -1612,42 +1591,44 @@ function handleSaveGroup(orderGroupList) {
|
|||||||
setValue(mergedDetail);
|
setValue(mergedDetail);
|
||||||
|
|
||||||
// 创建新的处方项目
|
// 创建新的处方项目
|
||||||
// 🔧 Bug #403 修复:关键字段使用 null-safe 回退到 mergedDetail(已由 setValue 填充完整数据)
|
|
||||||
const newRow = {
|
const newRow = {
|
||||||
...prescriptionList.value[rowIndex.value],
|
...prescriptionList.value[rowIndex.value],
|
||||||
patientId: patientInfo.value.patientId,
|
patientId: patientInfo.value.patientId,
|
||||||
encounterId: patientInfo.value.encounterId,
|
encounterId: patientInfo.value.encounterId,
|
||||||
accountId: accountId.value,
|
accountId: accountId.value,
|
||||||
quantity: item.quantity ?? mergedDetail.quantity,
|
// 🔧 修复 Bug #403:从 mergedDetail 读取明细字段,而非直接从 item 取
|
||||||
methodCode: item.methodCode ?? mergedDetail.methodCode,
|
// item.dose 等字段可能为 null,mergedDetail 已做 ?? 兜底
|
||||||
rateCode: item.rateCode ?? mergedDetail.rateCode,
|
quantity: mergedDetail.quantity ?? item.quantity,
|
||||||
dispensePerDuration: item.dispensePerDuration ?? mergedDetail.dispensePerDuration,
|
methodCode: mergedDetail.methodCode ?? item.methodCode,
|
||||||
dose: item.dose ?? mergedDetail.dose,
|
rateCode: mergedDetail.rateCode ?? item.rateCode,
|
||||||
doseQuantity: item.doseQuantity ?? mergedDetail.doseQuantity,
|
dispensePerDuration: mergedDetail.dispensePerDuration ?? item.dispensePerDuration,
|
||||||
|
dose: mergedDetail.dose ?? item.dose,
|
||||||
|
doseQuantity: mergedDetail.doseQuantity ?? item.doseQuantity,
|
||||||
executeNum: 1,
|
executeNum: 1,
|
||||||
unitCode: item.unitCode ?? mergedDetail.unitCode,
|
unitCode: mergedDetail.unitCode ?? item.unitCode,
|
||||||
unitCode_dictText: item.unitCodeName || mergedDetail.unitCodeName || '',
|
unitCode_dictText: item.unitCodeName || mergedDetail.unitCodeName || '',
|
||||||
statusEnum: 1,
|
statusEnum: 1,
|
||||||
orgId: resolveOrgId(item.orderDetailInfos?.orgId || mergedDetail.orgId || patientInfo.value?.inHospitalOrgId) || '',
|
orgId: resolveOrgId(mergedDetail.orgId || patientInfo.value?.inHospitalOrgId) || '',
|
||||||
// 🔧 修复:同时保存 orgName,确保树匹配不到时仍有中文名称可显示
|
// 🔧 修复:同时存储 orgName,确保树匹配不到时仍有中文名称可显示
|
||||||
orgName: findOrgName(item.orderDetailInfos?.orgId || mergedDetail.orgId || patientInfo.value?.inHospitalOrgId) || item.orderDetailInfos?.orgName || mergedDetail.orgName || patientInfo.value?.inHospitalOrgName || '',
|
orgName: findOrgName(mergedDetail.orgId || patientInfo.value?.inHospitalOrgId) || mergedDetail.orgName || patientInfo.value?.inHospitalOrgName || '',
|
||||||
dbOpType: prescriptionList.value[rowIndex.value].requestId ? '2' : '1',
|
dbOpType: prescriptionList.value[rowIndex.value].requestId ? '2' : '1',
|
||||||
conditionId: conditionId.value,
|
conditionId: conditionId.value,
|
||||||
conditionDefinitionId: conditionDefinitionId.value,
|
conditionDefinitionId: conditionDefinitionId.value,
|
||||||
encounterDiagnosisId: encounterDiagnosisId.value,
|
encounterDiagnosisId: encounterDiagnosisId.value,
|
||||||
therapyEnum: prescriptionList.value[rowIndex.value]?.therapyEnum || '1',
|
therapyEnum: prescriptionList.value[rowIndex.value]?.therapyEnum || mergedDetail.therapyEnum || '1',
|
||||||
};
|
};
|
||||||
|
|
||||||
// 计算价格和总量
|
// 计算价格和总量
|
||||||
const unitInfo = unitCodeList.value.find((k) => k.value == item.unitCode);
|
const resolvedUnitCode = mergedDetail.unitCode ?? item.unitCode;
|
||||||
|
const unitInfo = unitCodeList.value.find((k) => k.value == resolvedUnitCode);
|
||||||
if (unitInfo && unitInfo.type == 'minUnit') {
|
if (unitInfo && unitInfo.type == 'minUnit') {
|
||||||
newRow.price = newRow.minUnitPrice;
|
newRow.price = newRow.minUnitPrice;
|
||||||
newRow.totalPrice = (item.quantity * newRow.minUnitPrice).toFixed(6);
|
newRow.totalPrice = (newRow.quantity * newRow.minUnitPrice).toFixed(6);
|
||||||
newRow.minUnitQuantity = item.quantity;
|
newRow.minUnitQuantity = newRow.quantity;
|
||||||
} else {
|
} else {
|
||||||
newRow.price = newRow.unitPrice;
|
newRow.price = newRow.unitPrice;
|
||||||
newRow.totalPrice = (item.quantity * newRow.unitPrice).toFixed(6);
|
newRow.totalPrice = (newRow.quantity * newRow.unitPrice).toFixed(6);
|
||||||
newRow.minUnitQuantity = item.quantity * (item.orderDetailInfos?.partPercent || mergedDetail.partPercent || 1);
|
newRow.minUnitQuantity = newRow.quantity * (mergedDetail.partPercent || 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
newRow.contentJson = JSON.stringify(newRow);
|
newRow.contentJson = JSON.stringify(newRow);
|
||||||
|
|||||||
@@ -19,11 +19,9 @@
|
|||||||
<el-tab-pane label="检验申请" name="test">
|
<el-tab-pane label="检验申请" name="test">
|
||||||
<TestApplication ref="testApplicationRef" :show-status-column="true" />
|
<TestApplication ref="testApplicationRef" :show-status-column="true" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
```vue
|
|
||||||
<el-tab-pane label="检查申请" name="examine">
|
<el-tab-pane label="检查申请" name="examine">
|
||||||
<ExamineApplication ref="examineApplicationRef" />
|
<ExamineApplication ref="examineApplicationRef" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
```
|
|
||||||
<el-tab-pane label="汇总发药申请" name="summaryDrug">
|
<el-tab-pane label="汇总发药申请" name="summaryDrug">
|
||||||
<SummaryDrugApplication ref="summaryDrugApplicationRef" />
|
<SummaryDrugApplication ref="summaryDrugApplicationRef" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
@@ -49,6 +47,7 @@
|
|||||||
import {computed, onBeforeMount, onMounted, provide, reactive, ref, watch,} from 'vue';
|
import {computed, onBeforeMount, onMounted, provide, reactive, ref, watch,} from 'vue';
|
||||||
|
|
||||||
import Emr from './emr/index.vue';
|
import Emr from './emr/index.vue';
|
||||||
|
import SummaryDrugApplication from './components/applicationShow/summaryDrugApplication.vue';
|
||||||
import inPatientBarDoctorFold from '@/components/patientBar/inPatientBarDoctorFold.vue';
|
import inPatientBarDoctorFold from '@/components/patientBar/inPatientBarDoctorFold.vue';
|
||||||
import PatientList from '@/components/PatientList/patient-list.vue';
|
import PatientList from '@/components/PatientList/patient-list.vue';
|
||||||
import {localPatientInfo, updateLocalPatientInfo} from './store/localPatient';
|
import {localPatientInfo, updateLocalPatientInfo} from './store/localPatient';
|
||||||
@@ -84,6 +83,7 @@ const currentPatientInfo = ref({});
|
|||||||
const testApplicationRef = ref();
|
const testApplicationRef = ref();
|
||||||
const examineApplicationRef = ref();
|
const examineApplicationRef = ref();
|
||||||
const surgeryApplicationRef = ref();
|
const surgeryApplicationRef = ref();
|
||||||
|
const summaryDrugApplicationRef = ref();
|
||||||
const bloodTtransfusionAapplicationRef = ref();
|
const bloodTtransfusionAapplicationRef = ref();
|
||||||
|
|
||||||
// 患者列表相关逻辑
|
// 患者列表相关逻辑
|
||||||
|
|||||||
@@ -251,7 +251,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<!-- 划价组套选择对话框 -->
|
<!-- 划价组套选择对话框 -->
|
||||||
<el-dialog v-model="groupSetDialogVisible" title="划价组套选择" width="600px" :close-on-click-modal="false" append-to-body>
|
<el-dialog v-model="groupSetDialogVisible" title="划价组套选择" width="600px" :close-on-click-modal="false" append-to-body :z-index="3000">
|
||||||
<div style="margin-bottom: 15px; display: flex; align-items: center; gap: 10px">
|
<div style="margin-bottom: 15px; display: flex; align-items: center; gap: 10px">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="groupSetSearchText"
|
v-model="groupSetSearchText"
|
||||||
@@ -296,6 +296,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
<el-empty v-if="!groupSetLoading && groupSetList.length === 0" description="暂无划价组套数据" :image-size="80" />
|
||||||
<div style="margin-top: 15px; text-align: right">
|
<div style="margin-top: 15px; text-align: right">
|
||||||
<el-button @click="groupSetDialogVisible = false">取消</el-button>
|
<el-button @click="groupSetDialogVisible = false">取消</el-button>
|
||||||
<el-button type="primary" @click="applyGroupSet" :disabled="!selectedGroupSet">应用</el-button>
|
<el-button type="primary" @click="applyGroupSet" :disabled="!selectedGroupSet">应用</el-button>
|
||||||
@@ -481,19 +482,68 @@ watch(
|
|||||||
(visible) => {
|
(visible) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
executeTime.value = formatDateStr(new Date(), 'YYYY-MM-DD HH:mm:ss');
|
executeTime.value = formatDateStr(new Date(), 'YYYY-MM-DD HH:mm:ss');
|
||||||
|
// 弹窗打开时重新加载科室和位置选项,确保数据最新
|
||||||
|
loadDepartmentOptions();
|
||||||
|
getDiseaseInitLoc(16);
|
||||||
} else {
|
} else {
|
||||||
resetData();
|
resetData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// 加载科室选项
|
// 监听科室选项加载完成,为已添加的诊疗项目设置默认执行科室
|
||||||
|
watch(
|
||||||
|
() => departmentOptions.value,
|
||||||
|
(depts) => {
|
||||||
|
if (!depts || depts.length === 0) return;
|
||||||
|
feeItemsList.value.forEach(item => {
|
||||||
|
if (item.adviceType === 3 && !item.positionId) {
|
||||||
|
const patientOrgId = props.patientInfo.organizationId;
|
||||||
|
const matched = depts.find(d => String(d.id) === String(patientOrgId));
|
||||||
|
item.positionId = matched ? String(matched.id) : String(depts[0].id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听位置选项加载完成,为已添加的耗材项目设置默认位置
|
||||||
|
watch(
|
||||||
|
() => locationOptions.value,
|
||||||
|
(locs) => {
|
||||||
|
if (!locs || locs.length === 0) return;
|
||||||
|
feeItemsList.value.forEach(item => {
|
||||||
|
if (item.adviceType === 2 && !item.positionId) {
|
||||||
|
item.positionId = String(locs[0].value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 加载科室选项(支持树形/扁平两种数据结构)
|
||||||
function loadDepartmentOptions() {
|
function loadDepartmentOptions() {
|
||||||
getOrgList()
|
getOrgList()
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.data && res.data.records && res.data.records.length > 0) {
|
if (res.data) {
|
||||||
departmentOptions.value = res.data.records[0].children || [];
|
// 尝试从树形结构中取:records[0].children
|
||||||
|
if (res.data.records && res.data.records.length > 0) {
|
||||||
|
if (res.data.records[0].children && res.data.records[0].children.length > 0) {
|
||||||
|
departmentOptions.value = res.data.records[0].children;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
// 如果 records[0] 有 id 和 name(非树根节点),直接用所有 records
|
||||||
|
if (res.data.records[0].id) {
|
||||||
|
departmentOptions.value = res.data.records;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 兜底:如果 records 不存在或为空,尝试直接使用 data 本身
|
||||||
|
if (Array.isArray(res.data)) {
|
||||||
|
departmentOptions.value = res.data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 所有方式都失败,置空
|
||||||
|
departmentOptions.value = [];
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.warn('科室列表加载失败(可能无权限)');
|
console.warn('科室列表加载失败(可能无权限)');
|
||||||
@@ -527,23 +577,27 @@ function getDiseaseInitLoc() {
|
|||||||
locationOptions.value = [];
|
locationOptions.value = [];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 下拉框模糊搜索过滤
|
// 下拉框模糊搜索过滤(自定义filter-method,配合element-plus filterable使用)
|
||||||
function filterOptions(val, row, optionsKey) {
|
function filterOptions(val, row, optionsKey) {
|
||||||
const key = row.adviceDefinitionId + '_' + optionsKey;
|
const key = row.adviceDefinitionId + '_' + optionsKey;
|
||||||
filterKeywords.value[key] = val;
|
if (!val || val.trim() === '') {
|
||||||
|
delete filterKeywords.value[key];
|
||||||
|
} else {
|
||||||
|
filterKeywords.value[key] = val.toLowerCase();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function getFilteredOptions(row, optionsKey) {
|
function getFilteredOptions(row, optionsKey) {
|
||||||
const key = row.adviceDefinitionId + '_' + optionsKey;
|
const key = row.adviceDefinitionId + '_' + optionsKey;
|
||||||
const keyword = filterKeywords.value[key];
|
const keyword = filterKeywords.value[key];
|
||||||
const options = optionsKey === 'departmentOptions' ? departmentOptions.value : locationOptions.value;
|
const options = optionsKey === 'departmentOptions' ? departmentOptions.value : locationOptions.value;
|
||||||
if (!keyword || keyword.trim() === '') {
|
if (!keyword) {
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
const lower = keyword.toLowerCase();
|
|
||||||
return options.filter(item => {
|
return options.filter(item => {
|
||||||
const name = (item.name || item.label || '').toLowerCase();
|
const name = (item.name || item.label || '').toLowerCase();
|
||||||
const id = String(item.id || item.value || '').toLowerCase();
|
const id = String(item.id || item.value || '').toLowerCase();
|
||||||
return name.includes(lower) || id.includes(lower);
|
const py = (item.pyStr || '').toLowerCase();
|
||||||
|
return name.includes(keyword) || id.includes(keyword) || py.includes(keyword);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 获取组套类型文本
|
// 获取组套类型文本
|
||||||
@@ -552,19 +606,26 @@ function getItemType_Text(type) {
|
|||||||
return map[type] || '其他';
|
return map[type] || '其他';
|
||||||
}
|
}
|
||||||
function getUnitCodeOptions(row) {
|
function getUnitCodeOptions(row) {
|
||||||
const unitCodes = [
|
const unitCodes = [];
|
||||||
{ code: row.unitCode, codeText: row.unitCode_dictText },
|
// 大单位:优先用 code,code 缺失时用字典文本兜底
|
||||||
{ code: row.minUnitCode, codeText: row.minUnitCode_dictText },
|
if (row.unitCode != null && String(row.unitCode) !== '') {
|
||||||
];
|
unitCodes.push({ code: String(row.unitCode), codeText: row.unitCode_dictText });
|
||||||
// 使用 Set 来跟踪已经存在的 code
|
} else if (row.unitCode_dictText) {
|
||||||
|
unitCodes.push({ code: row.unitCode_dictText, codeText: row.unitCode_dictText });
|
||||||
|
}
|
||||||
|
// 小单位:同上
|
||||||
|
if (row.minUnitCode != null && String(row.minUnitCode) !== '') {
|
||||||
|
unitCodes.push({ code: String(row.minUnitCode), codeText: row.minUnitCode_dictText });
|
||||||
|
} else if (row.minUnitCode_dictText) {
|
||||||
|
unitCodes.push({ code: row.minUnitCode_dictText, codeText: row.minUnitCode_dictText });
|
||||||
|
}
|
||||||
|
// 去重
|
||||||
const seenCodes = new Set();
|
const seenCodes = new Set();
|
||||||
const uniqueUnitCodes = unitCodes.filter((item) => {
|
const uniqueUnitCodes = unitCodes.filter((item) => {
|
||||||
// 如果 Set 中没有这个 code,就保留它,并把它加入 Set
|
|
||||||
if (!seenCodes.has(item.code)) {
|
if (!seenCodes.has(item.code)) {
|
||||||
seenCodes.add(item.code);
|
seenCodes.add(item.code);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 如果已经存在,就过滤掉
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
return uniqueUnitCodes;
|
return uniqueUnitCodes;
|
||||||
@@ -575,11 +636,11 @@ function unitCodeChange(row) {
|
|||||||
// 获取价格
|
// 获取价格
|
||||||
const price = row.priceList?.[0]?.price || 0;
|
const price = row.priceList?.[0]?.price || 0;
|
||||||
|
|
||||||
// 根据选择的单位调整单价
|
// 根据选择的单位调整单价(统一用字符串比较)
|
||||||
if (row.selectUnitCode === row.unitCode) {
|
if (String(row.selectUnitCode) === String(row.unitCode)) {
|
||||||
// 如果选择的是大单位 (如 "盒")
|
// 如果选择的是大单位 (如 "盒")
|
||||||
row.unitPrice = price.toFixed(6); // 单价就是原价
|
row.unitPrice = price.toFixed(6); // 单价就是原价
|
||||||
} else if (row.selectUnitCode === row.minUnitCode) {
|
} else if (String(row.selectUnitCode) === String(row.minUnitCode)) {
|
||||||
// 如果选择的是小单位 (如 "个")
|
// 如果选择的是小单位 (如 "个")
|
||||||
row.unitPrice = (price / (row.partPercent || 1)).toFixed(6); // 单价 = 原价 / 拆零比
|
row.unitPrice = (price / (row.partPercent || 1)).toFixed(6); // 单价 = 原价 / 拆零比
|
||||||
}
|
}
|
||||||
@@ -627,14 +688,36 @@ function selectChange(row) {
|
|||||||
defaultUnitCode = String(row.minUnitCode);
|
defaultUnitCode = String(row.minUnitCode);
|
||||||
} else if (row.unitCode) {
|
} else if (row.unitCode) {
|
||||||
defaultUnitCode = String(row.unitCode);
|
defaultUnitCode = String(row.unitCode);
|
||||||
|
} else if (row.minUnitCode_dictText) {
|
||||||
|
// 兜底:如果 minUnitCode 为空但字典文本存在,使用文本作为选项值
|
||||||
|
defaultUnitCode = row.minUnitCode_dictText;
|
||||||
|
} else if (row.unitCode_dictText) {
|
||||||
|
defaultUnitCode = row.unitCode_dictText;
|
||||||
|
}
|
||||||
|
// 如果默认单位不在 uniqueUnitCodes 中,添加兜底选项
|
||||||
|
if (defaultUnitCode && !uniqueUnitCodes.some(u => u.code === defaultUnitCode)) {
|
||||||
|
uniqueUnitCodes.push({ code: defaultUnitCode, codeText: defaultUnitCode });
|
||||||
}
|
}
|
||||||
// 设置默认执行科室/位置(统一转为字符串,避免 el-select 类型不匹配)
|
// 设置默认执行科室/位置(统一转为字符串,避免 el-select 类型不匹配)
|
||||||
let defaultPositionId = undefined;
|
let defaultPositionId = undefined;
|
||||||
if (row.adviceType === 3 && departmentOptions.value.length > 0) {
|
if (row.adviceType === 3 && departmentOptions.value.length > 0) {
|
||||||
// 诊疗:优先使用患者所在科室,否则取第一个科室
|
// 诊疗:
|
||||||
const patientOrgId = props.patientInfo.organizationId;
|
// 1. 优先使用诊疗目录项目的"所属科室"(row.orgId)
|
||||||
const matched = departmentOptions.value.find(d => String(d.id) === String(patientOrgId));
|
// 2. 其次使用患者当前病房科室(patientInfo.organizationId)
|
||||||
defaultPositionId = matched ? String(matched.id) : String(departmentOptions.value[0].id);
|
// 3. 最后取第一个科室
|
||||||
|
const orgIdPriority = [row.orgId, props.patientInfo.organizationId];
|
||||||
|
for (const id of orgIdPriority) {
|
||||||
|
if (id) {
|
||||||
|
const matched = departmentOptions.value.find(d => String(d.id) === String(id));
|
||||||
|
if (matched) {
|
||||||
|
defaultPositionId = String(matched.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!defaultPositionId) {
|
||||||
|
defaultPositionId = String(departmentOptions.value[0].id);
|
||||||
|
}
|
||||||
} else if (row.adviceType === 2 && locationOptions.value.length > 0) {
|
} else if (row.adviceType === 2 && locationOptions.value.length > 0) {
|
||||||
// 耗材:默认取第一个药房/耗材房
|
// 耗材:默认取第一个药房/耗材房
|
||||||
defaultPositionId = String(locationOptions.value[0].value);
|
defaultPositionId = String(locationOptions.value[0].value);
|
||||||
@@ -763,6 +846,7 @@ function resetData() {
|
|||||||
|
|
||||||
// 划价组套相关功能
|
// 划价组套相关功能
|
||||||
function openGroupSetDialog() {
|
function openGroupSetDialog() {
|
||||||
|
console.log('openGroupSetDialog called');
|
||||||
groupSetDialogVisible.value = true;
|
groupSetDialogVisible.value = true;
|
||||||
groupSetSearchText.value = '';
|
groupSetSearchText.value = '';
|
||||||
selectedGroupSet.value = null;
|
selectedGroupSet.value = null;
|
||||||
@@ -771,19 +855,37 @@ function openGroupSetDialog() {
|
|||||||
|
|
||||||
function loadGroupSets() {
|
function loadGroupSets() {
|
||||||
groupSetLoading.value = true;
|
groupSetLoading.value = true;
|
||||||
getOrderGroup({ organizationId: orgId.value })
|
const params = { organizationId: orgId.value };
|
||||||
|
// 传递搜索关键字,后端 /group-package-for-order 虽不直接支持 searchKey,
|
||||||
|
// 但保持参数传递以便后续扩展
|
||||||
|
if (groupSetSearchText.value && groupSetSearchText.value.trim()) {
|
||||||
|
params.searchKey = groupSetSearchText.value.trim();
|
||||||
|
}
|
||||||
|
getOrderGroup(params)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const data = res?.data || {};
|
const data = res?.data || {};
|
||||||
|
let rawList = [];
|
||||||
if (groupSetRange.value === 1) {
|
if (groupSetRange.value === 1) {
|
||||||
groupSetList.value = data.personalList || [];
|
rawList = data.personalList || [];
|
||||||
} else if (groupSetRange.value === 2) {
|
} else if (groupSetRange.value === 2) {
|
||||||
groupSetList.value = data.organizationList || [];
|
rawList = data.organizationList || [];
|
||||||
} else {
|
} else {
|
||||||
groupSetList.value = data.hospitalList || [];
|
rawList = data.hospitalList || [];
|
||||||
|
}
|
||||||
|
// 客户端过滤:根据搜索关键字过滤组套名称
|
||||||
|
const keyword = groupSetSearchText.value?.trim()?.toLowerCase();
|
||||||
|
if (keyword) {
|
||||||
|
groupSetList.value = rawList.filter(item => {
|
||||||
|
const name = (item.name || item.Name || '').toLowerCase();
|
||||||
|
return name.includes(keyword);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
groupSetList.value = rawList;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((err) => {
|
||||||
console.warn('组套列表加载失败(可能无权限)');
|
console.warn('组套列表加载失败(可能无权限):', err);
|
||||||
|
ElMessage.warning('组套列表加载失败,当前暂无可用组套');
|
||||||
groupSetList.value = [];
|
groupSetList.value = [];
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export function getOrgList() {
|
|||||||
return request({
|
return request({
|
||||||
url: '/base-data-manage/organization/organization',
|
url: '/base-data-manage/organization/organization',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
|
params: { pageSize: 100, pageNum: 1 },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -463,20 +463,45 @@ function watchPatientSelection() {
|
|||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查询科室 */
|
/** 查询科室(支持树形/扁平多种响应结构) */
|
||||||
const getLocationInfo = () => {
|
const getLocationInfo = () => {
|
||||||
getOrgList().then((res) => {
|
getOrgList().then((res) => {
|
||||||
orgOptions.value = res.data?.records[0]?.children;
|
if (!res.data) {
|
||||||
|
orgOptions.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 尝试从树形结构取:records[0].children
|
||||||
|
if (res.data.records && res.data.records.length > 0) {
|
||||||
|
if (res.data.records[0].children && res.data.records[0].children.length > 0) {
|
||||||
|
orgOptions.value = res.data.records[0].children;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 如果 records[0] 有 id 和 name(非树根节点),直接用所有 records
|
||||||
|
if (res.data.records[0].id) {
|
||||||
|
orgOptions.value = res.data.records;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 兜底:如果 data 本身是数组
|
||||||
|
if (Array.isArray(res.data)) {
|
||||||
|
orgOptions.value = res.data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
orgOptions.value = [];
|
||||||
|
}).catch(() => {
|
||||||
|
console.warn('科室列表加载失败(可能无权限)');
|
||||||
|
orgOptions.value = [];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
getLocationInfo();
|
getLocationInfo();
|
||||||
|
|
||||||
// 映射
|
// 映射(查找失败时返回 '-' 而非显示内码)
|
||||||
const selectOrg = (itemid) => {
|
const selectOrg = (itemid) => {
|
||||||
|
if (!itemid) return '-';
|
||||||
const item = orgOptions.value.find((item) => {
|
const item = orgOptions.value.find((item) => {
|
||||||
return item.id == itemid;
|
return item.id == itemid;
|
||||||
});
|
});
|
||||||
return item?.name;
|
return item?.name || '-';
|
||||||
};
|
};
|
||||||
// 重置
|
// 重置
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
|
|||||||
@@ -66,13 +66,21 @@ const props = defineProps({
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: 1,
|
default: 1,
|
||||||
},
|
},
|
||||||
|
therapyEnum: {
|
||||||
|
type: Number,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
handleGetPrescription();
|
handleGetPrescription();
|
||||||
function handleGetPrescription() {
|
function handleGetPrescription() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
let encounterIds = patientInfoList.value.map((i) => i.encounterId).join(',');
|
let encounterIds = patientInfoList.value.map((i) => i.encounterId).join(',');
|
||||||
getMedicineSummary({}).then((res) => {
|
const params = {};
|
||||||
|
if (props.therapyEnum !== undefined) {
|
||||||
|
params.therapyEnum = props.therapyEnum;
|
||||||
|
}
|
||||||
|
getMedicineSummary(params).then((res) => {
|
||||||
medicineSummaryFormList.value = res.data.records;
|
medicineSummaryFormList.value = res.data.records;
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -85,7 +85,7 @@
|
|||||||
:deadline="deadline"
|
:deadline="deadline"
|
||||||
:therapyEnum="therapyEnum"
|
:therapyEnum="therapyEnum"
|
||||||
/>
|
/>
|
||||||
<SummaryMedicineList v-else />
|
<SummaryMedicineList v-else ref="summaryMedicineRefs" :therapyEnum="therapyEnum" />
|
||||||
<!-- <el-tabs v-model="activeName" class="demo-tabs centered-tabs" @tab-change="handleClick">
|
<!-- <el-tabs v-model="activeName" class="demo-tabs centered-tabs" @tab-change="handleClick">
|
||||||
<el-tab-pane
|
<el-tab-pane
|
||||||
v-for="tab in prescriptionTabs"
|
v-for="tab in prescriptionTabs"
|
||||||
@@ -129,6 +129,7 @@ const therapyEnum = ref(undefined);
|
|||||||
|
|
||||||
// 存储子组件引用的对象
|
// 存储子组件引用的对象
|
||||||
const prescriptionRefs = ref();
|
const prescriptionRefs = ref();
|
||||||
|
const summaryMedicineRefs = ref();
|
||||||
|
|
||||||
const navigationButtons = inpatientNurseNavs;
|
const navigationButtons = inpatientNurseNavs;
|
||||||
|
|
||||||
@@ -165,7 +166,11 @@ function handleClick(tabName) {
|
|||||||
|
|
||||||
function handleGetPrescription() {
|
function handleGetPrescription() {
|
||||||
chooseAll.value = false;
|
chooseAll.value = false;
|
||||||
|
if (isDetails.value == '1') {
|
||||||
prescriptionRefs.value?.handleGetPrescription();
|
prescriptionRefs.value?.handleGetPrescription();
|
||||||
|
} else {
|
||||||
|
summaryMedicineRefs.value?.handleGetPrescription();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handelSwicthChange(value) {
|
function handelSwicthChange(value) {
|
||||||
|
|||||||
@@ -458,7 +458,9 @@ const loadPatientInfo = () => {
|
|||||||
'YYYY-MM-DD HH:mm:ss'
|
'YYYY-MM-DD HH:mm:ss'
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
interventionForm.value.startTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss');
|
// 已有患者(entranceType == 1)不自动填充当前时间,避免覆盖历史数据
|
||||||
|
// 新入科患者由后端默认返回当前时间,或由用户手动选择
|
||||||
|
interventionForm.value.startTime = '';
|
||||||
}
|
}
|
||||||
interventionForm.value.height = res.data.height;
|
interventionForm.value.height = res.data.height;
|
||||||
interventionForm.value.weight = res.data.weight;
|
interventionForm.value.weight = res.data.weight;
|
||||||
|
|||||||
@@ -459,10 +459,15 @@ function handleGetPrescription(skipAutoSelectAll = false) {
|
|||||||
// 执行
|
// 执行
|
||||||
function handleExecute() {
|
function handleExecute() {
|
||||||
let list = getSelectRows();
|
let list = getSelectRows();
|
||||||
|
if (list.length === 0) {
|
||||||
|
proxy.$modal.msgWarning('请选择需要执行的医嘱');
|
||||||
|
return;
|
||||||
|
}
|
||||||
let encounterIds = patientInfoList.value.map((i) => i.encounterId).join(',');
|
let encounterIds = patientInfoList.value.map((i) => i.encounterId).join(',');
|
||||||
list = list.map((item) => {
|
list = list.map((item) => {
|
||||||
return {
|
return {
|
||||||
requestId: item.requestId,
|
requestId: item.requestId,
|
||||||
|
encounterId: item.encounterId,
|
||||||
accountId: item.accountId,
|
accountId: item.accountId,
|
||||||
adviceTable: item.adviceTable,
|
adviceTable: item.adviceTable,
|
||||||
executeTimes: item.executeTimes,
|
executeTimes: item.executeTimes,
|
||||||
@@ -471,21 +476,35 @@ function handleExecute() {
|
|||||||
console.log(list, 'list');
|
console.log(list, 'list');
|
||||||
adviceExecute({ exeDate: exeDate.value, adviceExecuteDetailList: list }).then((res) => {
|
adviceExecute({ exeDate: exeDate.value, adviceExecuteDetailList: list }).then((res) => {
|
||||||
if (res.code == 200) {
|
if (res.code == 200) {
|
||||||
// 仅当选中医嘱中包含耗材类医嘱时,才调用耗材批号匹配(排除纯药品医嘱场景)
|
// 仅当选中医嘱中包含诊疗类医嘱(可能绑定耗材)时,才调用耗材批号匹配
|
||||||
const hasDevice = list.some((item) =>
|
// adviceTable 取值为 med_medication_request(药品)或 wor_service_request(诊疗/耗材)
|
||||||
String(item.adviceTable || '').includes('device'),
|
const hasServiceRequest = list.some((item) =>
|
||||||
|
String(item.adviceTable || '') === 'wor_service_request',
|
||||||
);
|
);
|
||||||
if (hasDevice) {
|
if (hasServiceRequest) {
|
||||||
lotNumberMatch({ encounterIdList: encounterIds }, { skipErrorMsg: true }).catch((error) => {
|
// 仅传入选中医嘱对应的 encounterId,避免其他患者的耗材记录干扰
|
||||||
|
const selectedEncounterIds = [...new Set(list.map((item) => item.encounterId).filter(Boolean))];
|
||||||
|
if (selectedEncounterIds.length > 0) {
|
||||||
|
lotNumberMatch({ encounterIdList: selectedEncounterIds }, { skipErrorMsg: true })
|
||||||
|
.then((matchRes) => {
|
||||||
|
if (matchRes && matchRes.code !== 200) {
|
||||||
|
console.warn('lotNumberMatch returned error:', matchRes.msg);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
console.warn('lotNumberMatch failed after adviceExecute:', error);
|
console.warn('lotNumberMatch failed after adviceExecute:', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 刷新列表(不自动全选,保持用户操作前的选择状态)
|
// 刷新列表(不自动全选,保持用户操作前的选择状态)
|
||||||
handleGetPrescription(true);
|
handleGetPrescription(true);
|
||||||
proxy.$modal.msgSuccess(res.msg || '医嘱执行成功');
|
proxy.$modal.msgSuccess(res.msg || '医嘱执行成功');
|
||||||
} else {
|
} else {
|
||||||
proxy.$modal.msgError(res.msg || '医嘱执行失败');
|
proxy.$modal.msgError(res.msg || '医嘱执行失败');
|
||||||
}
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error('医嘱执行接口调用失败:', error);
|
||||||
|
proxy.$modal.msgError('医嘱执行失败,请稍后重试');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<span class="descriptions-item-label">全选:</span>
|
<span class="descriptions-item-label">全选:</span>
|
||||||
<el-switch v-model="chooseAll" @change="handelSwitchChange" />
|
<el-switch v-model="chooseAll" @change="handelSwitchChange" />
|
||||||
<el-button class="ml20" type="primary" @click="handleCheck"> 核对通过 </el-button>
|
<el-button class="ml20" type="primary" @click="handleCheck"> 核对通过 </el-button>
|
||||||
<el-button class="ml20 mr20" type="danger" @click="handleCancel"> 退回 </el-button>
|
<el-button class="ml20 mr20" type="danger" :disabled="hasDispensedSelected" @click="handleCancel"> 退回 </el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -152,6 +152,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import {ref, computed} from 'vue';
|
||||||
import {adviceVerify, cancel, getPrescriptionList} from './api';
|
import {adviceVerify, cancel, getPrescriptionList} from './api';
|
||||||
import {patientInfoList} from '../../components/store/patient.js';
|
import {patientInfoList} from '../../components/store/patient.js';
|
||||||
import {formatDateStr} from '@/utils/index';
|
import {formatDateStr} from '@/utils/index';
|
||||||
@@ -163,6 +164,11 @@ const type = ref(null);
|
|||||||
const { proxy } = getCurrentInstance();
|
const { proxy } = getCurrentInstance();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const chooseAll = ref(false);
|
const chooseAll = ref(false);
|
||||||
|
const selectionTrigger = ref(0);
|
||||||
|
const hasDispensedSelected = computed(() => {
|
||||||
|
selectionTrigger.value;
|
||||||
|
return getSelectRows().some(item => item.dispenseStatus === 4);
|
||||||
|
});
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
requestStatus: {
|
requestStatus: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@@ -262,7 +268,9 @@ function getGroupMarkers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 选择框改变时的处理
|
// 选择框改变时的处理
|
||||||
function handleSelectionChange(selection, row) {}
|
function handleSelectionChange(selection, row) {
|
||||||
|
selectionTrigger.value++;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 核对通过
|
* 核对通过
|
||||||
@@ -291,7 +299,7 @@ function handleCancel() {
|
|||||||
// 校验已发药的医嘱不允许退回
|
// 校验已发药的医嘱不允许退回
|
||||||
let dispensedItems = list.filter(item => item.dispenseStatus === 4);
|
let dispensedItems = list.filter(item => item.dispenseStatus === 4);
|
||||||
if (dispensedItems.length > 0) {
|
if (dispensedItems.length > 0) {
|
||||||
proxy.$message.error('该医嘱已发药,无法退回');
|
proxy.$message.error('该药品已由药房发放,请先执行退药处理,不可直接退回');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cancel(list).then((res) => {
|
cancel(list).then((res) => {
|
||||||
@@ -310,12 +318,16 @@ function getSelectRows() {
|
|||||||
// 获取选中的医嘱信息
|
// 获取选中的医嘱信息
|
||||||
let list = [];
|
let list = [];
|
||||||
prescriptionList.value.forEach((item, index) => {
|
prescriptionList.value.forEach((item, index) => {
|
||||||
list = [...list, ...proxy.$refs['tableRef' + index][0].getSelectionRows()];
|
const ref = proxy.$refs['tableRef' + index];
|
||||||
|
if (ref && ref[0]) {
|
||||||
|
list = [...list, ...ref[0].getSelectionRows()];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return list.map((item) => {
|
return list.map((item) => {
|
||||||
return {
|
return {
|
||||||
requestId: item.requestId,
|
requestId: item.requestId,
|
||||||
requestTable: item.adviceTable,
|
requestTable: item.adviceTable,
|
||||||
|
dispenseStatus: item.dispenseStatus,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1057,8 +1057,8 @@ function confirmCharge() {
|
|||||||
params.recordingDate = formData.value.recordingDate || moment(new Date()).format('YYYY-MM-DD');
|
params.recordingDate = formData.value.recordingDate || moment(new Date()).format('YYYY-MM-DD');
|
||||||
|
|
||||||
addVitalSigns(params).then(res => {
|
addVitalSigns(params).then(res => {
|
||||||
console.log('保存成功:', res);
|
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
|
proxy.msgSuccess('保存成功');
|
||||||
// 保存成功后刷新列表
|
// 保存成功后刷新列表
|
||||||
getPatientList();
|
getPatientList();
|
||||||
// 清空表单
|
// 清空表单
|
||||||
@@ -1087,8 +1087,6 @@ function confirmCharge() {
|
|||||||
urineVolume: '',
|
urineVolume: '',
|
||||||
stoolVolume: '',
|
stoolVolume: '',
|
||||||
};
|
};
|
||||||
// 保存成功后关闭弹窗
|
|
||||||
closeDialog();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,11 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
/** 表头所选出库仓库:传入后药品列表只含该仓有库存的行,避免选到别仓批号导致 inventory-item-info 一直为 0 */
|
||||||
|
orgLocationId: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const emit = defineEmits(['selectRow']);
|
const emit = defineEmits(['selectRow']);
|
||||||
const queryParams = ref({
|
const queryParams = ref({
|
||||||
@@ -89,11 +93,14 @@ watch(
|
|||||||
queryParams.value.searchKey = newValue.searchKey;
|
queryParams.value.searchKey = newValue.searchKey;
|
||||||
queryParams.value.itemType = newValue.itemType;
|
queryParams.value.itemType = newValue.itemType;
|
||||||
queryParams.value.purchaseFlag = 0;
|
queryParams.value.purchaseFlag = 0;
|
||||||
// queryParams.value.sourceLocationId = newValue.sourceLocationId;
|
if (newValue.orgLocationId != null && newValue.orgLocationId !== '') {
|
||||||
// queryParams.value.purposeLocationId = newValue.purposeLocationId;
|
queryParams.value.orgLocationId = newValue.orgLocationId;
|
||||||
|
} else {
|
||||||
|
delete queryParams.value.orgLocationId;
|
||||||
|
}
|
||||||
throttledGetList();
|
throttledGetList();
|
||||||
},
|
},
|
||||||
{ immdiate: true, deep: true }
|
{ immediate: true, deep: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
getList();
|
getList();
|
||||||
|
|||||||
@@ -133,6 +133,7 @@
|
|||||||
filterable
|
filterable
|
||||||
style="width: 200px"
|
style="width: 200px"
|
||||||
:disabled="data.isEdit"
|
:disabled="data.isEdit"
|
||||||
|
@change="onHeaderWarehouseChange"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in purposeTypeListOptions"
|
v-for="item in purposeTypeListOptions"
|
||||||
@@ -222,6 +223,7 @@
|
|||||||
@selectRow="(row) => selectRow(row, scope.$index)"
|
@selectRow="(row) => selectRow(row, scope.$index)"
|
||||||
:searchKey="medicineSearchKey"
|
:searchKey="medicineSearchKey"
|
||||||
:itemType="itemType"
|
:itemType="itemType"
|
||||||
|
:orgLocationId="receiptHeaderForm.headerLocationId"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</PopoverList>
|
</PopoverList>
|
||||||
@@ -483,6 +485,46 @@ import {useStore} from '@/store/store';
|
|||||||
import useTagsViewStore from '@/store/modules/tagsView';
|
import useTagsViewStore from '@/store/modules/tagsView';
|
||||||
import TraceNoDialog from '@/components/OpenHis/TraceNoDialog/index.vue'
|
import TraceNoDialog from '@/components/OpenHis/TraceNoDialog/index.vue'
|
||||||
|
|
||||||
|
/** 领用保存 IssueDto:后端 Jackson 只认 yyyy-MM-dd HH:mm:ss;库存接口可能回传 2025/4/2 00:00:00 等 */
|
||||||
|
function toIssueDateTimeStr(val) {
|
||||||
|
if (val == null || val === '') return undefined;
|
||||||
|
if (typeof val === 'string') {
|
||||||
|
const s = val.trim();
|
||||||
|
if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(s)) return s;
|
||||||
|
if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return `${s} 00:00:00`;
|
||||||
|
}
|
||||||
|
const d = val instanceof Date ? val : new Date(val);
|
||||||
|
if (Number.isNaN(d.getTime())) return undefined;
|
||||||
|
return formatDate(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 总库存 totalQuantity 为最小单位;领用数量按当前计量单位折算成最小单位后再比较 */
|
||||||
|
function getRequisitionQtyInMinUnit(r) {
|
||||||
|
const q = Number(r.itemQuantity ?? 0);
|
||||||
|
if (!Number.isFinite(q) || q < 0) return NaN;
|
||||||
|
const minCode = r.unitList?.minUnitCode;
|
||||||
|
if (!minCode || r.unitCode === minCode) return q;
|
||||||
|
const part = Number(r.partPercent ?? 1);
|
||||||
|
return q * part;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateRequisitionQtyVsStock(r, lineNo) {
|
||||||
|
const cap = Number(r.totalQuantity ?? 0);
|
||||||
|
const reqMin = getRequisitionQtyInMinUnit(r);
|
||||||
|
if (!Number.isFinite(reqMin)) {
|
||||||
|
return `第${lineNo}行:领用数量请输入有效数字`;
|
||||||
|
}
|
||||||
|
if (cap > 0 && reqMin > cap + 1e-9) {
|
||||||
|
const name = r.name || `第${lineNo}行`;
|
||||||
|
return `${name}:领用数量(折合最小单位)不能超过当前仓库可领库存 ${cap},请修改后再保存。`;
|
||||||
|
}
|
||||||
|
if (cap <= 0 && reqMin > 0) {
|
||||||
|
const name = r.name || `第${lineNo}行`;
|
||||||
|
return `${name}:当前仓库可领库存为 0,不能填写正数领用数量。`;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const tagsViewStore = useTagsViewStore();
|
const tagsViewStore = useTagsViewStore();
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
|
||||||
@@ -492,6 +534,7 @@ const userStore = useUserStore();
|
|||||||
const openTraceNoDialog = ref(false)
|
const openTraceNoDialog = ref(false)
|
||||||
const rowData = ref({})
|
const rowData = ref({})
|
||||||
const ypName = ref('')
|
const ypName = ref('')
|
||||||
|
const currentIndex = ref(-1)
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance();
|
const { proxy } = getCurrentInstance();
|
||||||
const { warehous_type, category_code, service_type_code, specialty_code, purchase_type } =
|
const { warehous_type, category_code, service_type_code, specialty_code, purchase_type } =
|
||||||
@@ -568,6 +611,8 @@ const data = reactive({
|
|||||||
medicationType: [{ required: true, message: '请选择药品类型', trigger: 'change' }],
|
medicationType: [{ required: true, message: '请选择药品类型', trigger: 'change' }],
|
||||||
locationId: [{ required: true, message: '请选择领用部门', trigger: 'change' }],
|
locationId: [{ required: true, message: '请选择领用部门', trigger: 'change' }],
|
||||||
practitionerId: [{ required: true, message: '请选择部门经手人', trigger: 'change' }],
|
practitionerId: [{ required: true, message: '请选择部门经手人', trigger: 'change' }],
|
||||||
|
// 领用出库按「表头仓库」查 /app-common/inventory-item-info,未选仓库会查不到库存并误报「仓库数量为0」
|
||||||
|
headerLocationId: [{ required: true, message: '请先选择仓库(按仓库查询可领用库存)', trigger: 'change' }],
|
||||||
},
|
},
|
||||||
tableRules: {
|
tableRules: {
|
||||||
name: [{ required: true, message: '项目不能为空', trigger: 'change' }],
|
name: [{ required: true, message: '项目不能为空', trigger: 'change' }],
|
||||||
@@ -988,10 +1033,9 @@ function selectRow(rowValue, index) {
|
|||||||
form.purchaseinventoryList[index].unitList = rowValue.unitList[0];
|
form.purchaseinventoryList[index].unitList = rowValue.unitList[0];
|
||||||
form.purchaseinventoryList[index].lotNumber = rowValue.lotNumber;
|
form.purchaseinventoryList[index].lotNumber = rowValue.lotNumber;
|
||||||
form.purchaseinventoryList[index].ybNo = rowValue.ybNo;
|
form.purchaseinventoryList[index].ybNo = rowValue.ybNo;
|
||||||
// #439 fix: 不清空sourceLocationId,保留handleAddRow设置的仓库ID
|
// 出库仓库:优先表头当前所选仓库(避免先选药后选仓时行上一直为空)
|
||||||
if (!form.purchaseinventoryList[index].sourceLocationId) {
|
form.purchaseinventoryList[index].sourceLocationId =
|
||||||
form.purchaseinventoryList[index].sourceLocationId = receiptHeaderForm.headerLocationId || '';
|
receiptHeaderForm.headerLocationId || form.purchaseinventoryList[index].sourceLocationId || '';
|
||||||
}
|
|
||||||
getPharmacyCabinetList().then((res) => {
|
getPharmacyCabinetList().then((res) => {
|
||||||
purposeTypeListOptions.value = res.data;
|
purposeTypeListOptions.value = res.data;
|
||||||
handleLocationClick(1, rowValue, index)
|
handleLocationClick(1, rowValue, index)
|
||||||
@@ -1004,78 +1048,125 @@ function selectRow(rowValue, index) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选择仓库
|
/** 多条库存记录时取可领数量最大的一条(避免仅取 res.data[0] 恰好为 0) */
|
||||||
function handleLocationClick(item, row, index) {
|
function pickBestOrgQuantityRow(list) {
|
||||||
getCount({
|
if (!Array.isArray(list) || list.length === 0) return null;
|
||||||
itemId: form.purchaseinventoryList[index].itemId,
|
return list.reduce((best, cur) => {
|
||||||
orgLocationId: form.purchaseinventoryList[index].sourceLocationId,
|
const cq = Number(cur?.orgQuantity ?? 0);
|
||||||
// objLocationId:purposeLocationId,
|
const bq = Number(best?.orgQuantity ?? 0);
|
||||||
lotNumber: form.purchaseinventoryList[index].lotNumber,
|
return cq > bq ? cur : best;
|
||||||
}).then((res) => {
|
});
|
||||||
if (res.data && res.data.length > 0) {
|
}
|
||||||
form.purchaseinventoryList[index].itemTable = res.data[0].itemTable || '';
|
|
||||||
form.purchaseinventoryList[index].totalQuantity = res.data[0].orgQuantity || 0;
|
|
||||||
|
|
||||||
if (res.data[0].price) {
|
/** 表头「仓库」变化:同步每行 sourceLocationId 并重新拉库存(修复先选药品后选仓库行上仍无仓库 ID) */
|
||||||
form.purchaseinventoryList[index].price = res.data[0].price.toFixed(4);
|
function onHeaderWarehouseChange() {
|
||||||
} else {
|
const hid = receiptHeaderForm.headerLocationId;
|
||||||
form.purchaseinventoryList[index].price = 0;
|
form.purchaseinventoryList.forEach((r) => {
|
||||||
|
r.sourceLocationId = hid || '';
|
||||||
|
});
|
||||||
|
form.purchaseinventoryList.forEach((r, idx) => {
|
||||||
|
if (hid && r.itemId) {
|
||||||
|
handleLocationClick(1, {}, idx);
|
||||||
}
|
}
|
||||||
// 获取供应商id
|
});
|
||||||
form.purchaseinventoryList[index].supplierId = res.data[0].supplierId || '';
|
}
|
||||||
// 生产日期
|
|
||||||
form.purchaseinventoryList[index].startTime = res.data[0].productionDate;
|
// 选择仓库 / 选药品后拉取该仓库存
|
||||||
// 有效期
|
function handleLocationClick(item, row, index) {
|
||||||
form.purchaseinventoryList[index].endTime = res.data[0].expirationDate;
|
const r = form.purchaseinventoryList[index];
|
||||||
form.purchaseinventoryList[index].unitCode =
|
let orgLocationId = r.sourceLocationId || receiptHeaderForm.headerLocationId || '';
|
||||||
form.purchaseinventoryList[index].unitList.minUnitCode;
|
if (!orgLocationId) {
|
||||||
form.purchaseinventoryList[index].unitCode_dictText =
|
proxy.$message.warning('请先在表头选择「仓库」。库存按仓库维度查询,未选仓库无法匹配您看到的总库存。');
|
||||||
form.purchaseinventoryList[index].unitList.minUnitCode_dictText;
|
r.totalQuantity = 0;
|
||||||
// 单价 大单位单价
|
r.price = 0;
|
||||||
console.log(
|
|
||||||
form.purchaseinventoryList[index].unitCode ==
|
|
||||||
form.purchaseinventoryList[index].unitList.minUnitCode,
|
|
||||||
1212121
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
form.purchaseinventoryList[index].unitCode ==
|
|
||||||
form.purchaseinventoryList[index].unitList.minUnitCode
|
|
||||||
) {
|
|
||||||
form.purchaseinventoryList[index].price =
|
|
||||||
res.data[0].price / form.purchaseinventoryList[index].partPercent || '';
|
|
||||||
form.purchaseinventoryList[index].price =
|
|
||||||
form.purchaseinventoryList[index].price.toFixed(4);
|
|
||||||
// parseFloat(form.purchaseinventoryList[index].price.toFixed(4))
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
form.purchaseinventoryList[index].price > 1,
|
|
||||||
1212,
|
|
||||||
form.purchaseinventoryList[index].price
|
|
||||||
);
|
|
||||||
if (form.purchaseinventoryList[index].price > 1) {
|
|
||||||
form.purchaseinventoryList[index].price =
|
|
||||||
form.purchaseinventoryList[index].price.toFixed(4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (form.purchaseinventoryList[index].totalQuantity == 0) {
|
|
||||||
proxy.$message.warning('仓库数量为0,无法调用!');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
if (!r.sourceLocationId) {
|
||||||
form.purchaseinventoryList[index].totalQuantity = 0;
|
r.sourceLocationId = orgLocationId;
|
||||||
form.purchaseinventoryList[index].price = 0;
|
|
||||||
// if(form.purchaseinventoryList[index].totalQuantity==0){
|
|
||||||
proxy.$message.warning('仓库数量为0,无法调用!');
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
|
||||||
form.purchaseinventoryList[index].totalQuantity = 0;
|
const lotTrimmed =
|
||||||
form.purchaseinventoryList[index].price = 0;
|
r.lotNumber != null && String(r.lotNumber).trim() !== '' ? String(r.lotNumber).trim() : null;
|
||||||
});
|
|
||||||
|
const runGet = (withLot) => {
|
||||||
|
const params = { itemId: r.itemId, orgLocationId };
|
||||||
|
if (withLot && lotTrimmed) {
|
||||||
|
params.lotNumber = lotTrimmed;
|
||||||
|
}
|
||||||
|
return getCount(params);
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyFromDto = (d, syncLotFromPick) => {
|
||||||
|
if (syncLotFromPick && d.lotNumber != null && d.lotNumber !== '') {
|
||||||
|
r.lotNumber = d.lotNumber;
|
||||||
|
}
|
||||||
|
r.itemTable = d.itemTable || '';
|
||||||
|
r.totalQuantity = d.orgQuantity || 0;
|
||||||
|
if (d.price) {
|
||||||
|
r.price = d.price.toFixed(4);
|
||||||
|
} else {
|
||||||
|
r.price = 0;
|
||||||
|
}
|
||||||
|
r.supplierId = d.supplierId || '';
|
||||||
|
r.startTime = toIssueDateTimeStr(d.productionDate) || '';
|
||||||
|
r.endTime = toIssueDateTimeStr(d.expirationDate) || '';
|
||||||
|
r.unitCode = r.unitList.minUnitCode;
|
||||||
|
r.unitCode_dictText = r.unitList.minUnitCode_dictText;
|
||||||
|
if (r.unitCode == r.unitList.minUnitCode) {
|
||||||
|
r.price = d.price / r.partPercent || '';
|
||||||
|
r.price = r.price.toFixed(4);
|
||||||
|
} else if (r.price > 1) {
|
||||||
|
r.price = r.price.toFixed(4);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const persistStore = () => {
|
||||||
store.setCurrentDataLYCK({
|
store.setCurrentDataLYCK({
|
||||||
purchaseinventoryList: form.purchaseinventoryList,
|
purchaseinventoryList: form.purchaseinventoryList,
|
||||||
receiptHeaderForm: receiptHeaderForm,
|
receiptHeaderForm: receiptHeaderForm,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
runGet(true)
|
||||||
|
.then((res) => {
|
||||||
|
const list = res.data || [];
|
||||||
|
const d = pickBestOrgQuantityRow(list);
|
||||||
|
const strictOk = d && Number(d.orgQuantity ?? 0) > 0;
|
||||||
|
if (strictOk) {
|
||||||
|
applyFromDto(d, false);
|
||||||
|
if (Number(r.totalQuantity) <= 0) {
|
||||||
|
proxy.$message.warning('仓库数量为0,无法调用!');
|
||||||
|
}
|
||||||
|
persistStore();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (lotTrimmed) {
|
||||||
|
return runGet(false).then((res2) => {
|
||||||
|
const list2 = res2.data || [];
|
||||||
|
const d2 = pickBestOrgQuantityRow(list2);
|
||||||
|
if (d2 && Number(d2.orgQuantity ?? 0) > 0) {
|
||||||
|
applyFromDto(d2, true);
|
||||||
|
proxy.$message.info(
|
||||||
|
'所选批号在本仓库无对应库存或批号不一致,已按仓库实物回显批号与可领数量,请核对。'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
r.totalQuantity = 0;
|
||||||
|
r.price = 0;
|
||||||
|
proxy.$message.warning('仓库数量为0,无法调用!');
|
||||||
|
}
|
||||||
|
persistStore();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
r.totalQuantity = 0;
|
||||||
|
r.price = 0;
|
||||||
|
proxy.$message.warning('仓库数量为0,无法调用!');
|
||||||
|
persistStore();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
r.totalQuantity = 0;
|
||||||
|
r.price = 0;
|
||||||
|
persistStore();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// 切换仓库类型获取药房/药库列表
|
// 切换仓库类型获取药房/药库列表
|
||||||
// function handleChangeLocationType(value) {
|
// function handleChangeLocationType(value) {
|
||||||
@@ -1232,20 +1323,19 @@ function getMaxCounts(row, index, counts) {
|
|||||||
}
|
}
|
||||||
// 计算总价
|
// 计算总价
|
||||||
function handleTotalPrice(index) {
|
function handleTotalPrice(index) {
|
||||||
form.purchaseinventoryList[index].olditemQuantity =
|
const r = form.purchaseinventoryList[index];
|
||||||
form.purchaseinventoryList[index].itemQuantity * row.partPercent;
|
r.olditemQuantity = r.itemQuantity * (r.partPercent ?? 1);
|
||||||
form.purchaseinventoryList[index].itemMaxQuantity =
|
r.itemMaxQuantity = r.itemQuantity;
|
||||||
form.purchaseinventoryList[index].itemQuantity;
|
let purchaseItem = r;
|
||||||
let purchaseItem = form.purchaseinventoryList[index];
|
|
||||||
if (purchaseItem.price > 0 && purchaseItem.itemQuantity > 0) {
|
if (purchaseItem.price > 0 && purchaseItem.itemQuantity > 0) {
|
||||||
form.purchaseinventoryList[index].totalPrice = purchaseItem.price * purchaseItem.itemQuantity;
|
r.totalPrice = purchaseItem.price * purchaseItem.itemQuantity;
|
||||||
form.purchaseinventoryList[index].totalPrice =
|
r.totalPrice = r.totalPrice.toFixed(4);
|
||||||
form.purchaseinventoryList[index].totalPrice.toFixed(4);
|
|
||||||
// parseFloat(form.purchaseinventoryList[index].totalPrice.toFixed(4))
|
|
||||||
}
|
}
|
||||||
if (form.purchaseinventoryList[index].itemQuantity == 0) {
|
if (r.itemQuantity == 0) {
|
||||||
form.purchaseinventoryList[index].totalPrice = 0;
|
r.totalPrice = 0;
|
||||||
}
|
}
|
||||||
|
const qtyErr = validateRequisitionQtyVsStock(r, index + 1);
|
||||||
|
r.error = !!qtyErr;
|
||||||
store.setCurrentDataLYCK({
|
store.setCurrentDataLYCK({
|
||||||
purchaseinventoryList: form.purchaseinventoryList,
|
purchaseinventoryList: form.purchaseinventoryList,
|
||||||
receiptHeaderForm: receiptHeaderForm,
|
receiptHeaderForm: receiptHeaderForm,
|
||||||
@@ -1254,6 +1344,15 @@ function handleTotalPrice(index) {
|
|||||||
// 保存
|
// 保存
|
||||||
function handleSave(row, index) {
|
function handleSave(row, index) {
|
||||||
rowList.value = [];
|
rowList.value = [];
|
||||||
|
for (let i = 0; i < form.purchaseinventoryList.length; i++) {
|
||||||
|
const line = form.purchaseinventoryList[i];
|
||||||
|
if (!line) continue;
|
||||||
|
const err = validateRequisitionQtyVsStock(line, i + 1);
|
||||||
|
if (err) {
|
||||||
|
proxy.$message.warning(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
form.purchaseinventoryList.map((row, index) => {
|
form.purchaseinventoryList.map((row, index) => {
|
||||||
if (row) {
|
if (row) {
|
||||||
// 触发校验
|
// 触发校验
|
||||||
@@ -1301,7 +1400,13 @@ function handleSave(row, index) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
function addTransferProducts(rowList) {
|
function addTransferProducts(rowList) {
|
||||||
addTransferProduct(JSON.parse(JSON.stringify(rowList))).then((res) => {
|
const payload = (Array.isArray(rowList) ? rowList : []).map((item) => ({
|
||||||
|
...item,
|
||||||
|
startTime: toIssueDateTimeStr(item.startTime),
|
||||||
|
endTime: toIssueDateTimeStr(item.endTime),
|
||||||
|
occurrenceTime: toIssueDateTimeStr(item.occurrenceTime) ?? item.occurrenceTime,
|
||||||
|
}));
|
||||||
|
addTransferProduct(JSON.parse(JSON.stringify(payload))).then((res) => {
|
||||||
// 当前行没有id视为首次新增
|
// 当前行没有id视为首次新增
|
||||||
// if (!row.id) {
|
// if (!row.id) {
|
||||||
// data.isAdding = false; // 允许新增下一行
|
// data.isAdding = false; // 允许新增下一行
|
||||||
@@ -1370,7 +1475,7 @@ function handleScan(row,index){
|
|||||||
rowData.value = row
|
rowData.value = row
|
||||||
rowData.value.itemType = receiptHeaderForm.medicationType
|
rowData.value.itemType = receiptHeaderForm.medicationType
|
||||||
ypName.value = row.name
|
ypName.value = row.name
|
||||||
openTraceNoDialog .value = true;
|
openTraceNoDialog.value = true;
|
||||||
currentIndex.value = index
|
currentIndex.value = index
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1593,6 +1698,17 @@ const exportRequiredParams = ref({
|
|||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
busNo: route.query.supplyBusNo
|
busNo: route.query.supplyBusNo
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 追溯码对话框提交处理
|
||||||
|
function submit(traceNoData) {
|
||||||
|
if (currentIndex.value >= 0 && form.purchaseinventoryList[currentIndex.value]) {
|
||||||
|
form.purchaseinventoryList[currentIndex.value].traceNo = traceNoData.traceNo;
|
||||||
|
form.purchaseinventoryList[currentIndex.value].ybNo = traceNoData.ybNo;
|
||||||
|
proxy.$message.success('追溯码保存成功');
|
||||||
|
}
|
||||||
|
openTraceNoDialog.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
function handleExport() {
|
function handleExport() {
|
||||||
proxy.downloadGet(
|
proxy.downloadGet(
|
||||||
'/issue-manage/requisition/excel-out',
|
'/issue-manage/requisition/excel-out',
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-table v-loading="loading" :data="surgeryList" row-key="id" :row-class-name="getRowClassName">
|
<el-table v-loading="loading" :data="surgeryList" row-key="id" :row-class-name="getRowClassName" highlight-current-row @current-change="handleCurrentChange">
|
||||||
<!-- 申请日期:datetime - 2025-09-19 14:15:00 - 不可操作 -->
|
<!-- 申请日期:datetime - 2025-09-19 14:15:00 - 不可操作 -->
|
||||||
<el-table-column label="申请日期" align="center" prop="createTime" width="160">
|
<el-table-column label="申请日期" align="center" prop="createTime" width="160">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
@@ -1405,6 +1405,12 @@ function getRowClassName({ row }) {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 当前选中行(高亮)
|
||||||
|
const currentRow = ref(null)
|
||||||
|
function handleCurrentChange(row) {
|
||||||
|
currentRow.value = row
|
||||||
|
}
|
||||||
|
|
||||||
// 时间格式化函数
|
// 时间格式化函数
|
||||||
function parseTime(time, pattern) {
|
function parseTime(time, pattern) {
|
||||||
if (!time) return ''
|
if (!time) return ''
|
||||||
|
|||||||
@@ -546,7 +546,7 @@
|
|||||||
v-model="form.admissionTime"
|
v-model="form.admissionTime"
|
||||||
type="datetime"
|
type="datetime"
|
||||||
placeholder="选择入室时间"
|
placeholder="选择入室时间"
|
||||||
value-format="YYYY-MM-DDTHH:mm:ss"
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
format="YYYY-MM-DD HH:mm"
|
format="YYYY-MM-DD HH:mm"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
@@ -558,7 +558,7 @@
|
|||||||
v-model="form.entryTime"
|
v-model="form.entryTime"
|
||||||
type="datetime"
|
type="datetime"
|
||||||
placeholder="选择进室时间"
|
placeholder="选择进室时间"
|
||||||
value-format="YYYY-MM-DDTHH:mm:ss"
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
format="YYYY-MM-DD HH:mm"
|
format="YYYY-MM-DD HH:mm"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
@@ -570,7 +570,7 @@
|
|||||||
v-model="form.anesStart"
|
v-model="form.anesStart"
|
||||||
type="datetime"
|
type="datetime"
|
||||||
placeholder="选择麻醉开始时间"
|
placeholder="选择麻醉开始时间"
|
||||||
value-format="YYYY-MM-DDTHH:mm:ss"
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
format="YYYY-MM-DD HH:mm"
|
format="YYYY-MM-DD HH:mm"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
@@ -584,7 +584,7 @@
|
|||||||
v-model="form.startTime"
|
v-model="form.startTime"
|
||||||
type="datetime"
|
type="datetime"
|
||||||
placeholder="选择切开时间"
|
placeholder="选择切开时间"
|
||||||
value-format="YYYY-MM-DDTHH:mm:ss"
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
format="YYYY-MM-DD HH:mm"
|
format="YYYY-MM-DD HH:mm"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
@@ -596,7 +596,7 @@
|
|||||||
v-model="form.endTime"
|
v-model="form.endTime"
|
||||||
type="datetime"
|
type="datetime"
|
||||||
placeholder="选择手术结束时间"
|
placeholder="选择手术结束时间"
|
||||||
value-format="YYYY-MM-DDTHH:mm:ss"
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
format="YYYY-MM-DD HH:mm"
|
format="YYYY-MM-DD HH:mm"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
@@ -608,7 +608,7 @@
|
|||||||
v-model="form.anesEnd"
|
v-model="form.anesEnd"
|
||||||
type="datetime"
|
type="datetime"
|
||||||
placeholder="选择麻醉结束时间"
|
placeholder="选择麻醉结束时间"
|
||||||
value-format="YYYY-MM-DDTHH:mm:ss"
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
format="YYYY-MM-DD HH:mm"
|
format="YYYY-MM-DD HH:mm"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
@@ -689,7 +689,7 @@
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 手术申请查询弹窗 -->
|
<!-- 手术申请查询弹窗 -->
|
||||||
<el-dialog :title="'手术申请查询'" v-model="showApplyDialog" width="1200px" @close="cancelApplyDialog">
|
<el-dialog :title="'手术申请查询'" v-model="showApplyDialog" width="1200px" @close="cancelApplyDialog" class="surgery-apply-dialog">
|
||||||
<!-- 查询条件区 -->
|
<!-- 查询条件区 -->
|
||||||
<el-form :model="applyQueryParams" ref="applyQueryRef" :inline="true" class="query-form">
|
<el-form :model="applyQueryParams" ref="applyQueryRef" :inline="true" class="query-form">
|
||||||
<el-form-item label="手术单号" prop="surgeryNo">
|
<el-form-item label="手术单号" prop="surgeryNo">
|
||||||
@@ -749,8 +749,8 @@
|
|||||||
@row-click="handleApplyRowClick"
|
@row-click="handleApplyRowClick"
|
||||||
:row-class-name="tableRowClassName"
|
:row-class-name="tableRowClassName"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
max-height="400"
|
max-height="340"
|
||||||
:scroll="{ y: 400 }"
|
:scroll="{ y: 340 }"
|
||||||
>
|
>
|
||||||
<el-table-column type="selection" width="55" :selectable="handleSelectable" />
|
<el-table-column type="selection" width="55" :selectable="handleSelectable" />
|
||||||
<el-table-column label="ID" align="center" width="80" fixed>
|
<el-table-column label="ID" align="center" width="80" fixed>
|
||||||
@@ -781,7 +781,7 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<!-- 底部分页区 -->
|
<!-- 底部分页区 -->
|
||||||
<div class="pagination-container" style="margin-top: 10px; padding-bottom: 10px">
|
<div class="pagination-container apply-pagination" style="margin-top: 10px; padding-bottom: 20px">
|
||||||
<pagination
|
<pagination
|
||||||
v-show="applyTotal > 0"
|
v-show="applyTotal > 0"
|
||||||
:total="applyTotal"
|
:total="applyTotal"
|
||||||
@@ -792,10 +792,12 @@
|
|||||||
@pagination="getSurgicalScheduleList"
|
@pagination="getSurgicalScheduleList"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 分页与底部操作区之间的间隔 -->
|
||||||
|
<div style="height: 48px"></div>
|
||||||
|
|
||||||
<!-- 底部操作区 -->
|
<!-- 底部操作区 -->
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer" style="margin-top: 24px; padding-top: 12px; border-top: 1px solid #ebeef5">
|
||||||
<el-button @click="cancelApplyDialog">取消</el-button>
|
<el-button @click="cancelApplyDialog">取消</el-button>
|
||||||
<el-button type="primary" @click="confirmApply">确认</el-button>
|
<el-button type="primary" @click="confirmApply">确认</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -803,7 +805,7 @@
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 手术计费弹窗 -->
|
<!-- 手术计费弹窗 -->
|
||||||
<el-dialog :title="chargeDialogTitle" v-model="showChargeDialog" width="1400px" @close="closeChargeDialog" append-to-body>
|
<el-dialog :title="chargeDialogTitle" v-model="showChargeDialog" width="1400px" @close="closeChargeDialog" append-to-body destroy-on-close>
|
||||||
<div style="display: flex; justify-content: space-between; height: 80vh">
|
<div style="display: flex; justify-content: space-between; height: 80vh">
|
||||||
<div style="width: 100%; border: 1px solid #eee; position: relative">
|
<div style="width: 100%; border: 1px solid #eee; position: relative">
|
||||||
<div style="padding: 10px; border: 1px solid #eee; height: 50px; border-left: 0">
|
<div style="padding: 10px; border: 1px solid #eee; height: 50px; border-left: 0">
|
||||||
@@ -829,7 +831,7 @@
|
|||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding: 10px">
|
<div style="padding: 10px">
|
||||||
<prescriptionlist v-if="showChargeDialog" :patientInfo="chargePatientInfo" ref="prescriptionRef"
|
<prescriptionlist :patientInfo="chargePatientInfo" ref="prescriptionRef"
|
||||||
:generateSourceEnum="1"
|
:generateSourceEnum="1"
|
||||||
:sourceBillNo="chargePatientInfo.sourceBillNo" />
|
:sourceBillNo="chargePatientInfo.sourceBillNo" />
|
||||||
<div class="overlay" v-if="disabled"></div>
|
<div class="overlay" v-if="disabled"></div>
|
||||||
@@ -881,16 +883,13 @@ import {
|
|||||||
deleteSurgerySchedule,
|
deleteSurgerySchedule,
|
||||||
getSurgeryScheduleDetail
|
getSurgeryScheduleDetail
|
||||||
} from '@/api/surgicalschedule'
|
} from '@/api/surgicalschedule'
|
||||||
import { listUser } from '@/api/system/user'
|
|
||||||
import { deptTreeSelect } from '@/api/system/user'
|
|
||||||
import { listOperatingRoom } from '@/api/operatingroom'
|
|
||||||
import { getSurgeryPage} from '@/views/inpatientDoctor/home/components/applicationShow/api.js'
|
import { getSurgeryPage} from '@/views/inpatientDoctor/home/components/applicationShow/api.js'
|
||||||
import { getContract } from '@/views/inpatientDoctor/home/components/api.js'
|
import { getContract } from '@/views/inpatientDoctor/home/components/api.js'
|
||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
import SurgeryCharge from '../charge/surgerycharge/index.vue'
|
import SurgeryCharge from '../charge/surgerycharge/index.vue'
|
||||||
import TemporaryMedical from './temporaryMedical.vue'
|
import TemporaryMedical from './temporaryMedical.vue'
|
||||||
|
|
||||||
// 静默获取卫生机构列表(跳过拦截器错误提示,手术室护士等角色可能无此权限)
|
// 静默获取字典列表(跳过拦截器错误提示,手术室护士等角色可能无此权限)
|
||||||
function getTenantPageSilent(query) {
|
function getTenantPageSilent(query) {
|
||||||
return request({
|
return request({
|
||||||
url: '/system/tenant/page',
|
url: '/system/tenant/page',
|
||||||
@@ -900,6 +899,36 @@ function getTenantPageSilent(query) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 静默获取科室树(跳过拦截器错误提示)
|
||||||
|
function deptTreeSelectSilent(params = {}) {
|
||||||
|
return request({
|
||||||
|
url: '/base-data-manage/organization/organization',
|
||||||
|
method: 'get',
|
||||||
|
params: { typeEnum: 2, ...params },
|
||||||
|
skipErrorMsg: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 静默获取用户列表(跳过拦截器错误提示)
|
||||||
|
function listUserSilent(query) {
|
||||||
|
return request({
|
||||||
|
url: '/base-data-manage/practitioner/user-practitioner-page',
|
||||||
|
method: 'get',
|
||||||
|
params: query,
|
||||||
|
skipErrorMsg: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 静默获取手术室列表(跳过拦截器错误提示)
|
||||||
|
function listOperatingRoomSilent(query) {
|
||||||
|
return request({
|
||||||
|
url: '/base-data-manage/operating-room/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query,
|
||||||
|
skipErrorMsg: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance()
|
const { proxy } = getCurrentInstance()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
@@ -939,7 +968,7 @@ const form = reactive({
|
|||||||
allergyRemark: undefined,
|
allergyRemark: undefined,
|
||||||
surgeryNature: undefined,
|
surgeryNature: undefined,
|
||||||
surgerySite: undefined,
|
surgerySite: undefined,
|
||||||
incisionLevel: undefined,
|
incisionType: undefined,
|
||||||
surgeryLevel: undefined,
|
surgeryLevel: undefined,
|
||||||
|
|
||||||
admissionTime: undefined,
|
admissionTime: undefined,
|
||||||
@@ -1111,6 +1140,30 @@ const {
|
|||||||
method_code
|
method_code
|
||||||
} = useDict('surgical_site', 'anesthesia_type', 'incision_level', 'isolation_type', 'surgery_type', 'surgery_level', 'surgery_nature', 'method_code')
|
} = useDict('surgical_site', 'anesthesia_type', 'incision_level', 'isolation_type', 'surgery_type', 'surgery_level', 'surgery_nature', 'method_code')
|
||||||
|
|
||||||
|
// Bug #433: 存储待转换的数据,等待字典加载后再设置类型
|
||||||
|
const pendingAnesData = ref(null)
|
||||||
|
|
||||||
|
// 监听麻醉字典加载,完成后立即设置表单值
|
||||||
|
let anesDataUnwatch = null
|
||||||
|
function setupAnesDataWatch() {
|
||||||
|
if (anesDataUnwatch) return // 防止重复设置
|
||||||
|
anesDataUnwatch = watch(
|
||||||
|
anesthesiaList,
|
||||||
|
(newList) => {
|
||||||
|
if (newList && newList.length > 0 && pendingAnesData.value) {
|
||||||
|
const data = pendingAnesData.value
|
||||||
|
if (data.anesMethod != null) form.anesMethod = Number(data.anesMethod)
|
||||||
|
if (data.incisionLevel != null) form.incisionType = Number(data.incisionLevel)
|
||||||
|
if (data.feeType != null) form.feeType = data.feeType
|
||||||
|
if (data.isExternalExpert != null) form.isExternalExpert = Number(data.isExternalExpert)
|
||||||
|
pendingAnesData.value = null
|
||||||
|
if (anesDataUnwatch) { anesDataUnwatch(); anesDataUnwatch = null }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 加载数据
|
// 加载数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const anesthesiaType = sessionStorage.getItem('anesthesiaType')
|
const anesthesiaType = sessionStorage.getItem('anesthesiaType')
|
||||||
@@ -1148,7 +1201,7 @@ function loadOrgList() {
|
|||||||
|
|
||||||
// 加载科室列表
|
// 加载科室列表
|
||||||
function loadDeptList() {
|
function loadDeptList() {
|
||||||
deptTreeSelect()
|
deptTreeSelectSilent()
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
const tree = res.data?.records || res.data || []
|
const tree = res.data?.records || res.data || []
|
||||||
@@ -1168,7 +1221,7 @@ function loadDeptList() {
|
|||||||
|
|
||||||
// 加载医生列表
|
// 加载医生列表
|
||||||
function loadDoctorList() {
|
function loadDoctorList() {
|
||||||
listUser({ pageNo: 1, pageSize: 1000 })
|
listUserSilent({ pageNo: 1, pageSize: 1000 })
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
const records = res.data?.records || []
|
const records = res.data?.records || []
|
||||||
@@ -1188,7 +1241,7 @@ function loadDoctorList() {
|
|||||||
|
|
||||||
// 加载护士列表
|
// 加载护士列表
|
||||||
function loadNurseList() {
|
function loadNurseList() {
|
||||||
listUser({ pageNo: 1, pageSize: 1000 })
|
listUserSilent({ pageNo: 1, pageSize: 1000 })
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
const records = res.data?.records || []
|
const records = res.data?.records || []
|
||||||
@@ -1208,7 +1261,7 @@ function loadNurseList() {
|
|||||||
|
|
||||||
// 加载手术室列表
|
// 加载手术室列表
|
||||||
function loadOperatingRoomList() {
|
function loadOperatingRoomList() {
|
||||||
listOperatingRoom({ pageNo: 1, pageSize: 1000, statusEnum: 1 })
|
listOperatingRoomSilent({ pageNo: 1, pageSize: 1000, statusEnum: 1 })
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
const records = res.data?.records || []
|
const records = res.data?.records || []
|
||||||
@@ -1296,13 +1349,16 @@ function handleEdit(row) {
|
|||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
const data = res.data
|
const data = res.data
|
||||||
Object.assign(form, data)
|
Object.assign(form, data)
|
||||||
// 修复#433:确保字典字段类型与下拉选项一致(Number类型)
|
// Bug #433: 如果字典已加载则立即转换,否则存入pending等待字典加载完成
|
||||||
// 后端OpSchedule.anesMethod为String类型,需转为Number与el-select匹配
|
if (anesthesiaList.value && anesthesiaList.value.length > 0) {
|
||||||
if (data.anesMethod != null) form.anesMethod = Number(data.anesMethod)
|
if (data.anesMethod != null) form.anesMethod = Number(data.anesMethod)
|
||||||
if (data.anesthesiaTypeEnum != null) form.anesMethod = Number(data.anesthesiaTypeEnum)
|
|
||||||
if (data.incisionLevel != null) form.incisionType = Number(data.incisionLevel)
|
if (data.incisionLevel != null) form.incisionType = Number(data.incisionLevel)
|
||||||
if (data.feeType != null) form.feeType = data.feeType
|
if (data.feeType != null) form.feeType = data.feeType
|
||||||
if (data.isExternalExpert != null) form.isExternalExpert = Number(data.isExternalExpert)
|
if (data.isExternalExpert != null) form.isExternalExpert = Number(data.isExternalExpert)
|
||||||
|
} else {
|
||||||
|
pendingAnesData.value = data
|
||||||
|
setupAnesDataWatch()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
proxy.$modal.msgError('获取手术安排详情失败')
|
proxy.$modal.msgError('获取手术安排详情失败')
|
||||||
}
|
}
|
||||||
@@ -1322,13 +1378,16 @@ function handleView(row) {
|
|||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
const data = res.data
|
const data = res.data
|
||||||
Object.assign(form, data)
|
Object.assign(form, data)
|
||||||
// 修复#433:确保字典字段类型与下拉选项一致(Number类型)
|
// Bug #433: 如果字典已加载则立即转换,否则存入pending等待字典加载完成
|
||||||
// 后端OpSchedule.anesMethod为String类型,需转为Number与el-select匹配
|
if (anesthesiaList.value && anesthesiaList.value.length > 0) {
|
||||||
if (data.anesMethod != null) form.anesMethod = Number(data.anesMethod)
|
if (data.anesMethod != null) form.anesMethod = Number(data.anesMethod)
|
||||||
if (data.anesthesiaTypeEnum != null) form.anesMethod = Number(data.anesthesiaTypeEnum)
|
|
||||||
if (data.incisionLevel != null) form.incisionType = Number(data.incisionLevel)
|
if (data.incisionLevel != null) form.incisionType = Number(data.incisionLevel)
|
||||||
if (data.feeType != null) form.feeType = data.feeType
|
if (data.feeType != null) form.feeType = data.feeType
|
||||||
if (data.isExternalExpert != null) form.isExternalExpert = Number(data.isExternalExpert)
|
if (data.isExternalExpert != null) form.isExternalExpert = Number(data.isExternalExpert)
|
||||||
|
} else {
|
||||||
|
pendingAnesData.value = data
|
||||||
|
setupAnesDataWatch()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
proxy.$modal.msgError('获取手术安排详情失败')
|
proxy.$modal.msgError('获取手术安排详情失败')
|
||||||
}
|
}
|
||||||
@@ -1429,17 +1488,18 @@ async function handleChargeCharge(row) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 关闭计费弹窗
|
// 关闭计费弹窗
|
||||||
function closeChargeDialog() {
|
async function closeChargeDialog() {
|
||||||
// 先关闭 prescriptionlist 内所有已打开的项目字典 popover
|
// 先关闭 prescriptionlist 内所有已打开的项目字典 popover
|
||||||
if (prescriptionRef.value && prescriptionRef.value.closeAllPopovers) {
|
if (prescriptionRef.value && prescriptionRef.value.closeAllPopovers) {
|
||||||
prescriptionRef.value.closeAllPopovers()
|
prescriptionRef.value.closeAllPopovers()
|
||||||
}
|
}
|
||||||
// 等 Vue 完成 DOM 更新后再关闭弹窗,确保 popover 先消失
|
// 等待 Vue 完成 popover 可见性更新的 DOM 操作,
|
||||||
nextTick(() => {
|
// 因为 el-popover 通过 teleport 渲染在 body 上,需要在 dialog 卸载前完成清理
|
||||||
|
await nextTick()
|
||||||
|
// 清空数据,避免下次打开时使用缓存
|
||||||
showChargeDialog.value = false
|
showChargeDialog.value = false
|
||||||
chargePatientInfo.value = {}
|
chargePatientInfo.value = {}
|
||||||
chargeSurgeryInfo.value = {}
|
chargeSurgeryInfo.value = {}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔧 新增:标志位,用于区分是"打开"还是"刷新"
|
// 🔧 新增:标志位,用于区分是"打开"还是"刷新"
|
||||||
@@ -1500,7 +1560,11 @@ function handleMedicalAdvice(row) {
|
|||||||
// 先清空旧数据
|
// 先清空旧数据
|
||||||
temporaryBillingMedicines.value = []
|
temporaryBillingMedicines.value = []
|
||||||
temporaryAdvices.value = []
|
temporaryAdvices.value = []
|
||||||
temporarySigned.value = false // 🔧 重置签名状态
|
// 🔧 修复 Bug #446: 如果是同一 encounter 且已有提交的医嘱(有 requestId),保留签名状态
|
||||||
|
const hasSubmittedAdvices = temporaryAdvices.value.length > 0 &&
|
||||||
|
temporaryAdvices.value[0]?.originalMedicine?.encounterId === row.visitId &&
|
||||||
|
temporaryAdvices.value.some(a => a.originalMedicine?.requestId);
|
||||||
|
temporarySigned.value = hasSubmittedAdvices; // 修复:根据已有数据状态设置,而非盲目重置
|
||||||
temporaryMedicalLoading.value = true // 🔧 新增:开始加载
|
temporaryMedicalLoading.value = true // 🔧 新增:开始加载
|
||||||
|
|
||||||
// 调用计费接口获取数据
|
// 调用计费接口获取数据
|
||||||
@@ -1515,9 +1579,13 @@ function handleMedicalAdvice(row) {
|
|||||||
const filteredItems = res.data.filter(item => {
|
const filteredItems = res.data.filter(item => {
|
||||||
// 匹配 encounterId
|
// 匹配 encounterId
|
||||||
if (item.encounterId !== row.visitId) return false;
|
if (item.encounterId !== row.visitId) return false;
|
||||||
|
// 只保留药品类型(adviceType=1),过滤掉耗材(2)和诊疗项目(3)
|
||||||
|
if (item.adviceType !== 1) return false;
|
||||||
// 过滤掉名称为空的项目
|
// 过滤掉名称为空的项目
|
||||||
const medicineName = item.adviceName || item.advice_name;
|
const medicineName = item.adviceName || item.advice_name;
|
||||||
if (!medicineName || medicineName.trim() === '') return false;
|
if (!medicineName || medicineName.trim() === '') return false;
|
||||||
|
// 🔧 修复 Bug #445: 过滤掉已生成医嘱的项目(已有 requestId 的不应出现在"待生成"列表)
|
||||||
|
if (item.requestId) return false;
|
||||||
// 根据药品请求ID去重,避免重复显示
|
// 根据药品请求ID去重,避免重复显示
|
||||||
const itemId = item.requestId || item.id;
|
const itemId = item.requestId || item.id;
|
||||||
if (itemId && seenIds.has(itemId)) return false;
|
if (itemId && seenIds.has(itemId)) return false;
|
||||||
@@ -1705,15 +1773,27 @@ function handleTemporaryMedicalSubmit(data) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 同步更新计费药品列表:移除已生成医嘱的项目,避免数据重复显示
|
// 🔧 修复 Bug #445: 使用稳定的字段组合匹配已提交项目,而不是依赖可能为空的 requestId/chargeItemId
|
||||||
const submittedIds = new Set(
|
// 构建已提交项目的匹配键集合(药品名称 + 规格 + 数量)
|
||||||
(data.temporaryAdvices || []).map(a => a.originalMedicine?.requestId || a.originalMedicine?.chargeItemId).filter(Boolean)
|
const submittedKeys = new Set(
|
||||||
)
|
(data.temporaryAdvices || [])
|
||||||
if (submittedIds.size > 0) {
|
.map(a => {
|
||||||
temporaryBillingMedicines.value = (data.billingMedicines || []).filter(
|
const om = a.originalMedicine || {}
|
||||||
m => !submittedIds.has(m.requestId || m.chargeItemId)
|
const name = om.medicineName || om.adviceName || om.advice_name || a.adviceName || ''
|
||||||
|
const spec = om.specification || om.volume || ''
|
||||||
|
const qty = om.quantity || 0
|
||||||
|
return `${name}|||${spec}|||${qty}`
|
||||||
|
})
|
||||||
|
.filter(k => k !== '|||0') // 过滤掉空项
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (submittedKeys.size > 0) {
|
||||||
|
temporaryBillingMedicines.value = (temporaryBillingMedicines.value || []).filter(m => {
|
||||||
|
const key = `${m.medicineName || ''}|||${m.specification || ''}|||${m.quantity || 0}`
|
||||||
|
return !submittedKeys.has(key)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
|
// 如果没有任何匹配键,清空待生成列表(所有项目都已提交)
|
||||||
temporaryBillingMedicines.value = []
|
temporaryBillingMedicines.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1773,13 +1853,19 @@ function handleQuoteBilling() {
|
|||||||
temporaryBillingMedicines.value = []
|
temporaryBillingMedicines.value = []
|
||||||
temporaryAdvices.value = []
|
temporaryAdvices.value = []
|
||||||
|
|
||||||
// 🔧 修复:显示所有药品请求数据,不管有没有计费项目
|
// 🔧 修复 Bug #445: 只保留药品类型(adviceType=1),过滤掉耗材(2)和诊疗项目(3)
|
||||||
|
// 同时过滤掉已有 requestId 的项目(已生成医嘱的不需要再次显示在"待生成"列表中)
|
||||||
const filteredItems = res.data.filter(item => {
|
const filteredItems = res.data.filter(item => {
|
||||||
// 匹配 encounterId
|
// 匹配 encounterId
|
||||||
if (item.encounterId !== temporaryPatientInfo.value.visitId) return false;
|
if (item.encounterId !== temporaryPatientInfo.value.visitId) return false;
|
||||||
|
// 只保留药品类型(adviceType=1),过滤掉耗材(2)和诊疗项目(3)
|
||||||
|
if (item.adviceType !== 1) return false;
|
||||||
// 过滤掉名称为空的项目
|
// 过滤掉名称为空的项目
|
||||||
const medicineName = item.adviceName || item.advice_name;
|
const medicineName = item.adviceName || item.advice_name;
|
||||||
return medicineName && medicineName.trim() !== '';
|
if (!medicineName || medicineName.trim() === '') return false;
|
||||||
|
// 🔧 修复 Bug #445: 过滤掉已生成医嘱的项目(已有 requestId)
|
||||||
|
if (item.requestId) return false;
|
||||||
|
return true;
|
||||||
})
|
})
|
||||||
// 🔧 修复:限制返回数量,最多显示前100条,避免数据过多导致页面卡死
|
// 🔧 修复:限制返回数量,最多显示前100条,避免数据过多导致页面卡死
|
||||||
const maxItems = 100
|
const maxItems = 100
|
||||||
@@ -1888,6 +1974,30 @@ function handleQuoteBilling() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 🔧 修复 Bug #445: 过滤掉已生成医嘱的项目,避免"引用计费"后已提交项目重新出现在"待生成"列表
|
||||||
|
// 原因:后端返回的计费数据中,已生成医嘱的项目可能没有 requestId 字段
|
||||||
|
// 方案:用 chargeItemId/requestId/id 与已有的 temporaryAdvices 做匹配,排除已生成项目
|
||||||
|
if (temporaryAdvices.value.length > 0) {
|
||||||
|
const existingAdviceIds = new Set()
|
||||||
|
temporaryAdvices.value.forEach(a => {
|
||||||
|
const om = a.originalMedicine || {}
|
||||||
|
if (om.requestId) existingAdviceIds.add(String(om.requestId))
|
||||||
|
if (om.chargeItemId) existingAdviceIds.add(String(om.chargeItemId))
|
||||||
|
if (om.id) existingAdviceIds.add(String(om.id))
|
||||||
|
})
|
||||||
|
if (existingAdviceIds.size > 0) {
|
||||||
|
temporaryBillingMedicines.value = temporaryBillingMedicines.value.filter(m => {
|
||||||
|
const mRequestId = m.requestId != null ? String(m.requestId) : null
|
||||||
|
const mChargeItemId = m.chargeItemId != null ? String(m.chargeItemId) : null
|
||||||
|
const mId = m.id != null ? String(m.id) : null
|
||||||
|
if (mRequestId && existingAdviceIds.has(mRequestId)) return false
|
||||||
|
if (mChargeItemId && existingAdviceIds.has(mChargeItemId)) return false
|
||||||
|
if (mId && existingAdviceIds.has(mId)) return false
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
temporaryMedicalLoading.value = false // 🔧 新增:加载完成
|
temporaryMedicalLoading.value = false // 🔧 新增:加载完成
|
||||||
ElMessage.success('已成功引用最新计费药品信息!')
|
ElMessage.success('已成功引用最新计费药品信息!')
|
||||||
} else {
|
} else {
|
||||||
@@ -1996,7 +2106,12 @@ function resetForm() {
|
|||||||
function submitForm() {
|
function submitForm() {
|
||||||
proxy.$refs['surgeryRef'].validate((valid) => {
|
proxy.$refs['surgeryRef'].validate((valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
const submitData = { ...form, orgId: userStore.orgId }
|
const submitData = {
|
||||||
|
...form,
|
||||||
|
orgId: userStore.orgId,
|
||||||
|
incisionLevel: form.incisionType
|
||||||
|
}
|
||||||
|
delete submitData.incisionType
|
||||||
if (!form.scheduleId) {
|
if (!form.scheduleId) {
|
||||||
// 新增手术安排
|
// 新增手术安排
|
||||||
addSurgerySchedule(submitData).then((res) => {
|
addSurgerySchedule(submitData).then((res) => {
|
||||||
@@ -2291,6 +2406,22 @@ function getRowClassName({ row, rowIndex }) {
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 手术申请查询弹窗 — 分页与footer间距 */
|
||||||
|
.surgery-apply-dialog :deep(.el-dialog__body) {
|
||||||
|
padding-bottom: 32px;
|
||||||
|
}
|
||||||
|
.surgery-apply-dialog :deep(.el-dialog__footer) {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
.surgery-apply-dialog :deep(.apply-pagination) {
|
||||||
|
padding-bottom: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
}
|
||||||
|
.surgery-apply-dialog :deep(.apply-pagination .el-pagination) {
|
||||||
|
margin-right: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
/* 选中行样式 */
|
/* 选中行样式 */
|
||||||
:deep(.el-table .selected-row) {
|
:deep(.el-table .selected-row) {
|
||||||
background-color: #ecf5ff !important;
|
background-color: #ecf5ff !important;
|
||||||
@@ -2301,3 +2432,21 @@ function getRowClassName({ row, rowIndex }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 手术申请查询弹窗 — 非 scoped 确保穿透 teleport */
|
||||||
|
.surgery-apply-dialog .apply-pagination {
|
||||||
|
padding-bottom: 24px !important;
|
||||||
|
margin-bottom: 16px !important;
|
||||||
|
border-bottom: 1px solid #ebeef5 !important;
|
||||||
|
}
|
||||||
|
.surgery-apply-dialog .apply-pagination .el-pagination {
|
||||||
|
margin-right: 80px !important;
|
||||||
|
}
|
||||||
|
.surgery-apply-dialog .el-dialog__body {
|
||||||
|
padding-bottom: 32px !important;
|
||||||
|
}
|
||||||
|
.surgery-apply-dialog .el-dialog__footer {
|
||||||
|
padding-top: 0 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -147,9 +147,10 @@
|
|||||||
<el-button class="cancel-btn" @click="handleCancel">取消</el-button>
|
<el-button class="cancel-btn" @click="handleCancel">取消</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
class="sign-btn"
|
class="sign-btn"
|
||||||
|
:disabled="allItemsSubmitted"
|
||||||
@click="handleSignAndSubmit"
|
@click="handleSignAndSubmit"
|
||||||
>
|
>
|
||||||
{{ isSigned ? '提交医嘱' : '一键签名并生成医嘱' }}
|
{{ allItemsSubmitted ? '已签发' : (isSigned ? '提交医嘱' : '一键签名并生成医嘱') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -310,8 +311,21 @@ const getMethodCodeDict = computed(() => {
|
|||||||
return dict
|
return dict
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 🔧 修复 Bug #446: 检查计费药品是否已全部提交(有 requestId),用于区分"首次签名"和"已提交重开"
|
||||||
|
const allItemsSubmitted = computed(() => {
|
||||||
|
const meds = props.billingMedicines || []
|
||||||
|
return meds.length > 0 && meds.every(m => m.requestId)
|
||||||
|
})
|
||||||
|
|
||||||
// 响应式数据 - isSigned 从父组件传入的 prop 初始化
|
// 响应式数据 - isSigned 从父组件传入的 prop 初始化
|
||||||
const isSigned = ref(props.isSignedProp)
|
const isSigned = ref(props.isSignedProp)
|
||||||
|
|
||||||
|
// 🔧 修复 Bug #446: 同步父组件 isSignedProp 的变化到本地 isSigned
|
||||||
|
// ref(props.isSignedProp) 只在初始化时读取一次,父组件后续更新不会自动同步
|
||||||
|
watch(() => props.isSignedProp, (newVal) => {
|
||||||
|
isSigned.value = newVal
|
||||||
|
})
|
||||||
|
|
||||||
const signatureTime = ref('')
|
const signatureTime = ref('')
|
||||||
const showSignDialog = ref(false)
|
const showSignDialog = ref(false)
|
||||||
const signPassword = ref('')
|
const signPassword = ref('')
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
-- Bug #462: 诊疗目录编辑弹窗中"所需标本"下拉框数据加载失败
|
-- Bug #462: 诊疗目录编辑弹窗中"所需标本"下拉框数据加载失败
|
||||||
-- 根因: sys_dict_type 表中缺少 specimen_code 字典类型,sys_dict_data 表中缺少对应数据
|
-- 根因: hisprd schema 中 sys_dict_type 存在 specimen_code 类型,但 sys_dict_data 中缺少对应的7条数据记录
|
||||||
-- 修复: 插入字典类型及7条标本数据
|
-- 修复: 在 hisprd schema 中插入7条标本数据
|
||||||
|
-- 验证: hisdev/histest1 已有数据,仅 hisprd 缺失
|
||||||
|
-- 注意: hisprd 的 sys_dict_data 表无 py_str 字段(旧表结构)
|
||||||
|
|
||||||
-- 插入字典类型
|
INSERT INTO hisprd.sys_dict_data (dict_sort, dict_label, dict_value, dict_type, status, create_by, create_time, remark)
|
||||||
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
|
|
||||||
VALUES ('标本类型', 'specimen_code', '0', 'admin', NOW(), '诊疗项目所需标本类型字典');
|
|
||||||
|
|
||||||
-- 插入标本数据
|
|
||||||
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, status, create_by, create_time, remark)
|
|
||||||
VALUES
|
VALUES
|
||||||
(1, '血液', '1', 'specimen_code', '0', 'admin', NOW(), '血液标本'),
|
(1, '血液', '1', 'specimen_code', '0', 'admin', NOW(), '血液标本'),
|
||||||
(2, '尿液', '2', 'specimen_code', '0', 'admin', NOW(), '尿液标本'),
|
(2, '尿液', '2', 'specimen_code', '0', 'admin', NOW(), '尿液标本'),
|
||||||
@@ -15,4 +12,4 @@ VALUES
|
|||||||
(4, '呼吸道', '4', 'specimen_code', '0', 'admin', NOW(), '呼吸道标本'),
|
(4, '呼吸道', '4', 'specimen_code', '0', 'admin', NOW(), '呼吸道标本'),
|
||||||
(5, '无菌体液', '5', 'specimen_code', '0', 'admin', NOW(), '无菌体液标本'),
|
(5, '无菌体液', '5', 'specimen_code', '0', 'admin', NOW(), '无菌体液标本'),
|
||||||
(6, '生殖道', '6', 'specimen_code', '0', 'admin', NOW(), '生殖道标本'),
|
(6, '生殖道', '6', 'specimen_code', '0', 'admin', NOW(), '生殖道标本'),
|
||||||
(7, '其他', '7', 'specimen_code', '0', 'admin', NOW(), '其他标本');
|
(7, '其他', '99', 'specimen_code', '0', 'admin', NOW(), '其他标本');
|
||||||
|
|||||||
Reference in New Issue
Block a user