2026-07-27 11:26:39 +08:00

744 lines
21 KiB
Vue
Raw Permalink 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>
<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>