Browse Source

医嘱批量统一保存

liudan 1 month ago
parent
commit
595776af6f

+ 189 - 0
docs/OutPatient_InsertMixedOrders_PRD.md

@@ -0,0 +1,189 @@
+# 门诊混合医嘱保存接口(西药/检查/检验/治疗)PRD
+
+> 文档状态:需求已与产品经理(John)对齐,待切到 craft 模式实现  
+> 接口 Code:`12100054`  
+> 接口层:`src.OutPatient.Interface`  
+> 业务层:`src.OutPatient.Order`
+
+---
+
+## 1. 背景与目标
+
+### 1.1 现状
+现有 `src.OutPatient.Interface.InsertOrder`(`code: 03050001`)已支持单次传入一组 `orders` 数组,数组每项通过 `ordTypeCode` 区分类型(西药 `M`、检查 `C`、检验 `L`、治疗 `T` 等),底层通过 `src.OutPatient.Order.InsertOrder` 完成保存(西药复用 `InsertOrderWithCustomPrice` 含自定义价格逻辑,其余走通用保存)。
+
+### 1.2 目标
+新增一个**独立接口**,一次调用即可同时保存「西药、检查、检验、治疗」四类医嘱。要求:
+- **新增独立接口方法**,核心保存流程复用已有方法;为支持混合保存跳过 HIS,给 `InsertOrderWithCustomPrice` 新增可选入参,并让 `InsertOrder` / `InsertOrderForPackage` 识别该入参。
+- 入参按**四类拆分字段**(方案 B),前端拼四个独立数组,后端分别归集处理。
+- **整体事务**:四类医嘱要么全部保存成功,要么全部回滚(原子性)。
+- 四类**全部复用** `InsertOrderWithCustomPrice`(自定义价格 + 打包项目灰度逻辑),通过新增可选入参 `skipHISFlag="Y"` 控制不发送 HIS,满足混合保存场景。
+- 返回**每类各自的 ordID 列表**(保留完整 ordID,含 admID 前缀,如 `365109||1`)。
+
+---
+
+## 2. 需求决策点(与 John 确认)
+
+| 项 | 决策 |
+|----|------|
+| 入参结构 | 方案 B:四字段拆分 `westOrders` / `checkOrders` / `lisOrders` / `treatOrders` |
+| 事务原子性 | 整体事务,任一子项失败整体回滚,返回首个错误 |
+| 自定义价格 / HIS 控制 | 四类全部走 `InsertOrderWithCustomPrice`;混合保存通过新增可选入参 `skipHISFlag="Y"` 跳过 HIS 发送,老两参数调用保持默认发送 HIS |
+| 返回结构 | 回每类各自的 ID 列表 |
+| 接口 Code | `12100054` |
+| 字段命名 | 四字段命名合适(非阻塞点确认) |
+| ordID 格式 | 保留完整 ordID(含 admID 前缀,与老 `ordIDMap` 一致) |
+
+---
+
+## 3. 改动文件与新增方法
+
+| 文件 | 新增/修改方法 | 作用 |
+|------|--------------|------|
+| `src.OutPatient.Interface` | `InsertMixedOrders(ByRef jsonObj)` | 接口入口:解析入参、调用业务层、统一出参 |
+| `src.OutPatient.Order` | `InsertMixedOrders(dataStr, sessionStr)` | 业务层:拆四类、整体事务、逐类调用通用保存、汇总各类 ID |
+| `src.OutPatient.Order` | `InsertOrderWithCustomPrice(..., skipHISFlag = "N")` | **修改签名**:新增可选入参,控制下游是否发送 HIS |
+| `src.OutPatient.Order` | `InsertOrder` / `InsertOrderForPackage` | **修改**:读取 `skipHISFlag`,为 `"Y"` 时跳过 HIS 发送代码 |
+
+`OrderDataTrans` / `CheckOrderData` 等其它老方法**不修改**。
+
+---
+
+## 4. 入参 Schema(方案 B:四字段拆分)
+
+```json
+{
+  "params": [{
+    "admID": "365109",
+    "locID": "539",
+    "docID": "1323",
+    "userID": "1323",
+    "hospID": "52",
+    "groupID": "307",
+    "medType": "WM",
+    "orderReSorce": "Doctor",
+    "westOrders":  [ { "medID": "...", "ordQty": 1, "ordDose": "...", "ordFreqID": "...", "ordTypeCode": "M", ... } ],
+    "checkOrders": [ { "medID": "...", "ordTypeCode": "C", ... } ],
+    "lisOrders":   [ { "medID": "...", "ordTypeCode": "L", ... } ],
+    "treatOrders": [ { "medID": "...", "ordTypeCode": "T", ... } ]
+  }],
+  "code": "12100054",
+  "session": [{
+    "userID": "1323",
+    "locID": "539",
+    "groupID": "307",
+    "hospID": "52",
+    "sessionID": "...",
+    "hospCode": "H11",
+    "language": "CN"
+  }]
+}
+```
+
+### 4.1 入参字段说明
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `admID` | string | 就诊记录 ID(必填) |
+| `locID` | string | 开单科室 ID |
+| `docID` | string | 开单医生 ID |
+| `userID` | string | 操作用户 ID |
+| `hospID` | string | 医院 ID(入参用 `hospID`) |
+| `groupID` | string | 用户组 ID |
+| `medType` | string | 医嘱类别,西药传 `"WM"` |
+| `orderReSorce` | string | 医嘱来源,如 `"Doctor"` |
+| `westOrders` | array | 西药医嘱数组,`ordTypeCode` 应为 `M` |
+| `checkOrders` | array | 检查医嘱数组,`ordTypeCode` 应为 `C` |
+| `lisOrders` | array | 检验医嘱数组,`ordTypeCode` 应为 `L` |
+| `treatOrders` | array | 治疗医嘱数组,`ordTypeCode` 应为 `T` |
+
+### 4.2 子类项字段
+每类内部的医嘱项字段结构**完全复用老接口 `orders` 数组的字段**(老 `InsertOrder` 已支持的 `{medID, ordQty, ordDose, ordFreqID, ordTypeCode, ...}`),前端无需重新造字段。`ordTypeCode` 仍由每条医嘱自带(M/C/L/T),后端按所属数组分类归集。
+
+> 任一分类数组为空(未定义或 `%Size()=0`)则跳过该类,不影响其它类保存。
+
+---
+
+## 5. 业务层 `InsertMixedOrders` 处理逻辑(核心)
+
+```text
+1. 解析 dataObj / session,取 admID、userID、hospID、groupID、locID、docID、medType、orderReSorce
+2. 组装四类数组:westOrders / checkOrders / lisOrders / treatOrders
+   (任一为空数组则跳过该类,不影响其它类)
+3. TStart 整体事务
+4. 对每一类,把该类数组塞回统一结构:
+   { admID, locID, docID, userID, hospID, groupID, medType, orders:[...], orderReSorce }
+5. 四类(西药/检查/检验/治疗)均调用 ##class(src.OutPatient.Order).InsertOrderWithCustomPrice(该结构, session, "Y")
+            // 复用自定义价格 + 打包项目灰度逻辑;通过 skipHISFlag="Y" 控制不发送 HIS
+            // 内部统一走 InsertOrder / InsertOrderForPackage(含 OrderDataTrans / CheckOrderData / 分处方号 /
+            // CreateSpecNo 检验标本号 / SaveApplicationNew 检查申请单等全部老逻辑),保留 ordIDMap 返回
+   - 取返回值中的 ordIDMap(按原始下标 → ordID 列表),归集到该类 ID 列表
+6. 任一类 errorCode '= "0" → TRollback 1,返回该错误(首个失败即止,保证原子性)
+7. 全部成功 → TCommit
+8. 出参 result 含四类 ID:
+   { westOrdIDs:[...], checkOrdIDs:[...], lisOrdIDs:[...], treatOrdIDs:[...] }
+```
+
+### 5.1 事务边界
+- 最外层 `TStart`,四类全部成功才 `TCommit`。
+- 任一分类调用返回 `errorCode'="0"`,立即 `TRollback 1` 并直接返回该错误(含 `errorMessage`),不做后续分类。
+- 分类本身为空则不参与事务内的保存调用(但仍处于同一事务上下文中,无副作用)。
+
+### 5.2 复用点说明
+- `InsertOrderWithCustomPrice`:四类**全部**复用,新增可选入参 `skipHISFlag`(默认 `"N"`),混合保存传 `"Y"` 以跳过 HIS 发送;其内部无打包项目时自动转调通用保存,内含全部老逻辑(OrderDataTrans / CheckOrderData / 分处方号 / CreateSpecNo 检验标本号 / SaveApplicationNew 检查申请单等),内部已批量处理该类数组(不循环调单条,符合批量规范)。
+- `InsertOrder` / `InsertOrderForPackage`:读取 `postParams.skipHISFlag`,为 `"Y"` 时跳过末尾的 `UpdateApplicationItem` / `UpdateCureApplication` HIS 发送代码,保持其它逻辑不变。
+
+---
+
+## 6. 出参 Schema
+
+### 6.1 成功
+```json
+{
+  "errorCode": "0",
+  "errorMessage": "成功",
+  "result": {
+    "westOrdIDs":  ["365109||1", "365109||2"],
+    "checkOrdIDs": ["365109||3"],
+    "lisOrdIDs":   ["365109||4"],
+    "treatOrdIDs": ["365109||5"]
+  }
+}
+```
+
+### 6.2 失败(如检验类校验不通过)
+```json
+{
+  "errorCode": "03050046",
+  "errorMessage": "医嘱主表不存在",
+  "result": {}
+}
+```
+失败时 `result` 为空对象,不返回任何已保存的 ID(已整体回滚)。
+
+---
+
+## 7. 代码规范与合规点
+
+- **入参解析**:`jsonObj.params.%Get(0)`、`jsonObj.session.%Get(0)`,按规范取 `hospID` 等;不用 `$GET` 获取入参。
+- **事务**:最外层 `TStart`,全部成功 `TCommit`,任一失败 `TRollback 1`;`try` 内不允许 `quit` 直接退出事务。
+- **不循环调单条**:每类一次性将该类数组传入 `InsertOrder`(老方法内部已批量处理),符合批量操作规范。
+- **出参格式**:遵循 `{"errorCode":"0","errorMessage":"成功","result":{}}`。
+- **实现前启用 iris 代码规范 skill**(按 workspace 规则)。
+
+---
+
+## 8. 非阻塞默认项(已确认)
+
+1. `medType`:西药传 `"WM"`;检查/检验/治疗依靠 `ordTypeCode` 区分,传入 `medType` 取默认 `"WM"` 或不强依赖。
+2. 返回 ID 保留完整 ordID(含 admID 前缀),与老 `ordIDMap` 一致。
+
+---
+
+## 9. 待办(实现阶段)
+
+- [ ] 实现 `src.OutPatient.Interface.InsertMixedOrders`
+- [ ] 实现 `src.OutPatient.Order.InsertMixedOrders`(整体事务 + 逐类归集)
+- [ ] 启用 iris 代码规范 skill 做合规检查
+- [ ] 编写/补充测试样例(四类混合入参)
+- [ ] 验证整体事务回滚行为

