feat: 病历模块优化

This commit is contained in:
李琦
2026-08-05 16:22:34 +08:00
parent d1b7e75a43
commit f9e871000a
5 changed files with 650 additions and 162 deletions

View File

@@ -3,15 +3,37 @@
<view class="safe-area-inset-bottom page-bg page-flex">
<!-- #ifdef MP-WEIXIN -->
<page-container
:if="pageBackGuardShow"
:show="pageBackGuardShow"
:overlay="false"
@leave="onPageBackGuardLeave"
/>
<!-- #endif -->
<!-- 导航栏 -->
<u-navbar class="navbar rx-flex-fixed" :is-back="true" :title="navbarTitle" :custom-back="boundCustomBack" title-color="#000" background="{ background: '#fff' }">
<!-- 导航栏background data 对象避免模板字面量在小程序渲染层报 not iterable -->
<u-navbar
class="navbar rx-flex-fixed"
:is-back="true"
:title="navbarTitle || '开方'"
:custom-back="boundCustomBack"
title-color="#000"
:background="navbarBackground"
>
</u-navbar>
<!-- 进入开方页先校验门店开方种类权限 -->
<view v-if="pageLoading" class="rx-permission-loading flex-col flex-ali-center flex-jus-center">
<u-loading mode="circle" size="48" color="#6ACDBB"></u-loading>
<text class="rx-permission-loading__text m-t-24">正在校验开方权限...</text>
</view>
<view
v-else-if="showRxDenied"
class="rx-permission-loading flex-col flex-ali-center flex-jus-center"
>
<text class="rx-permission-loading__text">{{ prescriptionTypeDenyMessage || '当前门店未开通开方权限' }}</text>
</view>
<!-- block 替代 template微信小程序对 template+v-else 大块切换易触发渲染层 not iterable -->
<block v-else>
<!-- 外层处方 | 病历VIP -->
<view v-if="canUseMedicalRecord" class="outer-tabs rx-flex-fixed flex-row">
<view
@@ -32,7 +54,7 @@
<!-- 以下处方区与 HEAD 一致直接挂在 page-flex 禁止再包一层 flex 会压矮 swiper -->
<!-- 处方类型 Tab与用户端订单列表一致u-tabs + 下方 swiper 双向同步 -->
<view v-show="isRxMainVisible" v-if="!isSalespersonTransferMode" class="tabs-wrap shadow-sm rx-flex-fixed">
<view v-if="isRxMainVisible && !isSalespersonTransferMode && prescriptionTabsList.length" class="tabs-wrap shadow-sm rx-flex-fixed">
<u-tabs
:list="prescriptionTabsList"
:is-scroll="true"
@@ -44,13 +66,12 @@
></u-tabs>
</view>
<view v-show="isRxMainVisible" v-if="!isSalespersonTransferMode && registerModeHint" class="register-mode-hint mx-32 m-t-12 rx-flex-fixed">
<view v-if="isRxMainVisible && !isSalespersonTransferMode && registerModeHint" class="register-mode-hint mx-32 m-t-12 rx-flex-fixed">
<text class="register-mode-hint__text">{{ registerModeHint }}</text>
</view>
<view
v-show="isRxMainVisible"
v-if="!isSalespersonTransferMode && hasSpecialPrescriptionRecord"
v-if="isRxMainVisible && !isSalespersonTransferMode && hasSpecialPrescriptionRecord"
class="special-rx-card mx-32 m-t-12 rx-flex-fixed"
>
<view class="special-rx-card__inner flex-row flex-ali-center">
@@ -101,17 +122,17 @@
</view>
</view>
<view v-show="isRxMainVisible" v-if="!isSalespersonTransferMode && allowInsuranceCategory" class="mx-32 m-t-12 flex flex-ali-center rx-flex-fixed">
<view v-if="isRxMainVisible && !isSalespersonTransferMode && allowInsuranceCategory" class="mx-32 m-t-12 flex flex-ali-center rx-flex-fixed">
<u-button size="mini" :type="feeCategory === 1 ? 'success' : 'default'" @click="feeCategory = 1">自费</u-button>
<u-button size="mini" class="m-l-16" :type="feeCategory === 2 ? 'success' : 'default'" @click="feeCategory = 2">医保</u-button>
</view>
<view v-show="isRxMainVisible" v-if="!isSalespersonTransferMode && activeCategory === 1 && hasSalespersonTransfer" class="mx-32 m-t-12 rx-flex-fixed">
<view v-if="isRxMainVisible && !isSalespersonTransferMode && activeCategory === 1 && hasSalespersonTransfer" class="mx-32 m-t-12 rx-flex-fixed">
<u-button size="mini" type="primary" plain @click="openSalespersonTransfer">查看传方</u-button>
</view>
<!-- 诊断swiper 外固定与改 VIP 前一致线下可手输也可点选在线复诊必须点选 -->
<view v-show="isRxMainVisible" v-if="!isSalespersonTransferMode" class="top white p-32 flex-col m-t-16 radius-12 mx-32 rx-flex-fixed">
<!-- 诊断病历 Tab 必须卸载微信 v-show 藏不住 fixed/flex 固定块 -->
<view v-if="isRxMainVisible && !isSalespersonTransferMode" class="top white p-32 flex-col m-t-16 radius-12 mx-32 rx-flex-fixed">
<view class="top_title flex-row flex-ali-center">
<image :src="require('@/static/image/js.png')" class="size-32"></image>
<d-text text="临床诊断" className="fs-32 m-l-16 font-bold" color="#333"></d-text>
@@ -144,9 +165,9 @@
</view>
</view>
<!-- 滚动内容swiper 必须是 page-flex 的直接子项flex:1 + height:0 才能吃满剩余高度 -->
<!-- 滚动内容病历 Tab v-if 卸载避免微信 v-show 残留 -->
<swiper
v-show="isRxMainVisible"
v-if="isRxMainVisible"
class="rx-order-swiper"
:current="swiperCurrent"
:disable-touch="isSalespersonTransferMode || swiperCategories.length < 2"
@@ -319,10 +340,10 @@
>先煎/后下请在选择中药中设置</text>
</view>
<view class="flex-row">
<template v-if="!isSpecialPrescriptionCartLocked">
<block v-if="!isSpecialPrescriptionCartLocked">
<text class="fs-26 color-primary m-r-32" @click="handleOpenChineseMedicineModal">修改剂量</text>
<text class="fs-26 color-danger" @click="handleRemoveAllDrugs">清空</text>
</template>
</block>
<text
v-else
class="fs-26 color-danger"
@@ -453,7 +474,7 @@
<text class="fs-28 color-title font-bold">本店已关闭推广员查看费用</text>
<text class="fs-24 color-sub m-t-8">提交后由接诊医生确认价格</text>
</view>
<template v-else>
<block v-else>
<view v-if="!isSalespersonTransferMode" class="flex-row flex-jus-sp flex-ali-center m-b-24">
<text class="fs-28 color-sub">诊疗费</text>
<view class="flex-row flex-ali-center">
@@ -507,7 +528,7 @@
<text class="fs-30 font-bold color-title">合计</text>
<text class="fs-48 font-bold color-price">{{totalPrice}}</text>
</view>
</template>
</block>
</view>
<!-- 提示信息传方模式不展示 -->
@@ -521,8 +542,8 @@
</swiper-item>
</swiper>
<!-- 底部操作栏fixed不占 flex须与 swiper 同为 page-flex 直接子项 -->
<view v-show="isRxMainVisible" class="bottom flex-jus-sp flex-ali-center safe-area-inset-bottom">
<!-- 底部操作栏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"
@@ -562,12 +583,14 @@
:diagnosis="diagnosisText"
:medical-advice="medicalAdvice"
:diagnosis-readonly="isOnlineRevisit"
:active="isMrMainVisible"
@update:diagnosis="onMrDiagnosisUpdate"
@update:medicalAdvice="onMrAdviceUpdate"
@open-diagnosis="handleOpenDiagnosisModal"
@open-doctor-order="handleOpenDoctorOrderModal"
@vip-ok="onMedicalRecordVipOk"
@vip-denied="onMedicalRecordVipDenied"
@overlay-change="onMedicalRecordOverlayChange"
/>
</view>
</view>
@@ -677,6 +700,7 @@
@close="closePreSendPriceAdjust"
@confirm="onPreSendPriceAdjustConfirm"
/>
</block>
</view>
</template>
@@ -754,6 +778,7 @@ const PRESCRIPTION_OVERLAY_KEYS = [
'showSaveCommonPrescriptionModal',
'showTransferPatientDrawer',
'showWarehouseSelectDrawer',
'showMedicalRecordOverlay',
];
export default {
@@ -793,6 +818,13 @@ export default {
],
/** 后端下发的默认处方类型;无本地草稿/锁定时使用 */
prescriptionTypeDefault: 2,
/** 进入开方页校验种类权限中 */
pageLoading: true,
/** 门店是否有可开方种类(传方模式跳过) */
prescriptionTypeAllowed: true,
prescriptionTypeDenyMessage: '',
/** u-navbar background勿在模板写对象字面量 */
navbarBackground: { background: '#fff' },
/** swiper / u-tabs 当前下标;默认与 activeCategory=2西药列表第 2 项)对齐,避免首屏 Tab/内容错位 */
swiperCurrent: 1,
/** 防止 tabs 与 swiper 互相触发形成回路(同订单列表) */
@@ -921,6 +953,8 @@ export default {
transferDrawerSubmitting: false,
/** 在线复诊选仓抽屉 */
showWarehouseSelectDrawer: false,
/** 病历面板引用/词条抽屉(子组件同步,供返回拦截) */
showMedicalRecordOverlay: false,
warehouseSelectRows: [],
/** resolvePharmacyWarehouseMap 的 Promise resolvefalse=取消null=无需选map=已选 */
_warehouseSelectResolve: null,
@@ -950,14 +984,20 @@ export default {
isMrMainVisible() {
return this.canUseMedicalRecord && this.mainTab === 'mr';
},
/** 无开方权限且非传方:展示拒绝态 */
showRxDenied() {
return !this.prescriptionTypeAllowed && !this.isSalespersonTransferMode;
},
/** u-tabs 需要 { name };有 icon_text 时拼到 name 后便于看见角标;始终返回数组避免 list 非数组警告 */
prescriptionTabsList() {
const list = this.visiblePrescriptionCategories || [];
const list = Array.isArray(this.visiblePrescriptionCategories)
? this.visiblePrescriptionCategories
: [];
return list.map((c) => {
let name = c.label || '';
if (c.icon_text) name = `${name} ${c.icon_text}`;
return { name, value: c.value, icon: c.icon || '' };
}) || [];
let name = (c && c.label) || '';
if (c && c.icon_text) name = `${name} ${c.icon_text}`;
return { name, value: c ? c.value : 0, icon: (c && c.icon) || '' };
});
},
/**
* 当前处方类型显示名:简单产品列表等共用区块按 Tab label 展示,
@@ -1222,19 +1262,41 @@ export default {
},
// #endif
async initializePageData() {
this.pageLoading = true;
try {
this.restoreFromLocalStorage();
if (this.isSalespersonTransferMode) {
await this.loadSalespersonTransferConfig();
await this.loadBasicConfigDataForTransfer();
this.prescriptionTypeAllowed = true;
} else {
// 先校验开方种类权限,再加载其余数据
const allowed = await this.loadPrescriptionTypeOptions();
if (!allowed) {
this.$toast(this.prescriptionTypeDenyMessage || '当前门店未开通开方权限');
setTimeout(() => {
uni.navigateBack({ fail: () => {} });
}, 500);
return;
}
await this.loadPatientInfo();
await this.loadRegisterOrderType();
await this.loadBasicConfigData();
await this.loadPrescriptionTypeOptions();
await this.loadRegisterStoreInfo();
// 无 URL/上次切换/草稿时,用后端 default 兜底
// 指令 default 压过本地缓存URL 锁定除外)
this.applyDefaultCategoryFromOptions();
// 当前 Tab 不在权限内时强制切到 default
if (
this.prescriptionCategories.length &&
!this.prescriptionCategories.some((c) => Number(c.value) === Number(this.activeCategory))
) {
const next = Number(this.prescriptionTypeDefault) || Number(this.prescriptionCategories[0].value);
this.activeCategory = next;
if (this.registerId) {
PrescriptionStorage.saveActiveCategory(next, this.registerId);
}
}
this.focusActiveTab();
}
this.loadCurrentCategoryDrugs();
this.syncSwiperCurrentFromCategory();
@@ -1262,6 +1324,11 @@ export default {
} catch (error) {
console.error('初始化页面数据失败:', error);
this.$toast('初始化失败,请重试');
} finally {
// 延后关掉 loading避免小程序在同帧大块挂载时触发 clickCheckTask / not iterable
this.$nextTick(() => {
this.pageLoading = false;
});
}
},
async loadSalespersonTransferConfig() {
@@ -1297,48 +1364,63 @@ export default {
}
},
/**
* 拉取后端处方类型列表与默认选中;失败时保留本地兜底列表
* 拉取后端处方类型列表与默认选中;无绑定时返回 false 供页面阻断
* 注意Laravel jok 业务体在 result需与页面其它接口一样先解包
*/
async loadPrescriptionTypeOptions() {
try {
const params = {};
if (this.registerId) params.register_id = this.registerId;
const storeId = uni.getStorageSync('store_id');
if (storeId) params.store_id = storeId;
const res = await getPrescriptionTypeOptionsApi(params);
// req 已解包为业务 data{ default, list }
const data = res || {};
// 与同页其它 newApi 一致resultLaravel/ dataYii兜底
const data = (res && (res.result || res.data)) || res || {};
const list = Array.isArray(data.list) ? data.list : [];
if (list.length) {
this.prescriptionCategories = list.map((item) => ({
value: Number(item.value),
label: item.label || '',
icon: item.icon || '',
icon_text: item.icon_text || '',
}));
}
const def = Number(data.default);
if (def) this.prescriptionTypeDefault = def;
this.prescriptionCategories = list.map((item) => ({
value: Number(item.value),
label: item.label || '',
icon: item.icon || '',
icon_text: item.icon_text || '',
}));
const def = Number(data.default) || 0;
this.prescriptionTypeDefault = def;
const allowed = data.allowed !== false && list.length > 0;
this.prescriptionTypeAllowed = allowed;
this.prescriptionTypeDenyMessage = allowed
? ''
: (data.message || '当前门店未开通开方权限');
return allowed;
} catch (e) {
console.error('加载处方类型失败:', e);
this.prescriptionCategories = [];
this.prescriptionTypeAllowed = false;
this.prescriptionTypeDenyMessage = '处方类型加载失败,请稍后重试';
return false;
}
},
/**
* 开方默认 Tab 兜底:仅当无 URL 锁定、无上次切换分类、无任何药品草稿时,
* 使用后端下发的 default。优先级URL > 上次切换 > 草稿 > 接口 default
* 开方默认 Tab后端「指令」default 暂高于本地缓存。
* 优先级URL 锁定 > 接口 default指令> 本地上次 Tab / 草稿分类
* 为什么暂时压过缓存:门店绑定的默认种类要以运营配置为准,避免旧缓存挡住新指令
*/
applyDefaultCategoryFromOptions() {
if (this.isSalespersonTransferMode || this._categoryLockedByQuery) return;
if (!this.registerId) return;
if (PrescriptionStorage.getActiveCategory(this.registerId)) return;
if (PrescriptionStorage.hasDraftWithDrugs(this.registerId)) return;
const next = Number(this.prescriptionTypeDefault) || 0;
if (!next || next === this.activeCategory) return;
if (!next) return;
// 指令种类必须在当前可开列表内
const allowed = Array.isArray(this.prescriptionCategories) ? this.prescriptionCategories : [];
if (allowed.length && !allowed.some((c) => Number(c.value) === next)) return;
if (next === this.activeCategory) {
this.focusActiveTab();
return;
}
this.activeCategory = next;
PrescriptionStorage.saveActiveCategory(next, this.registerId);
this.focusActiveTab();
},
/**
* 开方默认 Tab 兜底:仅当无 URL 锁定、无上次切换分类、无任何药品草稿时,
* 按诊所类型设中药(1)/西药(2)。优先级URL > 上次切换 > 草稿 > clinic_type
* @deprecated 已由 applyDefaultCategoryFromOptions 替代,保留兼容
*/
applyDefaultCategoryByClinicType() {
@@ -1744,6 +1826,12 @@ export default {
if (this.registerId) {
PrescriptionStorage.saveRxMrTab(tab, this.registerId);
}
// 切回处方时 swiper 会因 v-if 重新挂载,对齐下标避免错位
if (tab === 'rx') {
this.$nextTick(() => {
this.syncSwiperCurrentFromCategory();
});
}
},
/** 从本地恢复外层 Tab */
restoreMainTab() {
@@ -2983,12 +3071,23 @@ export default {
for (let i = 0; i < PRESCRIPTION_OVERLAY_KEYS.length; i++) {
const k = PRESCRIPTION_OVERLAY_KEYS[i];
if (this[k]) {
// 病历抽屉状态在子组件内,需先关子组件再清标志
if (k === 'showMedicalRecordOverlay') {
const panel = this.$refs.medicalRecordPanel;
if (panel && typeof panel.closeOverlays === 'function') {
panel.closeOverlays();
}
}
this[k] = false;
return true;
}
}
return false;
},
/** 病历面板抽屉开合 → 同步返回拦截标志 */
onMedicalRecordOverlayChange(open) {
this.showMedicalRecordOverlay = !!open;
},
/** 微信小程序:先关闭 page-container 拦截再退栈,避免 navigateBack 被吞 */
safeNavigateBack(delayMs = 0) {
const runNav = () => {
@@ -3042,6 +3141,17 @@ export default {
</script>
<style lang="scss" scoped>
.rx-permission-loading {
flex: 1;
width: 100%;
min-height: 60vh;
padding: 48rpx;
}
.rx-permission-loading__text {
font-size: 28rpx;
color: #909399;
text-align: center;
}
/* 基础与通用样式 */
.page-bg {
background-color: #F4F6F8;
@@ -3196,7 +3306,7 @@ export default {
}
/* 内部滚动区铺满 swiper底部留白避开 fixed 操作栏 */
.rx-order-scroll {
height: 100%;
height: 66vh;
box-sizing: border-box;
padding-bottom: calc(200rpx + env(safe-area-inset-bottom));
}