Files
xk-doctor-wx/subPackages/sub_workbench/prescription_v2/index.vue

3123 lines
124 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<!-- page-flex整页纵向 flexswiper 吃满剩余高度避免死高度裁切 -->
<view class="safe-area-inset-bottom page-bg page-flex">
<!-- #ifdef MP-WEIXIN -->
<page-container
:if="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' }">
</u-navbar>
<!-- 处方类型 Tab与用户端订单列表一致u-tabs + 下方 swiper 双向同步 -->
<view v-if="!isSalespersonTransferMode" class="tabs-wrap shadow-sm rx-flex-fixed">
<u-tabs
:list="prescriptionTabsList"
:is-scroll="true"
:current="swiperCurrent"
active-color="#6ACDBB"
inactive-color="#606266"
:bold="true"
@change="onRxTabChange"
></u-tabs>
</view>
<view v-if="!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-if="!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">
<u-image
v-if="specialPrescriptionRecord.special_prescription && specialPrescriptionRecord.special_prescription.cover_image"
width="96rpx"
height="96rpx"
border-radius="12"
:src="specialPrescriptionRecord.special_prescription.cover_image"
/>
<view class="flex-1 m-l-16">
<view class="fs-28 font-bold text-gray">
{{ (specialPrescriptionRecord.special_prescription && specialPrescriptionRecord.special_prescription.name) || '特色方' }}
</view>
<view class="fs-24 color-sub">
剂量{{ specialPrescriptionRecord.dose_count }}
</view>
<view class="fs-24 color-sub">
规格{{ specialPrescriptionPurchaseLabel }}
</view>
<view class="">
<u-tag
v-if="Number(specialPrescriptionRecord.status) === 0"
text="待导入"
type="warning"
size="mini"
/>
<u-tag
v-else-if="Number(specialPrescriptionRecord.status) === 1"
text="已应用"
type="success"
size="mini"
/>
<u-tag v-else text="已完成" type="info" size="mini" />
</view>
</view>
</view>
<view class="special-rx-card__action m-t-16">
<u-button
size="default"
type="primary"
:loading="applyingSpecialPrescription"
:custom-style="{ backgroundColor: '#6ACDBB', color: '#fff', border: 'none', width: '100%' }"
@click="handleApplySpecialPrescription"
>
导入
</u-button>
</view>
</view>
<view v-if="!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-if="!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>
<!-- 诊断输入区域传方模式不展示 -->
<view v-if="!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>
</view>
<view class="m-t-16 flex flex-wrap">
<view class="m-r-16 m-t-16" v-for="(item, i) in diagnoses" :key="item.id">
<u-tag
shape="circle"
:text="item.name"
bg-color="#E8F6F4"
color="#6ACDBB"
border-color="#E8F6F4"
close-color="#6ACDBB"
:closeable="true"
@close="handleRemoveDiagnosis(i)" />
</view>
</view>
<view class="m-t-24">
<u-input
disabled
placeholder="请选择或输入疾病诊断"
:custom-style="illnessStyle"
@click="handleOpenDiagnosisModal" />
</view>
</view>
<!-- 滚动内容swiper flex:1 吃满剩余高度对齐订单列表 order-swiper不算死高度 -->
<swiper
class="rx-order-swiper"
:current="swiperCurrent"
:disable-touch="isSalespersonTransferMode || swiperCategories.length < 2"
@change="onRxSwiperChange"
>
<swiper-item
v-for="(cat, idx) in swiperCategories"
:key="cat.value"
>
<scroll-view scroll-y class="rx-order-scroll">
<block v-if="swiperCurrent === idx">
<!-- 在线复诊 + 中药证候/治法/中医疾病接口 diseases_idmethod_idsyndrome_id PC 在线问诊处方一致 -->
<view v-if="activeCategory === 1 && isOnlineRevisit" class="white p-32 radius-12 mx-32 m-t-24 flex-col">
<view class="top_title flex-row flex-ali-center m-b-16">
<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>
</view>
<view class="m-t-8">
<d-text text="中医证候" className="fs-26 color-sub"></d-text>
<u-input
disabled
:value="onlineTcmDiseasesLabel"
placeholder="请点击选择中医证候"
:custom-style="illnessStyle"
class="m-t-8"
@click="handleOpenTcmPicker('diseases')" />
</view>
<view class="m-t-24">
<d-text text="中医治法" className="fs-26 color-sub"></d-text>
<u-input
disabled
:value="onlineTcmMethodLabel"
placeholder="请点击选择中医治法"
:custom-style="illnessStyle"
class="m-t-8"
@click="handleOpenTcmPicker('method')" />
</view>
<view class="m-t-24">
<d-text text="中医疾病" className="fs-26 color-sub"></d-text>
<u-input
disabled
:value="onlineTcmSyndromeLabel"
placeholder="请点击选择中医疾病"
:custom-style="illnessStyle"
class="m-t-8"
@click="handleOpenTcmPicker('syndrome')" />
</view>
</view>
<!-- 西药列表 -->
<view class="mx-32 m-t-24" v-if="activeCategory === 2">
<view class="flex-row flex-jus-sp flex-ali-center m-b-24">
<view class="flex-row flex-ali-center">
<view class="rp-icon">Rp</view>
<text class="fs-28 text-gray m-l-8">西药处方</text>
</view>
<view class="flex-row" style="height: 50rpx;">
<u-button
v-if="!isCommonPrescription && supportsCommonPrescriptionTemplate"
:throttle-time="0"
shape="circle"
size="mini"
:custom-style="{backgroundColor: 'transparent !important',border:'1px solid #6ACDBB',color:'#6ACDBB'}"
plain
@click="handleOpenCommonPrescriptionModal"
:ripple="true">
常用方
</u-button>
<view v-if="!isSpecialPrescriptionCartLocked" class="m-l-16">
<u-button
:throttle-time="0"
shape="circle"
size="mini"
:custom-style="{ backgroundColor:'#6ACDBB',color:'#fff', border: 'none' }"
@click="handleOpenWesternMedicineModal">
<u-icon name="plus" size="22" class="m-r-4"></u-icon> 添加药品
</u-button>
</view>
</view>
</view>
<!-- 西药卡片设计 -->
<view class="medicine-card" v-for="(item, index) in currentDrugs" :key="item.index_id">
<view class="flex-row">
<image
v-if="item.image"
:src="item.image"
class="drug-image-modern"
mode="aspectFill"
></image>
<view class="flex-1 m-l-24 flex-col">
<view class="flex-row flex-jus-sp flex-ali-start">
<text class="fs-30 font-bold color-title line-clamp-2 w-70">{{index+1}}. {{item.drug_name}}</text>
<text class="fs-32 font-bold color-price">{{parseFloat(item.price || 0).toFixed(2)}}</text>
</view>
<view class="flex-row flex-jus-sp flex-ali-center m-t-12">
<text class="fs-24 color-sub">规格{{item.specification || '--'}}</text>
<text class="fs-26 color-title font-bold">×{{item.select_number || item.number}}</text>
</view>
<view class="m-t-16 usage-capsule">
<u-icon name="info-circle" size="24" color="#6ACDBB" class="m-r-8"></u-icon>
<text class="fs-24" style="color:#008771">{{formatDrugUsage(item) || '请设置用法用量'}}</text>
</view>
</view>
</view>
<!-- 内敛的卡片操作区 -->
<view class="card-actions flex-row flex-jus-sp flex-ali-center">
<view v-if="!isSpecialPrescriptionCartLocked" class="action-btn-text danger" @click="handleRemoveDrug(index)">
<u-icon name="trash" size="28"></u-icon> 删除
</view>
<view v-if="!isSpecialPrescriptionCartLocked" class="action-btn-ghost primary" @click="handleEditWesternDrug(item, index)">
调整用量
</view>
</view>
</view>
<view class="white p-32 radius-12 m-t-24 flex-col" v-if="currentDrugs.length > 0 && !isSalespersonTransferMode">
<view class="flex-jus-sp flex-row flex-ali-center">
<d-text text="医嘱补充" color="#333" className="font-bold"></d-text>
<d-text @click="handleOpenDoctorOrderModal" text="选择常用医嘱" color="#6ACDBB"></d-text>
</view>
<u-input v-model="medicalAdvice" placeholder="请输入医嘱注意事项..." :custom-style="entrustStyle" />
</view>
<view class="flex-row flex-jus-center m-t-64" v-if="currentDrugs.length === 0 && !isSpecialPrescriptionCartLocked">
<d-empty @click="handleOpenWesternMedicineModal"></d-empty>
</view>
</view>
<!-- 中药列表 -->
<view class="mx-32 m-t-24" v-if="activeCategory === 1">
<view class="flex-row flex-jus-sp flex-ali-center m-b-24">
<view class="flex-row flex-ali-center">
<view class="rp-icon">Rp</view>
<text class="fs-28 text-gray m-l-8">中药处方已选 {{ currentDrugs.length }} </text>
</view>
<view class="flex-row" style="height: 50rpx;">
<u-button
v-if="!isSalespersonTransferMode && !isCommonPrescription && supportsCommonPrescriptionTemplate"
:throttle-time="0"
shape="circle"
size="mini"
:custom-style="{backgroundColor: 'transparent !important',border:'1px solid #6ACDBB',color:'#6ACDBB'}"
plain
@click="handleOpenCommonPrescriptionModal"
:ripple="true">
常用方
</u-button>
<view v-if="!isSpecialPrescriptionCartLocked" class="m-l-16">
<u-button
:throttle-time="0"
:custom-style="{ backgroundColor:'#6ACDBB',color:'#fff', border: 'none' }"
shape="circle"
size="mini"
@click="handleOpenChineseMedicineModal">
<u-icon name="plus" size="22" class="m-r-4"></u-icon> 添加药材
</u-button>
</view>
</view>
</view>
<!-- 中药极简高密度标签云设计 -->
<view class="white radius-12 p-32" v-if="currentDrugs.length > 0">
<view class="flex-row flex-jus-sp flex-ali-center m-b-24">
<view class="flex-col">
<text class="fs-28 font-bold color-title">药材清单{{currentDrugs.length}}</text>
<text
v-if="!isSpecialPrescriptionCartLocked"
class="fs-22 text-gray m-t-4"
>先煎/后下请在选择中药中设置</text>
</view>
<view class="flex-row">
<template 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>
<text
v-else
class="fs-26 color-danger"
@click="handleClearPrescriptionCart"
>清空药方</text>
</view>
</view>
<view class="tcm-tag-cloud">
<view
class="tcm-tag"
v-for="(it, tcmIndex) in currentDrugs"
:key="tcmTagKey(it, tcmIndex)"
:data-idx="tcmIndex"
@longpress.stop="!isSpecialPrescriptionCartLocked && onTcmTagLongPress">
<text class="tcm-name">{{it.drug_name || it.name}}</text>
<text class="tcm-weight">{{String(it.number)}}{{it.unit ? it.unit.name : 'g'}}</text>
<!-- 展示药材级先煎/后下在选择中药弹窗设置 -->
<text v-if="getTcmWayLabel(it)" class="tcm-way">{{ getTcmWayLabel(it) }}</text>
<text
v-if="seeRate == 1 && getChineseItemMargin(it) !== '--'"
class="tcm-margin"
>毛利率{{ getChineseItemMargin(it) }}%</text>
</view>
</view>
</view>
<view class="flex-row flex-jus-center m-t-64" v-else-if="!isSpecialPrescriptionCartLocked">
<d-empty @click="handleOpenChineseMedicineModal"></d-empty>
</view>
</view>
<!-- 简单产品列表保健食品产品服务包非药品医疗器械 -->
<view class="mx-32 m-t-24" v-if="[3, 5, 6, 7].includes(activeCategory)">
<view class="flex-row flex-jus-sp flex-ali-center m-b-24">
<view class="flex-row flex-ali-center">
<view class="rp-icon">Rp</view>
<!-- 与当前 Tab 名一致避免 3/5/6/7 共用模板时写死产品/器械 -->
<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"
:custom-style="{ backgroundColor:'#6ACDBB',color:'#fff', border: 'none' }"
shape="circle"
size="mini"
@click="handleOpenSimpleProductModal">
<u-icon name="plus" size="22" class="m-r-4"></u-icon> 添加商品
</u-button>
</view>
</view>
</view>
<!-- 简单产品卡片设计 -->
<view class="medicine-card" v-for="(item, index) in currentDrugs" :key="item.index_id">
<view class="flex-row">
<image
v-if="item.image"
:src="item.image"
class="drug-image-modern"
mode="aspectFill"
></image>
<view class="flex-1 m-l-24 flex-col">
<view class="flex-row flex-jus-sp flex-ali-start">
<text class="fs-30 font-bold color-title line-clamp-2 w-70">{{index+1}}. {{item.drug_name}}</text>
<text class="fs-32 font-bold color-price">{{parseFloat(item.price || 0).toFixed(2)}}</text>
</view>
<view class="flex-row flex-jus-sp flex-ali-center m-t-12">
<text class="fs-24 color-sub">规格{{item.specification || '--'}}</text>
<!-- 精致步进器 -->
<view class="modern-stepper flex-row flex-ali-center">
<view class="stepper-btn" @click.stop="handleDecrementSimpleProduct(index)">-</view>
<text class="stepper-val fs-26">{{item.select_number || item.number}}{{getProductUnit(activeCategory)}}</text>
<view class="stepper-btn" @click.stop="handleIncrementSimpleProduct(index)">+</view>
</view>
</view>
</view>
</view>
<view class="card-actions flex-row flex-jus-end">
<view v-if="!isSpecialPrescriptionCartLocked" class="action-btn-text danger" @click="handleRemoveDrug(index)">
<u-icon name="trash" size="28"></u-icon> 删除
</view>
</view>
</view>
<view class="white p-32 radius-12 m-t-24 flex-col" v-if="currentDrugs.length > 0 && !isSalespersonTransferMode">
<view class="flex-jus-sp flex-row flex-ali-center">
<d-text text="医嘱补充" color="#333" className="font-bold"></d-text>
<d-text @click="handleOpenDoctorOrderModal" text="选择常用医嘱" color="#6ACDBB"></d-text>
</view>
<u-input v-model="medicalAdvice" placeholder="请输入注意事项..." :custom-style="entrustStyle" />
</view>
<view class="flex-row flex-jus-center m-t-64" v-if="currentDrugs.length === 0">
<d-empty @click="handleOpenSimpleProductModal"></d-empty>
</view>
</view>
<!-- 医嘱输入区域传方模式不展示 -->
<view class="flex-col white p-32 radius-12 mx-32 m-t-24" v-if="activeCategory === 1 && currentDrugs.length > 0 && !isSalespersonTransferMode">
<view class="flex-jus-sp flex-row flex-ali-center">
<d-text text="医嘱补充" color="#333" className="font-bold"></d-text>
<d-text @click="handleOpenDoctorOrderModal" text="选择常用医嘱" color="#6ACDBB" />
</view>
<u-input
v-model="medicalAdvice"
placeholder="请输入服药注意事项..."
:custom-style="entrustStyle"
/>
</view>
<!-- 中药配置区域 -->
<view class="white radius-12 p-32 mx-32 m-t-24" v-if="activeCategory === 1">
<ChineseMedicineConfig
:config="chineseConfig"
:disabled="isSpecialPrescriptionCartLocked"
:processRuleList="processRuleList"
:childProcessRuleList="childProcessRuleList"
:processRuleNoteList="processRuleNoteList"
@update:config="handleUpdateChineseConfig"
/>
</view>
<!-- 费用计算区域 -->
<view class="fee-summary white radius-12 p-32 mx-32 m-t-24 m-b-40">
<view v-if="isTransferFeeHidden" class="transfer-fee-hidden-tip flex-col">
<text class="fs-28 color-title font-bold">本店已关闭推广员查看费用</text>
<text class="fs-24 color-sub m-t-8">提交后由接诊医生确认价格</text>
</view>
<template 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">
<text class="fs-28 m-r-8"></text>
<u-input
v-model="treatmentPrice"
placeholder="0.00"
input-align="right"
:custom-style="priceInputStyle" />
</view>
</view>
<view
v-if="!isSalespersonTransferMode && processingFee > 0"
class="flex-row flex-jus-sp flex-ali-center m-b-24">
<text class="fs-28 color-sub">加工费</text>
<text class="fs-30 color-title font-bold">{{processingFee.toFixed(2)}}</text>
</view>
<view class="flex-row flex-jus-sp flex-ali-center m-b-32">
<view class="flex-col">
<text class="fs-28 color-sub">商品计费</text>
<text
v-if="grossMarginText"
class="fs-24 color-primary m-t-8"
>毛利率 {{ grossMarginText }}%</text>
</view>
<text
v-if="canPreSendPriceAdjust"
class="fs-30 color-title font-bold price-link"
@click="openPreSendOrderPriceAdjust"
>{{totalProductCost.toFixed(2)}}</text>
<text v-else class="fs-30 color-title font-bold">{{totalProductCost.toFixed(2)}}</text>
</view>
<view
v-if="canPreSendPriceAdjust && currentDrugs.length > 0"
class="flex-row flex-jus-sp flex-ali-center m-b-24"
>
<text class="fs-26 color-sub">当前浮动{{ preSendDiscountLabel || '无' }}</text>
<text
v-if="priceDiscount !== 100"
class="fs-26 color-primary"
@click="clearPreSendPriceDiscount"
>清除浮动</text>
</view>
<view
class="total-price-bar flex-row flex-jus-sp flex-ali-center pt-32 border-t-dashed"
v-if="currentDrugs.length > 0">
<text class="fs-30 font-bold color-title">合计</text>
<text class="fs-48 font-bold color-price">{{totalPrice}}</text>
</view>
</template>
</view>
<!-- 提示信息传方模式不展示 -->
<view v-if="!isSalespersonTransferMode" class="ac p-b-40 p-x-32 flex-row flex-jus-center">
<u-icon name="shield-check" color="#B0B6C2" size="28" class="m-r-8"></u-icon>
<text class="fs-24 color-sub">请确认患者已在实体医院就诊并有明确诊断</text>
</view>
</block>
<view v-else class="rx-swiper-placeholder"></view>
</scroll-view>
</swiper-item>
</swiper>
<!-- 底部操作栏 -->
<view 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>
<u-button
:throttle-time="0"
@click="handleSendPrescription"
shape="circle"
:custom-style="{
backgroundColor: isSubmitting ? '#A5DCD2' : '#6ACDBB',
color: '#fff',
height: '86rpx',
width: bottomSendBtnWidth
}"
:disabled="isSubmitting">
{{ isCommonPrescription ? '保存常用方' : (isSalespersonTransferMode ? '提交传方' : '发送处方') }}
</u-button>
</view>
<TraditionalTermPickerModal
v-model="showTcmPicker"
:title="tcmPickerTitle"
:options="tcmPickerList"
:selected-id="tcmPickerCurrentSelectedId"
@confirm="handleTcmPickerConfirm"
/>
<!-- 诊断选择弹窗 -->
<DiagnosisModal
v-model="showDiagnosisModal"
:selectedDiagnosis="diagnosisText"
:salesperson-transfer-mode="isSalespersonTransferMode"
@confirm="handleDiagnosisConfirm"
/>
<!-- 医嘱选择弹窗 -->
<DoctorOrderModal
v-model="showDoctorOrderModal"
:selectedDoctorOrder="medicalAdvice"
@confirm="handleDoctorOrderConfirm"
/>
<!-- 常用方选择弹窗 -->
<CommonPrescriptionModal
v-model="showCommonPrescriptionModal"
:prescriptionType="activeCategory"
@select="handleSelectCommonPrescription"
/>
<!-- 西药选择弹窗 -->
<WesternMedicineModal
v-model="showWesternMedicineModal"
:currentDrugs="currentDrugs"
:initial-search-key="westernModalInitialKeyword"
@select="handleSelectWesternDrug"
/>
<!-- 中药选择弹窗 -->
<ChineseMedicineModal
v-model="showChineseMedicineModal"
:currentDrugs="currentDrugs"
:initial-search-key="chineseModalInitialKeyword"
:register-id="registerId"
:see-rate="seeRate"
:salesperson-transfer-mode="isSalespersonTransferMode"
:show-drug-price="showSalespersonTransferPrice"
:drug-use-way-list="drugUseList.drug_use_way || []"
@select="handleSelectChineseDrug"
/>
<TransferPatientDrawer
v-model="showTransferPatientDrawer"
:submitting="transferDrawerSubmitting"
ref="transferPatientDrawer"
@confirm="handleTransferPatientConfirm"
/>
<!-- 在线复诊卡片选配送仓 -->
<WarehouseSelectDrawer
v-model="showWarehouseSelectDrawer"
:rows="warehouseSelectRows"
@confirm="onWarehouseSelectConfirm"
@cancel="onWarehouseSelectCancel"
/>
<!-- 简单产品选择弹窗 -->
<SimpleProductModal
v-model="showSimpleProductModal"
:productType="activeCategory"
:currentProducts="currentDrugs"
@select="handleSelectSimpleProduct"
/>
<!-- 西药用法用量设置弹窗 -->
<WesternMedicineUsageModal
v-model="showWesternUsageModal"
:drug="editingDrug"
@confirm="handleConfirmWesternUsage"
/>
<!-- 保存常用方弹窗 -->
<u-modal
v-model="showSaveCommonPrescriptionModal"
:show-cancel-button="true"
:mask-close-able="true"
title="保存常用方"
@confirm="handleConfirmSaveCommonPrescription"
@cancel="showSaveCommonPrescriptionModal = false">
<view class="slot-content" style="padding: 20rpx;">
<u-input
v-model="commonPrescriptionName"
placeholder="请输入处方名称"
type="text"
:border="true" />
</view>
</u-modal>
<order-price-percent-adjust-popup
:visible="priceAdjustVisible"
:current-discount="priceDiscount"
:quick-options="priceAdjustQuickOptions"
@close="closePreSendPriceAdjust"
@confirm="onPreSendPriceAdjustConfirm"
/>
</view>
</template>
<script>
// 注意:这部分保持你原有的逻辑代码,不进行任何修改,确保业务完全正常
import {
addWestPrescription,
getDeliveryWarehouseOptionsByDrugs,
checkChineseMedicineConflictApi,
getDiseaseList,
getDrugUseList,
getProductListDoctorReception,
getPatientInfoByPatientId,
getPatientItem,
applySpecialPrescriptionApi,
getProcessRuleList,
getCommonPrescriptionListApi,
saveWestCommonPrescriptionApi,
saveChineseCommonPrescriptionApi,
getCurrentStoreTypeApi,
getPrescriptionTypeOptionsApi,
getPriceAdjustConfigApi,
getSalespersonTransferByRegisterApi,
getPrescriptionInfoApi,
getTraditionalChineseMedicineJson
} from '@/api/reception.js';
import { parseTransferPrescriptionContent } from '@/subPackages/sub_online_reception/utils/transferPrescriptionImport.js';
import { getChatRoomByRegisterApi, sendToUserHttpApi } from '@/api/chat.js';
import { PrescriptionStorage } from './utils/prescriptionStorage.js';
import { PrescriptionCalculator } from './utils/prescriptionCalculator.js';
import { PrescriptionValidator } from './utils/prescriptionValidator.js';
import DiagnosisModal from './components/modals/DiagnosisModal.vue';
import DoctorOrderModal from './components/modals/DoctorOrderModal.vue';
import CommonPrescriptionModal from './components/modals/CommonPrescriptionModal.vue';
import WesternMedicineModal from './components/modals/WesternMedicineModal.vue';
import ChineseMedicineModal from './components/modals/ChineseMedicineModal.vue';
import SimpleProductModal from './components/modals/SimpleProductModal.vue';
import WesternMedicineUsageModal from './components/modals/WesternMedicineUsageModal.vue';
import ChineseMedicineConfig from './components/ChineseMedicineConfig.vue';
import TraditionalTermPickerModal from './components/modals/TraditionalTermPickerModal.vue';
import TransferPatientDrawer from './components/modals/TransferPatientDrawer.vue';
import WarehouseSelectDrawer from './components/modals/WarehouseSelectDrawer.vue';
import OrderPricePercentAdjustPopup from '@/subPackages/sub_business_shared/components/OrderPricePercentAdjustPopup.vue';
import { applyRatioToDrugs, formatPriceDiscountLabel, normalizeQuickOptions } from '@/subPackages/sub_business_shared/common/pricePercentAdjust.js';
import {
createClinicSalespersonTransferPrescription,
getClinicSalespersonSeePriceConfig,
getClinicSalespersonProcessRuleList,
} from '@/api/clinicSalesperson.js';
import {
pickSpecialPrescriptionRecord,
applySpecialPrescriptionPayload,
resolveSpecialPrescriptionPurchaseLabel,
clearAppliedSpecialPrescription,
isSpecialPrescriptionCartLocked,
guardSpecialPrescriptionCartEdit,
} from './utils/specialPrescription.js';
const PENDING_RX_FROM_CHAT = 'xk_pending_rx_from_chat';
const SALESPERSON_TRANSFER_STORAGE_KEY = 'salesperson_transfer';
const SALESPERSON_HOME_URL = '/subPackages/sub_clinic_salesperson/home/index';
/** 与 closeTopOverlayIfAny 一致:任意为 true 时启用微信小程序 page-container 返回拦截 */
const PRESCRIPTION_OVERLAY_KEYS = [
'showWesternUsageModal',
'showTcmPicker',
'showWesternMedicineModal',
'showChineseMedicineModal',
'showSimpleProductModal',
'showCommonPrescriptionModal',
'showDiagnosisModal',
'showDoctorOrderModal',
'showSaveCommonPrescriptionModal',
'showTransferPatientDrawer',
'showWarehouseSelectDrawer',
];
export default {
name: 'PrescriptionV2',
components: {
DiagnosisModal,
DoctorOrderModal,
CommonPrescriptionModal,
WesternMedicineModal,
ChineseMedicineModal,
SimpleProductModal,
WesternMedicineUsageModal,
ChineseMedicineConfig,
TraditionalTermPickerModal,
TransferPatientDrawer,
WarehouseSelectDrawer,
OrderPricePercentAdjustPopup,
},
data() {
return {
registerId: '',
patientId: '',
pendingReusePrescriptionId: '',
patientInfo: null,
prescriptionCategories: [
{ label: '中药', value: 1, icon: '', icon_text: '' },
{ label: '西(中成)药', value: 2, icon: '', icon_text: '' },
{ label: '保健食品', value: 3, icon: '', icon_text: '' },
{ label: '产品服务包', value: 5, icon: '', icon_text: '' },
{ label: '非药品', value: 6, icon: '', icon_text: '' },
{ label: '医疗器械', value: 7, icon: '', icon_text: '' }
],
/** 后端下发的默认处方类型;无本地草稿/锁定时使用 */
prescriptionTypeDefault: 2,
/** swiper / u-tabs 当前下标;默认与 activeCategory=2西药列表第 2 项)对齐,避免首屏 Tab/内容错位 */
swiperCurrent: 1,
/** 防止 tabs 与 swiper 互相触发形成回路(同订单列表) */
tabChanging: false,
activeCategory: 2,
currentDrugs: [],
diagnoses: [],
diagnosisText: '',
medicalAdvice: '',
treatmentPrice: '',
chineseConfig: {
ruleType: 1,
packageMethodId: null,
processRuleId: null,
childProcessRuleId: null,
processRuleNoteId: null,
dosage: 7,
dayDosage: 2
},
processRuleList: [],
childProcessRuleList: [],
processRuleNoteList: [],
drugUseList: {
drug_use_num: [],
drug_use_frequency: [],
drug_use_type: [],
drug_unit: [],
drug_time: [],
drug_use_way: []
},
showDiagnosisModal: false,
showDoctorOrderModal: false,
showCommonPrescriptionModal: false,
showWesternMedicineModal: false,
westernModalInitialKeyword: '',
showChineseMedicineModal: false,
chineseModalInitialKeyword: '',
showSimpleProductModal: false,
showWesternUsageModal: false,
showSaveCommonPrescriptionModal: false,
editingDrug: null,
editingDrugIndex: -1,
commonPrescriptionName: '',
isCommonPrescription: false,
_presetActiveCategory: null,
/** URL/传方指定了分类时,禁止被诊所类型默认覆盖 */
_categoryLockedByQuery: false,
isSubmitting: false,
identity: uni.getStorageSync('role'),
registerStoreInfo: null,
feeCategory: 1,
salespersonTransferPrescriptionId: 0,
hasSalespersonTransfer: false,
/** 是否可查看毛利率 0/1xk-api see_rate */
seeRate: 0,
enableOrderPricePercentAdjust: 0,
priceAdjustVisible: false,
priceDiscount: 100,
priceAdjustQuickOptions: [],
priceAdjustScopeMode: 'sale_only',
sendMode: 0,
selectedStoreId: null,
registerOrderType: null,
specialPrescriptionRecord: null,
applyingSpecialPrescription: false,
pendingImportSpecial: false,
/** 医生已导入的特色方 ID仅 >0 时提交走特色方计价 */
appliedSpecialPrescriptionId: 0,
appliedSpecialPrescriptionFreeShipping: false,
/** 特色方 apply 返回的套餐/贴数总价,用于底部合计 */
appliedSpecialPrescriptionExpectedTotal: null,
/** 在线复诊中药术语表HTTP 拉取,内存缓存) */
traditionalTcmData: null,
traditionalTcmLoadPromise: null,
showTcmPicker: false,
tcmPickerTitle: '',
tcmPickerField: '',
tcmPickerList: [],
/** 与 PC prescriptionStore 提交字段一致diseases_id←证候(diseases)method_id←治法syndrome_id←中医疾病(syndrome) */
onlineTcmDiseasesId: null,
onlineTcmMethodId: null,
onlineTcmSyndromeId: null,
// UI Styles
tabsBtnStyle: {
padding: '8rpx 32rpx',
height: '60rpx',
fontSize: '28rpx',
borderRadius: '30rpx'
},
illnessStyle: {
fontSize: '28rpx',
backgroundColor: '#F7F8FA',
borderRadius: '16rpx',
height: '76rpx',
padding: '0 32rpx'
},
entrustStyle: {
fontSize: '28rpx',
backgroundColor: '#F7F8FA',
height: '80rpx',
padding: '0 24rpx',
borderRadius: '12rpx',
marginTop: '20rpx'
},
priceInputStyle: {
fontSize: '32rpx',
fontWeight: 'bold',
color: '#333',
width: '160rpx',
height: '60rpx',
backgroundColor: '#F7F8FA',
borderRadius: '8rpx',
padding: '0 16rpx'
},
/** 微信小程序 page-container有抽屉/弹层时为 true拦截侧滑等返回 */
pageBackGuardShow: false,
/** 程序化隐藏 page-container 时抑制 @leave避免误触发页面退出确认 */
_suppressPageBackGuardLeave: false,
/** 推广员传方模式 */
isSalespersonTransferMode: false,
/** 推广员是否可查看售价 0/1门店 salesperson see_price */
salespersonSeePrice: 0,
showTransferPatientDrawer: false,
transferDrawerSubmitting: false,
/** 在线复诊选仓抽屉 */
showWarehouseSelectDrawer: false,
warehouseSelectRows: [],
/** resolvePharmacyWarehouseMap 的 Promise resolvefalse=取消null=无需选map=已选 */
_warehouseSelectResolve: null,
};
},
computed: {
/** 可见处方类型identity=2 仅中药;其余展示后端下发的全部 */
visiblePrescriptionCategories() {
const list = Array.isArray(this.prescriptionCategories) ? this.prescriptionCategories : [];
if (Number(this.identity) === 2) {
return list.filter((c) => Number(c.value) === 1);
}
return list;
},
/** swiper 用分类:传方模式仅中药一项 */
swiperCategories() {
if (this.isSalespersonTransferMode) {
return [{ label: '中药', value: 1, icon: '', icon_text: '' }];
}
return this.visiblePrescriptionCategories;
},
/** u-tabs 需要 { name };有 icon_text 时拼到 name 后便于看见角标;始终返回数组避免 list 非数组警告 */
prescriptionTabsList() {
const list = 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 || '' };
}) || [];
},
/**
* 当前处方类型显示名:简单产品列表等共用区块按 Tab label 展示,
* 避免写死「产品/器械」导致保健食品等类型文案不对
*/
activeCategoryLabel() {
const list = this.visiblePrescriptionCategories || this.prescriptionCategories || [];
const hit = list.find((c) => Number(c.value) === Number(this.activeCategory));
return (hit && hit.label) || '产品';
},
allowInsuranceCategory() {
return Number(this.registerStoreInfo?.allow_insurance_category ?? 0) === 1;
},
canPreSendPriceAdjust() {
return Number(this.enableOrderPricePercentAdjust) === 1
&& this.currentDrugs.length > 0
&& !this.isSpecialPrescriptionCartLocked;
},
preSendDiscountLabel() {
return formatPriceDiscountLabel(this.priceDiscount);
},
effectivePriceAdjustStoreId() {
return this.selectedStoreId
|| this.registerStoreInfo?.store_id
|| uni.getStorageSync('store_id')
|| null;
},
totalProductCost() {
if (this.currentDrugs.length === 0) return 0;
if (this.activeCategory === 1) {
return PrescriptionCalculator.calculateChineseMedicineProductPrice(this.currentDrugs, this.chineseConfig.dosage);
} else if (this.activeCategory === 2) {
return PrescriptionCalculator.calculateWesternMedicineProductPrice(this.currentDrugs);
} else if ([3, 5, 6, 7].includes(this.activeCategory)) {
return PrescriptionCalculator.calculateSimpleProductPrice(this.currentDrugs);
}
return 0;
},
processingFee() {
if (this.activeCategory !== 1 || this.chineseConfig.ruleType !== 2) return 0;
const processRule = this.childProcessRuleList.find(item => item.id === this.chineseConfig.childProcessRuleId);
if (!processRule) return 0;
const totalDrugQuantity = this.currentDrugs.reduce((sum, drug) => sum + parseFloat(drug.number || 1), 0);
return PrescriptionCalculator.calculateProcessingFee(processRule, this.chineseConfig.dosage, totalDrugQuantity);
},
totalPrice() {
const expected = this.appliedSpecialPrescriptionExpectedTotal;
if (expected != null && !Number.isNaN(Number(expected)) && Number(expected) > 0) {
const base = parseFloat(expected);
const fee = parseFloat(this.treatmentPrice || 0);
const process = this.activeCategory === 1 ? parseFloat(this.processingFee || 0) : 0;
return (base + fee + process).toFixed(2);
}
const total = PrescriptionCalculator.calculateTotalPrice({
category: this.activeCategory,
drugs: this.currentDrugs,
dosage: this.chineseConfig.dosage,
processingFee: this.processingFee,
treatmentPrice: parseFloat(this.treatmentPrice || 0)
});
return total.toFixed(2);
},
grossMarginText() {
if (this.activeCategory !== 1 || Number(this.seeRate) !== 1) {
return '';
}
return PrescriptionCalculator.calculateChineseTotalGrossMarginPercent(
this.currentDrugs,
this.chineseConfig.dosage
);
},
showSalespersonTransferPrice() {
return !this.isSalespersonTransferMode || Number(this.salespersonSeePrice) === 1;
},
isTransferFeeHidden() {
return this.isSalespersonTransferMode && Number(this.salespersonSeePrice) === 0;
},
supportsCommonPrescriptionTemplate() {
return [1, 2].includes(this.activeCategory);
},
isOnlineRevisit() {
const t = Number(this.registerOrderType);
return t === 2 || t === 3;
},
registerModeHint() {
if (this.registerOrderType === null || this.registerOrderType === undefined || this.registerOrderType === '') return '';
return this.isOnlineRevisit ? '当前:在线复诊开方(提交后将向患者发送处方消息)' : '当前:线下开方';
},
hasSpecialPrescriptionRecord() {
return !!this.specialPrescriptionRecord;
},
/** 导入卡片SKU 名或单剂 */
specialPrescriptionPurchaseLabel() {
return resolveSpecialPrescriptionPurchaseLabel(this.specialPrescriptionRecord);
},
/** 已导入特色方时锁定药方不可编辑 */
isSpecialPrescriptionCartLocked() {
return isSpecialPrescriptionCartLocked(this);
},
/** 底部发送按钮宽度(左侧仅「另存常用方」时需留出空间) */
bottomSendBtnWidth() {
if (this.isSalespersonTransferMode || this.isCommonPrescription) {
return '686rpx';
}
if (this.supportsCommonPrescriptionTemplate) {
return '400rpx';
}
return '686rpx';
},
navbarTitle() {
if (this.isSalespersonTransferMode) {
return '传方';
}
if (this.registerOrderType === null || this.registerOrderType === undefined || this.registerOrderType === '') {
return '开方';
}
return this.isOnlineRevisit ? '开方 · 在线复诊' : '开方 · 线下';
},
hasTransferDraft() {
if (!this.isSalespersonTransferMode) return false;
return this.currentDrugs.length > 0
|| this.diagnoses.length > 0
|| !!(this.medicalAdvice && String(this.medicalAdvice).trim());
},
shouldEnablePageBackGuard() {
return this.hasPrescriptionOverlay;
},
onlineTcmDiseasesLabel() {
const data = this.traditionalTcmData;
const id = this.onlineTcmDiseasesId;
if (!data || id == null || id === '') return '';
const row = (data.diseases || []).find((x) => Number(x.id) === Number(id));
return row ? row.name : '';
},
onlineTcmMethodLabel() {
const data = this.traditionalTcmData;
const id = this.onlineTcmMethodId;
if (!data || id == null || id === '') return '';
const row = (data.method || []).find((x) => Number(x.id) === Number(id));
return row ? row.name : '';
},
onlineTcmSyndromeLabel() {
const data = this.traditionalTcmData;
const id = this.onlineTcmSyndromeId;
if (!data || id == null || id === '') return '';
const row = (data.syndrome || []).find((x) => Number(x.id) === Number(id));
return row ? row.name : '';
},
tcmPickerCurrentSelectedId() {
if (this.tcmPickerField === 'diseases') return this.onlineTcmDiseasesId;
if (this.tcmPickerField === 'method') return this.onlineTcmMethodId;
if (this.tcmPickerField === 'syndrome') return this.onlineTcmSyndromeId;
return null;
},
hasPrescriptionOverlay() {
for (let i = 0; i < PRESCRIPTION_OVERLAY_KEYS.length; i++) {
if (this[PRESCRIPTION_OVERLAY_KEYS[i]]) return true;
}
return false;
}
},
watch: {
showWesternMedicineModal(val) {
if (!val) this.westernModalInitialKeyword = '';
},
showChineseMedicineModal(val) {
if (!val) this.chineseModalInitialKeyword = '';
},
// #ifdef MP-WEIXIN
shouldEnablePageBackGuard(val) {
this.setPageBackGuardShow(!!val, { suppressLeave: !val });
this.syncSalespersonTransferUnloadAlert();
},
hasTransferDraft() {
this.syncSalespersonTransferUnloadAlert();
},
// #endif
},
created() {
// u-navbar 在小程序内 bind($parent) 可能拿不到页面实例,用箭头函数固定 this
this.boundCustomBack = () => this.handleCustomBack();
// 按默认 activeCategory 立刻对齐 swiper 下标,避免首屏 Tab 在中药、内容按西药渲染
this.syncSwiperCurrentFromCategory();
},
onLoad(options) {
this.isSalespersonTransferMode = String(options.salesperson_transfer) === '1';
if (this.isSalespersonTransferMode) {
this.registerId = SALESPERSON_TRANSFER_STORAGE_KEY;
this._presetActiveCategory = 1;
this._categoryLockedByQuery = true;
// 传方模式默认自制剂 + 剂包
this.chineseConfig = {
...this.chineseConfig,
ruleType: 1,
packageMethodId: 2,
};
} else {
this.registerId = options.register_id || '';
}
this.patientId = options.patient_id || '';
this.pendingReusePrescriptionId = options.reuse_prescription_id ? String(options.reuse_prescription_id) : '';
this.pendingImportSpecial = String(options.import_special) === '1';
this.isCommonPrescription = String(options.save_common) === '1';
if (!this.isCommonPrescription) {
uni.removeStorageSync('add');
}
if (options.initial_category !== undefined && options.initial_category !== '') {
const n = parseInt(options.initial_category, 10);
if (!Number.isNaN(n)) {
this._presetActiveCategory = n;
this._categoryLockedByQuery = true;
}
}
this.initializePageData();
},
async onShow() {
this.loadCurrentCategoryDrugs();
if (this.activeCategory === 1 && this.chineseConfig.ruleType === 2) {
await this.hydrateChineseProcessRuleListsFromConfig();
}
if (!this.isSalespersonTransferMode && this.activeCategory === 1) {
await this.checkAndShowTransferTip();
}
},
onUnload() {
if (this.isCommonPrescription) {
uni.removeStorageSync('add');
}
// #ifdef MP-WEIXIN
if (this.isSalespersonTransferMode) {
uni.disableAlertBeforeUnload();
}
// #endif
},
methods: {
// #ifdef MP-WEIXIN
setPageBackGuardShow(show, { suppressLeave = false } = {}) {
if (suppressLeave) this._suppressPageBackGuardLeave = true;
this.pageBackGuardShow = !!show;
},
syncSalespersonTransferUnloadAlert() {
if (!this.isSalespersonTransferMode) return;
if (this.hasTransferDraft && !this.hasPrescriptionOverlay) {
uni.enableAlertBeforeUnload({ message: '有未提交的传方内容,确定退出?' });
} else {
uni.disableAlertBeforeUnload();
}
},
// #endif
async initializePageData() {
try {
this.restoreFromLocalStorage();
if (this.isSalespersonTransferMode) {
await this.loadSalespersonTransferConfig();
await this.loadBasicConfigDataForTransfer();
} else {
await this.loadPatientInfo();
await this.loadRegisterOrderType();
await this.loadBasicConfigData();
await this.loadPrescriptionTypeOptions();
await this.loadRegisterStoreInfo();
// 无 URL/上次切换/草稿时,用后端 default 兜底
this.applyDefaultCategoryFromOptions();
}
this.loadCurrentCategoryDrugs();
this.syncSwiperCurrentFromCategory();
if (this.activeCategory === 1 && this.chineseConfig.ruleType === 2) {
await this.hydrateChineseProcessRuleListsFromConfig();
}
if (this.isSalespersonTransferMode) {
this.ensureSalespersonTransferPackageMethodDefault();
}
if (!this.isSalespersonTransferMode) {
await this.maybeApplyReusePrescription();
if (this.activeCategory === 1) await this.checkAndShowTransferTip();
this.applyPendingExperienceDrugFromChat();
if (this.pendingImportSpecial && this.hasSpecialPrescriptionRecord) {
await this.handleApplySpecialPrescription();
this.pendingImportSpecial = false;
}
}
// #ifdef MP-WEIXIN
this.$nextTick(() => {
this.setPageBackGuardShow(this.shouldEnablePageBackGuard);
this.syncSalespersonTransferUnloadAlert();
});
// #endif
} catch (error) {
console.error('初始化页面数据失败:', error);
this.$toast('初始化失败,请重试');
}
},
async loadSalespersonTransferConfig() {
try {
const wrap = await getClinicSalespersonSeePriceConfig();
if (wrap && wrap.ok) {
this.salespersonSeePrice = Number(wrap.data?.see_price ?? 0);
}
} catch (error) {
console.error('获取推广员价格配置失败:', error);
}
},
async loadBasicConfigDataForTransfer() {
try {
if (this.activeCategory === 1 && this.processRuleList.length === 0) {
await this.loadProcessRuleList(0, 0);
}
} catch (error) {
console.error('加载基础配置数据失败:', error);
}
},
/**
* 传方模式草稿未保存包法时默认剂包id=2
*/
ensureSalespersonTransferPackageMethodDefault() {
if (!this.isSalespersonTransferMode || this.activeCategory !== 1) return;
if (!this.chineseConfig.packageMethodId) {
this.chineseConfig = {
...this.chineseConfig,
ruleType: 1,
packageMethodId: 2,
};
}
},
/**
* 拉取后端处方类型列表与默认选中;失败时保留本地兜底列表
*/
async loadPrescriptionTypeOptions() {
try {
const params = {};
if (this.registerId) params.register_id = this.registerId;
const res = await getPrescriptionTypeOptionsApi(params);
// req 已解包为业务 data{ default, list }
const 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;
} catch (e) {
console.error('加载处方类型失败:', e);
}
},
/**
* 开方默认 Tab 兜底:仅当无 URL 锁定、无上次切换分类、无任何药品草稿时,
* 使用后端下发的 default。优先级URL > 上次切换 > 草稿 > 接口 default
*/
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;
this.activeCategory = next;
PrescriptionStorage.saveActiveCategory(next, this.registerId);
this.focusActiveTab();
},
/**
* 开方默认 Tab 兜底:仅当无 URL 锁定、无上次切换分类、无任何药品草稿时,
* 按诊所类型设中药(1)/西药(2)。优先级URL > 上次切换 > 草稿 > clinic_type
* @deprecated 已由 applyDefaultCategoryFromOptions 替代,保留兼容
*/
applyDefaultCategoryByClinicType() {
this.applyDefaultCategoryFromOptions();
},
/** 将当前 Tab 滚入可视区u-tabs is-scroll 已自动聚焦,此处同步 swiper 下标) */
focusActiveTab() {
this.syncSwiperCurrentFromCategory();
},
/** 根据 activeCategory 同步 swiperCurrent */
syncSwiperCurrentFromCategory() {
const list = this.swiperCategories || [];
const idx = list.findIndex((c) => Number(c.value) === Number(this.activeCategory));
const next = idx >= 0 ? idx : 0;
if (next === this.swiperCurrent) return;
// 程序化改 current 会触发 swiper @change用 tabChanging 挡住回路
this.tabChanging = true;
this.swiperCurrent = next;
this.$nextTick(() => {
this.tabChanging = false;
});
},
/** Tab 点击切换(与订单列表 onTabChange 一致) */
onRxTabChange(index) {
if (this.tabChanging) return;
this.tabChanging = true;
const idx = typeof index === 'object' ? Number(index.index ?? index) : Number(index);
const list = this.swiperCategories || [];
const cat = list[idx];
if (cat) {
this.swiperCurrent = idx;
this.handleSwitchCategory(cat.value, { skipFocus: true });
}
this.$nextTick(() => {
this.tabChanging = false;
});
},
/** swiper 滑动切换 */
onRxSwiperChange(e) {
if (this.tabChanging) return;
this.tabChanging = true;
const idx = Number((e.detail && e.detail.current) || 0);
const list = this.swiperCategories || [];
const cat = list[idx];
if (cat) {
this.swiperCurrent = idx;
this.handleSwitchCategory(cat.value, { skipFocus: true });
}
this.$nextTick(() => {
this.tabChanging = false;
});
},
restoreFromLocalStorage() {
if (this._presetActiveCategory !== null && this._presetActiveCategory !== undefined && !Number.isNaN(this._presetActiveCategory)) {
this.activeCategory = this._presetActiveCategory;
this._presetActiveCategory = null;
// 保留 _categoryLockedByQuery避免随后被诊所类型默认覆盖
return;
}
const savedCategory = PrescriptionStorage.getActiveCategory(this.registerId);
if (savedCategory) {
this.activeCategory = savedCategory;
} else {
const categories = [1, 2, 3, 5, 6, 7];
for (const category of categories) {
const data = PrescriptionStorage.loadPrescriptionData(category, this.registerId);
if (data && data.drugs && data.drugs.length > 0) {
this.activeCategory = category;
break;
}
}
}
},
loadCurrentCategoryDrugs() {
const data = PrescriptionStorage.loadPrescriptionData(this.activeCategory, this.registerId);
if (data) {
this.currentDrugs = data.drugs || [];
this.diagnoses = data.diagnoses || [];
this.diagnosisText = data.diagnosisText || '';
this.medicalAdvice = data.medicalAdvice || '';
this.treatmentPrice = data.treatmentPrice || '';
if (data.chineseConfig) this.chineseConfig = { ...this.chineseConfig, ...data.chineseConfig };
if (this.activeCategory === 1) {
if (data.onlineTcmDiseasesId !== undefined && data.onlineTcmDiseasesId !== null) this.onlineTcmDiseasesId = data.onlineTcmDiseasesId;
else this.onlineTcmDiseasesId = null;
if (data.onlineTcmMethodId !== undefined && data.onlineTcmMethodId !== null) this.onlineTcmMethodId = data.onlineTcmMethodId;
else this.onlineTcmMethodId = null;
if (data.onlineTcmSyndromeId !== undefined && data.onlineTcmSyndromeId !== null) this.onlineTcmSyndromeId = data.onlineTcmSyndromeId;
else this.onlineTcmSyndromeId = null;
} else {
this.onlineTcmDiseasesId = null;
this.onlineTcmMethodId = null;
this.onlineTcmSyndromeId = null;
}
} else {
this.currentDrugs = [];
this.onlineTcmDiseasesId = null;
this.onlineTcmMethodId = null;
this.onlineTcmSyndromeId = null;
}
},
/**
* 从 chineseConfig 中的 ID 拉取委托调剂三级列表(本地缓存只存 ID不存列表
*/
async hydrateChineseProcessRuleListsFromConfig() {
if (this.activeCategory !== 1 || this.chineseConfig.ruleType !== 2) return;
if (!this.processRuleList || this.processRuleList.length === 0) {
await this.loadProcessRuleList(0, 0);
}
if (this.chineseConfig.processRuleId) {
await this.loadProcessRuleList(this.chineseConfig.processRuleId, 0);
}
if (this.chineseConfig.childProcessRuleId) {
await this.loadProcessRuleList(0, this.chineseConfig.childProcessRuleId);
}
},
saveToLocalStorage() {
const data = {
drugs: this.currentDrugs,
diagnoses: this.diagnoses,
diagnosisText: this.diagnosisText,
medicalAdvice: this.medicalAdvice,
treatmentPrice: this.treatmentPrice,
chineseConfig: this.activeCategory === 1 ? this.chineseConfig : null,
onlineTcmDiseasesId: this.activeCategory === 1 ? this.onlineTcmDiseasesId : undefined,
onlineTcmMethodId: this.activeCategory === 1 ? this.onlineTcmMethodId : undefined,
onlineTcmSyndromeId: this.activeCategory === 1 ? this.onlineTcmSyndromeId : undefined
};
PrescriptionStorage.savePrescriptionData(this.activeCategory, this.registerId, data);
PrescriptionStorage.saveActiveCategory(this.activeCategory, this.registerId);
},
async loadPatientInfo() {
if (!this.patientId) return;
try {
const res = await getPatientInfoByPatientId(this.patientId);
const data = res.result || res.data || res;
if (data && data.user_patient) {
this.patientInfo = {
id: data.user_patient.id || this.patientId,
name: data.user_patient.name,
mobile: data.user_patient.mobile,
age: data.user_patient.age,
sex: data.user_patient.sex
};
}
} catch (error) {
console.error('获取患者信息失败:', error);
}
},
async loadRegisterOrderType() {
if (!this.registerId) {
this.registerOrderType = null;
this.specialPrescriptionRecord = null;
return;
}
try {
const res = await getPatientItem(this.registerId);
const data = res.result || res.data || res;
if (data && data.type !== undefined && data.type !== null) {
this.registerOrderType = data.type;
} else {
this.registerOrderType = null;
}
this.specialPrescriptionRecord = pickSpecialPrescriptionRecord(data);
} catch (e) {
console.warn(e);
this.registerOrderType = null;
this.specialPrescriptionRecord = null;
}
},
async loadBasicConfigData() {
try {
const drugUseRes = await getDrugUseList();
if (drugUseRes) {
const inner = drugUseRes.result || drugUseRes.data;
this.drugUseList = inner && (inner.drug_time != null || inner.drug_use_type != null || inner.drug_use_way != null) ? inner : drugUseRes;
}
if (this.activeCategory === 1 && this.processRuleList.length === 0) {
await this.loadProcessRuleList(0, 0);
}
} catch (error) {
console.error('加载基础配置数据失败:', error);
}
},
getChineseItemMargin(drug) {
if (Number(this.seeRate) !== 1) {
return '';
}
return PrescriptionCalculator.calculateItemGrossMarginPercent(
drug.price,
drug.buy_price
);
},
async loadPriceAdjustConfigForStore(storeId) {
try {
const res = await getPriceAdjustConfigApi(storeId);
if (res) {
this.priceAdjustScopeMode = res.order_price_adjust_scope === 'both' ? 'both' : 'sale_only';
this.priceAdjustQuickOptions = normalizeQuickOptions(res.order_discount_quick_options);
if (res.enable_order_price_percent_adjust != null) {
this.enableOrderPricePercentAdjust = Number(res.enable_order_price_percent_adjust);
}
}
} catch (e) {
console.error('获取调价配置失败:', e);
}
},
snapshotDrugOrigins(drugs) {
return (drugs || []).map((drug) => ({
...drug,
origin_price: drug.origin_price ?? drug.price,
origin_buy_price: drug.origin_buy_price ?? drug.buy_price,
}));
},
prepareDrugForCart(drug) {
const [prepared] = applyRatioToDrugs(
this.snapshotDrugOrigins([drug]),
this.priceDiscount,
this.priceAdjustScopeMode,
);
return prepared;
},
applyPreSendPriceDiscount(discount) {
this.priceDiscount = discount;
const scope = this.priceAdjustScopeMode;
this.currentDrugs = applyRatioToDrugs(this.snapshotDrugOrigins(this.currentDrugs), discount, scope);
uni.showToast({ title: discount === 100 ? '已清除浮动' : '调价成功', icon: 'none' });
},
clearPreSendPriceDiscount() {
if (guardSpecialPrescriptionCartEdit(this)) return;
this.applyPreSendPriceDiscount(100);
},
openPreSendOrderPriceAdjust() {
if (guardSpecialPrescriptionCartEdit(this)) return;
if (!this.canPreSendPriceAdjust) return;
this.currentDrugs = this.snapshotDrugOrigins(this.currentDrugs);
this.priceAdjustVisible = true;
},
closePreSendPriceAdjust() {
this.priceAdjustVisible = false;
},
onPreSendPriceAdjustConfirm(payload) {
this.applyPreSendPriceDiscount(payload.priceDiscount);
this.closePreSendPriceAdjust();
},
async fetchStoreSeeRate() {
try {
const storeId = uni.getStorageSync('store_id');
const params = {};
if (storeId) {
params.store_id = storeId;
}
if (this.registerId) {
params.register_id = this.registerId;
}
if (!params.store_id && !params.register_id) {
return;
}
const res = await getCurrentStoreTypeApi(params);
if (res) {
this.seeRate = Number(res.see_rate ?? 0);
this.enableOrderPricePercentAdjust = Number(res.enable_order_price_percent_adjust ?? 0);
await this.loadPriceAdjustConfigForStore(params.store_id || storeId);
}
} catch (error) {
console.error('获取毛利率权限失败:', error);
}
},
async loadRegisterStoreInfo() {
if (!this.registerId) {
await this.fetchStoreSeeRate();
return;
}
try {
const storeId = uni.getStorageSync('store_id');
const res = await getCurrentStoreTypeApi({
register_id: this.registerId,
...(storeId ? { store_id: storeId } : {})
});
if (res) {
this.registerStoreInfo = res;
this.seeRate = Number(res.see_rate ?? 0);
if (Number(res.allow_insurance_category ?? 0) !== 1) {
this.feeCategory = 1;
}
this.checkSalespersonTransfer();
if (res.is_from_transfer === 1 && res.delegate_store_id) {
this.selectedStoreId = res.delegate_store_id;
this.sendMode = 1;
} else if (res.store_id) {
this.selectedStoreId = res.store_id;
this.sendMode = res.can_change ? 1 : 0;
}
const priceStoreId = this.selectedStoreId || res.store_id || storeId;
this.enableOrderPricePercentAdjust = Number(res.enable_order_price_percent_adjust ?? 0);
await this.loadPriceAdjustConfigForStore(priceStoreId);
}
} catch (error) {
console.error('获取挂号诊所信息失败:', error);
}
},
async checkSalespersonTransfer() {
if (!this.registerId) {
this.hasSalespersonTransfer = false;
return;
}
try {
const res = await getSalespersonTransferByRegisterApi(this.registerId);
const list = res?.result || res?.data || res;
this.hasSalespersonTransfer = Array.isArray(list) && list.length > 0;
} catch {
this.hasSalespersonTransfer = false;
}
},
async openSalespersonTransfer() {
try {
const res = await getSalespersonTransferByRegisterApi(this.registerId);
const list = res?.result || res?.data || res;
if (!Array.isArray(list) || !list.length) {
this.$toast('暂无待导入传方');
return;
}
const item = list[0];
const parsed = parseTransferPrescriptionContent(item);
if (!parsed || !parsed.drugList?.length) {
this.$toast('传方数据无效');
return;
}
this.salespersonTransferPrescriptionId = item.id;
this.activeCategory = 1;
parsed.drugList.forEach((drug) => {
this.currentDrugs.push(this.prepareDrugForCart({
index_id: drug.index_id ?? drug.drug_id ?? drug.id,
id: drug.drug_id || drug.id,
drug_id: drug.drug_id || drug.id,
drug_name: drug.drug_name || drug.name,
name: drug.drug_name || drug.name,
number: drug.number || 1,
way_id: drug.way_id || 0,
price: drug.price ?? 0,
buy_price: drug.buy_price,
}));
});
if (parsed.clinical_diagnose) {
this.diagnoses = [{ id: Date.now(), name: parsed.clinical_diagnose }];
}
if (parsed.doctor_order) this.medicalAdvice = parsed.doctor_order;
this.saveToLocalStorage();
this.$toast('传方已导入');
} catch (e) {
console.error(e);
this.$toast('加载传方失败');
}
},
async handleSwitchCategory(category, options = {}) {
this.saveToLocalStorage();
this.activeCategory = category;
this.loadCurrentCategoryDrugs();
if (!options.skipFocus) {
this.focusActiveTab();
} else {
this.syncSwiperCurrentFromCategory();
}
if (category === 1) {
await this.checkSalespersonTransfer();
await this.checkAndShowTransferTip();
if (this.processRuleList.length === 0) await this.loadProcessRuleList(0, 0);
}
},
async checkAndShowTransferTip() {
try {
if (!this.registerStoreInfo) await this.loadRegisterStoreInfo();
const storeInfo = this.registerStoreInfo;
if (!storeInfo) return;
if (storeInfo.clinic_type === 1 && storeInfo.delegate_store) {
const delegateStore = storeInfo.delegate_store;
const doctorName = delegateStore.doctor?.name || '委托医生';
const storeName = delegateStore.name || '委托诊所';
uni.showModal({
title: '转诊提示',
content: `检测到当前诊所为西医诊所,本次处方开具后自动转接到【${storeName}】的【${doctorName}】的在线复诊中`,
showCancel: false,
confirmText: '我知道了'
});
}
} catch (error) {
console.error('获取诊所信息失败:', error);
}
},
handleOpenDiagnosisModal() {
this.showDiagnosisModal = true;
},
async ensureTraditionalTcmData() {
if (this.traditionalTcmData) return this.traditionalTcmData;
if (this.traditionalTcmLoadPromise) return this.traditionalTcmLoadPromise;
this.traditionalTcmLoadPromise = getTraditionalChineseMedicineJson()
.then((d) => {
this.traditionalTcmData = d;
return d;
})
.finally(() => {
this.traditionalTcmLoadPromise = null;
});
return this.traditionalTcmLoadPromise;
},
async handleOpenTcmPicker(field) {
if (!this.isOnlineRevisit || this.activeCategory !== 1) return;
try {
uni.showLoading({ title: '加载中', mask: true });
await this.ensureTraditionalTcmData();
uni.hideLoading();
this.tcmPickerField = field;
this.tcmPickerTitle = field === 'diseases' ? '中医证候' : field === 'method' ? '中医治法' : '中医疾病';
const d = this.traditionalTcmData;
this.tcmPickerList = field === 'diseases' ? (d.diseases || []) : field === 'method' ? (d.method || []) : (d.syndrome || []);
this.showTcmPicker = true;
} catch (e) {
uni.hideLoading();
console.error(e);
uni.showToast({ title: '术语表加载失败', icon: 'none' });
}
},
handleTcmPickerConfirm(id) {
const nid = id != null ? Number(id) : null;
if (this.tcmPickerField === 'diseases') this.onlineTcmDiseasesId = nid;
else if (this.tcmPickerField === 'method') this.onlineTcmMethodId = nid;
else if (this.tcmPickerField === 'syndrome') this.onlineTcmSyndromeId = nid;
this.saveToLocalStorage();
this.showTcmPicker = false;
},
handleDiagnosisConfirm(diagnosisText) {
this.diagnosisText = diagnosisText;
if (diagnosisText) {
const diagnosisNames = diagnosisText.split('').filter(Boolean);
this.diagnoses = diagnosisNames.map((name, index) => ({ id: `temp_${index}`, name: name }));
} else {
this.diagnoses = [];
}
this.saveToLocalStorage();
},
handleRemoveDiagnosis(index) {
this.diagnoses.splice(index, 1);
this.diagnosisText = this.diagnoses.map(item => item.name).join('');
this.saveToLocalStorage();
},
handleOpenDoctorOrderModal() {
this.showDoctorOrderModal = true;
},
handleDoctorOrderConfirm(doctorOrderText) {
this.medicalAdvice = doctorOrderText;
this.saveToLocalStorage();
},
handleOpenCommonPrescriptionModal() { this.showCommonPrescriptionModal = true; },
async handleApplySpecialPrescription() {
if (!this.registerId || this.applyingSpecialPrescription) return;
this.applyingSpecialPrescription = true;
try {
const res = await applySpecialPrescriptionApi({ register_id: this.registerId });
const data = res.result || res.data || res;
applySpecialPrescriptionPayload(this, data);
await this.loadRegisterOrderType();
this.$toast(`已导入特色方:${data.special_prescription_name || ''}`);
if (data.price_hint) {
uni.showModal({
title: '价格提示',
content: data.price_hint,
showCancel: false,
});
}
} catch (error) {
console.error('导入特色方失败:', error);
this.$toast('导入特色方失败,请稍后重试');
} finally {
this.applyingSpecialPrescription = false;
}
},
async handleSelectCommonPrescription(item) {
try {
clearAppliedSpecialPrescription(this);
const payload = item && item.apply_payload;
if (!payload || !Array.isArray(payload.drugs)) {
this.$toast('常用方数据异常');
return;
}
this.currentDrugs.splice(0, this.currentDrugs.length, ...payload.drugs);
if (payload.diagnosisText) {
this.diagnosisText = payload.diagnosisText;
const names = payload.diagnosisText.split('').filter(Boolean);
this.diagnoses = names.map((name, index) => ({ id: `temp_${index}`, name }));
}
if (payload.medicalAdvice) this.medicalAdvice = payload.medicalAdvice;
if (this.activeCategory === 1 && payload.chineseConfig) {
this.chineseConfig = { ...this.chineseConfig, ...payload.chineseConfig };
if (this.chineseConfig.processRuleId) await this.loadProcessRuleList(this.chineseConfig.processRuleId, 0);
if (this.chineseConfig.childProcessRuleId) await this.loadProcessRuleList(0, this.chineseConfig.childProcessRuleId);
}
this.saveToLocalStorage();
this.$toast('常用方已填充');
} catch (error) {
console.error('选择常用方失败:', error);
this.$toast('选择常用方失败');
}
},
getCommonPrescriptionType(category) {
if (category === 1) return 'chinese';
else if (category === 2) return 'west';
else return 'granular';
},
applyPendingExperienceDrugFromChat() {
let raw;
try {
raw = uni.getStorageSync(PENDING_RX_FROM_CHAT);
} catch (e) {
return;
}
if (!raw) return;
let p;
try {
p = typeof raw === 'string' ? JSON.parse(raw) : raw;
} catch (e2) {
try {
uni.removeStorageSync(PENDING_RX_FROM_CHAT);
} catch (e3) {}
return;
}
if (!p || String(p.register_id) !== String(this.registerId)) return;
if (p.source === 'register_drugs' && Array.isArray(p.drugs) && p.drugs.length > 0) {
try {
uni.removeStorageSync(PENDING_RX_FROM_CHAT);
} catch (e4) {}
this.activeCategory = 2;
this.saveToLocalStorage();
this.loadCurrentCategoryDrugs();
this.mergeWesternRowsFromRegisterDrugs(p.drugs);
return;
}
if (p.source === 'transfer_import' && Array.isArray(p.drugs) && p.drugs.length > 0) {
try {
uni.removeStorageSync(PENDING_RX_FROM_CHAT);
} catch (e5) {}
this.activeCategory = 1;
this.saveToLocalStorage();
this.loadCurrentCategoryDrugs();
this.mergeTransferChineseRows(p.drugs);
if (p.diagnosisText) {
this.diagnosisText = String(p.diagnosisText);
const names = this.diagnosisText.split('').filter(Boolean);
this.diagnoses = names.map((name, index) => ({ id: `temp_${index}`, name }));
}
if (p.medicalAdvice != null && String(p.medicalAdvice).trim() !== '') {
this.medicalAdvice = String(p.medicalAdvice);
}
this.saveToLocalStorage();
return;
}
try {
uni.removeStorageSync(PENDING_RX_FROM_CHAT);
} catch (e4) {}
this.activeCategory = 2;
this.westernModalInitialKeyword = String(p.drug_name || '').trim();
this.saveToLocalStorage();
this.loadCurrentCategoryDrugs();
this.$nextTick(() => {
this.showWesternMedicineModal = true;
});
},
mergeWesternRowsFromRegisterDrugs(rows) {
let added = 0;
for (const row of rows) {
const d = row.drug || {};
const qtyRaw = row.purchase_quantity != null ? Number(row.purchase_quantity) : null;
const qty =
qtyRaw != null && !Number.isNaN(qtyRaw) && qtyRaw >= 1 ? Math.min(99, qtyRaw) : Number(d.number || 1) || 1;
const drugId = row.drug_id || d.id;
const synthetic = {
id: drugId,
drug_id: drugId,
drug_name: d.drug_name,
number: qty,
select_number: qty,
price: parseFloat(row.price) || 0,
specification: d.specification || '',
image: d.image || '',
instruction: d.instruction || '',
time_id: d.time_id || 0,
type_id: d.type_id || 0,
frequency_id: d.frequency_id || 0,
unit_id: d.unit_id || 0,
drug: d
};
if (PrescriptionValidator.isDrugExists(this.currentDrugs, synthetic)) {
continue;
}
const newDrug = {
index_id: row.id,
id: drugId,
drug_id: drugId,
drug_name: d.drug_name,
number: qty,
select_number: qty,
price: parseFloat(row.price) || 0,
specification: d.specification || '',
image: d.image || '',
instruction: d.instruction || '',
time_id: d.time_id || 0,
type_id: d.type_id || 0,
frequency_id: d.frequency_id || 0,
unit_id: d.unit_id || 0,
use_num: this.drugUseList.drug_time?.find((item) => item.id === (d.time_id || 0)),
use_type: this.drugUseList.drug_use_type?.find((item) => item.id === (d.type_id || 0)),
use_frequency: this.drugUseList.drug_use_frequency?.find((item) => item.id === (d.frequency_id || 0)),
unit: this.drugUseList.drug_unit?.find((item) => item.id === (d.unit_id || 0))
};
this.currentDrugs.push(this.prepareDrugForCart(newDrug));
added++;
}
this.saveToLocalStorage();
if (added > 0) {
this.$toast(`已添加 ${added} 个药品`);
} else {
this.$toast('药品已在列表中或未能匹配');
}
},
mergeTransferChineseRows(rows) {
let added = 0;
for (const product of rows) {
if (PrescriptionValidator.isDrugExists(this.currentDrugs, product)) {
continue;
}
this.currentDrugs.push(this.prepareDrugForCart({ ...product }));
added++;
}
this.saveToLocalStorage();
if (added > 0) {
this.$toast(`已添加 ${added} 味药`);
} else {
this.$toast('药品已在列表中');
}
},
handleOpenWesternMedicineModal() {
if (guardSpecialPrescriptionCartEdit(this)) return;
this.westernModalInitialKeyword = '';
this.showWesternMedicineModal = true;
},
handleSelectWesternDrug(drug) {
if (guardSpecialPrescriptionCartEdit(this)) return;
if (PrescriptionValidator.isDrugExists(this.currentDrugs, drug)) {
this.$toast('该药品已在列表中');
return;
}
const newDrug = {
index_id: drug.id,
id: drug.drug_id || drug.drug?.id || drug.id,
drug_id: drug.drug_id || drug.drug?.id || drug.id,
drug_name: drug.drug_name || drug.drug?.drug_name,
number: drug.number || 1,
select_number: drug.select_number || 1,
price: drug.price || 0,
specification: drug.specification || drug.drug?.specification || '',
image: drug.image || drug.drug?.image || '',
instruction: drug.instruction || drug.drug?.instruction || '',
time_id: drug.time_id || drug.drug?.time_id || 0,
type_id: drug.type_id || drug.drug?.type_id || 0,
frequency_id: drug.frequency_id || drug.drug?.frequency_id || 0,
unit_id: drug.unit_id || drug.drug?.unit_id || 0,
use_num: this.drugUseList.drug_time?.find(item => item.id === (drug.time_id || drug.drug?.time_id)),
use_type: this.drugUseList.drug_use_type?.find(item => item.id === (drug.type_id || drug.drug?.type_id)),
use_frequency: this.drugUseList.drug_use_frequency?.find(item => item.id === (drug.frequency_id || drug.drug?.frequency_id)),
unit: this.drugUseList.drug_unit?.find(item => item.id === (drug.unit_id || drug.drug?.unit_id))
};
this.currentDrugs.push(this.prepareDrugForCart(newDrug));
const index = this.currentDrugs.length - 1;
this.handleEditWesternDrug(this.currentDrugs[index], index);
this.saveToLocalStorage();
this.$toast(`已添加 ${newDrug.drug_name}`);
},
handleOpenChineseMedicineModal() {
if (guardSpecialPrescriptionCartEdit(this)) return;
this.chineseModalInitialKeyword = '';
this.showChineseMedicineModal = true;
},
/** 微信小程序 :key 中勿写 it.id || it.index_id 等表达式,会编译异常 */
tcmTagKey(it, tcmIndex) {
if (it && it.id != null && it.id !== '') return `tcm-${it.id}`;
if (it && it.index_id != null && it.index_id !== '') return `tcm-i-${it.index_id}`;
return `tcm-idx-${tcmIndex}`;
},
/**
* 中药标签长按菜单(小程序 showActionSheet 回调里勿依赖闭包 item用 dataset.idx + currentDrugs
*/
onTcmTagLongPress(e) {
if (guardSpecialPrescriptionCartEdit(this)) return;
const raw = e && e.currentTarget && e.currentTarget.dataset ? e.currentTarget.dataset.idx : undefined;
const idx = raw === undefined || raw === '' ? NaN : Number(raw);
if (Number.isNaN(idx) || idx < 0 || idx >= this.currentDrugs.length) return;
uni.showActionSheet({
itemList: ['编辑', '删除'],
success: (res) => {
const row = this.currentDrugs[idx];
if (!row) return;
if (res.tapIndex === 0) {
this.chineseModalInitialKeyword = String(row.drug_name || row.name || '').trim();
this.showChineseMedicineModal = true;
} else if (res.tapIndex === 1) {
this.handleRemoveDrug(idx);
}
}
});
},
handleSelectChineseDrug(drugs) {
if (guardSpecialPrescriptionCartEdit(this)) return;
this.currentDrugs = applyRatioToDrugs(
this.snapshotDrugOrigins(drugs || []),
this.priceDiscount,
this.priceAdjustScopeMode,
);
this.saveToLocalStorage();
},
handleOpenSimpleProductModal() {
if (guardSpecialPrescriptionCartEdit(this)) return;
this.showSimpleProductModal = true;
},
handleSelectSimpleProduct(product) {
if (guardSpecialPrescriptionCartEdit(this)) return;
if (PrescriptionValidator.isDrugExists(this.currentDrugs, product)) {
this.$toast('该产品已在列表中');
return;
}
const newProduct = {
index_id: product.id,
id: product.drug_id || product.drug?.id || product.id,
drug_name: product.drug_name || product.drug?.drug_name || product.name,
number: 1,
price: product.price || 0,
image: product.image || product.drug?.image || '',
instruction: product.instruction || product.drug?.instruction || '',
type: product.drug?.type || product.type || this.activeCategory,
select_number: 1
};
this.currentDrugs.push(this.prepareDrugForCart(newProduct));
this.saveToLocalStorage();
this.$toast(`已添加 ${newProduct.drug_name}`);
},
handleEditWesternDrug(drug, index) {
if (guardSpecialPrescriptionCartEdit(this)) return;
this.editingDrug = JSON.parse(JSON.stringify(drug));
this.editingDrugIndex = index;
this.showWesternUsageModal = true;
},
handleConfirmWesternUsage(usageData) {
if (this.editingDrugIndex >= 0) {
const drug = this.currentDrugs[this.editingDrugIndex];
drug.time_id = usageData.time_id;
drug.type_id = usageData.type_id;
drug.frequency_id = usageData.frequency_id;
drug.unit_id = usageData.unit_id;
drug.number = usageData.number;
drug.select_number = usageData.select_number || drug.select_number;
drug.use_num = usageData.use_num;
drug.use_type = usageData.use_type;
drug.use_frequency = usageData.use_frequency;
drug.unit = usageData.unit;
this.saveToLocalStorage();
}
this.editingDrug = null;
this.editingDrugIndex = -1;
},
handleRemoveDrug(index) {
if (guardSpecialPrescriptionCartEdit(this)) return;
uni.showModal({
title: '移除药品',
content: '确定要从处方中移除该药品吗?',
confirmColor: '#FC3636',
success: ({ confirm }) => {
if (confirm) {
this.currentDrugs.splice(index, 1);
this.saveToLocalStorage();
}
}
});
},
handleIncrementSimpleProduct(index) {
if (index >= 0 && index < this.currentDrugs.length) {
const product = this.currentDrugs[index];
const currentNumber = product.select_number || product.number || 1;
this.$set(product, 'select_number', currentNumber + 1);
this.saveToLocalStorage();
}
},
handleDecrementSimpleProduct(index) {
if (index >= 0 && index < this.currentDrugs.length) {
const product = this.currentDrugs[index];
const currentNumber = product.select_number || product.number || 1;
if (currentNumber > 1) {
this.$set(product, 'select_number', currentNumber - 1);
this.saveToLocalStorage();
} else {
this.handleRemoveDrug(index);
}
}
},
handleRemoveAllDrugs() {
if (guardSpecialPrescriptionCartEdit(this)) return;
uni.showModal({
title: '清空药材',
content: '确定要清空所有中药材吗?',
confirmColor: '#FC3636',
success: ({ confirm }) => {
if (confirm) {
this.currentDrugs = [];
this.saveToLocalStorage();
}
}
});
},
async handleUpdateChineseConfig(config) {
if (guardSpecialPrescriptionCartEdit(this)) return;
const oldRuleType = this.chineseConfig.ruleType;
const oldProcessRuleId = this.chineseConfig.processRuleId;
const oldChildProcessRuleId = this.chineseConfig.childProcessRuleId;
if (config.ruleType === 2 && config.ruleType !== oldRuleType) {
if (this.processRuleList.length === 0) await this.loadProcessRuleList(0, 0);
}
if (config.ruleType === 1 && oldRuleType === 2) {
config.processRuleId = null;
config.childProcessRuleId = null;
config.processRuleNoteId = null;
this.processRuleList = [];
this.childProcessRuleList = [];
this.processRuleNoteList = [];
}
if (config.processRuleId && config.processRuleId !== oldProcessRuleId) {
config.childProcessRuleId = null;
config.processRuleNoteId = null;
this.childProcessRuleList = [];
this.processRuleNoteList = [];
}
if (config.childProcessRuleId && config.childProcessRuleId !== oldChildProcessRuleId) {
config.processRuleNoteId = null;
this.processRuleNoteList = [];
}
this.chineseConfig = { ...this.chineseConfig, ...config };
if (config.processRuleId && config.processRuleId !== oldProcessRuleId) {
await this.loadProcessRuleList(config.processRuleId, 0);
}
if (config.childProcessRuleId && config.childProcessRuleId !== oldChildProcessRuleId) {
await this.loadProcessRuleList(0, config.childProcessRuleId);
}
this.saveToLocalStorage();
},
async loadProcessRuleList(pid = 0, ruleId = 0) {
try {
const data = ruleId === 0 ? { pid } : { rule_id: ruleId };
let list = [];
if (this.isSalespersonTransferMode) {
const wrap = await getClinicSalespersonProcessRuleList(data);
if (wrap && wrap.ok) {
list = Array.isArray(wrap.data) ? wrap.data : [];
}
} else {
const res = await getProcessRuleList(data);
list = res?.data || res?.result || res || [];
}
if (ruleId !== 0) {
this.processRuleNoteList = list;
} else if (pid === 0) {
this.processRuleList = list;
} else {
this.childProcessRuleList = list;
}
} catch (error) {
console.error('加载加工规则列表失败:', error);
this.$toast('加载加工规则列表失败');
}
},
parsePrescriptionDetailContent(detail) {
let c = detail && detail.content;
if (typeof c === 'string') {
try { c = JSON.parse(c); } catch (e) { c = {}; }
}
return c && typeof c === 'object' ? c : {};
},
mapWestRepiceToProduct(recipe) {
if (!recipe) return null;
let drugObj = {};
try {
const raw = recipe.content;
drugObj = typeof raw === 'string' ? JSON.parse(raw) : (raw || {});
} catch (e) { return null; }
const drugId = Number(drugObj.id);
if (!drugId) return null;
const selectNumber = Math.max(1, Number(recipe.number ?? drugObj.select_number ?? drugObj.number ?? 1) || 1);
const grainNumber = Math.max(1, Number(drugObj.number ?? drugObj.grain_number ?? recipe.grain_number ?? 1) || 1);
const timeId = recipe.time_id ?? drugObj.time_id;
const typeId = recipe.type_id ?? drugObj.type_id;
const freqId = recipe.f_id ?? drugObj.frequency_id;
const unitId = recipe.wu_id ?? drugObj.unit_id;
const list = this.drugUseList || {};
const wayId = drugObj.way_id;
return {
index_id: drugId, id: drugId,
drug_name: drugObj.drug_name || drugObj.name || '',
number: grainNumber,
use_num: (list.drug_time || []).find((item) => item.id === timeId),
use_type: (list.drug_use_type || []).find((item) => item.id === typeId),
use_frequency: (list.drug_use_frequency || []).find((item) => item.id === freqId),
unit: (list.drug_unit || []).find((item) => item.id === unitId),
price: Number(drugObj.sell_price ?? drugObj.price ?? 0),
way_id: wayId,
use_ways: (list.drug_use_way || []).find((item) => item.id === wayId),
time_id: timeId, type_id: typeId, frequency_id: freqId, unit_id: unitId,
image: drugObj.image || '', instruction: drugObj.instruction || '',
type: drugObj.type, select_number: selectNumber, specification: drugObj.specification || ''
};
},
async applyHistoricalPrescriptionDetail(detail) {
const content = this.parsePrescriptionDetailContent(detail);
const pt = Number(detail.prescription_type);
if (![1, 2, 3, 5, 6, 7].includes(pt)) {
this.$toast('该处方类型暂不支持复用');
return false;
}
if (!this.patientId) {
this.$toast('患者信息异常');
return false;
}
this.saveToLocalStorage();
this.activeCategory = pt;
PrescriptionStorage.saveActiveCategory(pt, this.registerId);
const repiceList = Array.isArray(content.repice) ? content.repice : [];
if (pt === 1) {
const rep0 = repiceList[0];
if (!rep0) return false;
let rows = [];
try {
const raw = rep0.content;
rows = typeof raw === 'string' ? JSON.parse(raw) : (raw || []);
} catch (e) { return false; }
if (!Array.isArray(rows) || rows.length === 0) return false;
const list = this.drugUseList || {};
this.currentDrugs = rows.map((d) => {
const drugId = Number(d.drug_id ?? d.id);
const wayId = d.way_id ?? d.use_way?.id ?? d.useWay?.id ?? d.order;
const unitId = d.unit_id;
return {
index_id: d.id ?? drugId, id: drugId,
drug_name: d.name || d.drug_name || '',
number: d.number ?? 1, price: Number(d.price ?? 0),
way_id: wayId,
use_ways: (list.drug_use_way || []).find((item) => item.id === wayId) ?? d.use_way ?? d.useWay,
use_num: (list.drug_time || []).find((item) => item.id === d.time_id),
use_type: (list.drug_use_type || []).find((item) => item.id === d.type_id),
use_frequency: (list.drug_use_frequency || []).find((item) => item.id === d.frequency_id),
unit: (list.drug_unit || []).find((item) => item.id === unitId) || d.unit,
time_id: d.time_id, type_id: d.type_id, frequency_id: d.frequency_id, unit_id: unitId,
image: d.image || '', instruction: d.instruction || '', type: d.type
};
});
const ruleType = rep0.deployment === 2 ? 1 : 2;
this.chineseConfig = {
...this.chineseConfig, ruleType,
dosage: Number(rep0.dosage ?? 7), dayDosage: Number(rep0.consumption ?? 2),
packageMethodId: rep0.package_method_id ?? 2,
processRuleId: rep0.process_rule_id && Number(rep0.process_rule_id) > 0 ? rep0.process_rule_id : null,
childProcessRuleId: rep0.child_process_rule_id && Number(rep0.child_process_rule_id) > 0 ? rep0.child_process_rule_id : null,
processRuleNoteId: rep0.process_rule_note_id && Number(rep0.process_rule_note_id) > 0 ? rep0.process_rule_note_id : null
};
if (ruleType === 2) await this.hydrateChineseProcessRuleListsFromConfig();
} else {
const mapped = [];
for (const recipe of repiceList) {
const row = this.mapWestRepiceToProduct(recipe);
if (row) mapped.push(row);
}
if (mapped.length === 0) return false;
this.currentDrugs = mapped;
}
const dx = detail.clinical_diagnose ?? content.clinical_diagnose ?? '';
this.diagnosisText = dx;
if (dx) {
const names = String(dx).split('').filter(Boolean);
this.diagnoses = names.map((name, index) => ({ id: `temp_${index}`, name }));
} else {
this.diagnoses = [];
}
const fallbackOrder = Array.isArray(detail.doctor_order)
? detail.doctor_order.join('|')
: (detail.doctor_order ?? '');
this.medicalAdvice = content.doctor_order ?? fallbackOrder ?? '';
this.saveToLocalStorage();
return true;
},
async maybeApplyReusePrescription() {
const rawId = this.pendingReusePrescriptionId;
if (!rawId) return;
this.pendingReusePrescriptionId = '';
const pid = parseInt(rawId, 10);
if (!pid || !this.registerId) return;
const hasDraft = PrescriptionStorage.hasDraftWithDrugs(this.registerId);
const run = async () => {
try {
const res = await getPrescriptionInfoApi(pid);
if (res && ((typeof res.code === 'number' && res.code !== 0) || (typeof res.errcode === 'number' && res.errcode !== 0))) {
this.$toast(res.msg || res.message || '加载处方详情失败');
return;
}
const detail = res.result || res.data || res;
const ok = await this.applyHistoricalPrescriptionDetail(detail);
if (ok) this.$toast('已复用历史处方,请核对后发送');
} catch (e) {
console.error('复用处方失败:', e);
this.$toast('加载处方详情失败');
}
};
if (hasDraft) {
uni.showModal({
title: '覆盖当前草稿?', content: '当前处方清单中有药品,复用将替换为所选历史处方内容。',
success: (r) => { if (r.confirm) run(); }
});
} else {
await run();
}
},
formatDrugUsage(drug) {
if (!drug.use_type || !drug.use_frequency || !drug.use_num || !drug.unit) return '';
return `${drug.use_type.name}${drug.use_frequency.name}${drug.use_num.name},每次${drug.number}${drug.unit.name}`;
},
getProductUnit(category) {
if (category === 5) return '周期';
return '件';
},
validatePrescriptionData() {
return PrescriptionValidator.validatePrescriptionData({
category: this.activeCategory,
drugs: this.currentDrugs,
diagnoses: this.diagnoses,
medicalAdvice: this.medicalAdvice,
chineseConfig: this.activeCategory === 1 ? this.chineseConfig : null,
requireOnlineTcm: this.activeCategory === 1 && this.isOnlineRevisit,
onlineTcmDiseasesId: this.onlineTcmDiseasesId,
onlineTcmMethodId: this.onlineTcmMethodId,
onlineTcmSyndromeId: this.onlineTcmSyndromeId,
skipDiagnosisAndAdvice: this.isSalespersonTransferMode,
});
},
async checkChineseMedicineConflict() {
if (this.activeCategory !== 1) return { hasConflict: false };
try {
const names = this.currentDrugs.map((item) => item.drug_name || item.name);
const res = await checkChineseMedicineConflictApi({ names });
if (res && res.is_exist === true) return { hasConflict: true, message: res.message || '检测到该处方内具有药物相冲' };
return { hasConflict: false };
} catch (error) {
console.error('检查中药相冲失败:', error);
return { hasConflict: false };
}
},
async handleSendPrescription(doctorSecondSign = 0) {
if (this.isSubmitting) { this.$toast('正在处理中,请勿重复提交'); return; }
if (this.isCommonPrescription) { this.handleSaveAsCommonPrescription(); return; }
const validation = this.validatePrescriptionData();
if (!validation.valid) { this.$toast(validation.message); return; }
if (this.isSalespersonTransferMode) {
this.showTransferPatientDrawer = true;
return;
}
this.isSubmitting = true;
try {
if (!this.registerStoreInfo) await this.loadRegisterStoreInfo();
if (this.activeCategory === 1) {
const conflictCheck = await this.checkChineseMedicineConflict();
if (conflictCheck.hasConflict) {
uni.showModal({
title: '药物相冲提示',
content: conflictCheck.message || '检测到该处方内具有药物相冲,是否继续开方?',
showCancel: true,
success: ({ confirm }) => {
if (confirm) this.handleSendPrescriptionWithStoreSelection(1);
else this.isSubmitting = false;
}
});
return;
}
}
await this.handleSendPrescriptionWithStoreSelection(doctorSecondSign);
} catch (error) {
console.error('发送处方失败:', error);
this.$toast('发送处方失败,请重试');
this.isSubmitting = false;
}
},
async handleSendPrescriptionWithStoreSelection(doctorSecondSign = 0) {
const storeInfo = this.registerStoreInfo;
if (storeInfo?.is_from_transfer === 1 && storeInfo?.delegate_store_id) {
await this.executeSendPrescription(doctorSecondSign, 1, storeInfo.delegate_store_id);
} else if (storeInfo?.can_change) {
const registerStoreName = storeInfo?.store_name || '挂号诊所';
const registerStoreId = storeInfo?.store_id;
const currentStoreName = storeInfo?.current_store_name || '当前诊所';
uni.showModal({
title: '选择诊所',
content: `请选择使用哪个诊所开方:\n\n挂号诊所${registerStoreName}\n当前诊所${currentStoreName}`,
confirmText: '使用挂号诊所',
cancelText: '使用当前诊所',
success: ({ confirm, cancel }) => {
if (confirm) this.executeSendPrescription(doctorSecondSign, 1, registerStoreId);
else if (cancel) this.executeSendPrescription(doctorSecondSign, 0, null);
else this.isSubmitting = false;
},
fail: () => { this.isSubmitting = false; }
});
} else {
await this.executeSendPrescription(doctorSecondSign, 0, null);
}
},
/**
* 主界面药材清单展示先煎/后下文案(在选择中药弹窗设置;默认煎服不展示)
*/
getTcmWayLabel(drug) {
if (!drug) return '';
const wayId = Number(drug.way_id ?? drug.use_ways?.id ?? 0);
if (!wayId) return '';
const list = (this.drugUseList && this.drugUseList.drug_use_way) || [];
const hit = list.find((w) => Number(w.id) === wayId);
const name = (hit && hit.name) || (drug.use_ways && drug.use_ways.name) || '';
if (!name || name === '煎服') return '';
return name;
},
/**
* 在线复诊:有仓药绑定时弹卡片选仓;默认最低价;取消返回 false
*/
async resolvePharmacyWarehouseMap() {
if (!this.isOnlineRevisit) {
return null;
}
const drugs = this.currentDrugs || [];
const drugIds = [];
const needQtyMap = {};
for (const d of drugs) {
const id = Number(d?.id || 0);
const qty = Number(d?.select_number || d?.number || 0);
if (id <= 0 || qty <= 0) continue;
drugIds.push(id);
needQtyMap[id] = (needQtyMap[id] || 0) + qty;
}
if (!drugIds.length) {
return null;
}
try {
const res = await getDeliveryWarehouseOptionsByDrugs({
drug_ids: drugIds.join(','),
need_qty_map: needQtyMap,
});
const map = (res && (res.result || res.data || res)) || {};
const rows = [];
for (const d of drugs) {
const id = Number(d?.id || 0);
if (id <= 0) continue;
const options = map[String(id)] || map[id] || [];
if (!Array.isArray(options) || !options.length) continue;
rows.push({
drug_id: id,
// 优先用仓选项接口带回的药品信息,避免处方草稿未存规格/图
drug_name: String(
(options[0] && options[0].drug_name) ||
d.drug_name ||
d.name ||
`药品#${id}`,
),
image: String(
(options[0] && options[0].image) ||
d.image ||
d._image ||
(d.drug && d.drug.image) ||
'',
),
specification: String(
(options[0] && options[0].specification) ||
d.specification ||
(d.drug && d.drug.specification) ||
'',
),
options: options.map((o) => ({
warehouse_id: Number(o.warehouse_id),
warehouse_name: String(o.warehouse_name || ''),
quote: String(o.quote ?? '0'),
available_stock: Number(o.available_stock ?? 0),
// 保留药品展示字段,供抽屉从 options[0] 回退读取
drug_name: String(o.drug_name || ''),
image: String(o.image || ''),
specification: String(o.specification || ''),
})),
warehouse_id: Number(options[0].warehouse_id),
});
}
if (!rows.length) {
return null;
}
this.warehouseSelectRows = rows;
this.showWarehouseSelectDrawer = true;
return new Promise((resolve) => {
this._warehouseSelectResolve = resolve;
});
} catch (e) {
console.error('加载配送仓库失败', e);
this.$toast((e && e.message) || '加载配送仓库失败');
return false;
}
},
onWarehouseSelectConfirm(map) {
const resolve = this._warehouseSelectResolve;
this._warehouseSelectResolve = null;
this.showWarehouseSelectDrawer = false;
if (typeof resolve === 'function') resolve(map || {});
},
onWarehouseSelectCancel() {
const resolve = this._warehouseSelectResolve;
this._warehouseSelectResolve = null;
this.showWarehouseSelectDrawer = false;
if (typeof resolve === 'function') resolve(false);
},
async executeSendPrescription(doctorSecondSign = 0, sendMode = 0, customStoreId = null) {
try {
// 在线复诊有绑仓时先卡片选仓
const warehouseMap = await this.resolvePharmacyWarehouseMap();
if (warehouseMap === false) {
this.isSubmitting = false;
return;
}
if (this.activeCategory === 2) {
if (!this.patientInfo || !this.patientId) {
if (this.patientId && !this.patientInfo) await this.loadPatientInfo();
if (!this.patientInfo || !this.patientId) {
this.$toast('患者信息缺失,无法发送处方');
this.isSubmitting = false; return;
}
}
const invalidDrug = this.currentDrugs.find((drug) => {
return !(drug.id && (drug.select_number || drug.number) && drug.time_id && drug.type_id && drug.frequency_id && drug.unit_id && drug.use_type && drug.use_type.name && drug.use_num && drug.use_num.name && drug.unit && drug.unit.name);
});
if (invalidDrug) { this.$toast('请先为西药设置完整的用法用量'); return; }
}
let finalSendMode = sendMode; let finalStoreId = customStoreId;
const storeInfo = this.registerStoreInfo;
if (storeInfo?.is_from_transfer === 1 && storeInfo?.delegate_store_id) {
finalSendMode = 1; finalStoreId = storeInfo.delegate_store_id;
}
const clinical_diagnose = this.diagnosisText || this.diagnoses.map((item) => item.name).join(',');
let doctorSecondSignValue = typeof doctorSecondSign === 'number' ? doctorSecondSign : (typeof doctorSecondSign === 'object' && doctorSecondSign !== null && typeof doctorSecondSign.value === 'number' ? doctorSecondSign.value : (typeof doctorSecondSign === 'string' ? parseInt(doctorSecondSign, 10) || 0 : (doctorSecondSign ? 1 : 0)));
const patientData = { id: this.patientInfo.id || this.patientId, user_id: this.patientInfo.user_id || null, name: this.patientInfo.name || '', age: this.patientInfo.age || 0, sex: this.patientInfo.sex || 0, mobile: this.patientInfo.mobile || '' };
let drugsData;
if (this.activeCategory === 1) {
drugsData = this.currentDrugs.map((drug) => ({ index_id: drug.index_id, id: drug.id, drug_name: drug.drug_name || drug.name, number: drug.number || drug._quantity || 0, price: drug.price || '0.0000', way_id: drug.way_id || 0, select_number: drug.select_number || 1 }));
} else if ([3, 5, 6, 7].includes(this.activeCategory)) {
drugsData = this.currentDrugs.map((drug) => {
const cleanDrug = { index_id: drug.index_id, id: drug.id, drug_name: drug.drug_name || drug.name, number: drug.number || 1, price: drug.price || '0.0000', select_number: drug.select_number || 1 };
if (drug.image) cleanDrug.image = drug.image;
if (drug.instruction) cleanDrug.instruction = drug.instruction;
if (drug.type) cleanDrug.type = drug.type;
return cleanDrug;
});
} else {
drugsData = this.currentDrugs.map(drug => {
const drugData = { ...drug };
if (!drugData.use_type || typeof drugData.use_type !== 'object' || !drugData.use_type.name) drugData.use_type = (drugData.type_id && this.drugUseList.drug_use_type) ? this.drugUseList.drug_use_type.find(item => item.id === drugData.type_id) || { name: '' } : { name: '' };
if (!drugData.use_num || typeof drugData.use_num !== 'object' || !drugData.use_num.name) drugData.use_num = (drugData.time_id && this.drugUseList.drug_time) ? this.drugUseList.drug_time.find(item => item.id === drugData.time_id) || { name: '' } : { name: '' };
if (!drugData.unit || typeof drugData.unit !== 'object' || !drugData.unit.name) drugData.unit = (drugData.unit_id && this.drugUseList.drug_unit) ? this.drugUseList.drug_unit.find(item => item.id === drugData.unit_id) || { name: '' } : { name: '' };
return drugData;
});
}
const params = {
patient: patientData, drugs: drugsData, diagnosis: clinical_diagnose, medicalAdvice: this.medicalAdvice, total: parseFloat(this.totalPrice), category: this.feeCategory, drug_type: 2, register_id: parseInt(this.registerId), treatment_price: parseFloat(this.treatmentPrice || 0), prescription_type: this.activeCategory, doctor_second_sign: doctorSecondSignValue, send_mode: finalSendMode, custom_store_id: finalSendMode === 1 ? finalStoreId : null,
salesperson_transfer_prescription_id: this.salespersonTransferPrescriptionId || undefined,
price_discount: this.priceDiscount,
special_prescription_id: Number(this.appliedSpecialPrescriptionId) || 0,
};
if (warehouseMap && typeof warehouseMap === 'object') {
// 列表格式避免数字键对象在 form/部分环境序列化丢失
params.warehouse_map = Object.keys(warehouseMap).map((drugId) => ({
drug_id: Number(drugId),
warehouse_id: Number(warehouseMap[drugId]),
}));
}
if (this.activeCategory === 1) {
params.package_method_id = this.chineseConfig.packageMethodId || null; params.process_rule_id = this.chineseConfig.processRuleId || null; params.process_rule_note_id = this.chineseConfig.processRuleNoteId || null; params.child_process_rule_id = this.chineseConfig.childProcessRuleId || null; params.process_rule_type = this.chineseConfig.ruleType || 1; params.processing_fee = this.processingFee; params.dosage = this.chineseConfig.dosage || 7; params.day_dosage = this.chineseConfig.dayDosage || 2;
if (this.isOnlineRevisit) {
params.diseases_id = this.onlineTcmDiseasesId;
params.method_id = this.onlineTcmMethodId;
params.syndrome_id = this.onlineTcmSyndromeId;
}
}
const res = await addWestPrescription(params);
if (res && (res.code === 0 || res.errcode === 0)) {
this.$toast('发送成功');
const needTransfer = res?.result?.need_transfer || res?.data?.need_transfer || res?.need_transfer;
if (needTransfer) {
const transferId = res?.result?.transfer_prescription_id || res?.data?.transfer_prescription_id || res?.transfer_prescription_id;
if (transferId) {
uni.showModal({
title: '转诊提示', content: '该处方已触发转诊流程,转诊消息已发送给委托诊所的在线问诊医生。', showCancel: false, confirmText: '确定',
success: () => { this.clearPrescriptionData(); PrescriptionStorage.clearAllPrescriptionData(this.registerId); this.safeNavigateBack(500); }
});
return;
}
}
if (this.isOnlineRevisit) {
try {
await this.trySendPrescriptionChatMessage(res);
} catch (e) {
console.error('处方消息同步失败', e);
uni.showToast({ title: '处方消息同步失败', icon: 'none' });
}
}
this.clearPrescriptionData();
PrescriptionStorage.clearAllPrescriptionData(this.registerId);
this.safeNavigateBack(1500);
} else {
this.$toast(res.msg || res.message || '发送失败');
}
} catch (error) {
console.error('发送处方失败:', error);
this.$toast('发送处方失败,请重试');
throw error;
} finally {
this.isSubmitting = false;
}
},
async trySendPrescriptionChatMessage(res) {
const inner = res && (res.result !== undefined ? res.result : res.data !== undefined ? res.data : null);
if (!inner || typeof inner !== 'object') return;
if (!this.registerId) return;
const roomRes = await getChatRoomByRegisterApi(this.registerId);
const roomPayload = roomRes && (roomRes.result || roomRes.data || roomRes);
const roomId = roomPayload && roomPayload.room_id;
const receiverUserId = roomPayload && roomPayload.receiver_user_id;
if (!roomId || !receiverUserId) return;
const doctorId = uni.getStorageSync('doctor_id') || uni.getStorageSync('user_id');
if (!doctorId) return;
const senderUserId = String(doctorId).startsWith('doctor-') ? String(doctorId) : `doctor-${doctorId}`;
try {
await sendToUserHttpApi({
room_id: roomId,
sender_user_id: senderUserId,
receiver_user_id: receiverUserId,
message_type: 4,
message_content: JSON.stringify(inner),
duration: 0
});
} catch (e) {
console.warn('trySendPrescriptionChatMessage', e);
}
},
clearPrescriptionData() {
this.currentDrugs = []; this.diagnoses = []; this.diagnosisText = ''; this.medicalAdvice = ''; this.treatmentPrice = '';
this.chineseConfig = { ruleType: 1, packageMethodId: null, processRuleId: null, childProcessRuleId: null, processRuleNoteId: null, dosage: 7, dayDosage: 2 };
this.onlineTcmDiseasesId = null;
this.onlineTcmMethodId = null;
this.onlineTcmSyndromeId = null;
clearAppliedSpecialPrescription(this);
},
/**
* 清空药方(含特色方导入状态),与 PC 接诊「清空药方」一致
*/
handleClearPrescriptionCart() {
const hasContent = this.currentDrugs.length > 0
|| this.diagnoses.length > 0
|| !!(this.diagnosisText && String(this.diagnosisText).trim())
|| !!(this.medicalAdvice && String(this.medicalAdvice).trim())
|| Number(this.appliedSpecialPrescriptionId) > 0;
if (!hasContent) {
this.$toast('当前无药方内容');
return;
}
uni.showModal({
title: '清空药方',
content: '确定清空当前药方吗?特色方将需重新导入。',
confirmColor: '#FC3636',
success: ({ confirm }) => {
if (!confirm) return;
this.clearPrescriptionData();
this.saveToLocalStorage();
PrescriptionStorage.clearAllPrescriptionData(this.registerId);
this.$toast('已清空药方');
},
});
},
handleSaveAsCommonPrescription() {
if (this.currentDrugs.length === 0) { this.$toast('请先添加药品后再保存为常用方'); return; }
if (![1, 2].includes(this.activeCategory)) { this.$toast('当前分类不支持保存为常用方'); return; }
this.showSaveCommonPrescriptionModal = true;
},
async handleConfirmSaveCommonPrescription() {
if (!this.commonPrescriptionName || !this.commonPrescriptionName.trim()) { this.$toast('请输入常用方名称'); return; }
if (![1, 2].includes(this.activeCategory)) { this.$toast('当前分类不支持保存为常用方'); return; }
try {
const storeId = uni.getStorageSync('store_id') || 11001;
if (this.activeCategory === 1) {
const drugs = this.currentDrugs.map((drug) => ({ drug_id: drug.id || drug.drug_id, drug_name: drug.drug_name || drug.name, number: drug.number || 1, price: drug.price || 0, way_id: drug.way_id || 0 }));
const rt = this.chineseConfig.ruleType;
const payload = { name: this.commonPrescriptionName.trim(), drugs, store_id: storeId, clinical_diagnose: this.diagnosisText || '', doctor_order: this.medicalAdvice || '', category: '1', dosage: this.chineseConfig.dosage || 7, day_dosage: this.chineseConfig.dayDosage || 2, rule_type: rt };
if (rt === 1) payload.package_method_id = this.chineseConfig.packageMethodId;
if (rt === 2) { payload.process_rule_id = this.chineseConfig.processRuleId || undefined; payload.child_process_rule_id = this.chineseConfig.childProcessRuleId || undefined; payload.process_rule_note_id = this.chineseConfig.processRuleNoteId || undefined; }
const resCn = await saveChineseCommonPrescriptionApi(payload);
if (!resCn || resCn.code !== 0) { this.$toast(resCn.msg || resCn.message || '保存失败'); return; }
} else {
const drugs = this.currentDrugs.map((drug) => ({ drug_id: drug.id || drug.drug_id, drug_name: drug.drug_name || drug.name, number: drug.number || 1, select_number: drug.select_number || 1, price: drug.price || 0, time_id: drug.time_id || 0, type_id: drug.type_id || 0, frequency_id: drug.frequency_id || 0, unit_id: drug.unit_id || 0, image: drug.image || '', instruction: drug.instruction || '', specification: drug.specification || '' }));
const resW = await saveWestCommonPrescriptionApi({ name: this.commonPrescriptionName.trim(), drugs, store_id: storeId, clinical_diagnose: this.diagnosisText || '', doctor_order: this.medicalAdvice || '', category: '1' });
if (!resW || resW.code !== 0) { this.$toast(resW.msg || resW.message || '保存失败'); return; }
}
this.$toast('保存成功');
this.showSaveCommonPrescriptionModal = false; this.commonPrescriptionName = '';
if (this.isCommonPrescription) { uni.removeStorageSync('add'); this.safeNavigateBack(400); }
} catch (error) { console.error('保存常用方失败:', error); this.$toast('保存常用方失败,请重试'); }
},
buildSalespersonTransferPayload(patientData) {
const drugs = this.currentDrugs.map((drug) => ({
drug_id: drug.id || drug.drug_id || drug.index_id,
drug_name: drug.drug_name || drug.name,
number: drug.number || drug._quantity || 1,
way_id: drug.way_id || 0,
}));
return {
patient_name: patientData.patient_name,
patient_mobile: patientData.patient_mobile,
patient_address: patientData.patient_address,
prescription_images: patientData.prescription_images || [],
remark: patientData.remark || '',
transfer_mode: 1,
clinical_diagnose: '',
doctor_order: '',
dosage: this.chineseConfig.dosage || 7,
day_dosage: this.chineseConfig.dayDosage || 2,
drugs,
};
},
async handleTransferPatientConfirm(patientData) {
this.transferDrawerSubmitting = true;
try {
const wrap = await createClinicSalespersonTransferPrescription(
this.buildSalespersonTransferPayload(patientData)
);
if (wrap && wrap.ok) {
PrescriptionStorage.clearAllPrescriptionData(SALESPERSON_TRANSFER_STORAGE_KEY);
this.showTransferPatientDrawer = false;
if (this.$refs.transferPatientDrawer && this.$refs.transferPatientDrawer.resetForm) {
this.$refs.transferPatientDrawer.resetForm();
}
// #ifdef MP-WEIXIN
this.setPageBackGuardShow(false, { suppressLeave: true });
uni.disableAlertBeforeUnload();
// #endif
this.$toast('提交成功');
setTimeout(() => {
uni.navigateTo({ url: '/subPackages/sub_clinic_salesperson/transfer-prescription/list' });
}, 500);
} else {
this.$toast(wrap?.res?.msg || wrap?.res?.message || '提交失败');
}
} catch (error) {
console.error('提交传方失败:', error);
this.$toast('提交传方失败,请重试');
} finally {
this.transferDrawerSubmitting = false;
}
},
tryLeaveSalespersonTransferPage(onConfirm) {
if (this.hasTransferDraft) {
uni.showModal({
title: '提示',
content: '有未提交的传方内容,确定退出?',
success: ({ confirm }) => {
if (confirm) {
// #ifdef MP-WEIXIN
this.setPageBackGuardShow(false, { suppressLeave: true });
uni.disableAlertBeforeUnload();
// #endif
if (typeof onConfirm === 'function') onConfirm();
} else {
// #ifdef MP-WEIXIN
this.setPageBackGuardShow(this.shouldEnablePageBackGuard, {
suppressLeave: !this.shouldEnablePageBackGuard,
});
// #endif
}
},
});
return;
}
// #ifdef MP-WEIXIN
this.setPageBackGuardShow(false, { suppressLeave: true });
uni.disableAlertBeforeUnload();
// #endif
if (typeof onConfirm === 'function') onConfirm();
},
navigateToSalespersonWorkbench() {
const pages = getCurrentPages();
if (pages.length > 1) {
this.safeNavigateBack(0);
return;
}
// #ifdef MP-WEIXIN
this.setPageBackGuardShow(false, { suppressLeave: true });
uni.disableAlertBeforeUnload();
// #endif
uni.reLaunch({ url: SALESPERSON_HOME_URL });
},
/** 若有弹层打开则关闭第一个并返回 true用于导航返回 / 微信 page-container leave 先关抽屉) */
closeTopOverlayIfAny() {
for (let i = 0; i < PRESCRIPTION_OVERLAY_KEYS.length; i++) {
const k = PRESCRIPTION_OVERLAY_KEYS[i];
if (this[k]) {
this[k] = false;
return true;
}
}
return false;
},
/** 微信小程序:先关闭 page-container 拦截再退栈,避免 navigateBack 被吞 */
safeNavigateBack(delayMs = 0) {
const runNav = () => {
// #ifdef MP-WEIXIN
this.setPageBackGuardShow(false, { suppressLeave: true });
setTimeout(() => {
uni.navigateBack();
}, 16);
// #endif
// #ifndef MP-WEIXIN
uni.navigateBack();
// #endif
};
if (delayMs > 0) {
setTimeout(runNav, delayMs);
} else {
runNav();
}
},
// #ifdef MP-WEIXIN
onPageBackGuardLeave() {
if (this._suppressPageBackGuardLeave) {
this._suppressPageBackGuardLeave = false;
return;
}
if (this.closeTopOverlayIfAny()) {
this.$nextTick(() => {
this.setPageBackGuardShow(this.shouldEnablePageBackGuard, { suppressLeave: true });
this.syncSalespersonTransferUnloadAlert();
});
return;
}
this.setPageBackGuardShow(false);
if (this.isSalespersonTransferMode) {
this.tryLeaveSalespersonTransferPage(() => this.navigateToSalespersonWorkbench());
return;
}
this.safeNavigateBack(0);
},
// #endif
handleCustomBack() {
if (this.closeTopOverlayIfAny()) return;
if (this.isSalespersonTransferMode) {
this.tryLeaveSalespersonTransferPage(() => this.navigateToSalespersonWorkbench());
return;
}
this.safeNavigateBack(0);
}
}
};
</script>
<style lang="scss" scoped>
/* 基础与通用样式 */
.page-bg {
background-color: #F4F6F8;
}
/* 整页纵向 flex顶部 chrome 不收缩swiper 吃满剩余高度 */
.page-flex {
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
box-sizing: border-box;
}
.rx-flex-fixed {
flex-shrink: 0;
}
.safe-area-inset-bottom { padding-bottom: env(safe-area-inset-bottom); }
.mx-32 { margin-left: 32rpx; margin-right: 32rpx; }
.m-x-32 { margin-left: 32rpx; margin-right: 32rpx; }
.m-b-24 { margin-bottom: 24rpx; }
.m-b-32 { margin-bottom: 32rpx; }
.m-b-40 { margin-bottom: 40rpx; }
.m-t-12 { margin-top: 12rpx; }
.m-t-16 { margin-top: 16rpx; }
.m-t-24 { margin-top: 24rpx; }
.m-t-64 { margin-top: 64rpx; }
.m-l-8 { margin-left: 8rpx; }
.m-l-16 { margin-left: 16rpx; }
.m-l-24 { margin-left: 24rpx; }
.m-l-32 { margin-left: 32rpx; }
.m-r-4 { margin-right: 4rpx; }
.m-r-8 { margin-right: 8rpx; }
.m-r-16 { margin-right: 16rpx; }
.m-r-32 { margin-right: 32rpx; }
.p-32 { padding: 32rpx; }
.p-b-40 { padding-bottom: 40rpx; }
.p-x-32 { padding-left: 32rpx; padding-right: 32rpx; }
.pt-20 { padding-top: 20rpx; }
.pt-32 { padding-top: 32rpx; }
/* 字体与颜色层级 */
.fs-24 { font-size: 24rpx; }
.fs-26 { font-size: 26rpx; }
.fs-28 { font-size: 28rpx; }
.fs-30 { font-size: 30rpx; }
.fs-32 { font-size: 32rpx; }
.fs-48 { font-size: 48rpx; }
.font-bold { font-weight: bold; }
.color-title { color: #2A2E35; }
.color-sub { color: #8A92A3; }
.color-primary { color: #00A88A; }
.color-danger { color: #FF4D4F; }
.color-price { color: #F53F3F; }
.price-link { color: #6ACDBB; text-decoration: underline; }
.text-gray { color: #6C7380; }
.white { background: #fff; }
.radius-12 { border-radius: 12rpx; }
.shadow-sm { box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.03); }
.border-t-dashed { border-top: 1px dashed #E2E8F0; }
.line-clamp-2 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.w-70 { width: 70%; }
/* Flex 布局辅助 */
.flex { display: flex; }
.flex-wrap { flex-wrap: wrap; }
.flex-row { display: flex; flex-direction: row; }
.flex-col { display: flex; flex-direction: column; }
.flex-jus-sp { justify-content: space-between; }
.flex-ali-center { align-items: center; }
.flex-ali-start { align-items: flex-start; }
.flex-jus-center { justify-content: center; }
.flex-1 { flex: 1; }
.ac { text-align: center; }
/* 定制化组件样式 */
.rp-icon {
background: #2A2E35;
color: #fff;
font-size: 20rpx;
font-weight: bold;
padding: 4rpx 10rpx;
border-radius: 6rpx 2rpx 6rpx 2rpx;
}
.tabs-wrap {
background: #fff;
border-bottom: 1px solid #F0F2F5;
position: relative;
z-index: 10;
}
.rx-order-swiper {
flex: 1;
height: 0;
min-height: 0;
width: 100%;
}
/* 内部滚动区铺满 swiper底部留白避开 fixed 操作栏 */
.rx-order-scroll {
height: 100%;
box-sizing: border-box;
padding-bottom: calc(200rpx + env(safe-area-inset-bottom));
}
.rx-swiper-placeholder {
min-height: 400rpx;
}
/* 现代卡片设计 (西药 / 产品) */
.medicine-card {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 16rpx rgba(42, 46, 53, 0.04);
position: relative;
overflow: hidden;
}
.drug-image-modern {
width: 140rpx;
height: 140rpx;
border-radius: 12rpx;
background-color: #F7F8FA;
flex-shrink: 0;
border: 1px solid #F0F2F5;
}
.usage-capsule {
display: inline-flex;
align-items: center;
background: #E8F6F4;
padding: 8rpx 16rpx;
border-radius: 6rpx;
}
.card-actions {
margin-top: 24rpx;
padding-top: 20rpx;
border-top: 1px dashed #F0F2F5;
}
.action-btn-text {
display: flex;
align-items: center;
font-size: 26rpx;
&.danger { color: #8A92A3; } /* 弱化删除,避免误触 */
}
.action-btn-ghost {
padding: 10rpx 32rpx;
border-radius: 30rpx;
font-size: 26rpx;
font-weight: bold;
&.primary {
color: #00A88A;
background: #E8F6F4;
}
}
/* 中药极简高密度标签云 */
.tcm-tag-cloud {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.tcm-tag {
display: inline-flex;
align-items: center;
background: #F7F8FA;
padding: 12rpx 20rpx;
border-radius: 8rpx;
border: 1px solid #EBEFF5;
transition: all 0.2s;
.tcm-name {
font-size: 28rpx;
color: #2A2E35;
font-weight: 500;
}
.tcm-weight {
font-size: 26rpx;
color: #00A88A;
margin-left: 12rpx;
}
.tcm-way {
font-size: 22rpx;
color: #5b8ff9;
margin-left: 8rpx;
font-weight: 500;
}
.tcm-margin {
font-size: 22rpx;
color: #ff9800;
margin-left: 8rpx;
font-weight: bold;
}
}
/* 简单产品精美步进器 */
.modern-stepper {
background: #F7F8FA;
border-radius: 8rpx;
overflow: hidden;
border: 1px solid #E2E8F0;
.stepper-btn {
width: 50rpx;
height: 50rpx;
display: flex;
align-items: center;
justify-content: center;
color: #6C7380;
font-size: 32rpx;
font-weight: bold;
background: #fff;
}
.stepper-val {
min-width: 60rpx;
text-align: center;
color: #2A2E35;
font-weight: bold;
}
}
/* 底部操作栏(恢复原状) */
.register-mode-hint {
padding: 16rpx 24rpx;
background: #f0faf8;
border-radius: 12rpx;
border: 1rpx solid #d4efe8;
}
.register-mode-hint__text {
font-size: 24rpx;
color: #008771;
line-height: 1.5;
}
.special-rx-card {
padding: 20rpx 24rpx;
background: #fffaf5;
border-radius: 12rpx;
border: 1rpx solid #ffe0c2;
}
.special-rx-card__inner {
width: 100%;
}
.special-rx-card__action {
width: 100%;
}
.bottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 32rpx;
background: #fff;
border-top: 1px solid #f0f0f0;
z-index: 99;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.btnText {
color: #6ACDBB;
font-size: 28rpx;
padding: 0 32rpx;
flex-shrink: 0;
}
.btnText--danger {
color: #FC3636;
}
</style>