+ 190 - 0
docs/文档.md

@@ -0,0 +1,190 @@
+
+    1.批量查询已选项目的默认数据后提交保存
+        fetchDefaultDataAndSave(selected) {
+            const patientData = Cache.get('patientData');
+            const userData = Cache.get('userData');
+            const { admID } = patientData;
+            const { userID, locID, hospID } = userData;
+            const { date, time } = this.formatDateTime();
+
+            // 构造 03050020 的 orders 入参
+            const queryOrders = selected.map((item) => ({
+                medID: item.medID,
+                ordSetID: ''
+            }));
+
+            $http.post('urlDeault', this, {
+                code: '03050020',
+                data: {
+                    params: [
+                        {
+                            admID: admID || '',
+                            orders: queryOrders
+                        }
+                    ]
+                },
+                success: (res) => {
+                    if (res.errorCode === '0') {
+                        const dataList = res.data || [];
+                        // medID -> defaultData 映射,便于按选中顺序构造 orders
+                        const defaultMap = {};
+                        dataList.forEach((d) => {
+                            if (d && d.defaultData && d.defaultData.medID) {
+                                defaultMap[d.defaultData.medID] = d.defaultData;
+                            }
+                        });
+
+                        const orders = selected.map((item, index) => {
+                            const def = defaultMap[item.medID] || {};
+                            // 仅检查(mainCode='E')需要 bodyPart 三层结构:
+                            // orders.ordbodyPart=[null] -> appMessage.bodyPart[].bodyPart[].bodyPart=[]。
+                            const isExamination = (item.mainCode || '').toUpperCase() === 'E';
+                            const order = {
+                                ordMedItemDesc: item.medDesc,
+                                medID: item.medID,
+                                ordPrice: def.ordPrice || 0,
+                                ordQty: (item.mainCode === 'O') ? (item.qty || 1) : (def.ordQty || 1),
+                                ordDose: def.ordDose || 1,
+                                ordDate: date,
+                                ordTime: time,
+                                ordStartDate: date,
+                                ordStartTime: time,
+                                ordSpecID: def.ordSpecID || '',
+                                ordEMFlag: def.ordEMFlag || '',
+                                ordPriorityID: def.ordPriorityID || '10',
+                                ordAdmReasonID: def.ordAdmReasonID || '1',
+                                // 保持原有规则:每个选中项目按其顺序分配 tabIndex。
+                                tabIndex: index + 1,
+                                ordRecLocID: def.ordRecLocID || locID || '',
+                                ordQtyUomID: def.ordQtyUomID || '',
+                                ordSeqNo: index,
+                                // 03050098 接口返参 appOrderID,传入 03050001 的 orders 中
+                                appOrderID: item.appOrderID || '',
+                                // 内部字段:保存时区分检验/检查,提交给后端前会被剔除
+                                _mainCode: item.mainCode || '',
+                                extvalue: [
+                                    {
+                                        code: 'Diag',
+                                        value: (this.diagData && this.diagData.id) || '',
+                                        refDescripts: (this.diagData && this.diagData.diagCode) || ''
+                                    },
+                                    { code: 'RegDate', value: '', refDescripts: '预约执行日期' }
+                                ]
+                            };
+
+                            if (isExamination) {
+                                // 后端允许最内层 bodyPart 为空,但该字段不能缺失。
+                                order.ordbodyPart = [null];
+                            }
+                            return order;
+                        });
+
+                        this.submitOrders(orders, admID, locID, userID, hospID);
+                    } else {
+                        this.setData({ saving: false });
+                        Dialog.alert({
+                            title: '提示',
+                            message: res.errorMessage || '获取医嘱默认数据失败',
+                            context: this
+                        });
+                    }
+                },
+                fail: (err) => {
+                    this.setData({ saving: false });
+                    Dialog.alert({
+                        title: '提示',
+                        message: (err && err.errorMessage) || '获取医嘱默认数据失败',
+                        context: this
+                    });
+                }
+            });
+        },
+
+   2.调用 03050001 提交 orders
+        submitOrders(orders, admID, locID, userID, hospID) {
+            // 依据每个 order 所属类型(_mainCode)区分检验(L)与检查(E):
+            // - 检查(E):按 tabIndex 分组生成 appMessage.bodyPart 数组,每项 { bodyPart:[...], tabIndex }
+            //   内层 bodyPart 数组由该 tab 下各 order 的 ordbodyPart 元素映射而来,
+            //   null(未选部位) 映射为空数组 [],非空则取部位本身
+            // - 检验(L):不传 bodyPart,不参与分组
+            const bodyPartGroup = {};
+            orders.forEach((order) => {
+                // L=检验,E=检查
+                const isInspection = (order._mainCode || '').toUpperCase() === 'E';
+                if (!isInspection) {
+                    return;
+                }
+                const tabIndex = order.tabIndex;
+                if (!bodyPartGroup[tabIndex]) {
+                    bodyPartGroup[tabIndex] = [];
+                }
+                (order.ordbodyPart || []).forEach((part) => {
+                    bodyPartGroup[tabIndex].push({ bodyPart: part == null ? [] : part });
+                });
+            });
+            const bodyPartArr = Object.keys(bodyPartGroup).map((tabIndex) => ({
+                bodyPart: bodyPartGroup[tabIndex],
+                tabIndex: Number(tabIndex)
+            }));
+
+            // 剔除内部字段 _mainCode,避免提交给后端
+            const cleanOrders = orders.map((order) => {
+                const { _mainCode, ...rest } = order;
+                return rest;
+            });
+
+            const appMessage = {
+                appPurpose: '',
+                HPIllness: '',
+                sign: '',
+                chiefComplaint: '',
+                // 检验项目不参与 bodyPart 分组;全是检验时传空数组。
+                bodyPart: bodyPartArr.length > 0 ? bodyPartArr : [],
+            };
+            $http.post('urlDeault', this, {
+                code: '03050001',
+                data: {
+                    params: [
+                        {
+                            admID: admID || '',
+                            locID: locID || '',
+                            docID: userID || '',
+                            userID: userID || '',
+                            hospID: hospID || '',
+                            medType: 'WM',
+                            orders: cleanOrders,
+                            appMessage: appMessage
+                        }
+                    ]
+                },
+                success: (res) => {
+                    if (res.errorCode == 0) {
+                        // 设置标记,供 prescription.vue 的 handlePageShow 检测并刷新已确认数据
+                        try {
+                            Cache.set('inspectionExamineSaved', true);
+                        } catch (e) {}
+                        Toast({ type: 'success', message: '保存成功', context: this });
+                        setTimeout(() => {
+                            this.setData({ saving: false });
+                            uni.navigateBack({ delta: 1 });
+                        }, 800);
+                    } else {
+                        this.setData({ saving: false });
+                        Dialog.alert({
+                            title: '提示',
+                            message: res.errorMessage || '保存失败',
+                            context: this
+                        });
+                    }
+                },
+                fail: (res) => {
+                    this.setData({ saving: false });
+                    Dialog.alert({
+                        title: '提示',
+                        message: (res && res.errorMessage) || '保存失败',
+                        context: this
+                    });
+                }
+            });
+        }
+    },

