744 lines
21 KiB
Vue
Raw Normal View History

2026-07-27 11:26:39 +08:00
<template>
<full-page mainStyle="background:#f0f0f0">
<view class="apply-list" :class="{ 'apply-list--elder': elderMode }" :style="elderVarStyle">
<view class="apply-head justify-between" :class="{ 'apply-head--elder': elderMode }">
<view class="apply-title" :class="{ 'apply-title--elder': elderMode }">申请药品清单</view>
<view class="apply-head-actions">
<view v-if="order.drugs.length" class="apply-head-action text-base text-primary" @click="importHistory()">
<uni-icons type="download" :color="themeColor" :size="elderMode ? 24 : 16" />
<text>导入历史</text>
</view>
<view class="apply-head-action text-base text-primary" @click="toAddDrug()">
<uni-icons type="scan" :color="themeColor" :size="elderMode ? 24 : 16" />
<text>扫码</text>
</view>
</view>
</view>
<template v-if="order.drugs.length > 0">
<view v-for="(drug, idx) in (order.drugs || [])" :key="drug._id" class="drug"
:class="{ 'drug--elder': elderMode }">
<view class="drug-main">
<view class="drug-info">
<view class="drug-name" :class="{ 'drug-name--elder': elderMode }">{{ drug.name }}</view>
<view v-if="drug.medication_proof_desc" class="drug-proof-tip"
:class="{ 'drug-proof-tip--elder': elderMode }">
*购买该药品需上传用药证明
</view>
<view v-if="drug.quantity > drug.recommended_quantity" class="drug-spec drug-spec--error"
:class="{ 'drug-spec--elder': elderMode }">
已超过建议开药数量
</view>
<view v-if="drug.specification" class="drug-spec" :class="{ 'drug-spec--elder': elderMode }">
药品规格{{ drug.specification }}
</view>
<view v-if="drug.manufacturer" class="drug-spec" :class="{ 'drug-spec--elder': elderMode }">
{{ drug.manufactuer }}
</view>
<view v-if="drug.memo" class="drug-memo" :class="{ 'drug-memo--elder': elderMode }">
用法用量{{ drug.memo }}
</view>
</view>
<view class="drug-side">
<input-number-box v-model="drug.quantity" :min="1" :elderMode="elderMode"
:style="drug.quantity > drug.recommended_quantity ? 'color:red' : ''" />
<view class="drug-action" :class="{ 'drug-action--elder': elderMode }">
<view class="drug-action-item drug-action-item--active" :class="{ 'drug-action-item--elder': elderMode }"
@click="edit(drug)">
编辑
</view>
<view class="drug-action-item" :class="{ 'drug-action-item--elder': elderMode }" @click="remove(idx)">
删除
</view>
</view>
</view>
</view>
</view>
<view class="count-bar" :class="{ 'count-bar--elder': elderMode }">
<view class="clear-all" :class="{ 'clear-all--elder': elderMode }" @click="clear()">
<uni-icons color="#999" type="trash" :size="elderMode ? 28 : 20"></uni-icons>
<text>清空</text>
</view>
<view class="continue-add" :class="{ 'continue-add--elder': elderMode }" @click="toAddDrug()">
<uni-icons :color="themeColor" style="margin-right: 6rpx;" type="plusempty"
:size="elderMode ? 16 : 12"></uni-icons>
<text>继续添加</text>
</view>
</view>
</template>
<view v-else class="apply-empty text-center">
<view class="text-base text-dark mb-10">最多添加5种药品</view>
<view class="empty-actions mx-auto flex flex-col items-center px-15 py-10">
<view class="relative overflow-hidden w-full border-primary text-base text-primary rounded"
:class="elderMode ? 'py-20' : 'py-12'" @click="importHistory()">
导入历史处方记录
</view>
<view class="flex items-center split-box w-full border-box">
<view class="split-border flex-grow"></view>
<view class="flex-shrink-0 text-sm text-gray mx-10"></view>
<view class="split-border flex-grow"></view>
</view>
<view class="w-full text-base border-primary text-primary rounded" :class="elderMode ? 'py-20' : 'py-12'"
@click="toAddDrug()">
手动添加药品
</view>
</view>
</view>
</view>
<view class="disease-section">
<view class="disease-title">
<text class="text-danger">*</text>
<text>已在实体医疗机构确诊的疾病</text>
</view>
<view class="disease-list">
<view v-for="disease in order.diseases" :key="disease" class="disease-item">{{ disease }}</view>
<view class="disease-add" @click="toAddDisease()">+ 添加</view>
</view>
</view>
<template #footer>
<footer-button text="提交问诊单" @onClick="submit" />
</template>
</full-page>
<edit-popup :drug="editDrug" :visible="showEditPopup" :elderMode="elderMode" @close="showEditPopup = false"
@change="changeDrug" />
<medication-proof-upload :visible="showProofUpload" :drugs="order.drugs" :elderMode="elderMode"
@close="showProofUpload = false" @save="onProofSaved" />
<confirm-drug-popup :visible="showDrugPopup" :drugs="order.drugs" @close="showDrugPopup = false"
@confirm="confirmDrug" />
<unsuit-drug-popup :visible="showUnsuitDrugPopup" :elderVarStyle="elderVarStyle" :elderMode="elderMode"
@close="showUnsuitDrugPopup = false" @confirm="submit(true, false)" />
<repeat-drug-popup :visible="showRepeatDrugPopup" :elderVarStyle="elderVarStyle" :elderMode="elderMode"
@close="showRepeatDrugPopup = false" @confirm="submit(true, true)" />
<!-- 自定义确认弹窗 -->
<confirm-popup :visible="customConfirmVisible" :title="customConfirmData.title" :content="customConfirmData.content"
:showCancel="customConfirmData.showCancel" :cancelText="customConfirmData.cancelText"
:confirmText="customConfirmData.confirmText" @confirm="customConfirmData.onConfirm"
@cancel="customConfirmData.onCancel" @close="customConfirmVisible = false" />
<confirm-import-popup :consultType="order.consultType" :drugs="displayDrugs" :visible="visible" @close="visible = false"
@confirm="changeDrugs($event)" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { onLoad, onUnload } from '@dcloudio/uni-app';
import { storeToRefs } from "pinia";
import BigNumber from "bignumber.js";
import useElder from '@/hooks/useElder';
import useUser from "@/hooks/useUser";
import useDb from "@/store/db";
import orderStore from '@/store/order';
import { addConsultOrder, checkOnlineDrugInfoStock, getOnlineSimilarDrugInfo, startConsultOrder } from "@/utils/api";
import { toast, loading, hideLoading } from "@/utils/widget";
import { themeColor } from '@/utils/theme-config';
import { useIMStore } from "@/store/tim";
import footerButton from '@/components/footer-button.vue';
import fullPage from '@/components/full-page.vue';
import ConfirmDrugPopup from "./confirm-drug-popup.vue";
import medicationProofUpload from './components/medication-proof-upload.vue';
import editPopup from './select-drug/edit-popup.vue'
import inputNumberBox from './select-drug/input-number-box.vue'
import UnsuitDrugPopup from './components/unsuit-drug-popup.vue';
import RepeatDrugPopup from './components/repeat-drug-popup.vue';
import confirmPopup from '@/components/confirm-popup.vue';
import confirmImportPopup from './history-drugs/confirm-popup.vue'
const editDrug = ref({})
const showDrugPopup = ref(false);
const showEditPopup = ref(false);
const showProofUpload = ref(false);
const showUnsuitDrugPopup = ref(false);
const showRepeatDrugPopup = ref(false);
const displayDrugs = ref([])
const visible = ref(false);
const { order, repeatDrugs, ybVisitRecord, inappropriateDrugs, formatDrugs } = storeToRefs(orderStore());
const { storeId } = useUser();
const waiting = ref(false);
const orderId = ref("");
const orderDoctor = ref({});
// 自定义确认弹窗状态
const customConfirmVisible = ref(false);
const customConfirmData = ref({
title: '提示',
content: '',
showCancel: true,
cancelText: '取消',
confirmText: '确定',
onConfirm: null,
onCancel: null
});
const { elderMode, elderVarStyle } = useElder();
const { attachMedicineConfig } = useDb();
function changeDrug(drug) {
const index = order.value.drugs.findIndex(item => item._id === drug._id)
if (index >= 0) {
order.value.drugs.splice(index, 1, drug)
showEditPopup.value = false
}
}
function confirmDrug(drugs) {
order.value.drugs = drugs
showDrugPopup.value = false;
submit(true, true);
}
function clear() {
customConfirmData.value = {
title: '提示',
content: '确定清空所有药品吗?',
showCancel: true,
cancelText: '取消',
confirmText: '确定',
onConfirm: () => {
order.value.drugs = [];
customConfirmVisible.value = false;
},
onCancel: () => {
customConfirmVisible.value = false;
}
};
customConfirmVisible.value = true;
}
function edit(drug) {
editDrug.value = { ...drug };
showEditPopup.value = true
}
function importHistory() {
uni.navigateTo({ url: '/pages/consult/history-drugs/history-drugs' })
}
function onProofSaved(imagesMap) {
order.value.drugs.forEach(drug => {
if (imagesMap[drug._id]) {
drug.proofImages = imagesMap[drug._id].map(item => item.url)
}
})
submit(true, true)
}
function remove(idx) {
customConfirmData.value = {
title: '提示',
content: '确定删除该药品吗?',
showCancel: true,
cancelText: '取消',
confirmText: '确定',
onConfirm: () => {
order.value.drugs.splice(idx, 1);
customConfirmVisible.value = false;
},
onCancel: () => {
customConfirmVisible.value = false;
}
};
customConfirmVisible.value = true;
}
function toAddDrug() {
uni.navigateTo({ url: '/pages/consult/select-drug/select-drug' })
}
function toAddDisease() {
uni.setStorageSync('diseases-selected', JSON.stringify(order.value.diseases || []));
uni.navigateTo({ url: '/pages/consult/select-disease/select-disease' });
}
function onDiseaseSelected(disease) {
if (!Array.isArray(order.value.diseases)) order.value.diseases = [];
if (!order.value.diseases.includes(disease) && order.value.diseases.length < 5) {
order.value.diseases.push(disease);
}
}
async function checkStock() {
loading('获取药品信息...')
const { success, message, data } = await checkOnlineDrugInfoStock({
accountId: order.value.accountId,
patientName: order.value.name,
patientId: order.value.patientId,
phone: order.value.mobile,
areaCode: order.value.areaCode,
drugs: order.value.drugs.map(drug => ({
insurance_code: drug.insurance_code,
num: drug.quantity
}))
});
hideLoading()
if (success) {
const stockInfo = Array.isArray(data) ? data : [];
order.value.drugs.forEach(med => {
const hisInfo = stockInfo.find(i => med.insurance_code == i.insuranceCode);
const stock = hisInfo ? hisInfo.stock : 0;
const price = hisInfo ? hisInfo.price : 0;
const quantity = Number(med.quantity) > 0 ? Number(med.quantity) : 0;
med.price = price;
med.stock = stock;
med.quantity = quantity;
med.totalFee = new BigNumber(quantity).multipliedBy(price).toNumber();
})
if (order.value.drugs.some(item => !(item.stock >= item.quantity))) {
showDrugPopup.value = true;
return Promise.reject('存在库存不足的药品')
}
} else {
toast(message || '查询his药品信息失败')
return Promise.reject('查询his药品信息失败')
}
}
async function submit(ignoreUnsuit = false, ignoreRepeat = false) {
if (waiting.value) return;
if (order.value.drugs.length == 0) {
toast('请添加药品');
return;
}
if (inappropriateDrugs.value.length && !ignoreUnsuit) {
showUnsuitDrugPopup.value = true;
return
}
if (repeatDrugs.value.length && !ignoreRepeat) {
showRepeatDrugPopup.value = true;
return
}
if (order.value.consultType === 'onlineMedicinePurchase') {
await checkStock()
}
await createConsultOrder()
}
async function createConsultOrder() {
waiting.value = true;
try {
if (orderId.value) {
await enterRoom();
return;
}
const params = {
...order.value,
expectVideoConsult: false,
drugs: formatDrugs.value,
drugStoreId: storeId.value,
doctorCode: '',
};
const { success, data, message } = await addConsultOrder(params);
if (!success) {
toast(message || '提交订单失败');
return;
}
orderId.value = data.orderId;
orderDoctor.value = {
doctorName: data.doctorName,
doctorTitle: data.doctorTitle,
deptName: data.deptName,
deptCode: data.deptCode,
doctorCode: data.doctorCode
};
await enterRoom();
} catch (e) {
console.error("提交订单失败:", e);
toast(e.message || '提交订单失败');
} finally {
waiting.value = false;
}
}
async function enterRoom() {
const { sex, age, name } = order.value;
const doctorCode = orderDoctor.value.doctorCode;
const userId = orderId.value;
if (!doctorCode) {
toast("进入视频间错误,医生视频账号不存在");
return;
}
if (!userId) {
toast("进入视频间错误,用户视频账号不存在");
return;
}
loading("正在进入视频间, 请稍后等待");
const imStore = useIMStore();
try {
const res = await imStore.login(userId);
if (!res) {
hideLoading();
toast("IM 登录失败");
return;
}
await new Promise((resolve) => {
if (imStore.isSDKReady) {
resolve();
} else {
const timer = setInterval(() => {
if (imStore.isSDKReady) {
clearInterval(timer);
resolve();
}
}, 300);
}
});
await imStore.updateNickname(`${name}/${sex}/${age}`);
const conversationID = `C2C${doctorCode}`;
await imStore.enterConversation(conversationID);
hideLoading();
uni.redirectTo({
url: `/pages/chat/index?conversationID=${conversationID}&orderId=${orderId.value}`,
});
} catch (error) {
hideLoading();
console.error("进入会话失败:", error);
toast(`进入会话失败: ${error.message || "未知错误"}`);
}
}
async function autoFillMedicine() {
const drugList = Array.isArray(ybVisitRecord.value?.drugList) ? ybVisitRecord.value?.drugList : [];
if (order.value.consultType === 'onlineMedicinePurchase' && drugList.length > 0 && order.value.drugs.length === 0) {
const payload = {
accountId: order.value.accountId,
patientId: order.value.patientId,
patientName: order.value.name,
phone: order.value.mobile,
drugs: drugList.map(i => ({
drugName: i.drugName,
spec: i.spec,
manufacturer: i.manufacturer,
insurance_code: i.insurance_code
}))
}
const res = await getOnlineSimilarDrugInfo(payload);
const matchDrugs = Array.isArray(res.drugs) ? res.drugs : [];
const similarDrugs = Array.isArray(res.similarDrugs) ? res.similarDrugs : [];
displayDrugs.value = drugList.map(i => {
const matchDrug = matchDrugs.find(drug => drug.insurance_code === i.insurance_code);
if (matchDrug) {
return { ...i, matchDrug, quantity: matchDrug.recommended_quantity }
}
const key = i.insurance_code.slice(0, -5);
const replaceDrugs = similarDrugs.filter(i => i.insurance_code.startsWith(key)).slice(0, 2);
return { ...i, usePreUsage: false, replaceDrugs }
})
visible.value = true;
}
}
function changeDrugs(drugs) {
order.value.drugs = drugs;
visible.value = false;
}
onLoad(() => {
attachMedicineConfig()
uni.$on('on-disease-selected', onDiseaseSelected)
});
onUnload(() => {
uni.$off('on-disease-selected', onDiseaseSelected)
})
onMounted(() => {
autoFillMedicine()
})
</script>
<style lang="scss" scoped>
@import './drug.scss';
.drug-proof-tip {
color: #ff4757;
font-size: 24rpx;
line-height: 36rpx;
margin-top: 8rpx;
font-weight: 500;
}
.apply-list {
margin-top: 0;
background: white;
}
.apply-head {
display: flex;
align-items: center;
color: #333;
padding: 24rpx 30rpx;
border-bottom: 1px solid #e5e5e5;
}
.apply-title {
font-size: 32rpx;
font-weight: bold;
}
.apply-head-actions {
display: flex;
align-items: center;
gap: 28rpx;
}
.apply-head-action {
display: flex;
align-items: center;
gap: 6rpx;
white-space: nowrap;
}
.drug-main {
display: flex;
align-items: flex-start;
justify-content: space-between;
width: 100%;
}
.drug-info {
min-width: 0;
flex: 1;
padding-right: 24rpx;
}
.drug-side {
display: flex;
flex-shrink: 0;
flex-direction: column;
align-items: flex-end;
}
.apply-subtitle {
font-size: 28rpx;
color: #999;
}
.apply-text {
font-size: 28rpx;
line-height: 48rpx;
color: #999;
}
.apply-btn {
margin: 30rpx auto 0;
width: 240rpx;
height: 60rpx;
font-size: 28rpx;
line-height: 60rpx;
color: white;
background: $theme-brown-primary;
border-radius: 40rpx;
}
.count-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 30rpx;
}
.empty-actions {
width: 480rpx;
}
.clear-all {
display: flex;
align-items: center;
color: #999;
font-size: 28rpx;
}
.continue-add {
display: flex;
align-items: center;
justify-content: center;
padding: 2rpx 0 4rpx;
width: 240rpx;
height: 44rpx;
border: 1px solid $theme-brown-primary;
font-size: 28rpx;
color: $theme-brown-primary;
border-radius: 26rpx;
}
.disease-section {
margin-top: 12rpx;
padding: 30rpx;
background: white;
}
.disease-title {
display: flex;
align-items: center;
gap: 10rpx;
font-size: 28rpx;
}
.disease-list {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 16rpx;
margin-top: 20rpx;
}
.disease-item,
.disease-add {
padding: 8rpx 18rpx;
border: 1px solid $theme-brown-primary;
border-radius: 6rpx;
font-size: 26rpx;
color: $theme-brown-primary;
}
.drug-spec--error {
color: red !important;
}
/* 长辈模式样式 */
.user-header--elder {
padding: 36rpx 40rpx; // 增加内边距
font-size: 44rpx !important; // 32 * 1.375
font-weight: bold;
line-height: 60rpx; // 增加行高
}
.apply-list--elder {
margin-top: 0;
}
.apply-head--elder {
padding: 36rpx 40rpx; // 增加内边距
border-bottom: 2rpx solid #e5e5e5; // 加粗边框
}
.apply-title--elder {
font-size: 44rpx !important; // 32 * 1.375
font-weight: bold;
line-height: 60rpx; // 增加行高
}
.drug--elder {
padding: 36rpx 40rpx; // 增加内边距
border-bottom: 2rpx solid #e5e5e5; // 加粗边框
}
.drug-name-bar--elder {
margin-bottom: 12rpx; // 增加间距
}
.drug-name--elder {
font-size: 42rpx !important; // 30 * 1.4
font-weight: bold;
line-height: 58rpx; // 增加行高
}
.drug-quantity--elder {
font-size: 44rpx !important; // 32 * 1.375
font-weight: bold;
line-height: 58rpx; // 增加行高
}
.drug-spec--elder {
font-size: 36rpx !important; // 28 * 1.29
line-height: 50rpx; // 增加行高
margin-bottom: 8rpx; // 增加间距
font-weight: 500;
}
.drug-memo--elder {
font-size: 36rpx !important; // 28 * 1.29
line-height: 50rpx; // 增加行高
margin-bottom: 8rpx; // 增加间距
font-weight: 500;
}
.drug-proof-tip--elder {
color: #ff4757;
font-size: 32rpx; // 24 * 1.33
line-height: 48rpx; // 36 * 1.33
margin-top: 12rpx; // 8 * 1.5
font-weight: 600;
}
.drug-action--elder {
padding-top: 16rpx; // 增加上边距
margin-top: 16rpx; // 增加外边距
}
.drug-action-item--elder {
height: 56rpx; // 48 * 1.17
line-height: 56rpx; // 增加行高
padding: 0 48rpx; // 增加内边距
font-size: 36rpx !important; // 28 * 1.29
border-radius: 36rpx; // 30 * 1.2
font-weight: bold;
border: 2rpx solid #e5e5e5; // 加粗边框
&.drug-action-item--active {
border: 2rpx solid $theme-brown-primary; // 加粗边框
}
}
.count-bar--elder {
padding: 36rpx 40rpx; // 增加内边距
}
.clear-all--elder {
font-size: 36rpx; // 28 * 1.29
font-weight: 500;
}
.continue-add--elder {
width: 280rpx; // 240 * 1.17
height: 56rpx; // 44 * 1.27
font-size: 36rpx; // 28 * 1.29
border: 2rpx solid $theme-brown-primary; // 加粗边框
border-radius: 32rpx; // 26 * 1.23
font-weight: bold;
}
.apply-empty {
padding: 48rpx 0 56rpx;
}
.apply-text--elder {
font-size: 36rpx !important; // 28 * 1.29
line-height: 60rpx !important; // 48 * 1.25
font-weight: 500;
}
.apply-btn--elder {
margin: 40rpx auto 0; // 增加外边距
width: 280rpx; // 240 * 1.17
height: 80rpx; // 60 * 1.33
font-size: 36rpx; // 28 * 1.29
line-height: 80rpx; // 调整行高
border-radius: 48rpx; // 40 * 1.2
font-weight: bold;
}
.split-box {
margin: 16rpx 40rpx;
}
.split-border {
border-top: 1px solid #eee;
}
.days-tag {
position: absolute;
padding: 6rpx 10rpx;
right: 0;
top: 0;
background: #f56c6c;
border-bottom-left-radius: 8rpx;
border-top-right-radius: 8rpx;
}
.py-20 {
padding-top: 40rpx;
padding-bottom: 40rpx;
}
</style>