hn-hlw-app/pages/consult/confirm.vue
2026-07-27 11:26:39 +08:00

717 lines
22 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>
<full-page mainStyle="background:#f0f0f0" :class="{ 'elder-mode-active': elderMode }">
<!-- <template #header> -->
<view class="form-item-row bg-white" :class="{ 'form-item-row--elder': elderMode }" @click="selectDoctor()">
<view class="form-item-label" :class="{ 'form-item-label--elder': elderMode }" @click.stop="showTip()">
<text>医生</text>
<uni-icons color="#999" type="help" :size="elderMode ? 24 : 18"></uni-icons>
</view>
<view v-if="doctor.doctorName" class="form-item-content" :class="{ 'form-item-content--elder': elderMode }">
{{ doctor.doctorName }} {{ doctor.title }}
</view>
<view v-else class="form-item-content" :class="{ 'form-item-content--elder': elderMode }" style="color: #999">
系统推荐医生
</view>
<view class="form-item-right" :class="{ 'form-item-right--elder': elderMode }">
<image class="doctor-icon" :class="{ 'doctor-icon--elder': elderMode }" src="/static//doctor.svg" />
<uni-icons color="#666" type="right" :size="elderMode ? 24 : 18"></uni-icons>
</view>
</view>
<view v-if="showExpectVideoConsult" class="form-item-row between bg-white"
:class="{ 'form-item-row--elder': elderMode }">
<view class="form-item-label" :class="{ 'form-item-label--elder': elderMode }">期望视频问诊:</view>
<view class="form-item-content flex items-center" :class="{ 'form-item-content--elder': elderMode }"
:style="elderVarStyle">
<view class="flex items-center mr-10" @click="expectVideoConsult = false">
<uni-icons :color="themeColor" :type="expectVideoConsult ? 'circle' : 'circle-filled'" size="24"></uni-icons>
<view class="text-base ml-10" :class="expectVideoConsult ? 'text-dark' : 'text-primary'">否</view>
</view>
<view class="flex items-center" @click="expectVideoConsult = true">
<uni-icons :color="themeColor" :type="expectVideoConsult ? 'circle-filled' : 'circle'" size="24"></uni-icons>
<view class="text-base ml-10" :class="expectVideoConsult ? 'text-primary' : 'text-dark'">是</view>
</view>
<!-- <switch :checked="expectVideoConsult" @change="onExpectVideoConsultChange" /> -->
</view>
</view>
<view v-if="order.consultType == 'onlineMedicinePurchase'" class="form-item-row between bg-white"
:class="{ 'form-item-row--elder': elderMode }" style="margin-top: 20rpx">
<view class="form-item-label" :class="{ 'form-item-label--elder': elderMode }">互联网诊查费:</view>
<view class="form-item-content fee" :class="{ 'form-item-content--elder': elderMode, 'fee--elder': elderMode }">
¥{{ Number(regFee).toFixed(2) }}
</view>
</view>
<template v-else>
<view class="form-item-row between bg-white" :class="{ 'form-item-row--elder': elderMode }"
style="margin-top: 20rpx;">
<view class="form-item-label" :class="{ 'form-item-label--elder': elderMode }">来源:</view>
<view class="form-item-content" :class="{ 'form-item-content--elder': elderMode }">
{{ drugStore && drugStore.name ? drugStore.name : "" }}
</view>
</view>
<view class="form-item-row between bg-white" :class="{ 'form-item-row--elder': elderMode }">
<view class="form-item-label" :class="{ 'form-item-label--elder': elderMode }">互联网诊查费:</view>
<view class="form-item-content fee" :class="{ 'form-item-content--elder': elderMode, 'fee--elder': elderMode }">
{{ fee }}
</view>
</view>
</template>
<view style="height: 20rpx"></view>
<view class="consult-content" :class="{ 'consult-content--elder': elderMode }">
<view class="form-item-row content-title"
:class="{ 'form-item-row--elder': elderMode, 'content-title--elder': elderMode }">问诊内容 </view>
<view class="form-item-row" :class="{ 'form-item-row--elder': elderMode }">
<view class="form-item-label" :class="{ 'form-item-label--elder': elderMode }">患者信息:</view>
<view class="form-item-content" :class="{ 'form-item-content--elder': elderMode }">
{{ order.name }} {{ order.sex }}
{{ order.age ? order.age + "岁" : "" }}
</view>
</view>
<view class="form-item-row" :class="{ 'form-item-row--elder': elderMode }">
<view class="form-item-label" :class="{ 'form-item-label--elder': elderMode }">线下确诊疾病:</view>
<view class="form-item-content" :class="{ 'form-item-content--elder': elderMode }">{{ diseaseStr }}</view>
</view>
<view class="form-item-row" :class="{ 'form-item-row--elder': elderMode }">
<view class="form-item-label" :class="{ 'form-item-label--elder': elderMode }">病情描述:</view>
<view class="form-item-content" :class="{ 'form-item-content--elder': elderMode }">{{ order.description }}
</view>
</view>
<view class="form-item-row" :class="{ 'form-item-row--elder': elderMode }">
<view class="form-item-label" :class="{ 'form-item-label--elder': elderMode }">上传图片:</view>
<view v-if="allImages.length" class="form-item-content" :class="{ 'form-item-content--elder': elderMode }">
<text>{{ allImages.length }}张</text>
<text class="text-primary" :class="{ 'text-primary--elder': elderMode }" @click="onPreviewImages">查看</text>
</view>
</view>
<view class="form-item-row" :class="{ 'form-item-row--elder': elderMode }">
<view class="form-item-label" :class="{ 'form-item-label--elder': elderMode }">既往史:</view>
<view class="form-item-content" :class="{ 'form-item-content--elder': elderMode }">{{ order.pastHistoryStr }}
</view>
</view>
<view class="form-item-row" :class="{ 'form-item-row--elder': elderMode }" :style="elderVarStyle">
<view v-if="order.consultType == 'onlineMedicinePurchase'" class="form-item-label"
:class="{ 'form-item-label--elder': elderMode }">配药申请:</view>
<view v-else class="form-item-label" :class="{ 'form-item-label--elder': elderMode }">历史用药:</view>
<view class="form-item-content" :class="{ 'form-item-content--elder': elderMode }">
<view v-for="(drug, idx) in order.drugs" :key="drug._id" class="drug" :class="{ 'drug--elder': elderMode }">
<view style="flex-grow: 1">
<view class="flex">
<view class="w-0 flex-grow mr-10 text-base text-dark ">
{{ drug.name }}
</view>
<view v-if="drug.price > 0"> ¥{{ drug.price.toFixed(2) }} </view>
</view>
<view class="flex items-center">
<view class="mr-10 w-0 flex-grow">
<view v-if="drug.specification" class="drug-spec" :class="{ 'drug-spec--elder': elderMode }">
{{ drug.specification }}
</view>
</view>
<view class="drug-quantity flex-shrink-0" :class="{ 'drug-quantity--elder': elderMode }">
x {{ drug.quantity }} {{ drug.unit || "" }}
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view style="height: 80rpx"></view>
<template #footer>
<view :style="elderVarStyle">
<view class="protocol" :class="{ 'protocol--elder': elderMode }" @click="agree = !agree">
<uni-icons v-if="agree" type="checkbox-filled" :size="elderMode ? 32 : 24" :color="themeColor"></uni-icons>
<uni-icons v-else type="circle" :size="elderMode ? 32 : 24" color="#999"></uni-icons>
<view class="protocol_text" :class="{ 'protocol_text--elder': elderMode }">
我已阅读并同意<text class="active" :class="{ 'active--elder': elderMode }"
@click.stop="viewTxt('protocol', '用户服务协议')">用户服务协议</text><text class="active"
:class="{ 'active--elder': elderMode }" @click.stop="viewTxt('agreement', '知情同意书')">知情同意书</text>
</view>
</view>
<view class="confirm-footer" :class="{ 'confirm-footer--elder': elderMode }">
<view class="counter" :class="{ 'counter--elder': elderMode }">
<view v-if="order.consultType === 'onlineMedicinePurchase'" class="leading-normal">
<view class="flex items-center">
<view class="flex-shrink-0 text-base text-dark">合计 </view>
<view class="text-lg font-semibold text-danger">{{ costDetails.totalFee.toFixed(2) }}</view>
</view>
<view>
<text class="text-sm text-gray"></text>
<text class="text-sm text-gray">药品费{{ costDetails.medFee.toFixed(2) }}+</text>
<text class="text-sm text-gray">诊查费{{ costDetails.regFee.toFixed(2) }}</text>
<text class="text-sm text-gray"></text>
</view>
</view>
<view v-else>
合计<text class="fee" :class="{ 'fee--elder': elderMode }">{{ fee }}</text>
</view>
</view>
<view class="pay-btn" :class="{ 'pay-btn--elder': elderMode }" @click="pay()">
{{ order.consultType === 'onlineMedicinePurchase' ? '发起问诊' : '立即支付' }}
</view>
</view>
</view>
</template>
</full-page>
</template>
<script setup>
import { computed, ref } from "vue";
import { storeToRefs } from "pinia";
import { onLoad, onUnload, onShow } from "@dcloudio/uni-app";
import useUser from "@/hooks/useUser";
import useElder from "@/hooks/useElder";
import BigNumber from "bignumber.js";
import orderStore from "@/store/order";
import {
addConsultOrder,
updateConsultOrderStatus,
refreshOrderPayStatus,
} from "@/utils/api";
import { toast, confirm, loading, hideLoading } from "@/utils/widget";
import { useIMStore } from "@/store/tim";
import { startConsultOrder } from "@/utils/api";
import fullPage from "@/components/full-page.vue";
import ybPlugin from "@/utils/insurance-plugin";
import { themeColor } from '@/utils/theme-config';
// 长辈模式状态
const { elderMode, elderVarStyle } = storeToRefs(useElder())
const { getAuthCode, drugStore, getStore, storeId } = useUser();
const { order, regFee, formatDrugs } = storeToRefs(orderStore());
const diseaseStr = computed(() => order.value.diseases.join(","));
const fee = computed(() => {
const chargeItem = drugStore.value && Array.isArray(drugStore.value.chargeItems) ? drugStore.value.chargeItems[0] : null;
const chargeFee = chargeItem && typeof chargeItem.fee == 'number' ? `${chargeItem.fee.toFixed(2)}` : '';
return chargeFee;
})
const orderId = ref("");
const medOrderNo = ref("");
const registerId = ref("");
const doctor = ref({});
const agree = ref(false);
const waiting = ref(false);
const paying = ref(false);
const paidSuccess = ref(false)
const orderDoctor = ref({})
const showExpectVideoConsult = false
const expectVideoConsult = ref(false)
// 用药证明图片
const drugProofImages = computed(() => order.value.drugs.reduce((list, med) => {
if (Array.isArray(med.proofImages)) {
return [...list, ...med.proofImages]
}
return list
}, []))
const allImages = computed(() => [...order.value.images, ...drugProofImages.value])
const costDetails = computed(() => {
const medFee = order.value.drugs.reduce((acc, item) => {
if (item.price > 0) {
return acc.plus(new BigNumber(item.totalFee))
}
return acc
}, new BigNumber(0))
const totalFee = medFee.plus(regFee.value > 0 ? regFee.value : 0)
return { medFee: medFee.toNumber(), regFee: regFee.value, totalFee: totalFee.toNumber() }
})
function onExpectVideoConsultChange(e) {
expectVideoConsult.value = e.detail.value
}
function selectDoctor() {
if (orderId.value) {
toast('已生成订单,请勿重新选择医生')
} else {
uni.navigateTo({ url: "/pages/consult/doctor-list" });
}
}
function showTip() {
toast("默认匹配空闲医生,也可选择指定医生问诊");
}
function viewTxt(type, title) {
uni.navigateTo({
url: `/pages/consult/agreement?type=${type}&title=${title}`,
});
}
async function pay() {
if (waiting.value) return;
if (!agree.value) {
toast("请阅读并同意《用户服务协议》和《知情同意书》");
return;
}
if (order.value.drugs.length === 0) {
toast('请添加药品');
return;
}
if (medOrderNo.value) {
waiting.value = true;
const canPay = await loopPayStatus('正在查询订单支付状态...', 3000);
if (canPay) {
pluginPay(medOrderNo.value);
setTimeout(() => (waiting.value = false), 1000)
}
return;
}
waiting.value = true;
try {
const params = {
...order.value,
expectVideoConsult: expectVideoConsult.value,
drugs: formatDrugs.value,
drugStoreId: storeId.value,
doctorCode: doctor.value.doctorNo || '',
};
const {
success,
data,
message
} = await addConsultOrder(params);
if (success) {
orderId.value = data.orderId;
orderDoctor.value = {
doctorName: data.doctorName,
doctorTitle: data.doctorTitle,
deptName: data.deptName,
deptCode: data.deptCode,
doctorCode: data.doctorCode
}
if (params.consultType === 'onlineConsult') {
medOrderNo.value = data.medorg_order_no;
registerId.value = data.registerId;
pluginPay(data.medorg_order_no);
} else if (params.consultType === 'onlineMedicinePurchase') {
enterRoom();
}
} else {
toast(message);
}
} catch (e) {
console.log(e)
toast(e.message || '提交订单失败')
}
waiting.value = false;
}
async function pluginPay(medOrgOrd) {
try {
paying.value = true;
const authCode = await getAuthCode("nhsamp");
ybPlugin.toAuthAndPay({
authCode,
cardNo: order.value.blhno,
medOrgOrd
});
} catch (e) {
paying.value = false;
toast(e);
}
}
// 进入视频间
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 {
// 登录 IM
const res = await imStore.login(userId);
if (!res) {
hideLoading();
toast("IM 登录失败");
return;
}
// 等待 SDK 准备好
await new Promise((resolve) => {
if (imStore.isSDKReady) {
resolve();
} else {
const timer = setInterval(() => {
if (imStore.isSDKReady) {
clearInterval(timer);
resolve();
}
}, 300);
}
});
// SDK 准备好后,更新昵称
await imStore.updateNickname(`${name}/${sex}/${age}`);
await startConsultOrder({
orderId: userId,
doctorCode,
});
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 onPaid() {
await confirm("支付成功", { showCancel: false });
loopPayStatus();
}
async function loopPayStatus(msg = '正在获取结果...', duration = 5000) {
loading(msg);
await new Promise((resolve) => setTimeout(resolve, duration));
hideLoading();
const { success, status } = await payStatus();
if (success && status === '0') {
paidSuccess.value = true;
await updateConsultOrderStatus({
orderId: orderId.value,
payStatus: "success"
});
enterRoom();
return false
}
if (success && status && status !== '5') {
await updateConsultOrderStatus({
orderId: orderId.value,
payStatus: "failed"
});
uni.redirectTo({
url: `/pages/record/order-detail?&orderId=${orderId.value}`
})
return false
}
return true
}
async function payStatus() {
const { success, status } = await refreshOrderPayStatus({
orderId: orderId.value,
patientId: order.value.patientId,
registerId: registerId.value,
medorgOrderNo: medOrderNo.value,
}, false);
return {
status,
success,
};
}
onLoad(async (e) => {
orderId.value = "";
if (order.value.consultType === 'onlineConsult' && !drugStore.value) {
await getStore().catch(() => {
const pages = getCurrentPages();
uni.navigateBack({ delta: pages.length - 1 })
})
}
uni.$on("on-select-doctor", (d) => (doctor.value = d));
ybPlugin.init({
payCallback: onPaid,
tokenCallback: () => {
uni.redirectTo({
url: "/pages/consult/confirm",
});
},
});
});
onShow(() => {
if (paying.value && orderId.value && paidSuccess.value) {
uni.redirectTo({
url: `/pages/record/order-detail?&orderId=${orderId.value}`
})
} else if (paying.value && orderId.value) {
loopPayStatus()
}
paying.value = false
})
onUnload(() => {
uni.$off("on-select-doctor");
});
function onPreviewImages() {
uni.previewImage({
current: 0,
urls: allImages.value,
});
}
</script>
<style lang="scss" scoped>
@import "./drug.scss";
@import "../../elder-mode.scss";
.drug-proof-tip {
color: #ff4757;
font-size: 24rpx;
line-height: 36rpx;
margin-top: 8rpx;
font-weight: 500;
}
.form-item-row {
display: flex;
align-items: baseline;
padding: 24rpx 30rpx;
font-size: 28rpx;
line-height: 48rpx;
@at-root &+& {
border-top: 1px solid #eee;
}
}
.form-item-label {
display: flex;
align-items: center;
font-size: 28rpx;
color: #666;
width: 200rpx;
}
.form-item-content {
width: 0;
flex-grow: 1;
margin-left: 20rpx;
}
.form-item-right {
margin-left: 20rpx;
flex-shrink: 0;
}
.consult-content {
background: white;
}
.content-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.doctor-icon {
display: inline-block;
transform: translateY(4rpx);
// margin-right: 20rpx;
width: 36rpx;
height: 36rpx;
}
.internet-fee {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 20rpx;
background: white;
padding: 24rpx 30rpx;
font-size: 28rpx;
color: #666;
line-height: 48rpx;
.active {
color: orange;
}
}
.confirm-footer {
padding: 24rpx 30rpx;
background: white;
display: flex;
align-items: center;
justify-content: space-between;
}
.counter {
font-size: 28rpx;
line-height: 80rpx;
}
.fee {
font-size: 32rpx;
color: #ff0000;
}
.pay-btn {
width: 300rpx;
height: 80rpx;
border-radius: 16rpx;
font-size: 32rpx;
color: white;
text-align: center;
background: $theme-brown-primary;
line-height: 80rpx;
}
.text-primary {
color: $theme-brown-primary;
margin-left: 20rpx;
}
.protocol {
display: flex;
align-items: center;
padding: 30rpx 30rpx 0;
background: white;
line-height: 40rpx;
color: #666;
.active {
color: $theme-brown-primary;
}
.protocol_text {
font-size: 24rpx;
margin-left: 10rpx;
}
}
/* 长辈模式样式 */
.form-item-row--elder {
padding: 36rpx 40rpx; // 增加内边距
font-size: 36rpx !important; // 28 * 1.29
line-height: 60rpx !important; // 48 * 1.25
min-height: 80rpx; // 增加最小高度
border-top: 2rpx solid #eee; // 加粗边框
}
.form-item-label--elder {
font-size: 36rpx !important; // 28 * 1.29
font-weight: bold;
width: 240rpx; // 200 * 1.2
}
.form-item-content--elder {
font-size: 36rpx !important; // 28 * 1.29
line-height: 60rpx !important; // 48 * 1.25
font-weight: 500;
margin-left: 24rpx; // 20 * 1.2
}
.form-item-right--elder {
margin-left: 24rpx; // 20 * 1.2
}
.consult-content--elder {
border-radius: 20rpx; // 增加圆角
}
.content-title--elder {
font-size: 44rpx !important; // 32 * 1.375
font-weight: bold !important;
line-height: 60rpx !important;
}
.doctor-icon--elder {
width: 48rpx !important; // 36 * 1.33
height: 48rpx !important; // 36 * 1.33
transform: translateY(6rpx); // 调整位置
}
.drug--elder {
padding: 16rpx 0; // 增加内边距
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;
}
.text-primary--elder {
font-size: 36rpx !important; // 28 * 1.29
font-weight: bold;
margin-left: 24rpx; // 20 * 1.2
}
.fee--elder {
font-size: 44rpx !important; // 32 * 1.375
font-weight: bold;
}
.protocol--elder {
padding: 40rpx 40rpx 0; // 增加内边距
line-height: 56rpx; // 40 * 1.4
}
.protocol_text--elder {
font-size: 32rpx !important; // 24 * 1.33
margin-left: 16rpx !important; // 10 * 1.6
line-height: 48rpx !important;
font-weight: 500;
}
.active--elder {
font-size: 32rpx !important; // 确保链接文字大小一致
font-weight: bold;
}
.confirm-footer--elder {
padding: 36rpx 40rpx; // 增加内边距
}
.counter--elder {
font-size: 36rpx !important; // 28 * 1.29
line-height: 100rpx !important; // 80 * 1.25
font-weight: bold;
}
.pay-btn--elder {
width: 360rpx; // 300 * 1.2
height: 100rpx; // 80 * 1.25
border-radius: 20rpx; // 16 * 1.25
font-size: 40rpx; // 32 * 1.25
line-height: 100rpx; // 调整行高
font-weight: bold;
}
.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;
}
.ml-10 {
margin-left: 20rpx;
}
</style>