feat: 病历模块优化、AI辅助问诊

This commit is contained in:
李琦
2026-08-06 08:14:22 +08:00
parent e1f35db940
commit dec768424c
5 changed files with 2703 additions and 382 deletions

View File

@@ -237,6 +237,17 @@
:ripple="true">
常用方
</u-button>
<view v-if="!isSpecialPrescriptionCartLocked" class="m-l-16">
<u-button
:throttle-time="0"
shape="circle"
size="mini"
:custom-style="{backgroundColor: 'transparent !important',border:'1px solid #6ACDBB',color:'#6ACDBB'}"
plain
@click="handleOpenAiPrescriptionDrawer">
AI辅助出方
</u-button>
</view>
<view v-if="!isSpecialPrescriptionCartLocked" class="m-l-16">
<u-button
:throttle-time="0"
@@ -316,6 +327,17 @@
:ripple="true">
常用方
</u-button>
<view v-if="!isSpecialPrescriptionCartLocked" class="m-l-16">
<u-button
:throttle-time="0"
shape="circle"
size="mini"
:custom-style="{backgroundColor: 'transparent !important',border:'1px solid #6ACDBB',color:'#6ACDBB'}"
plain
@click="handleOpenAiPrescriptionDrawer">
AI辅助出方
</u-button>
</view>
<view v-if="!isSpecialPrescriptionCartLocked" class="m-l-16">
<u-button
:throttle-time="0"
@@ -385,6 +407,17 @@
<text class="fs-28 text-gray m-l-8">{{ activeCategoryLabel }}</text>
</view>
<view class="flex-row m-l-163" style="height: 50rpx;">
<view v-if="!isSpecialPrescriptionCartLocked" class="m-l-16">
<u-button
:throttle-time="0"
shape="circle"
size="mini"
:custom-style="{backgroundColor: 'transparent !important',border:'1px solid #6ACDBB',color:'#6ACDBB'}"
plain
@click="handleOpenAiPrescriptionDrawer">
AI辅助出方
</u-button>
</view>
<view v-if="!isSpecialPrescriptionCartLocked" class="m-l-16">
<u-button
:throttle-time="0"
@@ -544,11 +577,18 @@
<!-- 底部操作栏fixed病历 Tab 必须 v-if 卸载微信 v-show fixed 无效会串到病历 -->
<view v-if="isRxMainVisible" class="bottom flex-jus-sp flex-ali-center safe-area-inset-bottom">
<view
v-if="!isSalespersonTransferMode && !isCommonPrescription && supportsCommonPrescriptionTemplate"
class="flex-row flex-ali-center"
>
<view @click="handleSaveAsCommonPrescription" class="btnText">另存常用方</view>
<view class="flex-row flex-ali-center bottom-left-actions">
<view
v-if="!isSalespersonTransferMode && !isCommonPrescription && supportsCommonPrescriptionTemplate"
@click="handleSaveAsCommonPrescription"
class="btnText"
>另存常用方</view>
<!-- AI入口放底部避免顶部三按钮挤出屏幕 -->
<view
v-if="!isSpecialPrescriptionCartLocked && !isSalespersonTransferMode && !isCommonPrescription"
@click="handleOpenAiPrescriptionDrawer"
class="btnText"
>AI辅助出方</view>
</view>
<u-button
:throttle-time="0"
@@ -584,6 +624,7 @@
:medical-advice="medicalAdvice"
:diagnosis-readonly="isOnlineRevisit"
:active="isMrMainVisible"
:patient-name="(patientInfo && patientInfo.name) || ''"
:patient-sex="(patientInfo && patientInfo.sex) || 0"
:patient-age="(patientInfo && patientInfo.age) || 0"
@update:diagnosis="onMrDiagnosisUpdate"
@@ -702,6 +743,23 @@
@close="closePreSendPriceAdjust"
@confirm="onPreSendPriceAdjustConfirm"
/>
<!-- AI 给处方确认主诉 生成 预览对照 确认导入 -->
<AiPrescriptionDrawer
v-model="showAiPrescriptionDrawer"
:register-id="registerId"
:store-id="currentStoreId"
:prescription-type="activeCategory"
:patient-name="(patientInfo && patientInfo.name) || ''"
:patient-sex="(patientInfo && patientInfo.sex) || 0"
:patient-age="(patientInfo && patientInfo.age) || 0"
:chief-complaint="aiRxChiefComplaint"
:medical-record="aiRxMedicalRecord"
@overlay-change="onAiPrescriptionOverlayChange"
@import="handleImportAiPrescription"
@search-drug="handleAiSearchUnmatchedDrug"
@sync-medical-record="handleSyncAiMedicalRecord"
/>
</block>
</view>
</template>
@@ -737,6 +795,7 @@ import { PrescriptionValidator } from './utils/prescriptionValidator.js';
import DiagnosisModal from './components/modals/DiagnosisModal.vue';
import DoctorOrderModal from './components/modals/DoctorOrderModal.vue';
import MedicalRecordPanel from './components/MedicalRecordPanel.vue';
import AiPrescriptionDrawer from './components/AiPrescriptionDrawer.vue';
import { saveMedicalRecordApi } from '@/api/reception.js';
import CommonPrescriptionModal from './components/modals/CommonPrescriptionModal.vue';
import WesternMedicineModal from './components/modals/WesternMedicineModal.vue';
@@ -781,6 +840,7 @@ const PRESCRIPTION_OVERLAY_KEYS = [
'showTransferPatientDrawer',
'showWarehouseSelectDrawer',
'showMedicalRecordOverlay',
'showAiPrescriptionDrawer',
];
export default {
@@ -789,6 +849,7 @@ export default {
DiagnosisModal,
DoctorOrderModal,
MedicalRecordPanel,
AiPrescriptionDrawer,
CommonPrescriptionModal,
WesternMedicineModal,
ChineseMedicineModal,
@@ -860,6 +921,11 @@ export default {
showDiagnosisModal: false,
showDoctorOrderModal: false,
showCommonPrescriptionModal: false,
showAiPrescriptionDrawer: false,
/** 打开 AI 出方时带入的主诉(可在抽屉内再编辑) */
aiRxChiefComplaint: '',
/** 打开 AI 出方时带入的完整病历快照 */
aiRxMedicalRecord: {},
showWesternMedicineModal: false,
westernModalInitialKeyword: '',
showChineseMedicineModal: false,
@@ -1113,15 +1179,16 @@ export default {
isSpecialPrescriptionCartLocked() {
return isSpecialPrescriptionCartLocked(this);
},
/** 底部发送按钮宽度(左侧「另存常用方」时需留出空间 */
/** 底部发送按钮宽度(左侧「另存常用方 / AI辅助出方」时收窄 */
bottomSendBtnWidth() {
if (this.isSalespersonTransferMode || this.isCommonPrescription) {
return '686rpx';
}
// 左侧至少有 AI辅助出方有常用方模板时再多一个入口
if (this.supportsCommonPrescriptionTemplate) {
return '400rpx';
return '320rpx';
}
return '686rpx';
return '480rpx';
},
navbarTitle() {
if (this.isSalespersonTransferMode) {
@@ -1975,6 +2042,175 @@ export default {
this.saveToLocalStorage();
},
handleOpenCommonPrescriptionModal() { this.showCommonPrescriptionModal = true; },
/**
* 打开 AI 给处方抽屉:带入完整病历(含未保存),先确认再生成
*/
handleOpenAiPrescriptionDrawer() {
if (guardSpecialPrescriptionCartEdit(this)) return;
if (!this.registerId) {
this.$toast('挂号无效');
return;
}
if (!this.activeCategory) {
this.$toast('请先选择处方类型');
return;
}
const panel = this.$refs.medicalRecordPanel;
const payload = panel && panel.getPayload ? panel.getPayload() : {};
this.aiRxChiefComplaint = String((payload && payload.chief_complaint) || '');
this.aiRxMedicalRecord = Object.assign({}, payload || {});
this.showAiPrescriptionDrawer = true;
},
onAiPrescriptionOverlayChange(open) {
this.showAiPrescriptionDrawer = !!open;
},
/**
* AI 出方预览改病历字段时回写病历面板
*/
handleSyncAiMedicalRecord(partial) {
const panel = this.$refs.medicalRecordPanel;
if (panel && panel.patchFields) {
panel.patchFields(partial || {});
}
// 本地快照也更新,避免再次打开仍是旧值
this.aiRxMedicalRecord = Object.assign({}, this.aiRxMedicalRecord || {}, partial || {});
if (partial && partial.chief_complaint != null) {
this.aiRxChiefComplaint = String(partial.chief_complaint || '');
}
},
/**
* 未对照药名:打开对应选药弹窗并带入关键词
*/
handleAiSearchUnmatchedDrug(keyword) {
const key = String(keyword || '');
if (this.activeCategory === 1) {
this.chineseModalInitialKeyword = key;
this.showChineseMedicineModal = true;
return;
}
if (this.activeCategory === 2) {
this.westernModalInitialKeyword = key;
this.showWesternMedicineModal = true;
return;
}
this.showSimpleProductModal = true;
},
/**
* 将 AI 对照结果覆盖导入当前处方(非追加)
* 中药同步剂数/每日次数usage 映射先煎后下 way_id
* @param {Object} payload { rows, dosage, day_dosage }
*/
handleImportAiPrescription(payload) {
if (guardSpecialPrescriptionCartEdit(this)) return;
const list = Array.isArray(payload && payload.rows) ? payload.rows : (Array.isArray(payload) ? payload : []);
if (!list.length) {
this.$toast('没有可导入的药品');
return;
}
const wayList = (this.drugUseList && this.drugUseList.drug_use_way) || [];
const resolveWayId = (usage) => {
const u = String(usage || '').trim();
if (!u) return 0;
const found = wayList.find((w) => String(w.name || '') === u || String(w.name || '').indexOf(u) >= 0);
return found ? Number(found.id || 0) : 0;
};
const doOverwrite = () => {
clearAppliedSpecialPrescription(this);
const next = [];
for (let i = 0; i < list.length; i++) {
const row = list[i] || {};
const c = row.candidate || {};
const drugId = Number(c.drug_id || 0);
if (!drugId) continue;
if (next.find((item) => Number(item.id) === drugId || Number(item.drug_id) === drugId)) {
continue;
}
const doseNum = parseFloat(row.dose);
const qty = !isNaN(doseNum) && doseNum > 0 ? doseNum : (c.number || 1);
if (this.activeCategory === 1) {
const wayId = resolveWayId(row.usage || '');
const newDrug = {
index_id: c.id || drugId,
id: drugId,
drug_id: drugId,
drug_name: c.drug_name || row.ai_name,
name: c.drug_name || row.ai_name,
number: qty,
price: parseFloat(c.price) || 0,
specification: c.specification || '',
image: c.image || '',
unit: c.unit || { id: 0, name: row.unit || 'g' },
way_id: wayId,
use_ways: wayList.find((item) => item.id === wayId),
};
next.push(this.prepareDrugForCart(newDrug));
continue;
}
if (this.activeCategory === 2) {
const d = c.drug || {};
const newDrug = {
index_id: c.id || drugId,
id: drugId,
drug_id: drugId,
drug_name: c.drug_name || d.drug_name || row.ai_name,
number: qty,
select_number: qty,
price: parseFloat(c.price) || 0,
specification: c.specification || d.specification || '',
image: c.image || d.image || '',
time_id: c.time_id || d.time_id || 0,
type_id: c.type_id || d.type_id || 0,
frequency_id: c.frequency_id || d.frequency_id || 0,
unit_id: c.unit_id || d.unit_id || 0,
use_num: this.drugUseList.drug_time && this.drugUseList.drug_time.find((item) => item.id === (c.time_id || d.time_id || 0)),
use_type: this.drugUseList.drug_use_type && this.drugUseList.drug_use_type.find((item) => item.id === (c.type_id || d.type_id || 0)),
use_frequency: this.drugUseList.drug_use_frequency && this.drugUseList.drug_use_frequency.find((item) => item.id === (c.frequency_id || d.frequency_id || 0)),
unit: this.drugUseList.drug_unit && this.drugUseList.drug_unit.find((item) => item.id === (c.unit_id || d.unit_id || 0)),
};
next.push(this.prepareDrugForCart(newDrug));
continue;
}
const newProduct = {
index_id: c.id || drugId,
id: drugId,
drug_id: drugId,
drug_name: c.drug_name || row.ai_name,
number: 1,
select_number: 1,
price: parseFloat(c.price) || 0,
image: c.image || '',
type: this.activeCategory,
};
next.push(this.prepareDrugForCart(newProduct));
}
if (!next.length) {
this.$toast('没有可导入的药品');
return;
}
this.currentDrugs = next;
if (this.activeCategory === 1) {
if (payload && Number(payload.dosage) > 0) {
this.chineseConfig.dosage = Number(payload.dosage);
}
if (payload && Number(payload.day_dosage) > 0) {
this.chineseConfig.dayDosage = Number(payload.day_dosage);
}
}
this.saveToLocalStorage();
this.$toast(`已覆盖导入 ${next.length} 个药品`);
};
if (this.currentDrugs && this.currentDrugs.length > 0) {
uni.showModal({
title: '覆盖当前处方',
content: '导入将清空并覆盖现有处方药品,是否继续?',
success: ({ confirm }) => {
if (confirm) doOverwrite();
},
});
return;
}
doOverwrite();
},
async handleApplySpecialPrescription() {
if (!this.registerId || this.applyingSpecialPrescription) return;
this.applyingSpecialPrescription = true;