+ 236 - 11
src/pages/entryWrite/page/Entry/prescription/addPrescription/customDiagnostics/western/western.vue

@@ -1099,20 +1099,245 @@ export default {
 		},
 
 		/* ============ 保存 ============ */
+		// 仅组装保存数据(参考 docs/文档.md),不走接口保存
 		saveAll() {
-			// 药品:已确认对象
-			Object.keys(this.medObjMap).forEach((k) => {
-				this.addInfo(this.medObjMap[k]);
+			const patientData = Cache.get('patientData');
+			const userData = Cache.get('userData');
+			const { admID } = patientData;
+			const { userID, locID, hospID, groupID } = userData;
+
+			// ============ 1. 西药(03100050) ============
+			const westOrders = [];
+			Object.keys(this.medObjMap).forEach((k, index) => {
+				const item = this.medObjMap[k] || {};
+				westOrders.push({
+					ordSeqNo: item.xh || '',                          // 序号
+					ordLinkSeqNo: '',                                 // 关联序号
+					ordDate: '',                                      // 开医嘱日期
+					ordTime: '',                                      // 开医嘱时间
+					ordStartDate: '',                                 // 医嘱开始日期
+					ordStartTime: '',                                 // 医嘱开始时间
+					ordEndDate: '',                                   // 医嘱结束日期
+					ordEndTime: '',                                   // 医嘱结束时间
+					ordPriorityID: item.ordPriorityID || '',          // 医嘱优先级10
+					ordAdmReasonID: this.listId('selectData1', 'picakIndex1', 'id'), // 结算类型
+					medID: item.medID || '',                          // 医嘱项id
+					ordFirstDayNum: '',                               // 首日次数
+					ordUsageID: this.listId('selectData3', 'picakIndex3', 'id'),     // 药品用法
+					ordFreqID: this.listId('selectData2', 'picakIndex2', 'id'),      // 频次
+					ordDose: item.dose || '',                         // 剂量
+					ordDoseUnitID: this.listId('selectData4', 'picakIndex4', 'doseUnitID'), // 剂量单位
+					ordQty: item.qty || item.stepper2 || '',          // 数量
+					ordQtyUomID: this.listId('selectData5', 'picakIndex5', 'unitID'), // 数量单位
+					ordDurQty: item.stepper || '',                    // 疗程数量
+					ordDurationID: this.listId('courseUnitList', 'picakIndex9', 'id') || item.ordDurationID || '', // 疗程单位
+					ordInfuseSpeed: '',                               // 滴速
+					ordSpeedUnitID: '',                               // 滴速单位
+					ordStage: '',                                     // 医嘱阶段
+					ordRecLocID: this.listId('selectData8', 'picakIndex8', 'recLocID'), // 接收科室
+					ordOPer: '',                                      // 手术id
+					ordEMFlag: '',                                    // 加急标志
+					ordPriorityExtID: '',                             // 二级医嘱优先级
+					ordNote: item.remark || '',                       // 备注
+					orderResourceCode: '',                            // 医嘱来源代码
+					ordSeqNo: index,
+					ordTypeCode: 'M',                                 // 医嘱类型 M=西药
+					extvalue: [                                       // 诊断信息(03100050 通过 extvalue 传递)
+						{
+							code: 'Diag',
+							refDescripts: (this.diagData && this.diagData.diagCode) || '',
+							value: (this.diagData && this.diagData.id) || ''
+						}
+					]
+				});
+			});
+			// ============ 2. 检验检查治疗:先 03050020 获取默认数据,再按 03050001 组装 ============
+			const selectedKeys = Object.keys(this.selectedMap);
+			if (selectedKeys.length === 0) {
+				// 仅西药:直接提交
+				this.submitMixedOrders(westOrders, [], [], [], admID, locID, userID, hospID, groupID);
+				return;
+			}
+			const queryOrders = selectedKeys.map((k) => {
+				const it = this.selectedMap[k] || {};
+				return { medID: it.medID || '', ordSetID: '' };
+			});
+			$http.post('urlDeault', this, {
+				code: '03050020',
+				data: {
+					params: [{
+						admID: admID || '',
+						orders: queryOrders
+					}]
+				},
+				success: (res) => {
+					if (res.errorCode === '0') {
+						const dataList = res.data || [];
+						// medID -> defaultData 映射,便于按选中顺序构造 orders
+						const defaultMap = {};
+						dataList.forEach((d) => {
+							if (d && d.defaultData && d.defaultData.medID) {
+								defaultMap[d.defaultData.medID] = d.defaultData;
+							}
+						});
+						const { checkOrders, lisOrders, treatOrders } = this.buildCheckTreatOrders(
+							selectedKeys,
+							defaultMap,
+							locID
+						);
+						this.submitMixedOrders(
+							westOrders,
+							checkOrders,
+							lisOrders,
+							treatOrders,
+							admID,
+							locID,
+							userID,
+							hospID,
+							groupID
+						);
+					} else {
+						uni.showToast({ title: res.errorMessage || '获取医嘱默认数据失败', icon: 'none' });
+					}
+				},
+				fail: () => {
+					uni.showToast({ title: '获取医嘱默认数据失败', icon: 'none' });
+				}
+			});
+		},
+		// 按 03050001 组装检验检查治疗订单,并按 ordTypeCode 拆分为 check/lis/treat
+		// mainCode: E=检查 L=检验 O=治疗 -> ordTypeCode: C / L / T
+		buildCheckTreatOrders(keys, defaultMap, locID) {
+			const typeMap = { E: 'C', L: 'L', O: 'T' };
+			const checkOrders = [];
+			const lisOrders = [];
+			const treatOrders = [];
+			keys.forEach((k, index) => {
+				const item = this.selectedMap[k] || {};
+				const mainCode = item.mainCode || item.orderTypeCode || '';
+				const ordTypeCode = typeMap[mainCode] || 'T';
+				const isTreat = mainCode === 'O';
+				const isExamination = mainCode === 'E';
+				// 03050020 返回的该医嘱默认数据
+				const def = defaultMap[item.medID] || {};
+				const order = {
+					ordMedItemDesc: item.medDesc || item.descripts || '',
+					medID: item.medID || '',
+					ordPrice: def.ordPrice || 0,
+					ordQty: isTreat ? (this.treatQtyMap[k] || item.qty || 1) : (def.ordQty || 1),
+					ordDose: def.ordDose || 1,
+					ordDate: '',
+					ordTime: '',
+					ordStartDate: '',
+					ordStartTime: '',
+					ordSpecID: def.ordSpecID || '',
+					ordEMFlag: def.ordEMFlag || '',
+					ordPriorityID: def.ordPriorityID || '10',
+					ordAdmReasonID: def.ordAdmReasonID || '1',
+					tabIndex: index + 1,
+					ordRecLocID: def.ordRecLocID || locID || '',
+					ordQtyUomID: def.ordQtyUomID || '',
+					ordSeqNo: index,
+					appOrderID: item.appOrderID || '',
+					ordTypeCode: ordTypeCode,
+					extvalue: [
+						{
+							code: 'Diag',
+							value: (this.diagData && this.diagData.id) || '',
+							refDescripts: (this.diagData && this.diagData.diagCode) || ''
+						},
+						{ code: 'RegDate', value: '', refDescripts: '预约执行日期' }
+					]
+				};
+				if (isExamination) {
+					// 仅检查(ordTypeCode='C')需要 bodyPart 三层结构:
+					// orders.ordbodyPart=[null] -> bodyPart[].bodyPart[].bodyPart=[]
+					order.ordbodyPart = [null];
+				}
+				if (mainCode === 'E') checkOrders.push(order);
+				else if (mainCode === 'L') lisOrders.push(order);
+				else treatOrders.push(order);
+			});
+			return { checkOrders, lisOrders, treatOrders };
+		},
+		// 组装统一提交参数并调用 12100054 混合医嘱保存,成功后跳转医嘱录入页
+		submitMixedOrders(westOrders, checkOrders, lisOrders, treatOrders, admID, locID, userID, hospID, groupID) {
+			// 参考 inspectionExamine.vue(03050001):检查(E)按 tabIndex 分组生成 appMessage.bodyPart,
+			// 每项 { bodyPart:[...], tabIndex };检验(L)/治疗(T)不参与分组
+			const bodyPartGroup = {};
+			checkOrders.forEach((order) => {
+				const tabIndex = order.tabIndex;
+				if (!bodyPartGroup[tabIndex]) {
+					bodyPartGroup[tabIndex] = [];
+				}
+				(order.ordbodyPart || []).forEach((part) => {
+					bodyPartGroup[tabIndex].push({ bodyPart: part == null ? [] : part });
+				});
 			});
-			// 检验检查/治疗
-			Object.keys(this.selectedMap).forEach((k) => {
-				const it = this.selectedMap[k];
-				const qty = this.treatQtyMap[k] || 1;
-				this.addInfo(this.buildCheckObj(it, qty));
+			const bodyPartArr = Object.keys(bodyPartGroup).map((tabIndex) => ({
+				bodyPart: bodyPartGroup[tabIndex],
+				tabIndex: Number(tabIndex)
+			}));
+			const appMessage = {
+				appPurpose: '',
+				HPIllness: '',
+				sign: '',
+				chiefComplaint: '',
+				// 检查项目参与 bodyPart 分组;无检查项时传空数组
+				bodyPart: bodyPartArr.length > 0 ? bodyPartArr : []
+			};
+			const saveData = {
+				admID: admID || '',          // 就诊id
+				locID: locID || '',          // 开单科室
+				docID: userID || '',         // 开单医生
+				userID: userID || '',        // 用户
+				hospID: hospID || '',        // 登录医院id
+				groupID: groupID || '',      // 登录角色id
+				medType: 'WM',
+				orderReSorce: 'Doctor',
+				westOrders: westOrders,      // 西药 M
+				checkOrders: checkOrders,    // 检查 C
+				lisOrders: lisOrders,        // 检验 L
+				treatOrders: treatOrders,    // 治疗 T
+				appMessage: appMessage       // 检查部位分组(参考 inspectionExamine.vue 03050001 入参)
+			};
+			if (!westOrders.length && !checkOrders.length && !lisOrders.length && !treatOrders.length) {
+				uni.showToast({ title: '请先选择医嘱', icon: 'none' });
+				return;
+			}
+			$http.post('urlDeault', this, {
+				code: '12100054',
+				data: {
+					params: [saveData]
+				},
+				success: (res) => {
+					if (res.errorCode === '0') {
+						uni.showToast({ title: '保存成功', icon: 'success' });
+						// 保存成功后跳转到医嘱录入页
+						setTimeout(() => {
+							uni.navigateTo({
+								url: '/pages/entryWrite/page/Entry/Entry?select=2&addText=添加医嘱'
+							});
+						}, 800);
+					} else {
+						const msg =
+							(res.errorList && res.errorList[0] && res.errorList[0].errorMessage) ||
+							res.errorMessage ||
+							'保存失败';
+						uni.showToast({ title: msg, icon: 'none' });
+					}
+				},
+				fail: () => {
+					uni.showToast({ title: '保存失败,请重试', icon: 'none' });
+				}
 			});
-			console.log('this.added', this.medObjMap, this.selectedMap);
-			// this.setData({ added: true });
-			// uni.showToast({ title: '已保存', icon: 'success' });
+		},
+		// 从下拉数据源按选中索引取 ID(找不到返回 '')
+		listId(listKey, pickKey, idField) {
+			const list = this[listKey] || [];
+			const idx = this[pickKey];
+			if (!list.length || idx < 0 || !list[idx]) return '';
+			return list[idx][idField] || '';
 		},
 		buildCheckObj(it, qty) {
 			return {