hn-hlw-app/pages/consult/confirm.vue

717 lines
22 KiB
Vue
Raw Normal View History

2026-07-27 11:26:39 +08:00
<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>