在线复诊选择药品、说明主诉
This commit is contained in:
541
components/AssistantConsultationShell.vue
Normal file
541
components/AssistantConsultationShell.vue
Normal file
@@ -0,0 +1,541 @@
|
||||
<template>
|
||||
<view class="consultation-container safe-area-inset-bottom">
|
||||
<view class="consultation-header">
|
||||
<view class="header-back" @click="$emit('back')">
|
||||
<text class="iconfont icon-arrow-left"></text>
|
||||
</view>
|
||||
<view class="header-title">{{ headerTitle }}</view>
|
||||
<view class="header-placeholder"></view>
|
||||
</view>
|
||||
|
||||
<view class="notice-box" v-if="showNotice">
|
||||
<view class="notice-text">
|
||||
<text v-for="(part, index) in parsedNoticeParts" :key="index">
|
||||
<text v-if="part.type === 'text'">{{ part.text }}</text>
|
||||
<text v-else-if="part.type === 'agreement'" class="notice-link" @click="$emit('viewAgreement', part.agreementId)">
|
||||
《{{ part.text }}》
|
||||
</text>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<u-popup
|
||||
v-model="agreementPopupVisible"
|
||||
:safe-area-inset-bottom="true"
|
||||
mode="bottom"
|
||||
height="88%"
|
||||
border-radius="20"
|
||||
:closeable="true"
|
||||
z-index="20000"
|
||||
>
|
||||
<view class="agreement-popup">
|
||||
<view class="agreement-header">
|
||||
<text class="agreement-title">{{ currentAgreementName || '协议详情' }}</text>
|
||||
</view>
|
||||
<view class="agreement-content">
|
||||
<u-parse :html="agreementContent"></u-parse>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
<scroll-view class="messages-scroll" scroll-y="true" :scroll-into-view="scrollIntoView" scroll-with-animation>
|
||||
<view class="messages-container">
|
||||
<view v-for="message in displayedMessages" :key="message.id">
|
||||
<view v-if="message.type === 'welcome'" class="message-item other">
|
||||
<image class="avatar" :src="assistantAvatar" mode="aspectFill"></image>
|
||||
<view class="message-content">
|
||||
<view class="assistant-label">{{ assistantName }}</view>
|
||||
<view class="message-bubble">
|
||||
<text>您好! 我是医生助理, 已收到您的用药申请:「{{ prescriptionInfo }}」, 为了帮助医生判断准确, 请按照您的真实情况回答以下问题。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else-if="message.type === 'question'" class="message-item other" :id="'question-' + message.questionId">
|
||||
<image class="avatar" :src="assistantAvatar" mode="aspectFill"></image>
|
||||
<view class="message-content">
|
||||
<view class="assistant-label">{{ assistantName }}</view>
|
||||
<view class="message-bubble">
|
||||
<text>{{ message.content }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else-if="message.type === 'answer'" class="message-item mine">
|
||||
<image class="avatar" :src="userAvatar" mode="aspectFill"></image>
|
||||
<view class="message-content">
|
||||
<view class="message-bubble">
|
||||
<text>{{ message.content }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else-if="message.type === 'waiting'" class="message-item other">
|
||||
<image class="avatar" :src="assistantAvatar" mode="aspectFill"></image>
|
||||
<view class="message-content">
|
||||
<view class="assistant-label">{{ assistantName }}</view>
|
||||
<view class="message-bubble">
|
||||
<text>请稍等, 正在为您转接医生进行复诊开方...</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else-if="message.type === 'hint'" class="message-item other">
|
||||
<image class="avatar" :src="assistantAvatar" mode="aspectFill"></image>
|
||||
<view class="message-content">
|
||||
<view class="assistant-label">{{ assistantName }}</view>
|
||||
<view class="message-bubble">
|
||||
<text>{{ message.content }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="isTyping" class="message-item other typing-indicator">
|
||||
<image class="avatar" :src="assistantAvatar" mode="aspectFill"></image>
|
||||
<view class="message-content">
|
||||
<view class="assistant-label">{{ assistantName }}</view>
|
||||
<view class="message-bubble typing-bubble">
|
||||
<view class="typing-dots">
|
||||
<view class="dot"></view>
|
||||
<view class="dot"></view>
|
||||
<view class="dot"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view :id="bottomAnchorId" style="height: 1px;"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<slot v-if="customFooterActive && $slots.footer" name="footer"></slot>
|
||||
<view
|
||||
v-else-if="showQuestionButtons && currentQuestion && !isSubmitting && !showWaiting"
|
||||
class="action-footer safe-area-inset-bottom"
|
||||
>
|
||||
<button
|
||||
v-for="(answer, index) in currentQuestionAnswers"
|
||||
:key="index"
|
||||
:class="['action-btn', answerButtonClass(answer, index)]"
|
||||
@click="$emit('answer', answer)"
|
||||
>
|
||||
{{ answer }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="submitting-overlay" v-if="isSubmitting">
|
||||
<view class="submitting-content">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="submitting-text">{{ submittingText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AssistantConsultationShell',
|
||||
props: {
|
||||
headerTitle: {
|
||||
type: String,
|
||||
default: '转诊咨询',
|
||||
},
|
||||
showNotice: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
parsedNoticeParts: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
showAgreement: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
agreementContent: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
currentAgreementName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
displayedMessages: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
assistantAvatar: {
|
||||
type: String,
|
||||
default: '/static/images/assistant-avatar.png',
|
||||
},
|
||||
assistantName: {
|
||||
type: String,
|
||||
default: '医生助理',
|
||||
},
|
||||
userAvatar: {
|
||||
type: String,
|
||||
default: '/static/images/user-avatar.png',
|
||||
},
|
||||
prescriptionInfo: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isTyping: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
scrollIntoView: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
bottomAnchorId: {
|
||||
type: String,
|
||||
default: 'bottom-anchor',
|
||||
},
|
||||
/** 为 true 时展示底部「是/否」等选项(与转诊一致) */
|
||||
showQuestionButtons: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
currentQuestion: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
currentQuestionAnswers: {
|
||||
type: Array,
|
||||
default: () => ['是', '否'],
|
||||
},
|
||||
isSubmitting: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showWaiting: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
submittingText: {
|
||||
type: String,
|
||||
default: '正在提交...',
|
||||
},
|
||||
/** 为 true 时使用 #footer 插槽,避免父组件始终占位导致无法显示默认答题按钮 */
|
||||
customFooterActive: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
agreementPopupVisible: {
|
||||
get() {
|
||||
return this.showAgreement
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:showAgreement', val)
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
answerButtonClass(answer, index) {
|
||||
const answers = this.currentQuestionAnswers
|
||||
const totalAnswers = answers.length
|
||||
if (totalAnswers === 2) {
|
||||
if (answer === '是') return 'yes-btn'
|
||||
if (answer === '否') return 'no-btn'
|
||||
}
|
||||
if (index === 0) return 'yes-btn'
|
||||
return 'no-btn'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.consultation-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: #f8fafc;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.consultation-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 30rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
|
||||
.header-back {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.iconfont {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.header-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-placeholder {
|
||||
width: 60rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.notice-box {
|
||||
margin: 20rpx 30rpx;
|
||||
padding: 24rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 12rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
|
||||
.notice-text {
|
||||
display: block;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.notice-link {
|
||||
color: #0a84ff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.messages-scroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.messages-container {
|
||||
padding: 30rpx;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.avatar {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 10rpx;
|
||||
flex-shrink: 0;
|
||||
background: #f2f2f2;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 70%;
|
||||
margin-left: 16rpx;
|
||||
|
||||
.assistant-label {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
padding: 18rpx 24rpx;
|
||||
font-size: 30rpx;
|
||||
line-height: 1.5;
|
||||
border-radius: 4rpx 12rpx 12rpx 12rpx;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
&.mine {
|
||||
flex-direction: row-reverse;
|
||||
|
||||
.message-content {
|
||||
margin-left: 0;
|
||||
margin-right: 16rpx;
|
||||
align-items: flex-end;
|
||||
|
||||
.message-bubble {
|
||||
background: #0a84ff;
|
||||
color: #fff;
|
||||
border-radius: 12rpx 4rpx 12rpx 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.typing-indicator {
|
||||
.typing-bubble {
|
||||
padding: 18rpx 24rpx;
|
||||
min-width: 80rpx;
|
||||
}
|
||||
|
||||
.typing-dots {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
|
||||
.dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
background: #999;
|
||||
animation: typing 1.4s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.dot:nth-child(1) {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.dot:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.dot:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes typing {
|
||||
0%,
|
||||
60%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: 0.7;
|
||||
}
|
||||
30% {
|
||||
transform: translateY(-10rpx);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.action-footer {
|
||||
padding: 20rpx 30rpx;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
background: #fff;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
border-radius: 44rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.yes-btn {
|
||||
background: #0a84ff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.no-btn {
|
||||
background: #fff;
|
||||
color: #333;
|
||||
border: 2rpx solid #ddd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submitting-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
|
||||
.submitting-content {
|
||||
background: #fff;
|
||||
padding: 60rpx 80rpx;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
|
||||
.loading-spinner {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border: 4rpx solid #f3f3f3;
|
||||
border-top: 4rpx solid #0a84ff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.submitting-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.agreement-popup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.agreement-header {
|
||||
padding: 30rpx 50rpx 20rpx;
|
||||
border-bottom: 1rpx solid #e5e7eb;
|
||||
background: #fff;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.agreement-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.agreement-content {
|
||||
flex: 1;
|
||||
padding: 30rpx 50rpx 40rpx;
|
||||
overflow-y: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
10
pages.json
10
pages.json
@@ -5,7 +5,8 @@
|
||||
// npm安装方式
|
||||
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue",
|
||||
// 消息通知组件
|
||||
"^MessageNotification": "@/components/MessageNotification/MessageNotification.vue"
|
||||
"^MessageNotification": "@/components/MessageNotification/MessageNotification.vue",
|
||||
"^FollowUpDrugDrawer$": "@/subPackages/doctor/components/FollowUpDrugDrawer.vue"
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
@@ -185,6 +186,13 @@
|
||||
"navigationBarTitleText": "商品详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "follow-up/medication-info",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用药信息",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
import {get, post, uploadFile} from './http'
|
||||
|
||||
const prefix = '/order';
|
||||
|
||||
/**
|
||||
* 在线复诊:创建就诊信息(挂号前)
|
||||
*/
|
||||
export async function createRegisterInfoApi(params) {
|
||||
return await post(`${prefix}/create-register-info`, params, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 在线复诊:可选西药列表(双端上架)
|
||||
*/
|
||||
export async function getWesternDrugsForFollowUpApi(params) {
|
||||
return await get(`${prefix}/western-drugs-for-follow-up`, params, 3)
|
||||
}
|
||||
/**
|
||||
* 获取扫码诊所的商品详情
|
||||
* @param params
|
||||
@@ -26,6 +40,11 @@ export async function saveRegisterInfoApi(params) {
|
||||
return await post(`${prefix}/save-register-info`, params, 3)
|
||||
}
|
||||
|
||||
/** 诊所在线复诊:确认是否曾用过所选西药 */
|
||||
export async function confirmFollowUpDrugUseApi(params) {
|
||||
return await post(`${prefix}/confirm-follow-up-drug-use`, params, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询订单的处方状态
|
||||
* @param params
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
@viewPrescription="viewPrescription"
|
||||
@goToOrderMedicine="goToOrderMedicine"
|
||||
@viewOrder="viewOrder"
|
||||
@followUpDrugAnswered="onFollowUpDrugAnswered"
|
||||
/>
|
||||
|
||||
</view>
|
||||
@@ -404,6 +405,25 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
onFollowUpDrugAnswered({ messageId, answerStatus }) {
|
||||
const roomId = this.doctorUserInfo.room_id;
|
||||
const idx = this.messages.findIndex((m) => m.id === messageId);
|
||||
if (idx === -1) return;
|
||||
const row = this.messages[idx];
|
||||
try {
|
||||
const o = typeof row.message_content === 'string' ? JSON.parse(row.message_content) : { ...row.message_content };
|
||||
o.answer_status = answerStatus;
|
||||
const updated = { ...row, message_content: JSON.stringify(o) };
|
||||
this.$set(this.messages, idx, updated);
|
||||
if (typeof ChatManager !== 'undefined' && roomId && ChatManager.roomMessages && ChatManager.roomMessages[roomId]) {
|
||||
const ri = ChatManager.roomMessages[roomId].findIndex((m) => m.id === messageId);
|
||||
if (ri !== -1) this.$set(ChatManager.roomMessages[roomId], ri, updated);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('onFollowUpDrugAnswered', e);
|
||||
}
|
||||
},
|
||||
|
||||
// 加载助理配置信息
|
||||
async loadAssistantConfig() {
|
||||
try {
|
||||
@@ -1011,7 +1031,7 @@ export default {
|
||||
getRegisterStatusText(status) { const map = { 0: '待就诊', 1: '已缴费', 2: '已就诊', 3: '已取消', 4: '已退费' }; return map[status] || '未知状态'; },
|
||||
viewPrescription(id, orderNo) { uni.navigateTo({ url: "/subPackages/my/myrecord-detail?id=" + id + '&no=' + orderNo }); },
|
||||
goToOrderMedicine(id, orderNo) { uni.navigateTo({ url: "/subPackages/my/my-drug/drug-info?id=" + id + '&order_type=prescription' }); },
|
||||
viewRegister(id, orderNo) { uni.navigateTo({ url: "/subPackages/register/register-detail?id=" + id + '&no=' + orderNo }); },
|
||||
viewRegister(id, orderNo) { uni.navigateTo({ url: `/subPackages/register/register-info?id=${id}&type=1` }); },
|
||||
viewOrder(orderId, orderNo) { uni.navigateTo({ url: "/subPackages/my/my-drug/drug-info?id=" + orderId }); },
|
||||
|
||||
// 处理返回操作(自定义返回逻辑)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
>
|
||||
|
||||
<!-- 0. 文本 -->
|
||||
<text v-if="msg.message_type === 0" class="text-content" user-select>{{ parsedContent }}</text>
|
||||
<text v-if="msg.message_type === 0" class="text-content" user-select="true">{{ parsedContent }}</text>
|
||||
|
||||
<!-- 1. 图片 -->
|
||||
<image v-else-if="msg.message_type === 1"
|
||||
@@ -142,10 +142,45 @@
|
||||
<view class="info-row"><text class="label">订单号</text><text class="value order-font">{{ parsedContent.order_no }}</text></view>
|
||||
<view class="info-row"><text class="label">就诊人</text><text class="value">{{ (parsedContent.user_patient && parsedContent.user_patient.name) || '未知' }}</text></view>
|
||||
<view class="info-row highlight"><text class="label">费用</text><text class="value price">¥{{ parsedContent.price }}</text></view>
|
||||
<view class="text-block" v-if="parsedContent.chief_complaint">
|
||||
<text class="block-label">主诉</text>
|
||||
<text class="block-content">{{ parsedContent.chief_complaint }}</text>
|
||||
</view>
|
||||
<view class="info-row" v-if="registerCardDrugNamesLine">
|
||||
<text class="label">所选药品</text>
|
||||
<text class="value">{{ registerCardDrugNamesLine }}</text>
|
||||
</view>
|
||||
<view class="info-row" v-if="parsedContent.number != null && parsedContent.number !== ''">
|
||||
<text class="label">数量</text>
|
||||
<text class="value">各 {{ parsedContent.number }} 盒</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-footer"><button class="action-btn" @click="$emit('viewRegister', parsedContent.id)">查看详情</button></view>
|
||||
</block>
|
||||
|
||||
<!-- 类型11: 复诊用药确认(扩展 flow,与药店购药 type11 互斥展示) -->
|
||||
<block v-else-if="msg.message_type === 11 && parsedContent && parsedContent.flow === 'follow_up_drug'">
|
||||
<view class="card-header bg-gray">
|
||||
<view class="title-group"><u-icon name="question-circle-fill" size="16" color="#64748B"></u-icon><text class="card-title">医生助理</text></view>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view class="text-block" v-if="parsedContent.question">
|
||||
<text class="block-content">{{ parsedContent.question }}</text>
|
||||
</view>
|
||||
<view class="info-row" v-for="(row, idx) in (parsedContent.drugs || [])" :key="'fd-' + idx">
|
||||
<text class="label">{{ row.name || '药品' }}</text>
|
||||
<text class="value">×{{ row.quantity != null ? row.quantity : 1 }}</text>
|
||||
</view>
|
||||
<view v-if="followUpAnsweredLabel" class="info-row">
|
||||
<text class="label">您的选择</text>
|
||||
<text class="value">{{ followUpAnsweredLabel }}</text>
|
||||
</view>
|
||||
<view v-if="parsedContent.answer_status === 'pending'" class="follow-up-pending-readonly">
|
||||
<text class="follow-up-pending-tip">请在挂号后的用药确认页完成选择,此处不可更改</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 类型11: 患者就诊经历 -->
|
||||
<block v-else-if="msg.message_type === 11">
|
||||
<view class="card-header bg-gray">
|
||||
@@ -231,7 +266,6 @@ import {
|
||||
agreeTransferPrescriptionApi,
|
||||
rejectTransferPrescriptionApi
|
||||
} from '@/request/api/transferPrescription'
|
||||
|
||||
export default {
|
||||
name: "MessageBubble",
|
||||
components: {
|
||||
@@ -279,6 +313,26 @@ export default {
|
||||
}
|
||||
}
|
||||
return message_content;
|
||||
},
|
||||
/** 挂号卡片:多选西药名称串联,无则单药 drug.name */
|
||||
registerCardDrugNamesLine() {
|
||||
if (this.msg.message_type !== 10) return '';
|
||||
const pc = this.parsedContent;
|
||||
if (!pc || typeof pc !== 'object') return '';
|
||||
const arr = pc.selected_western_drugs;
|
||||
if (Array.isArray(arr) && arr.length) {
|
||||
return arr.map((d) => d && d.name).filter(Boolean).join('、');
|
||||
}
|
||||
if (pc.drug && pc.drug.name) return pc.drug.name;
|
||||
return '';
|
||||
},
|
||||
followUpAnsweredLabel() {
|
||||
const pc = this.parsedContent;
|
||||
if (!pc || pc.flow !== 'follow_up_drug') return '';
|
||||
const st = pc.answer_status;
|
||||
if (st === 'yes' || st === '1') return '是,曾使用过';
|
||||
if (st === 'no' || st === '0') return '否,未使用过';
|
||||
return '';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -572,6 +626,18 @@ $success-color: #059669;
|
||||
}
|
||||
&.split { display: flex; justify-content: space-between; align-items: center; .link-btn { font-size: 24rpx; color: $primary; padding: 10rpx; } .action-btn { width: 160rpx; } }
|
||||
}
|
||||
|
||||
.follow-up-pending-readonly {
|
||||
margin-top: 12rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
background: #f7f8fa;
|
||||
border-radius: 12rpx;
|
||||
.follow-up-pending-tip {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 原生风格服务通知 (Service Notify Card) ==========
|
||||
|
||||
300
subPackages/doctor/components/FollowUpDrugDrawer.vue
Normal file
300
subPackages/doctor/components/FollowUpDrugDrawer.vue
Normal file
@@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<view class="follow-up-drug-drawer">
|
||||
<view class="trigger" @click="openPopup">
|
||||
<view class="trigger-main">
|
||||
<text v-if="!displaySummary" class="placeholder">请选择药品</text>
|
||||
<text v-else class="summary">{{ displaySummary }}</text>
|
||||
</view>
|
||||
<u-icon name="arrow-right" color="#94A3B8" size="28"></u-icon>
|
||||
</view>
|
||||
|
||||
<u-popup
|
||||
v-model="popupVisible"
|
||||
mode="bottom"
|
||||
border-radius="24"
|
||||
height="66%"
|
||||
:safe-area-inset-bottom="true"
|
||||
:closeable="true"
|
||||
@close="onPopupClose"
|
||||
>
|
||||
<view class="drawer-inner">
|
||||
<view class="drawer-title">选择相关西药</view>
|
||||
<view v-if="!drugList.length" class="drawer-empty">暂无可选西药</view>
|
||||
<scroll-view v-else scroll-y class="drawer-scroll">
|
||||
<view
|
||||
v-for="d in drugList"
|
||||
:key="d.id"
|
||||
class="drawer-row"
|
||||
>
|
||||
<view class="drug-check" :class="{ on: getQty(d.id) > 0 }" @click="toggleRow(d)">
|
||||
<text v-if="getQty(d.id) > 0" class="check-mark">✓</text>
|
||||
</view>
|
||||
<image class="drug-thumb" :src="drugImage(d)" mode="aspectFill" @click="toggleRow(d)" />
|
||||
<view class="drug-meta" @click="toggleRow(d)">
|
||||
<text class="dn">{{ d.drug_name }}</text>
|
||||
<text class="ds" v-if="d.specification">{{ d.specification }}</text>
|
||||
</view>
|
||||
<view class="drawer-row-actions">
|
||||
<u-number-box
|
||||
:value="getQty(d.id)"
|
||||
:min="0"
|
||||
:max="99"
|
||||
:size="22"
|
||||
:index="d.id"
|
||||
@input="onQtyInput(d, $event)"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="drawer-footer safe-area-inset-bottom">
|
||||
<view class="btn-text" @click="clearDraft">清空</view>
|
||||
<view class="btn-primary" @click="confirm">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FollowUpDrugDrawer',
|
||||
props: {
|
||||
drugList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
popupVisible: false,
|
||||
draftQty: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
displaySummary() {
|
||||
const v = this.value || [];
|
||||
if (!v.length) return '';
|
||||
return v.map((i) => `${i.drug_name} * ${i.quantity}`).join(',');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
drugImage(d) {
|
||||
const u = d && d.image;
|
||||
return u && String(u).trim() ? u : '/static/mine/avatar_1.png';
|
||||
},
|
||||
getQty(id) {
|
||||
const q = this.draftQty[id];
|
||||
return q != null && q > 0 ? Number(q) : 0;
|
||||
},
|
||||
initDraftFromValue() {
|
||||
const next = {};
|
||||
for (const it of this.value || []) {
|
||||
const id = it.id;
|
||||
const q = Number(it.quantity);
|
||||
if (id != null && q > 0) {
|
||||
next[id] = q;
|
||||
}
|
||||
}
|
||||
this.draftQty = next;
|
||||
},
|
||||
openPopup() {
|
||||
this.initDraftFromValue();
|
||||
this.popupVisible = true;
|
||||
},
|
||||
onPopupClose() {
|
||||
this.popupVisible = false;
|
||||
},
|
||||
toggleRow(d) {
|
||||
const id = d.id;
|
||||
if (this.getQty(id) > 0) {
|
||||
this.$delete(this.draftQty, id);
|
||||
} else {
|
||||
this.$set(this.draftQty, id, 1);
|
||||
}
|
||||
},
|
||||
onQtyInput(d, val) {
|
||||
const n = Number(val);
|
||||
if (!n || n < 1) {
|
||||
this.$delete(this.draftQty, d.id);
|
||||
} else {
|
||||
this.$set(this.draftQty, d.id, Math.min(99, n));
|
||||
}
|
||||
},
|
||||
clearDraft() {
|
||||
this.draftQty = {};
|
||||
},
|
||||
confirm() {
|
||||
const out = [];
|
||||
for (const d of this.drugList) {
|
||||
const q = this.getQty(d.id);
|
||||
if (q > 0) {
|
||||
out.push({
|
||||
id: d.id,
|
||||
drug_name: d.drug_name || '',
|
||||
quantity: q,
|
||||
});
|
||||
}
|
||||
}
|
||||
this.$emit('input', out);
|
||||
this.$emit('confirm', out);
|
||||
this.popupVisible = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.follow-up-drug-drawer {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 20rpx;
|
||||
margin-top: 12rpx;
|
||||
background: #f8fafc;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #e2e8f0;
|
||||
}
|
||||
|
||||
.trigger-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
font-size: 28rpx;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.summary {
|
||||
font-size: 28rpx;
|
||||
color: #0f172a;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.drawer-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 24rpx 24rpx 0;
|
||||
}
|
||||
|
||||
.drawer-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
text-align: center;
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.drawer-empty {
|
||||
flex: 1;
|
||||
padding: 48rpx 0;
|
||||
text-align: center;
|
||||
color: #94a3b8;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.drawer-scroll {
|
||||
flex: 1;
|
||||
max-height: 55vh;
|
||||
min-height: 200rpx;
|
||||
}
|
||||
|
||||
.drawer-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1rpx solid #f1f5f9;
|
||||
}
|
||||
|
||||
.drug-check {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border: 2rpx solid #cbd5e1;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.on {
|
||||
background: #4175ee;
|
||||
border-color: #4175ee;
|
||||
}
|
||||
|
||||
.check-mark {
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.drug-thumb {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-right: 16rpx;
|
||||
flex-shrink: 0;
|
||||
background: #f1f5f9;
|
||||
}
|
||||
|
||||
.drug-meta {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.dn {
|
||||
font-size: 28rpx;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.ds {
|
||||
font-size: 24rpx;
|
||||
color: #64748b;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-row-actions {
|
||||
flex-shrink: 0;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.drawer-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 0 32rpx;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
color: #64748b;
|
||||
padding: 22rpx 0;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
flex: 2;
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
background: #4175ee;
|
||||
border-radius: 12rpx;
|
||||
padding: 22rpx 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,18 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 标题 -->
|
||||
<view class="title">
|
||||
您需要为谁{{ registerType === '3'? '购药' : '挂号' }}
|
||||
<text>请选择患者信息,以便医生给出更准确的诊疗建议,患者信息仅医生可见</text>
|
||||
|
||||
<!-- 优化1:标题与挂号类型合并区域 -->
|
||||
<view class="header-section">
|
||||
<view class="title-row">
|
||||
<text class="main-title">您需要为谁{{ registerType === '3' || registerType === '2' ? '复诊' : '挂号' }}</text>
|
||||
<view class="register-type-tag">
|
||||
<text class="type-label">{{ registerTypeLabel }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="sub-title">请选择患者信息,以便医生给出更准确的诊疗建议,患者信息仅医生可见</text>
|
||||
</view>
|
||||
|
||||
<!-- 就诊人 -->
|
||||
<view class="detail">
|
||||
<!-- relation -->
|
||||
@@ -19,22 +26,50 @@
|
||||
<image src="@/static/choose/choosed.png" mode=""></image>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 添加就诊人 -->
|
||||
<view class="other" @click="goAdd(0)">
|
||||
<text>+</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 挂号类型展示(只读) -->
|
||||
<view class="card" style="padding-bottom: 0">
|
||||
<view class="card_title">
|
||||
挂号类型
|
||||
<!-- 优化2:现代化的复诊表单 UI(主诉 + 西药多选) -->
|
||||
<view class="card follow-up-card modern-card" v-if="showFollowUpForm">
|
||||
|
||||
<!-- 患者主诉 -->
|
||||
<view class="section-header">
|
||||
<text class="title-text">患者主诉</text>
|
||||
<text class="req-tag">* 必填</text>
|
||||
</view>
|
||||
<view class="card_names">
|
||||
<view class="register-type-display">
|
||||
<text class="type-label">{{ registerTypeLabel }}</text>
|
||||
<view class="input-wrapper">
|
||||
<textarea
|
||||
class="chief-textarea"
|
||||
v-model="chiefComplaint"
|
||||
placeholder="请简要描述症状、持续时间等..."
|
||||
placeholder-style="color:#94A3B8; font-size: 28rpx;"
|
||||
maxlength="2000"
|
||||
auto-height
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="section-divider"></view>
|
||||
|
||||
<!-- 选择西药 -->
|
||||
<view class="section-header">
|
||||
<text class="title-text">选择相关西药</text>
|
||||
<text class="req-tag">* 必选</text>
|
||||
</view>
|
||||
|
||||
<view class="hint-box">
|
||||
<text class="sub-hint">请至少选择一种药品;仅展示本门店与总后台均已上架的西药;数量仅作展示,提交按药品种类关联。</text>
|
||||
</view>
|
||||
|
||||
<view class="drug-container">
|
||||
<view v-if="followUpDrugList.length === 0" class="empty-drugs">暂无可选西药</view>
|
||||
<FollowUpDrugDrawer
|
||||
v-else
|
||||
v-model="selectedFollowUpDrugs"
|
||||
:drug-list="followUpDrugList"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -126,7 +161,7 @@ import {
|
||||
registe
|
||||
} from '@/request/api/api.js'
|
||||
import {checkDev} from "@/utils/utils";
|
||||
import {genOrderApi, saveRegisterInfoApi} from "@/request/api/order";
|
||||
import {createRegisterInfoApi, getWesternDrugsForFollowUpApi, genOrderApi, saveRegisterInfoApi} from "@/request/api/order";
|
||||
import request from "@/request/api/request";
|
||||
export default {
|
||||
data() {
|
||||
@@ -182,6 +217,9 @@ export default {
|
||||
rInfo: [], // 健康信息
|
||||
registerType: "0", // 新增:挂号类型(0线下/1线上)
|
||||
delegateStoreId: '', // 委托诊所ID(购药时使用)
|
||||
chiefComplaint: '',
|
||||
followUpDrugList: [],
|
||||
selectedFollowUpDrugs: [],
|
||||
}
|
||||
},
|
||||
onLoad(e) {
|
||||
@@ -225,17 +263,112 @@ export default {
|
||||
},
|
||||
// 挂号类型标签
|
||||
registerTypeLabel() {
|
||||
// 与 Yii RegisterForm 一致:2=诊所在线复诊,3=药店购药/委托复诊
|
||||
const typeMap = {
|
||||
'0': '线下就诊',
|
||||
'1': '在线问诊',
|
||||
'2': '预约购药',
|
||||
'2': '在线复诊',
|
||||
'3': '在线复诊'
|
||||
};
|
||||
return typeMap[this.registerType] || '未知类型';
|
||||
},
|
||||
/** symptoms 等流程已 create 过就诊信息,不再展示主诉/选药 */
|
||||
hasPrefilledFollowUpInfo() {
|
||||
const r = this.rInfo;
|
||||
return !!(r && typeof r === 'object' && !Array.isArray(r) && r.info_id);
|
||||
},
|
||||
/** 诊所在线复诊(2) 与 药店/症状复诊(3) 共用主诉与多选西药 */
|
||||
showFollowUpForm() {
|
||||
const t = this.registerType;
|
||||
return (t === '2' || t === '3') && !this.hasPrefilledFollowUpInfo;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
checkDev,
|
||||
resolveRegisterStoreId() {
|
||||
return (this.registerType !== '0' && this.delegateStoreId)
|
||||
? this.delegateStoreId
|
||||
: (uni.getStorageSync('store_id') || '11001');
|
||||
},
|
||||
async loadFollowUpWesternDrugs() {
|
||||
if (!this.showFollowUpForm) return;
|
||||
const storeId = this.resolveRegisterStoreId();
|
||||
try {
|
||||
const res = await getWesternDrugsForFollowUpApi({ store_id: storeId });
|
||||
const list = res.data?.result;
|
||||
this.followUpDrugList = Array.isArray(list) ? list : [];
|
||||
} catch (e) {
|
||||
console.error('加载复诊西药列表失败', e);
|
||||
this.followUpDrugList = [];
|
||||
}
|
||||
},
|
||||
followUpDrugIdsForApi() {
|
||||
const ids = [];
|
||||
const seen = new Set();
|
||||
for (const it of this.selectedFollowUpDrugs || []) {
|
||||
const id = Number(it.id);
|
||||
if (id > 0 && !seen.has(id)) {
|
||||
seen.add(id);
|
||||
ids.push(id);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
},
|
||||
/** 在线复诊主诉 + 选药非空(无预填 info_id 时) */
|
||||
validateFollowUpFields() {
|
||||
const chief = String(this.chiefComplaint || '').trim();
|
||||
if (!chief) return '请填写患者主诉';
|
||||
if (this.followUpDrugIdsForApi().length === 0) return '请至少选择一种药品';
|
||||
return '';
|
||||
},
|
||||
async prepareFollowUpRegisterInfo() {
|
||||
let params = typeof this.rInfo === 'string' ? JSON.parse(this.rInfo) : { ...(this.rInfo || {}) };
|
||||
if (params.info_id) {
|
||||
return params;
|
||||
}
|
||||
const err = this.validateFollowUpFields();
|
||||
if (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
const chief = String(this.chiefComplaint || '').trim();
|
||||
const storeId = this.resolveRegisterStoreId();
|
||||
const westernDrugQty = {};
|
||||
let qtySum = 0;
|
||||
for (const d of this.selectedFollowUpDrugs || []) {
|
||||
const id = Number(d.id);
|
||||
const q = Math.min(99, Math.max(1, Number(d.quantity) || 1));
|
||||
if (id > 0) {
|
||||
westernDrugQty[String(id)] = q;
|
||||
qtySum += q;
|
||||
}
|
||||
}
|
||||
const createRes = await createRegisterInfoApi({
|
||||
doctor_id: Number(this.Did),
|
||||
drug_id: '',
|
||||
has_visited: '',
|
||||
has_used_drug: '',
|
||||
illnessInfo: '',
|
||||
type: 2,
|
||||
number: qtySum >= 1 ? qtySum : 1,
|
||||
store_id: Number(storeId) || storeId,
|
||||
chief_complaint: chief,
|
||||
drug_ids: this.followUpDrugIdsForApi(),
|
||||
western_drug_qty: JSON.stringify(westernDrugQty),
|
||||
});
|
||||
const infoId = createRes.data?.result?.info_id;
|
||||
if (!infoId) {
|
||||
throw new Error('创建就诊信息失败');
|
||||
}
|
||||
const followUpDrugs = (this.selectedFollowUpDrugs || [])
|
||||
.map((d) => ({
|
||||
drug_id: Number(d.id),
|
||||
name: d.drug_name || d.name || '',
|
||||
quantity: Math.min(99, Math.max(1, Number(d.quantity) || 1)),
|
||||
}))
|
||||
.filter((d) => d.drug_id > 0);
|
||||
const followUpNumber = followUpDrugs.reduce((s, r) => s + (Number(r.quantity) || 1), 0) || 1;
|
||||
return { ...params, info_id: infoId, follow_up_drugs: followUpDrugs, follow_up_number: followUpNumber, number: followUpNumber };
|
||||
},
|
||||
// 处理挂号类型切换
|
||||
handleTypeChange(type) {
|
||||
this.registerType = type;
|
||||
@@ -336,15 +469,28 @@ export default {
|
||||
},
|
||||
|
||||
// 挂号
|
||||
getAsk() {
|
||||
async getAsk() {
|
||||
uni.showLoading({
|
||||
title: "加载中...",
|
||||
mask: true
|
||||
});
|
||||
// 如果是购药类型(register_type=3)且有委托诊所ID,则使用委托诊所ID;否则使用当前门店ID
|
||||
const storeId = (this.registerType != '0' && this.delegateStoreId)
|
||||
? this.delegateStoreId
|
||||
: (uni.getStorageSync('store_id') || '11001');
|
||||
const storeId = this.resolveRegisterStoreId();
|
||||
let followUpRInfo = this.rInfo;
|
||||
if (this.showFollowUpForm) {
|
||||
try {
|
||||
followUpRInfo = await this.prepareFollowUpRegisterInfo();
|
||||
this.rInfo = followUpRInfo;
|
||||
} catch (e) {
|
||||
uni.hideLoading();
|
||||
this.$refs.uToast.show({
|
||||
title: e.message || '提交失败',
|
||||
type: 'default',
|
||||
icon: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
// const storeId = ((this.registerType === '3' || this.registerType == '2') && this.delegateStoreId)
|
||||
// ? this.delegateStoreId
|
||||
// : (uni.getStorageSync('store_id') || '11001');
|
||||
@@ -362,10 +508,9 @@ export default {
|
||||
|
||||
if (res.data.errcode == 0) {
|
||||
this.register_id = res.data.data.register_id
|
||||
// if (this.registerType === "3" || this.registerType === "2") {
|
||||
if (this.registerType === "3") {
|
||||
// this.rInfo 在 onLoad 中已经解析为对象,直接使用
|
||||
let params = typeof this.rInfo === 'string' ? JSON.parse(this.rInfo) : this.rInfo;
|
||||
if (this.registerType === "3" || this.registerType === "2") {
|
||||
// this.rInfo 在 onLoad 中已经解析为对象;纯复诊流程可能已在 getAsk 中写入 info_id
|
||||
let params = typeof followUpRInfo === 'string' ? JSON.parse(followUpRInfo) : followUpRInfo;
|
||||
|
||||
// 如果有 info_id,使用新接口更新关联;否则用旧接口保存
|
||||
if (params.info_id) {
|
||||
@@ -378,7 +523,7 @@ export default {
|
||||
}
|
||||
}).then(() => {
|
||||
// 跳转到挂号页面,让用户确认挂号并支付
|
||||
const rInfoStr = typeof this.rInfo === 'string' ? this.rInfo : JSON.stringify(this.rInfo);
|
||||
const rInfoStr = typeof followUpRInfo === 'string' ? followUpRInfo : JSON.stringify(followUpRInfo);
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/register/register?id=${this.register_id}&doctor_id=${this.Did}&r_info=${encodeURIComponent(rInfoStr)}`
|
||||
});
|
||||
@@ -386,7 +531,7 @@ export default {
|
||||
console.error('更新就诊信息关联失败:', error);
|
||||
// 即使更新失败,也跳转到挂号页面
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/register/register?id=${this.register_id}&doctor_id=${this.Did}&r_info=${encodeURIComponent(typeof this.rInfo === 'string' ? this.rInfo : JSON.stringify(this.rInfo))}`
|
||||
url: `/subPackages/register/register?id=${this.register_id}&doctor_id=${this.Did}&r_info=${encodeURIComponent(typeof followUpRInfo === 'string' ? followUpRInfo : JSON.stringify(followUpRInfo))}`
|
||||
});
|
||||
});
|
||||
} else {
|
||||
@@ -403,13 +548,13 @@ export default {
|
||||
store_id: uni.getStorageSync('store_id') || '11001',
|
||||
}).then(() => {
|
||||
// 跳转到挂号页面,让用户确认挂号并支付
|
||||
const rInfoStr = typeof this.rInfo === 'string' ? this.rInfo : JSON.stringify(this.rInfo);
|
||||
const rInfoStr = typeof followUpRInfo === 'string' ? followUpRInfo : JSON.stringify(followUpRInfo);
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/register/register?id=${this.register_id}&doctor_id=${this.Did}&r_info=${encodeURIComponent(rInfoStr)}`
|
||||
});
|
||||
}).catch((error) => {
|
||||
console.error('保存患者就诊信息失败:', error);
|
||||
const rInfoStr = typeof this.rInfo === 'string' ? this.rInfo : JSON.stringify(this.rInfo);
|
||||
const rInfoStr = typeof followUpRInfo === 'string' ? followUpRInfo : JSON.stringify(followUpRInfo);
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/register/register?id=${this.register_id}&doctor_id=${this.Did}&r_info=${encodeURIComponent(rInfoStr)}`
|
||||
});
|
||||
@@ -419,7 +564,7 @@ export default {
|
||||
}
|
||||
uni.setStorageSync('register_id', this.register_id)
|
||||
|
||||
const rInfoEncoded = encodeURIComponent(typeof this.rInfo === 'string' ? this.rInfo : JSON.stringify(this.rInfo || {}));
|
||||
const rInfoEncoded = encodeURIComponent(typeof followUpRInfo === 'string' ? followUpRInfo : JSON.stringify(followUpRInfo || {}));
|
||||
if (res.data.data.isHas == 0) {
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/register/register?id=${this.register_id}&doctor_id=${this.Did}&r_info=${rInfoEncoded}`,
|
||||
@@ -481,6 +626,19 @@ export default {
|
||||
},
|
||||
//订阅消息
|
||||
subScriptionMessage() {
|
||||
// 优化:在此处前置执行表单非空校验,阻止无效信息的订阅授权弹窗
|
||||
if (this.showFollowUpForm) {
|
||||
const err = this.validateFollowUpFields();
|
||||
if (err) {
|
||||
this.$refs.uToast.show({
|
||||
title: err,
|
||||
type: 'default',
|
||||
icon: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uni.requestSubscribeMessage({
|
||||
tmplIds: ['O5P8ZZB6T01V5Z66tkbhmzlobSaSRoEfdoCLATuP2Mw',
|
||||
'ddT3v-m_Sr4kXwGCBLf0O4P89mOS5q10LFMZeEuQm10'
|
||||
@@ -494,9 +652,11 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.getInfolist()
|
||||
this.loadFollowUpWesternDrugs()
|
||||
},
|
||||
onShow() {
|
||||
this.getInfolist()
|
||||
this.loadFollowUpWesternDrugs()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -506,25 +666,53 @@ export default {
|
||||
width: 750rpx;
|
||||
max-height: 100%;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
background-color: #F8FAFC; /* 增加浅灰色底层背景,让白色卡片更具呼吸感 */
|
||||
padding-bottom: 200rpx;
|
||||
padding-bottom: calc(140rpx + constant(safe-area-inset-bottom));
|
||||
padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
|
||||
|
||||
.title {
|
||||
width: 686rpx;
|
||||
height: 120rpx;
|
||||
font-size: 36rpx;
|
||||
/* 顶部重构区域 */
|
||||
.header-section {
|
||||
padding: 32rpx 32rpx 0;
|
||||
|
||||
.title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
.main-title {
|
||||
font-size: 38rpx;
|
||||
font-family: PingFang SC-Medium, PingFang SC;
|
||||
font-weight: 500;
|
||||
color: #1E293B;
|
||||
margin: 32rpx 32rpx 0 32rpx;
|
||||
font-weight: 600;
|
||||
color: #0F172A;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
text {
|
||||
display: inline-block;
|
||||
.register-type-tag {
|
||||
background: linear-gradient(135deg, #4175EE 0%, #298dff 100%);
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(65, 117, 238, 0.2);
|
||||
|
||||
.type-label {
|
||||
color: #FFFFFF;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
font-weight: 400;
|
||||
color: #6C7380;
|
||||
margin-top: 16rpx;
|
||||
color: #64748B;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* 原有就诊人卡片区 */
|
||||
.detail {
|
||||
width: 710rpx;
|
||||
height: 280rpx;
|
||||
@@ -540,6 +728,7 @@ export default {
|
||||
margin: 0 16rpx 16rpx 0;
|
||||
border: 2rpx solid #CBD5E1;
|
||||
position: relative;
|
||||
background: #FFFFFF;
|
||||
|
||||
.name {
|
||||
margin: 16rpx 0 8rpx 32rpx;
|
||||
@@ -576,14 +765,11 @@ export default {
|
||||
bottom: -1rpx;
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.active {
|
||||
|
||||
border: 2rpx solid #536DFE;
|
||||
|
||||
background: rgba(83, 109, 254, 0.04);
|
||||
image {
|
||||
display: block;
|
||||
}
|
||||
@@ -595,36 +781,34 @@ export default {
|
||||
line-height: 104rpx;
|
||||
text-align: center;
|
||||
border-radius: 8rpx;
|
||||
border: 2rpx solid #CBD5E1; /* 修复:移除多余的b字符 */
|
||||
border: 2rpx dashed #CBD5E1;
|
||||
background: #FFFFFF;
|
||||
margin-top: -10rpx;
|
||||
|
||||
text {
|
||||
font-size: 68rpx;
|
||||
color: #CBD5E1;
|
||||
color: #94A3B8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 基础卡片样式 */
|
||||
.card {
|
||||
width: 710rpx;
|
||||
background: #FFFFFF;
|
||||
border-radius: 8rpx;
|
||||
border-radius: 16rpx; /* 增加圆角,提升现代感 */
|
||||
margin: 20rpx auto 40rpx;
|
||||
padding: 0 32rpx 190rpx;
|
||||
padding: 0 32rpx 40rpx;
|
||||
font-family: PingFang SC-Regular, PingFang SC;
|
||||
box-shadow: 0 4rpx 24rpx rgba(15, 23, 42, 0.02); /* 添加极弱阴影 */
|
||||
|
||||
.card_title {
|
||||
height: 114rpx;
|
||||
line-height: 114rpx;
|
||||
font-size: 36rpx;
|
||||
font-weight: 500;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #1E293B;
|
||||
border-bottom: 1rpx solid #E2E8F0;
|
||||
}
|
||||
|
||||
.card_name:last-child {
|
||||
border-bottom: 0;
|
||||
border-bottom: 1rpx solid #F1F5F9;
|
||||
}
|
||||
|
||||
.card_names {
|
||||
@@ -634,38 +818,19 @@ export default {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 30rpx;
|
||||
border-bottom: 1rpx solid #E2E8F0;
|
||||
border-bottom: 1rpx solid #F1F5F9;
|
||||
|
||||
text {
|
||||
font-weight: 400;
|
||||
color: #1E293B;
|
||||
}
|
||||
|
||||
::v-deep .u-input {
|
||||
width: 380rpx;
|
||||
}
|
||||
|
||||
.register-type-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.type-label {
|
||||
display: inline-block;
|
||||
padding: 8rpx 24rpx;
|
||||
background: linear-gradient(135deg, #4175EE 0%, #298dff 100%);
|
||||
color: #FFFFFF;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card_history {
|
||||
width: 646rpx;
|
||||
padding: 34rpx 0 32rpx 0;
|
||||
font-size: 30rpx;
|
||||
border-bottom: 1rpx solid #E2E8F0;
|
||||
border-bottom: 1rpx solid #F1F5F9;
|
||||
|
||||
.name {
|
||||
display: flex;
|
||||
@@ -703,7 +868,6 @@ export default {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.tabs {
|
||||
@@ -725,152 +889,146 @@ export default {
|
||||
padding: 4rpx 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.card_history:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
.card_relation {
|
||||
width: 646rpx;
|
||||
height: 106rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 30rpx;
|
||||
border-bottom: 1rpx solid #E2E8F0;
|
||||
|
||||
text {
|
||||
font-weight: 400;
|
||||
color: #1E293B;
|
||||
}
|
||||
|
||||
.u-icon {
|
||||
vertical-align: middle;
|
||||
margin-top: 2rpx;
|
||||
}
|
||||
|
||||
::v-deep .u-input {
|
||||
margin-left: 130rpx;
|
||||
}
|
||||
|
||||
::v-deep .u-input__right-icon ::before {
|
||||
content: ""
|
||||
}
|
||||
|
||||
.choose {
|
||||
font-weight: 400;
|
||||
color: #94A3B8;
|
||||
}
|
||||
}
|
||||
|
||||
.card_telephone {
|
||||
width: 646rpx;
|
||||
height: 106rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 30rpx;
|
||||
|
||||
text {
|
||||
font-weight: 400;
|
||||
color: #1E293B;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.floor {
|
||||
z-index: 99;
|
||||
|
||||
.floor_item {
|
||||
/* 现代化的复诊卡片重构 */
|
||||
.modern-card {
|
||||
padding: 32rpx;
|
||||
font-size: 32rpx;
|
||||
text-align: center;
|
||||
font-family: PingFang SC-Regular, PingFang SC;
|
||||
font-weight: 400;
|
||||
border: none;
|
||||
|
||||
.title {
|
||||
height: 66rpx;
|
||||
width: 620rpx;
|
||||
font-size: 36rpx;
|
||||
font-family: PingFang SC-Regular, PingFang SC;
|
||||
font-weight: 400;
|
||||
color: #1E293B;
|
||||
}
|
||||
|
||||
.u_btn {
|
||||
margin: 40rpx 0;
|
||||
}
|
||||
|
||||
.u_inp {
|
||||
margin: 40rpx 0;
|
||||
|
||||
::v-deep .u-input {
|
||||
background: #F1F5F9;
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
width: 620rpx;
|
||||
margin: 40rpx 0;
|
||||
|
||||
.addinfo {
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 620rpx;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.words {
|
||||
height: 42rpx;
|
||||
font-size: 30rpx;
|
||||
font-family: PingFang SC-Regular, PingFang SC;
|
||||
font-weight: 400;
|
||||
color: #1E293B;
|
||||
margin: 20rpx 0 10rpx;
|
||||
}
|
||||
.title-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #0F172A;
|
||||
position: relative;
|
||||
padding-left: 18rpx;
|
||||
|
||||
.radios {}
|
||||
}
|
||||
|
||||
}
|
||||
/* 使用左侧主题色强调线 */
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 8rpx;
|
||||
height: 28rpx;
|
||||
background: #4175EE;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.req-tag {
|
||||
color: #EF4444;
|
||||
font-size: 24rpx;
|
||||
background: rgba(239, 68, 68, 0.08);
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.section-divider {
|
||||
height: 1rpx;
|
||||
background-color: #F1F5F9;
|
||||
margin: 40rpx 0 32rpx;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
background: #F8FAFC;
|
||||
border-radius: 12rpx;
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:focus-within {
|
||||
border-color: #CBD5E1;
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
.chief-textarea {
|
||||
width: 100%;
|
||||
min-height: 160rpx;
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
font-size: 28rpx;
|
||||
color: #334155;
|
||||
line-height: 1.5;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.hint-box {
|
||||
background: #F1F5F9;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.sub-hint {
|
||||
font-size: 24rpx;
|
||||
color: #64748B;
|
||||
line-height: 1.6;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-drugs {
|
||||
padding: 32rpx 0;
|
||||
text-align: center;
|
||||
color: #94A3B8;
|
||||
font-size: 28rpx;
|
||||
background: #F8FAFC;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 底部悬浮按钮 */
|
||||
.footer {
|
||||
width: 750rpx;
|
||||
height: 104rpx;
|
||||
min-height: 104rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 2rpx solid #F2F3F5;
|
||||
border-top: 2rpx solid #F1F5F9;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: env(safe-area-inset-bottom);
|
||||
background-color: #fff;
|
||||
|
||||
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
background-color: #FFFFFF;
|
||||
box-sizing: border-box;
|
||||
z-index: 100;
|
||||
|
||||
.infos {
|
||||
width: 686rpx;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 16rpx 0;
|
||||
font-size: 32rpx;
|
||||
font-family: PingFang SC-Medium, PingFang SC;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
height: 86rpx;
|
||||
background: #4175EE;
|
||||
background: linear-gradient(135deg, #4175EE 0%, #298dff 100%);
|
||||
box-shadow: 0 8rpx 20rpx rgba(65, 117, 238, 0.3);
|
||||
border-radius: 198rpx;
|
||||
color: #FFFFFF;
|
||||
margin-left: 32rpx;
|
||||
transition: opacity 0.3s;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
863
subPackages/follow-up/medication-info.vue
Normal file
863
subPackages/follow-up/medication-info.vue
Normal file
@@ -0,0 +1,863 @@
|
||||
<template>
|
||||
<view v-if="mode === 'clinic_post_register'">
|
||||
<AssistantConsultationShell
|
||||
header-title="复诊用药确认"
|
||||
:show-notice="showPostRegisterNotice"
|
||||
:parsed-notice-parts="parsedPostRegisterNoticeParts"
|
||||
:show-agreement.sync="showPostRegisterAgreement"
|
||||
:agreement-content="postRegisterAgreementContent"
|
||||
:current-agreement-name="postRegisterCurrentAgreementName"
|
||||
:displayed-messages="postRegisterDisplayedMessages"
|
||||
:assistant-avatar="postRegAssistantAvatar"
|
||||
:assistant-name="postRegAssistantName"
|
||||
user-avatar="/static/images/user-avatar.png"
|
||||
:prescription-info="postRegisterPrescriptionInfo"
|
||||
:is-typing="postRegisterIsTyping"
|
||||
:scroll-into-view="postRegisterScrollIntoView"
|
||||
:show-question-buttons="postRegisterStep === 'await_answer'"
|
||||
:current-question="postRegisterCurrentQuestion"
|
||||
:current-question-answers="postRegisterQuestionAnswers"
|
||||
:is-submitting="postRegisterSubmitting"
|
||||
:show-waiting="postRegisterWaiting"
|
||||
@back="handlePostRegisterBack"
|
||||
@answer="onPostRegisterShellAnswer"
|
||||
@view-agreement="viewPostRegisterAgreement"
|
||||
/>
|
||||
<u-toast ref="uToast" />
|
||||
</view>
|
||||
|
||||
<view v-else class="page">
|
||||
<view class="page-inner safe-area-bottom">
|
||||
<view class="page-title">{{ pageTitle }}</view>
|
||||
|
||||
<!-- 药店:商品摘要 -->
|
||||
<block v-if="mode === 'pharmacy'">
|
||||
<view class="product-info-row">
|
||||
<image class="p-img" :src="productDetail.image" mode="aspectFill"></image>
|
||||
<view class="p-info">
|
||||
<view class="p-name">{{ productDetail.drug_name }}</view>
|
||||
<view class="p-spec">{{ productDetail.specification || '暂无规格' }}</view>
|
||||
<view class="p-price">¥{{ productPrice }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="quantity-row">
|
||||
<text class="label">购买数量</text>
|
||||
<u-number-box v-model="prescriptionQuantity" :min="1" :max="999" integer :step="1"></u-number-box>
|
||||
</view>
|
||||
|
||||
<u-form :model="prescriptionFormData" ref="prescriptionForm">
|
||||
<u-form-item label="是否就诊过" prop="hasVisited" label-width="200">
|
||||
<u-radio-group v-model="prescriptionFormData.hasVisited">
|
||||
<u-radio name="1" active-color="#2B85E4" style="margin-right: 40rpx;">是</u-radio>
|
||||
<u-radio name="0" active-color="#2B85E4">否</u-radio>
|
||||
</u-radio-group>
|
||||
</u-form-item>
|
||||
<u-form-item label="是否用过此药" prop="hasUsedDrug" label-width="200">
|
||||
<u-radio-group v-model="prescriptionFormData.hasUsedDrug">
|
||||
<u-radio name="1" active-color="#2B85E4" style="margin-right: 40rpx;">是</u-radio>
|
||||
<u-radio name="0" active-color="#2B85E4">否</u-radio>
|
||||
</u-radio-group>
|
||||
</u-form-item>
|
||||
<u-form-item label="症状" prop="illnessInfo" label-width="200">
|
||||
<u-input
|
||||
v-model="prescriptionFormData.illnessInfo"
|
||||
type="select"
|
||||
placeholder="请选择症状"
|
||||
@click="showIllnessPicker = true"
|
||||
disabled
|
||||
/>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
</block>
|
||||
|
||||
<!-- 诊所复诊:用药确认(仅聊天内入口) -->
|
||||
<block v-else-if="mode === 'clinic_followup'">
|
||||
<view v-if="clinicQuestion" class="clinic-question">{{ clinicQuestion }}</view>
|
||||
<view v-if="clinicDrugs.length" class="clinic-drugs">
|
||||
<view class="sub-title">涉及药品</view>
|
||||
<view v-for="(row, idx) in clinicDrugs" :key="'d-' + idx" class="drug-line">
|
||||
<text class="n">{{ row.name || '药品' }}</text>
|
||||
<text class="q">×{{ row.quantity != null ? row.quantity : 1 }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<u-form :model="clinicForm" ref="clinicFormRef">
|
||||
<u-form-item label="是否用过此类药" prop="hasUsedDrug" label-width="220">
|
||||
<u-radio-group v-model="clinicForm.hasUsedDrug">
|
||||
<u-radio name="1" active-color="#2B85E4" style="margin-right: 40rpx;">是</u-radio>
|
||||
<u-radio name="0" active-color="#2B85E4">否</u-radio>
|
||||
</u-radio-group>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
</block>
|
||||
|
||||
<view class="popup-actions">
|
||||
<button class="btn-cancel" @click="goBack">取消</button>
|
||||
<button class="btn-confirm" @click="onSubmit">{{ submitButtonText }}</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<u-picker
|
||||
v-model="showIllnessPicker"
|
||||
mode="selector"
|
||||
:range="illnessOptions"
|
||||
range-key="label"
|
||||
@confirm="onIllnessConfirm"
|
||||
:mask-close-able="true"
|
||||
confirm-color="#2B85E4"
|
||||
></u-picker>
|
||||
|
||||
<u-toast ref="uToast" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getStoreDrugDetailApi, checkOnlineConsultationConfigApi } from '@/request/api/product';
|
||||
import request from '@/request/api/request';
|
||||
import { confirmFollowUpDrugUseApi } from '@/request/api/order';
|
||||
import {
|
||||
CLINIC_POST_REGISTER_STORAGE_KEY,
|
||||
markFollowUpAssistSent,
|
||||
sendFollowUpDrugAssistantMessage,
|
||||
wasFollowUpAssistSent,
|
||||
} from '@/utils/clinicFollowUpAssistantIm';
|
||||
import AssistantConsultationShell from '@/components/AssistantConsultationShell.vue';
|
||||
import {
|
||||
getTransferAssistantConfigApi,
|
||||
getTransferConsultationAgreementInfoApi,
|
||||
getAgreementDetailApi,
|
||||
} from '@/request/api/transferPrescription.js';
|
||||
|
||||
const FOLLOWUP_PAYLOAD_KEY = '__medication_followup_payload';
|
||||
|
||||
export default {
|
||||
components: { AssistantConsultationShell },
|
||||
data() {
|
||||
return {
|
||||
mode: 'pharmacy',
|
||||
productDetail: {
|
||||
id: '',
|
||||
type: 2,
|
||||
drug_name: '',
|
||||
image: '',
|
||||
price: 0,
|
||||
specification: '',
|
||||
indications_array: [],
|
||||
},
|
||||
prescriptionQuantity: 1,
|
||||
prescriptionFormData: {
|
||||
hasVisited: '1',
|
||||
hasUsedDrug: '1',
|
||||
illnessInfo: '',
|
||||
},
|
||||
illnessOptions: [],
|
||||
showIllnessPicker: false,
|
||||
delegateStoreId: '',
|
||||
doctorIdFromQuery: '',
|
||||
submitted: false,
|
||||
// clinic
|
||||
registerInfoId: '',
|
||||
messageId: '',
|
||||
clinicQuestion: '',
|
||||
clinicDrugs: [],
|
||||
clinicForm: {
|
||||
hasUsedDrug: '1',
|
||||
},
|
||||
postRegisterRoomId: '',
|
||||
postRegisterDoctorId: '',
|
||||
postRegisterPatientId: '',
|
||||
postRegisterDoctorPrefixed: '',
|
||||
postRegisterRegisterId: '',
|
||||
// clinic_post_register(与转诊咨询壳共用 UI)
|
||||
postRegisterQuestionList: [],
|
||||
postRegisterQuestionIndex: -1,
|
||||
postRegisterUserAnswers: [],
|
||||
postRegisterReplyDelay: 0.65,
|
||||
postRegisterIsTyping: false,
|
||||
postRegisterShowWelcome: true,
|
||||
/** idle | await_answer | typing_after | submitting */
|
||||
postRegisterStep: 'idle',
|
||||
postRegisterScrollIntoView: '',
|
||||
postRegisterSubmitting: false,
|
||||
postRegisterWaiting: false,
|
||||
postRegAssistantAvatar: '/static/images/assistant-avatar.png',
|
||||
postRegAssistantName: '医生助理',
|
||||
showPostRegisterNotice: true,
|
||||
parsedPostRegisterNoticeParts: [],
|
||||
postRegisterAgreements: [],
|
||||
postRegisterNoticeText: '',
|
||||
showPostRegisterAgreement: false,
|
||||
postRegisterAgreementContent: '',
|
||||
postRegisterCurrentAgreementName: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageTitle() {
|
||||
if (this.mode === 'clinic_followup') return '请填写用药信息';
|
||||
return '请填写用药信息';
|
||||
},
|
||||
submitButtonText() {
|
||||
return '提交';
|
||||
},
|
||||
postRegisterPrescriptionInfo() {
|
||||
if (!this.clinicDrugs || !this.clinicDrugs.length) return '相关药品';
|
||||
return this.clinicDrugs
|
||||
.map((row) => `${row.name || '药品'}×${row.quantity != null ? row.quantity : 1}`)
|
||||
.join('、');
|
||||
},
|
||||
postRegisterDisplayedMessages() {
|
||||
const messages = [];
|
||||
if (this.postRegisterShowWelcome) {
|
||||
messages.push({ type: 'welcome', id: 'welcome-msg', timestamp: 0 });
|
||||
}
|
||||
const qs = this.postRegisterQuestionList;
|
||||
const idx = this.postRegisterQuestionIndex;
|
||||
for (let i = 0; i <= idx && i < qs.length; i++) {
|
||||
const q = qs[i];
|
||||
messages.push({
|
||||
type: 'question',
|
||||
id: `question-${q.id}`,
|
||||
questionId: q.id,
|
||||
content: q.question,
|
||||
timestamp: i * 2 + 1,
|
||||
});
|
||||
if (i < this.postRegisterUserAnswers.length) {
|
||||
messages.push({
|
||||
type: 'answer',
|
||||
id: `answer-${i}`,
|
||||
content: this.postRegisterUserAnswers[i],
|
||||
timestamp: i * 2 + 2,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (this.postRegisterWaiting) {
|
||||
messages.push({
|
||||
type: 'waiting',
|
||||
id: 'waiting-msg',
|
||||
timestamp: messages.length,
|
||||
});
|
||||
}
|
||||
return messages;
|
||||
},
|
||||
postRegisterCurrentQuestion() {
|
||||
if (this.postRegisterStep !== 'await_answer') return null;
|
||||
if (!this.postRegisterQuestionList.length) return null;
|
||||
return this.postRegisterQuestionList[0];
|
||||
},
|
||||
postRegisterQuestionAnswers() {
|
||||
return ['是', '否'];
|
||||
},
|
||||
productPrice() {
|
||||
if (this.productDetail.drug_store_drug && this.productDetail.drug_store_drug.price) {
|
||||
return parseFloat(this.productDetail.drug_store_drug.price);
|
||||
}
|
||||
return parseFloat(this.productDetail.price || 0);
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
if (options.mode === 'clinic_post_register') {
|
||||
this.mode = 'clinic_post_register';
|
||||
try {
|
||||
const raw = uni.getStorageSync(CLINIC_POST_REGISTER_STORAGE_KEY);
|
||||
if (raw) {
|
||||
const o = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
||||
this.registerInfoId = String(o.register_info_id || '');
|
||||
this.clinicQuestion = o.question || '您是否曾使用过此类药品?';
|
||||
this.clinicDrugs = Array.isArray(o.drugs) ? o.drugs : [];
|
||||
this.postRegisterRoomId = o.room_id || '';
|
||||
this.postRegisterDoctorId = o.doctor_id != null ? String(o.doctor_id) : '';
|
||||
this.postRegisterPatientId = String(o.patient_id || '');
|
||||
this.postRegisterDoctorPrefixed = o.doctor_prefixed || '';
|
||||
this.postRegisterRegisterId = String(o.register_id || '');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('medication-info clinic_post_register', e);
|
||||
}
|
||||
uni.removeStorageSync(CLINIC_POST_REGISTER_STORAGE_KEY);
|
||||
if (!this.registerInfoId || !this.postRegisterRoomId || !this.postRegisterDoctorId || !this.postRegisterRegisterId) {
|
||||
uni.showToast({ title: '参数无效', icon: 'none' });
|
||||
} else {
|
||||
this.postRegisterQuestionList = [
|
||||
{
|
||||
id: 'clinic-post-reg-1',
|
||||
question: this.clinicQuestion || '您是否曾使用过此类药品?',
|
||||
answers: ['是', '否'],
|
||||
},
|
||||
];
|
||||
this.loadPostRegisterAssistantConfig();
|
||||
this.loadPostRegisterAgreementInfo();
|
||||
this.$nextTick(() => this.beginPostRegisterAssistantFlow());
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.mode = options.mode === 'clinic_followup' ? 'clinic_followup' : 'pharmacy';
|
||||
if (this.mode === 'clinic_followup') {
|
||||
this.messageId = options.message_id || '';
|
||||
try {
|
||||
const raw = uni.getStorageSync(FOLLOWUP_PAYLOAD_KEY);
|
||||
if (raw) {
|
||||
const o = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
||||
this.registerInfoId = String(o.register_info_id || '');
|
||||
this.clinicQuestion = o.question || '';
|
||||
this.clinicDrugs = Array.isArray(o.drugs) ? o.drugs : [];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('medication-info clinic payload', e);
|
||||
}
|
||||
uni.removeStorageSync(FOLLOWUP_PAYLOAD_KEY);
|
||||
if (!this.registerInfoId) {
|
||||
uni.showToast({ title: '参数无效', icon: 'none' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.productDetail.id = options.id || '';
|
||||
this.doctorIdFromQuery = options.doctor_id || '';
|
||||
this.delegateStoreId = options.delegate_store_id || '';
|
||||
if (options.drug_name) {
|
||||
try {
|
||||
this.productDetail.drug_name = decodeURIComponent(options.drug_name);
|
||||
} catch {
|
||||
this.productDetail.drug_name = options.drug_name;
|
||||
}
|
||||
}
|
||||
if (options.image) {
|
||||
try {
|
||||
this.productDetail.image = decodeURIComponent(options.image);
|
||||
} catch {
|
||||
this.productDetail.image = options.image;
|
||||
}
|
||||
}
|
||||
if (options.price) this.productDetail.price = parseFloat(options.price);
|
||||
if (options.type) this.productDetail.type = parseInt(options.type, 10) || 2;
|
||||
if (this.productDetail.id) {
|
||||
this.getDetail();
|
||||
} else {
|
||||
uni.showToast({ title: '缺少商品信息', icon: 'none' });
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
postRegisterScrollToBottom() {
|
||||
this.postRegisterScrollIntoView = 'bottom-anchor';
|
||||
setTimeout(() => {
|
||||
this.postRegisterScrollIntoView = '';
|
||||
}, 120);
|
||||
},
|
||||
beginPostRegisterAssistantFlow() {
|
||||
this.postRegisterStep = 'idle';
|
||||
this.postRegisterShowWelcome = true;
|
||||
this.postRegisterUserAnswers = [];
|
||||
this.postRegisterWaiting = false;
|
||||
this.postRegisterQuestionIndex = -1;
|
||||
this.postRegisterIsTyping = true;
|
||||
const delayMs = Math.max(200, (this.postRegisterReplyDelay || 0.65) * 1000);
|
||||
setTimeout(() => {
|
||||
this.postRegisterIsTyping = false;
|
||||
this.postRegisterQuestionIndex = 0;
|
||||
this.postRegisterStep = 'await_answer';
|
||||
this.$nextTick(() => this.postRegisterScrollToBottom());
|
||||
}, delayMs);
|
||||
},
|
||||
onPostRegisterShellAnswer(answer) {
|
||||
if (this.postRegisterStep !== 'await_answer' || this.submitted) return;
|
||||
this.postRegisterUserAnswers = [answer];
|
||||
this.clinicForm.hasUsedDrug = answer === '是' ? '1' : '0';
|
||||
this.postRegisterStep = 'typing_after';
|
||||
this.postRegisterIsTyping = true;
|
||||
this.$nextTick(() => this.postRegisterScrollToBottom());
|
||||
const typingMs = Math.max(200, (this.postRegisterReplyDelay || 0.65) * 1000);
|
||||
setTimeout(() => {
|
||||
this.postRegisterIsTyping = false;
|
||||
this.$nextTick(() => this.postRegisterScrollToBottom());
|
||||
// 与转诊一致:最后一题答完后短延迟再自动提交
|
||||
setTimeout(() => {
|
||||
this.submitClinicPostRegister();
|
||||
}, 500);
|
||||
}, typingMs);
|
||||
},
|
||||
resetPostRegisterAfterSubmitFailure() {
|
||||
this.submitted = false;
|
||||
this.postRegisterWaiting = false;
|
||||
this.postRegisterStep = 'await_answer';
|
||||
if (this.clinicForm.hasUsedDrug === '0' || this.clinicForm.hasUsedDrug === '1') {
|
||||
this.postRegisterUserAnswers = [this.clinicForm.hasUsedDrug === '1' ? '是' : '否'];
|
||||
} else {
|
||||
this.postRegisterUserAnswers = [];
|
||||
}
|
||||
this.$nextTick(() => this.postRegisterScrollToBottom());
|
||||
},
|
||||
async loadPostRegisterAssistantConfig() {
|
||||
try {
|
||||
const res = await getTransferAssistantConfigApi();
|
||||
if (res.data && res.data.code === 0) {
|
||||
const config = res.data.result || {};
|
||||
this.postRegAssistantName = config.name || '医生助理';
|
||||
this.postRegAssistantAvatar = config.avatar || '/static/images/assistant-avatar.png';
|
||||
const d = parseFloat(config.reply_delay);
|
||||
if (!Number.isNaN(d) && d > 0) {
|
||||
this.postRegisterReplyDelay = d;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('loadPostRegisterAssistantConfig', e);
|
||||
}
|
||||
},
|
||||
async loadPostRegisterAgreementInfo() {
|
||||
try {
|
||||
const res = await getTransferConsultationAgreementInfoApi();
|
||||
if (res.data && res.data.code === 0) {
|
||||
const result = res.data.result || {};
|
||||
this.postRegisterNoticeText = result.notice_text || '';
|
||||
this.postRegisterAgreements = result.agreements || [];
|
||||
this.parsePostRegisterNoticeText();
|
||||
this.showPostRegisterNotice =
|
||||
this.parsedPostRegisterNoticeParts.length > 0 || !!this.postRegisterNoticeText;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('loadPostRegisterAgreementInfo', e);
|
||||
this.postRegisterNoticeText =
|
||||
'根据国家互联网医院管理办法要求,平台仅为复诊患者提供服务。为了保障您的用药安全,请根据真实情况回答,并请仔细阅读《互联网医疗风险告知及知情同意书》,继续咨询即表示您已知悉相关规则与风险并同意相关条款。';
|
||||
this.parsePostRegisterNoticeText();
|
||||
this.showPostRegisterNotice =
|
||||
this.parsedPostRegisterNoticeParts.length > 0 || !!this.postRegisterNoticeText;
|
||||
}
|
||||
},
|
||||
parsePostRegisterNoticeText() {
|
||||
if (!this.postRegisterNoticeText) {
|
||||
this.parsedPostRegisterNoticeParts = [];
|
||||
return;
|
||||
}
|
||||
const parts = [];
|
||||
const regex = /《([^》]+)》/g;
|
||||
let lastIndex = 0;
|
||||
let match;
|
||||
while ((match = regex.exec(this.postRegisterNoticeText)) !== null) {
|
||||
if (match.index > lastIndex) {
|
||||
parts.push({
|
||||
type: 'text',
|
||||
text: this.postRegisterNoticeText.substring(lastIndex, match.index),
|
||||
});
|
||||
}
|
||||
const agreementName = match[1];
|
||||
const agreement = this.postRegisterAgreements.find(
|
||||
(ag) => ag.name === agreementName || (ag.name && ag.name.includes(agreementName)),
|
||||
);
|
||||
if (agreement) {
|
||||
parts.push({
|
||||
type: 'agreement',
|
||||
text: agreementName,
|
||||
agreementId: agreement.id,
|
||||
});
|
||||
} else {
|
||||
parts.push({ type: 'text', text: match[0] });
|
||||
}
|
||||
lastIndex = regex.lastIndex;
|
||||
}
|
||||
if (lastIndex < this.postRegisterNoticeText.length) {
|
||||
parts.push({
|
||||
type: 'text',
|
||||
text: this.postRegisterNoticeText.substring(lastIndex),
|
||||
});
|
||||
}
|
||||
this.parsedPostRegisterNoticeParts = parts;
|
||||
},
|
||||
async viewPostRegisterAgreement(agreementId) {
|
||||
if (!agreementId) {
|
||||
uni.showToast({ title: '协议ID不能为空', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const agreement = this.postRegisterAgreements.find((ag) => ag.id === agreementId);
|
||||
this.postRegisterCurrentAgreementName = agreement ? agreement.name : '协议详情';
|
||||
this.showPostRegisterAgreement = true;
|
||||
try {
|
||||
const res = await getAgreementDetailApi(agreementId);
|
||||
if (res.data && res.data.code === 0) {
|
||||
const result = res.data.result || {};
|
||||
this.postRegisterAgreementContent = result.content || '';
|
||||
if (result.name) {
|
||||
this.postRegisterCurrentAgreementName = result.name;
|
||||
}
|
||||
} else {
|
||||
throw new Error((res.data && res.data.message) || '获取协议详情失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('viewPostRegisterAgreement', error);
|
||||
uni.showToast({
|
||||
title: (error && error.message) || '获取协议详情失败',
|
||||
icon: 'none',
|
||||
});
|
||||
this.showPostRegisterAgreement = false;
|
||||
}
|
||||
},
|
||||
handlePostRegisterBack() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定返回首页吗?未完成确认将无法同步给医生。',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.switchTab({ url: '/pages/home/home' });
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
goBack() {
|
||||
if (this.mode === 'clinic_post_register') {
|
||||
uni.switchTab({ url: '/pages/home/home' });
|
||||
return;
|
||||
}
|
||||
uni.navigateBack({ delta: 1 });
|
||||
},
|
||||
getDetail() {
|
||||
getStoreDrugDetailApi({ id: this.productDetail.id }).then((res) => {
|
||||
if (res.data.code === 0) {
|
||||
const data = res.data.result;
|
||||
Object.assign(this.productDetail, data);
|
||||
if (data.indications_array && data.indications_array.length > 0) {
|
||||
this.illnessOptions = data.indications_array.map((item) => ({
|
||||
value: item.label || item.value,
|
||||
label: item.label || item.value,
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
onIllnessConfirm(e) {
|
||||
const index = e[0];
|
||||
this.prescriptionFormData.illnessInfo = this.illnessOptions[index].value;
|
||||
this.showIllnessPicker = false;
|
||||
},
|
||||
async getDefaultDoctorId() {
|
||||
try {
|
||||
const storeId = uni.getStorageSync('store_id');
|
||||
if (!storeId) {
|
||||
return { data: { result: { doctor_id: null } } };
|
||||
}
|
||||
const res = await request('/xkApi/online-consultation/get-default-doctor-id', {
|
||||
method: 'GET',
|
||||
data: { store_id: storeId },
|
||||
}, 0);
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.error('获取默认医生ID失败:', error);
|
||||
return { data: { result: { doctor_id: null } } };
|
||||
}
|
||||
},
|
||||
onSubmit() {
|
||||
if (this.mode === 'clinic_followup') {
|
||||
this.submitClinic();
|
||||
} else {
|
||||
this.submitPharmacy();
|
||||
}
|
||||
},
|
||||
submitClinicPostRegister() {
|
||||
if (!this.registerInfoId || !this.postRegisterRoomId || !this.postRegisterDoctorId || !this.postRegisterRegisterId) {
|
||||
uni.showToast({ title: '参数缺失', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (this.clinicForm.hasUsedDrug !== '0' && this.clinicForm.hasUsedDrug !== '1') {
|
||||
uni.showToast({ title: '请先选择是否用过药', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (this.submitted) return;
|
||||
this.submitted = true;
|
||||
this.postRegisterSubmitting = true;
|
||||
this.postRegisterWaiting = true;
|
||||
this.postRegisterStep = 'submitting';
|
||||
this.$nextTick(() => this.postRegisterScrollToBottom());
|
||||
const val = this.clinicForm.hasUsedDrug;
|
||||
const answerStatus = val === '1' ? 'yes' : 'no';
|
||||
confirmFollowUpDrugUseApi({
|
||||
register_info_id: this.registerInfoId,
|
||||
has_used_drug: val,
|
||||
})
|
||||
.then(async (res) => {
|
||||
const d = res.data || {};
|
||||
const ok = d.errcode === 0 || d.code === 0 || d.status === true;
|
||||
if (!ok) {
|
||||
this.resetPostRegisterAfterSubmitFailure();
|
||||
uni.showToast({ title: d.msg || d.message || '提交失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (!wasFollowUpAssistSent(this.postRegisterRegisterId)) {
|
||||
await sendFollowUpDrugAssistantMessage({
|
||||
roomId: this.postRegisterRoomId,
|
||||
doctorId: this.postRegisterDoctorId,
|
||||
registerId: this.postRegisterRegisterId,
|
||||
registerInfoId: this.registerInfoId,
|
||||
drugsPayload: this.clinicDrugs,
|
||||
question: this.clinicQuestion,
|
||||
answerStatus,
|
||||
});
|
||||
markFollowUpAssistSent(this.postRegisterRegisterId);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('sendFollowUpDrugAssistantMessage', e);
|
||||
this.resetPostRegisterAfterSubmitFailure();
|
||||
uni.showToast({ title: '发送消息失败,请重试', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const encUser = encodeURIComponent(
|
||||
this.postRegisterDoctorPrefixed || `doctor-${this.postRegisterDoctorId}`,
|
||||
);
|
||||
const chatUrl = `/subPackages/chat/chat?user_id=${encUser}&room_id=${this.postRegisterRoomId}®ister_id=${this.postRegisterRegisterId}`;
|
||||
uni.redirectTo({ url: chatUrl });
|
||||
})
|
||||
.catch(() => {
|
||||
this.resetPostRegisterAfterSubmitFailure();
|
||||
uni.showToast({ title: '提交失败', icon: 'none' });
|
||||
})
|
||||
.finally(() => {
|
||||
this.postRegisterSubmitting = false;
|
||||
this.postRegisterWaiting = false;
|
||||
});
|
||||
},
|
||||
submitClinic() {
|
||||
if (!this.registerInfoId) {
|
||||
uni.showToast({ title: '缺少登记信息', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (this.clinicForm.hasUsedDrug !== '0' && this.clinicForm.hasUsedDrug !== '1') {
|
||||
uni.showToast({ title: '请选择是否用过药', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.showLoading({ title: '提交中', mask: true });
|
||||
confirmFollowUpDrugUseApi({
|
||||
register_info_id: this.registerInfoId,
|
||||
has_used_drug: this.clinicForm.hasUsedDrug,
|
||||
})
|
||||
.then((res) => {
|
||||
const d = res.data || {};
|
||||
const ok = d.errcode === 0 || d.code === 0 || d.status === true;
|
||||
if (ok) {
|
||||
const val = this.clinicForm.hasUsedDrug;
|
||||
const answerStatus = val === '1' ? 'yes' : 'no';
|
||||
try {
|
||||
const ec = this.getOpenerEventChannel && this.getOpenerEventChannel();
|
||||
if (ec && ec.emit) {
|
||||
ec.emit('followUpDrugCompleted', {
|
||||
messageId: this.messageId,
|
||||
answerStatus,
|
||||
raw: val,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('eventChannel', e);
|
||||
}
|
||||
uni.showToast({ title: '已记录', icon: 'success' });
|
||||
setTimeout(() => uni.navigateBack({ delta: 1 }), 400);
|
||||
} else {
|
||||
uni.showToast({ title: d.msg || d.message || '提交失败', icon: 'none' });
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: '提交失败', icon: 'none' });
|
||||
})
|
||||
.finally(() => {
|
||||
uni.hideLoading();
|
||||
});
|
||||
},
|
||||
async submitPharmacy() {
|
||||
if (!this.prescriptionFormData.hasVisited || !this.prescriptionFormData.hasUsedDrug || !this.prescriptionFormData.illnessInfo) {
|
||||
uni.showToast({ title: '请填写完整信息', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (this.submitted) return;
|
||||
const formData = { ...this.prescriptionFormData };
|
||||
const quantity = this.prescriptionQuantity;
|
||||
this.submitted = true;
|
||||
try {
|
||||
const storeId = uni.getStorageSync('store_id');
|
||||
let delegateStoreId = this.delegateStoreId;
|
||||
let doctorId = this.doctorIdFromQuery || null;
|
||||
if (!delegateStoreId) {
|
||||
const configRes = await checkOnlineConsultationConfigApi({ store_id: storeId });
|
||||
if (!configRes.data?.result?.can_use) {
|
||||
uni.showToast({
|
||||
title: configRes.data?.result?.message || '该门店暂不支持在线复诊功能',
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
});
|
||||
this.submitted = false;
|
||||
return;
|
||||
}
|
||||
delegateStoreId = configRes.data.result.delegate_store_id || storeId;
|
||||
doctorId = configRes.data.result.doctor_id;
|
||||
} else if (!doctorId) {
|
||||
const doctorRes = await this.getDefaultDoctorId();
|
||||
doctorId = doctorRes.data.result?.doctor_id;
|
||||
}
|
||||
if (!doctorId) {
|
||||
uni.showToast({ title: '该门店暂不支持在线复诊', icon: 'none', duration: 2000 });
|
||||
this.submitted = false;
|
||||
return;
|
||||
}
|
||||
const createRes = await request('/xkApi/order/create-register-info', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
doctor_id: doctorId,
|
||||
drug_id: this.productDetail.id,
|
||||
has_visited: formData.hasVisited,
|
||||
has_used_drug: formData.hasUsedDrug,
|
||||
illnessInfo: formData.illnessInfo,
|
||||
type: this.productDetail.type || 2,
|
||||
number: quantity,
|
||||
},
|
||||
}, 1);
|
||||
const infoId = createRes.data?.result?.info_id || createRes.data?.info_id;
|
||||
if (!infoId) {
|
||||
throw new Error('创建就诊信息失败');
|
||||
}
|
||||
const rInfo = {
|
||||
info_id: infoId,
|
||||
doctor_id: doctorId,
|
||||
has_visited: formData.hasVisited,
|
||||
has_used_drug: formData.hasUsedDrug,
|
||||
illness_info: formData.illnessInfo,
|
||||
drug_name: this.productDetail.drug_name,
|
||||
drug_id: this.productDetail.id,
|
||||
number: quantity,
|
||||
type: this.productDetail.type || 2,
|
||||
created_at: new Date().toLocaleString('zh-CN'),
|
||||
};
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/doctor/doctor-userinfo?id=${doctorId}&r_type=3&r_info=${encodeURIComponent(JSON.stringify(rInfo))}&delegate_store_id=${delegateStoreId}`,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('提交处方药信息失败:', error);
|
||||
uni.showToast({ title: '提交失败,请重试', icon: 'none' });
|
||||
this.submitted = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
.page-inner {
|
||||
padding: 30rpx;
|
||||
background: #fff;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.page-title {
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
.product-info-row {
|
||||
display: flex;
|
||||
margin-bottom: 30rpx;
|
||||
.p-img {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #f8f8f8;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
.p-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
.p-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
.p-spec {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
.p-price {
|
||||
font-size: 36rpx;
|
||||
color: #ff4d4f;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
.quantity-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
border-top: 1rpx solid #f5f5f5;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
margin-bottom: 40rpx;
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
.popup-actions {
|
||||
display: flex;
|
||||
gap: 30rpx;
|
||||
margin-top: 40rpx;
|
||||
&.post-register-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
button {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 30rpx;
|
||||
border: none;
|
||||
&.btn-cancel {
|
||||
background: #f5f7fa;
|
||||
color: #606266;
|
||||
}
|
||||
&.btn-confirm {
|
||||
background: #2b85e4;
|
||||
color: #fff;
|
||||
}
|
||||
&.btn-confirm-block {
|
||||
flex: none;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.post-register-exit {
|
||||
text-align: center;
|
||||
margin-top: 28rpx;
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
.clinic-question {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 24rpx;
|
||||
padding: 20rpx;
|
||||
background: #f7f8fa;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
.clinic-drugs {
|
||||
margin-bottom: 24rpx;
|
||||
.sub-title {
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.drug-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 28rpx;
|
||||
padding: 12rpx 0;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
.n {
|
||||
color: #333;
|
||||
}
|
||||
.q {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
.safe-area-bottom {
|
||||
padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -232,85 +232,15 @@
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
<!-- 处方药购买表单弹窗 -->
|
||||
<u-popup
|
||||
v-model="showPrescriptionFormPopup"
|
||||
mode="bottom"
|
||||
border-radius="24"
|
||||
:closeable="true"
|
||||
@close="closePrescriptionFormPopup"
|
||||
>
|
||||
<view class="popup-wrapper safe-area-bottom">
|
||||
<view class="popup-header-title">请填写用药信息</view>
|
||||
|
||||
<view class="product-info-row">
|
||||
<image class="p-img" :src="productDetail.image" mode="aspectFill"></image>
|
||||
<view class="p-info">
|
||||
<view class="p-name">{{ productDetail.drug_name }}</view>
|
||||
<view class="p-spec">{{ productDetail.specification || '暂无规格' }}</view>
|
||||
<view class="p-price">¥{{ productPrice }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="quantity-row">
|
||||
<text class="label">购买数量</text>
|
||||
<u-number-box v-model="prescriptionQuantity" :min="1" :max="999" integer :step="1"></u-number-box>
|
||||
</view>
|
||||
|
||||
<u-form :model="prescriptionFormData" ref="prescriptionForm">
|
||||
<u-form-item label="是否就诊过" prop="hasVisited" label-width="200">
|
||||
<u-radio-group v-model="prescriptionFormData.hasVisited">
|
||||
<u-radio name="1" active-color="#2B85E4" style="margin-right: 40rpx;">是</u-radio>
|
||||
<u-radio name="0" active-color="#2B85E4">否</u-radio>
|
||||
</u-radio-group>
|
||||
</u-form-item>
|
||||
<u-form-item label="是否用过此药" prop="hasUsedDrug" label-width="200">
|
||||
<u-radio-group v-model="prescriptionFormData.hasUsedDrug">
|
||||
<u-radio name="1" active-color="#2B85E4" style="margin-right: 40rpx;">是</u-radio>
|
||||
<u-radio name="0" active-color="#2B85E4">否</u-radio>
|
||||
</u-radio-group>
|
||||
</u-form-item>
|
||||
<u-form-item label="症状" prop="illnessInfo" label-width="200">
|
||||
<u-input
|
||||
v-model="prescriptionFormData.illnessInfo"
|
||||
type="select"
|
||||
placeholder="请选择症状"
|
||||
@click="showIllnessPicker = true"
|
||||
disabled
|
||||
/>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
|
||||
<view class="popup-actions" style="margin-top:40rpx;">
|
||||
<button class="btn-cancel" @click="closePrescriptionFormPopup">取消</button>
|
||||
<button class="btn-confirm" @click="submitPrescriptionForm">提交信息</button>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
<!-- 患病信息选择器 -->
|
||||
<u-picker
|
||||
v-model="showIllnessPicker"
|
||||
mode="selector"
|
||||
:range="illnessOptions"
|
||||
range-key="label"
|
||||
@confirm="onIllnessConfirm"
|
||||
:mask-close-able="true"
|
||||
confirm-color="#2B85E4"
|
||||
></u-picker>
|
||||
|
||||
<u-toast ref="uToast" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 完整引入原有API
|
||||
import { getOrderTextApi, getStoreDrugDetailApi, checkOnlineConsultationConfigApi } from "@/request/api/product";
|
||||
import { getOrderTextApi, getStoreDrugDetailApi } from "@/request/api/product";
|
||||
import { genOrderApi } from "@/request/api/order";
|
||||
import { getCartCountsApi, saveCartApi } from "@/request/api/cart";
|
||||
import { sendToUserApi } from "@/request/api/im";
|
||||
import request from "@/request/api/request";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -349,20 +279,10 @@ export default {
|
||||
productImages: [],
|
||||
isFavorite: false,
|
||||
cartCount: 0,
|
||||
submitted: false,
|
||||
showCartPopup: false,
|
||||
cartQuantity: 1,
|
||||
showBuyPopup: false,
|
||||
buyQuantity: 1,
|
||||
showPrescriptionFormPopup: false,
|
||||
prescriptionQuantity: 1,
|
||||
prescriptionFormData: {
|
||||
hasVisited: '1',
|
||||
hasUsedDrug: '1',
|
||||
illnessInfo: '',
|
||||
},
|
||||
illnessOptions: [],
|
||||
showIllnessPicker: false,
|
||||
orderText: '本次商品物流配送,由萧康医药提供服务。',
|
||||
introductionImages: [], // 商品介绍图数组
|
||||
delegateStoreId: '', // 委托诊所ID
|
||||
@@ -428,12 +348,6 @@ export default {
|
||||
const data = res.data.result;
|
||||
Object.assign(this.productDetail, data);
|
||||
this.productImages = data.image ? [data.image] : [];
|
||||
if (data.indications_array && data.indications_array.length > 0) {
|
||||
this.illnessOptions = data.indications_array.map(item => ({
|
||||
value: item.label || item.value,
|
||||
label: item.label || item.value
|
||||
}));
|
||||
}
|
||||
if (!this.productDetail.indications && data.function) {
|
||||
this.productDetail.indications = data.function;
|
||||
}
|
||||
@@ -510,7 +424,19 @@ export default {
|
||||
},
|
||||
handleBuyNow() {
|
||||
if (this.productDetail.is_otc === 0 && this.productDetail.type == 2) {
|
||||
this.showPrescriptionFormPopup = true;
|
||||
const q = [
|
||||
`mode=pharmacy`,
|
||||
`id=${encodeURIComponent(this.productDetail.id)}`,
|
||||
`doctor_id=${encodeURIComponent(this.formData.doctor_id || '')}`,
|
||||
`delegate_store_id=${encodeURIComponent(this.delegateStoreId || '')}`,
|
||||
`type=${encodeURIComponent(String(this.productDetail.type || 2))}`,
|
||||
`image=${encodeURIComponent(this.productDetail.image || '')}`,
|
||||
`price=${encodeURIComponent(String(this.productPrice))}`,
|
||||
`drug_name=${encodeURIComponent(this.productDetail.drug_name || '')}`,
|
||||
].join('&');
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/follow-up/medication-info?${q}`,
|
||||
});
|
||||
} else {
|
||||
this.showBuyPopup = true;
|
||||
}
|
||||
@@ -527,129 +453,6 @@ export default {
|
||||
});
|
||||
})
|
||||
},
|
||||
async submitPrescriptionForm() {
|
||||
if (!this.prescriptionFormData.hasVisited || !this.prescriptionFormData.hasUsedDrug || !this.prescriptionFormData.illnessInfo) {
|
||||
uni.showToast({
|
||||
title: '请填写完整信息',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 先保存表单数据到局部变量(因为 closePrescriptionFormPopup 会清空表单)
|
||||
const formData = {
|
||||
hasVisited: this.prescriptionFormData.hasVisited,
|
||||
hasUsedDrug: this.prescriptionFormData.hasUsedDrug,
|
||||
illnessInfo: this.prescriptionFormData.illnessInfo,
|
||||
};
|
||||
const quantity = this.prescriptionQuantity;
|
||||
|
||||
this.submitted = true;
|
||||
this.closePrescriptionFormPopup();
|
||||
|
||||
try {
|
||||
const storeId = uni.getStorageSync('store_id');
|
||||
let delegateStoreId = this.delegateStoreId;
|
||||
let doctorId = null;
|
||||
|
||||
// 如果没有传入委托诊所ID,需要先检查在线复诊配置
|
||||
if (!delegateStoreId) {
|
||||
const configRes = await checkOnlineConsultationConfigApi({ store_id: storeId });
|
||||
if (!configRes.data?.result?.can_use) {
|
||||
uni.showToast({
|
||||
title: configRes.data?.result?.message || '该门店暂不支持在线复诊功能',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
this.submitted = false;
|
||||
return;
|
||||
}
|
||||
// 获取委托诊所ID和医生ID
|
||||
delegateStoreId = configRes.data.result.delegate_store_id || storeId;
|
||||
doctorId = configRes.data.result.doctor_id;
|
||||
} else {
|
||||
// 如果已有委托诊所ID,获取默认医生ID
|
||||
const doctorRes = await this.getDefaultDoctorId();
|
||||
doctorId = doctorRes.data.result?.doctor_id;
|
||||
}
|
||||
|
||||
// 检查是否配置了在线复诊负责人
|
||||
if (!doctorId) {
|
||||
uni.showToast({
|
||||
title: '该门店暂不支持在线复诊',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
this.submitted = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 先调用后端API保存就诊信息,获取info_id
|
||||
const createRes = await request('/xkApi/order/create-register-info', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
doctor_id: doctorId,
|
||||
drug_id: this.productDetail.id,
|
||||
has_visited: formData.hasVisited,
|
||||
has_used_drug: formData.hasUsedDrug,
|
||||
illnessInfo: formData.illnessInfo,
|
||||
type: this.productDetail.type || 2,
|
||||
number: quantity,
|
||||
}
|
||||
}, 1);
|
||||
|
||||
const infoId = createRes.data?.result?.info_id || createRes.data?.info_id;
|
||||
if (!infoId) {
|
||||
throw new Error('创建就诊信息失败');
|
||||
}
|
||||
|
||||
// 3. 准备患者就诊信息(包含info_id,用于后续更新register_id)
|
||||
const rInfo = {
|
||||
info_id: infoId, // 新增:用于挂号后更新关联
|
||||
doctor_id: doctorId,
|
||||
has_visited: formData.hasVisited,
|
||||
has_used_drug: formData.hasUsedDrug,
|
||||
illness_info: formData.illnessInfo,
|
||||
drug_name: this.productDetail.drug_name,
|
||||
drug_id: this.productDetail.id,
|
||||
number: quantity,
|
||||
type: this.productDetail.type || 2, // 2=西药,3=保健食品
|
||||
created_at: new Date().toLocaleString('zh-CN')
|
||||
};
|
||||
|
||||
// 4. 跳转到就诊人选择页面,传递 register_type=3、患者就诊信息和委托诊所ID
|
||||
// 用户选择就诊人后,会创建挂号记录并跳转到 register.vue
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/doctor/doctor-userinfo?id=${doctorId}&r_type=3&r_info=${encodeURIComponent(JSON.stringify(rInfo))}&delegate_store_id=${delegateStoreId}`
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('提交处方药信息失败:', error);
|
||||
uni.showToast({
|
||||
title: '提交失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 获取默认医生ID(根据门店配置)
|
||||
async getDefaultDoctorId() {
|
||||
try {
|
||||
const storeId = uni.getStorageSync('store_id');
|
||||
if (!storeId) {
|
||||
console.warn('未找到store_id,无法获取默认医生');
|
||||
return { data: { result: { doctor_id: null, message: '未找到门店信息' } } };
|
||||
}
|
||||
const res = await request('/xkApi/online-consultation/get-default-doctor-id', {
|
||||
method: 'GET',
|
||||
data: { store_id: storeId }
|
||||
}, 0);
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.error('获取默认医生ID失败:', error);
|
||||
// 如果接口失败,返回空值
|
||||
return { data: { result: { doctor_id: null, message: '获取失败' } } };
|
||||
}
|
||||
},
|
||||
goToCart() {
|
||||
uni.navigateTo({
|
||||
url: '/subPackages/shop/shop-cate'
|
||||
@@ -666,11 +469,6 @@ export default {
|
||||
url: '/pages/index/platform-info?store_id=0'
|
||||
});
|
||||
},
|
||||
onIllnessConfirm(e) {
|
||||
const index = e[0];
|
||||
this.prescriptionFormData.illnessInfo = this.illnessOptions[index].value;
|
||||
this.showIllnessPicker = false;
|
||||
},
|
||||
closeCartPopup() {
|
||||
this.showCartPopup = false;
|
||||
this.cartQuantity = 1;
|
||||
@@ -679,15 +477,6 @@ export default {
|
||||
this.showBuyPopup = false;
|
||||
this.buyQuantity = 1;
|
||||
},
|
||||
closePrescriptionFormPopup() {
|
||||
this.showPrescriptionFormPopup = false;
|
||||
this.prescriptionQuantity = 1;
|
||||
this.prescriptionFormData = {
|
||||
hasVisited: '1',
|
||||
hasUsedDrug: '1',
|
||||
illnessInfo: '',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -141,6 +141,10 @@
|
||||
import {checkDev} from "@/utils/utils";
|
||||
import {genOrderApi, saveRegisterInfoApi} from "@/request/api/order";
|
||||
import request from "@/request/api/request";
|
||||
import {
|
||||
CLINIC_POST_REGISTER_STORAGE_KEY,
|
||||
resolveFollowUpDrugsFromRInfo,
|
||||
} from '@/utils/clinicFollowUpAssistantIm';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -270,6 +274,12 @@
|
||||
console.error('waitForRegisterDetail', err);
|
||||
}
|
||||
},
|
||||
/** 诊所复诊(2)且存在复诊药:支付后先走独立用药确认页,再进聊天 */
|
||||
needsClinicPostRegisterDrugPage() {
|
||||
if (!this.registList || this.registList.register_type !== 2) return false;
|
||||
const { registerInfoId, drugsPayload } = resolveFollowUpDrugsFromRInfo(this.rInfo);
|
||||
return !!(registerInfoId && drugsPayload.length);
|
||||
},
|
||||
// 挂号信息
|
||||
getInfo() {
|
||||
const that = this;
|
||||
@@ -370,21 +380,41 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 6. 生成订单(如果需要)
|
||||
// 6. 生成订单(如果需要);诊所复诊有药时先 redirect 用药确认页,助理卡改在页内提交后发
|
||||
const chatUrl = `/subPackages/chat/chat?user_id=${encodeURIComponent(doctorPrefixed)}&room_id=${roomId}®ister_id=${that.register_id}`;
|
||||
const goAfterGenOrder = () => {
|
||||
if (that.needsClinicPostRegisterDrugPage()) {
|
||||
const { registerInfoId, drugsPayload } = resolveFollowUpDrugsFromRInfo(that.rInfo);
|
||||
uni.setStorageSync(
|
||||
CLINIC_POST_REGISTER_STORAGE_KEY,
|
||||
JSON.stringify({
|
||||
register_info_id: registerInfoId,
|
||||
register_id: that.register_id,
|
||||
drugs: drugsPayload,
|
||||
question: '您是否曾使用过此类药品?',
|
||||
room_id: roomId,
|
||||
doctor_id: doctorId,
|
||||
patient_id: patientId,
|
||||
doctor_prefixed: doctorPrefixed,
|
||||
}),
|
||||
);
|
||||
uni.hideLoading();
|
||||
uni.redirectTo({
|
||||
url: '/subPackages/follow-up/medication-info?mode=clinic_post_register',
|
||||
});
|
||||
return;
|
||||
}
|
||||
uni.hideLoading();
|
||||
uni.navigateTo({ url: chatUrl });
|
||||
};
|
||||
genOrderApi({
|
||||
register_id: that.register_id,
|
||||
register_info: typeof that.rInfo === 'string' ? that.rInfo : JSON.stringify(that.rInfo),
|
||||
}).then((resOrder) => {
|
||||
uni.hideLoading();
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/chat/chat?user_id=${encodeURIComponent(doctorPrefixed)}&room_id=${roomId}®ister_id=${that.register_id}`
|
||||
});
|
||||
}).then(() => {
|
||||
goAfterGenOrder();
|
||||
}).catch((error) => {
|
||||
console.error('生成订单失败:', error);
|
||||
uni.hideLoading();
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/chat/chat?user_id=${encodeURIComponent(doctorPrefixed)}&room_id=${roomId}®ister_id=${that.register_id}`
|
||||
});
|
||||
goAfterGenOrder();
|
||||
});
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
|
||||
@@ -1,131 +1,31 @@
|
||||
<template>
|
||||
<view class="consultation-container safe-area-inset-bottom">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="consultation-header">
|
||||
<view class="header-back" @click="handleBack">
|
||||
<text class="iconfont icon-arrow-left"></text>
|
||||
</view>
|
||||
<view class="header-title">转诊咨询</view>
|
||||
<view class="header-placeholder"></view>
|
||||
</view>
|
||||
|
||||
<!-- 提示信息(类似图片中的灰色提示框) -->
|
||||
<view class="notice-box" v-if="showNotice">
|
||||
<view class="notice-text">
|
||||
<text v-for="(part, index) in parsedNoticeParts" :key="index">
|
||||
<text v-if="part.type === 'text'">{{ part.text }}</text>
|
||||
<text v-else-if="part.type === 'agreement'" class="notice-link" @click="viewAgreement(part.agreementId)">
|
||||
《{{ part.text }}》
|
||||
</text>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 协议抽屉 -->
|
||||
<u-popup v-model="showAgreement" :safe-area-inset-bottom="true" mode="bottom" height="88%" border-radius="20"
|
||||
:closeable="true" z-index="20000">
|
||||
<view class="agreement-popup">
|
||||
<view class="agreement-header">
|
||||
<text class="agreement-title">{{ currentAgreementName || '协议详情' }}</text>
|
||||
</view>
|
||||
<view class="agreement-content">
|
||||
<u-parse :html="agreementContent"></u-parse>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
<!-- 聊天消息区域 -->
|
||||
<scroll-view class="messages-scroll" scroll-y="true" :scroll-into-view="scrollIntoView" scroll-with-animation>
|
||||
<view class="messages-container">
|
||||
<!-- 按时间顺序渲染消息 -->
|
||||
<view v-for="(message, index) in displayedMessages" :key="message.id">
|
||||
<!-- 欢迎消息 -->
|
||||
<view v-if="message.type === 'welcome'" class="message-item other">
|
||||
<image class="avatar" :src="assistantAvatar" mode="aspectFill"></image>
|
||||
<view class="message-content">
|
||||
<view class="assistant-label">{{ assistantName }}</view>
|
||||
<view class="message-bubble">
|
||||
<text>您好! 我是医生助理, 已收到您的用药申请:「{{ prescriptionInfo }}」, 为了帮助医生判断准确, 请按照您的真实情况回答以下问题。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 问题消息 -->
|
||||
<view v-else-if="message.type === 'question'" class="message-item other" :id="'question-' + message.questionId">
|
||||
<image class="avatar" :src="assistantAvatar" mode="aspectFill"></image>
|
||||
<view class="message-content">
|
||||
<view class="assistant-label">{{ assistantName }}</view>
|
||||
<view class="message-bubble">
|
||||
<text>{{ message.content }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 用户回答消息 -->
|
||||
<view v-else-if="message.type === 'answer'" class="message-item mine">
|
||||
<image class="avatar" :src="userAvatar" mode="aspectFill"></image>
|
||||
<view class="message-content">
|
||||
<view class="message-bubble">
|
||||
<text>{{ message.content }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 等待转接消息 -->
|
||||
<view v-else-if="message.type === 'waiting'" class="message-item other">
|
||||
<image class="avatar" :src="assistantAvatar" mode="aspectFill"></image>
|
||||
<view class="message-content">
|
||||
<view class="assistant-label">{{ assistantName }}</view>
|
||||
<view class="message-bubble">
|
||||
<text>请稍等, 正在为您转接医生进行复诊开方...</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- "正在回复"提示气泡 -->
|
||||
<view v-if="isTyping" class="message-item other typing-indicator">
|
||||
<image class="avatar" :src="assistantAvatar" mode="aspectFill"></image>
|
||||
<view class="message-content">
|
||||
<view class="assistant-label">{{ assistantName }}</view>
|
||||
<view class="message-bubble typing-bubble">
|
||||
<view class="typing-dots">
|
||||
<view class="dot"></view>
|
||||
<view class="dot"></view>
|
||||
<view class="dot"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部锚点 -->
|
||||
<view id="bottom-anchor" style="height: 1px;"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部操作按钮(根据问题的answers动态生成) -->
|
||||
<view class="action-footer safe-area-inset-bottom" v-if="currentQuestion && !isSubmitting && !showWaiting">
|
||||
<button
|
||||
v-for="(answer, index) in currentQuestionAnswers"
|
||||
:key="index"
|
||||
:class="['action-btn', getAnswerButtonClass(answer, index)]"
|
||||
@click="handleAnswer(answer)"
|
||||
>
|
||||
{{ answer }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 提交中提示 -->
|
||||
<view class="submitting-overlay" v-if="isSubmitting">
|
||||
<view class="submitting-content">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="submitting-text">正在提交...</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<AssistantConsultationShell
|
||||
header-title="转诊咨询"
|
||||
:show-notice="showNotice"
|
||||
:parsed-notice-parts="parsedNoticeParts"
|
||||
:show-agreement.sync="showAgreement"
|
||||
:agreement-content="agreementContent"
|
||||
:current-agreement-name="currentAgreementName"
|
||||
:displayed-messages="displayedMessages"
|
||||
:assistant-avatar="assistantAvatar"
|
||||
:assistant-name="assistantName"
|
||||
:user-avatar="userAvatar"
|
||||
:prescription-info="prescriptionInfo"
|
||||
:is-typing="isTyping"
|
||||
:scroll-into-view="scrollIntoView"
|
||||
:show-question-buttons="true"
|
||||
:current-question="currentQuestion"
|
||||
:current-question-answers="currentQuestionAnswers"
|
||||
:is-submitting="isSubmitting"
|
||||
:show-waiting="showWaiting"
|
||||
@back="handleBack"
|
||||
@answer="handleAnswer"
|
||||
@view-agreement="viewAgreement"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AssistantConsultationShell from '@/components/AssistantConsultationShell.vue'
|
||||
import {
|
||||
getTransferPrescriptionDetailApi,
|
||||
getTransferQuestionsApi,
|
||||
@@ -137,6 +37,7 @@ import {
|
||||
|
||||
export default {
|
||||
name: 'TransferConsultation',
|
||||
components: { AssistantConsultationShell },
|
||||
data() {
|
||||
return {
|
||||
transferId: 0, // 转诊ID
|
||||
@@ -545,27 +446,6 @@ export default {
|
||||
})
|
||||
}
|
||||
},
|
||||
// 获取答案按钮的样式类
|
||||
getAnswerButtonClass(answer, index) {
|
||||
const answers = this.currentQuestionAnswers
|
||||
const totalAnswers = answers.length
|
||||
|
||||
// 如果只有两个答案,使用原来的样式(是/否)
|
||||
if (totalAnswers === 2) {
|
||||
if (answer === '是') {
|
||||
return 'yes-btn'
|
||||
} else if (answer === '否') {
|
||||
return 'no-btn'
|
||||
}
|
||||
}
|
||||
|
||||
// 多个答案时,第一个按钮使用主色调,其他使用次要样式
|
||||
if (index === 0) {
|
||||
return 'yes-btn' // 第一个按钮使用主色调
|
||||
} else {
|
||||
return 'no-btn' // 其他按钮使用次要样式
|
||||
}
|
||||
},
|
||||
// 返回
|
||||
handleBack() {
|
||||
uni.showModal({
|
||||
@@ -699,283 +579,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.consultation-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: #F8FAFC;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.consultation-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 30rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
position: relative;
|
||||
|
||||
.header-back {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.iconfont {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.header-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-placeholder {
|
||||
width: 60rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.notice-box {
|
||||
margin: 20rpx 30rpx;
|
||||
padding: 24rpx;
|
||||
background: #F5F5F5;
|
||||
border-radius: 12rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
color: #666;
|
||||
|
||||
.notice-text {
|
||||
display: block;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.notice-link {
|
||||
color: #0A84FF;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.messages-scroll {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.messages-container {
|
||||
padding: 30rpx;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.avatar {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 10rpx;
|
||||
flex-shrink: 0;
|
||||
background: #f2f2f2;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 70%;
|
||||
margin-left: 16rpx;
|
||||
|
||||
.assistant-label {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
padding: 18rpx 24rpx;
|
||||
font-size: 30rpx;
|
||||
line-height: 1.5;
|
||||
border-radius: 4rpx 12rpx 12rpx 12rpx;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
&.mine {
|
||||
flex-direction: row-reverse;
|
||||
|
||||
.message-content {
|
||||
margin-left: 0;
|
||||
margin-right: 16rpx;
|
||||
align-items: flex-end;
|
||||
|
||||
.message-bubble {
|
||||
background: #0A84FF;
|
||||
color: #fff;
|
||||
border-radius: 12rpx 4rpx 12rpx 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.typing-indicator {
|
||||
.typing-bubble {
|
||||
padding: 18rpx 24rpx;
|
||||
min-width: 80rpx;
|
||||
}
|
||||
|
||||
.typing-dots {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
|
||||
.dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
background: #999;
|
||||
animation: typing 1.4s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.dot:nth-child(1) {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.dot:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.dot:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes typing {
|
||||
0%, 60%, 100% {
|
||||
transform: translateY(0);
|
||||
opacity: 0.7;
|
||||
}
|
||||
30% {
|
||||
transform: translateY(-10rpx);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.action-footer {
|
||||
padding: 20rpx 30rpx;
|
||||
background: #fff;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
justify-content: space-between;
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
border-radius: 44rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.yes-btn {
|
||||
background: #0A84FF;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.no-btn {
|
||||
background: #fff;
|
||||
color: #333;
|
||||
border: 2rpx solid #ddd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submitting-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
|
||||
.submitting-content {
|
||||
background: #fff;
|
||||
padding: 60rpx 80rpx;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
|
||||
.loading-spinner {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border: 4rpx solid #f3f3f3;
|
||||
border-top: 4rpx solid #0A84FF;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.submitting-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.agreement-popup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.agreement-header {
|
||||
padding: 30rpx 50rpx 20rpx;
|
||||
border-bottom: 1rpx solid #e5e7eb;
|
||||
background: #fff;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.agreement-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.agreement-content {
|
||||
flex: 1;
|
||||
padding: 30rpx 50rpx 40rpx;
|
||||
overflow-y: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
80
utils/clinicFollowUpAssistantIm.js
Normal file
80
utils/clinicFollowUpAssistantIm.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import { sendToUserApi } from '@/request/api/im';
|
||||
|
||||
/** 挂号支付后进「复诊用药确认」页时写入,与聊天内打开的 payload 分离 */
|
||||
export const CLINIC_POST_REGISTER_STORAGE_KEY = '__clinic_post_register_payload';
|
||||
|
||||
/**
|
||||
* 从 r_info 解析复诊西药列表(与 register.vue 原 sendClinicFollowUpAssistantMessage 一致)
|
||||
* @returns {{ registerInfoId: string, drugsPayload: Array<{drug_id:number,name:string,quantity:number}> }}
|
||||
*/
|
||||
export function resolveFollowUpDrugsFromRInfo(rInfo) {
|
||||
const empty = { registerInfoId: '', drugsPayload: [] };
|
||||
if (!rInfo || typeof rInfo !== 'object') return empty;
|
||||
const infoId = rInfo.info_id;
|
||||
if (!infoId) return empty;
|
||||
let drugs = Array.isArray(rInfo.follow_up_drugs) ? rInfo.follow_up_drugs : [];
|
||||
if (!drugs.length && rInfo.drug_ids) {
|
||||
try {
|
||||
const ids = typeof rInfo.drug_ids === 'string' ? JSON.parse(rInfo.drug_ids) : rInfo.drug_ids;
|
||||
if (Array.isArray(ids)) {
|
||||
drugs = ids.map((id) => ({ drug_id: Number(id), name: '' })).filter((d) => d.drug_id > 0);
|
||||
}
|
||||
} catch (e) {
|
||||
drugs = [];
|
||||
}
|
||||
}
|
||||
if (!drugs.length) return empty;
|
||||
const fallbackQty = Math.min(99, Math.max(1, Number(rInfo.follow_up_number || rInfo.number || 1) || 1));
|
||||
const drugsPayload = drugs.map((d) => {
|
||||
const row = Number(d.quantity ?? d.number);
|
||||
const quantity =
|
||||
Number.isFinite(row) && row >= 1 ? Math.min(99, row) : fallbackQty;
|
||||
return {
|
||||
drug_id: Number(d.drug_id || d.id),
|
||||
name: d.name || d.drug_name || '药品',
|
||||
quantity,
|
||||
};
|
||||
});
|
||||
return { registerInfoId: String(infoId), drugsPayload };
|
||||
}
|
||||
|
||||
/**
|
||||
* 助理号发送 type11 follow_up_drug 卡片
|
||||
* @param {string} answerStatus 'pending' | 'yes' | 'no'
|
||||
*/
|
||||
export async function sendFollowUpDrugAssistantMessage({
|
||||
roomId,
|
||||
doctorId,
|
||||
registerId,
|
||||
registerInfoId,
|
||||
drugsPayload,
|
||||
question,
|
||||
answerStatus,
|
||||
}) {
|
||||
const payload = {
|
||||
flow: 'follow_up_drug',
|
||||
register_info_id: registerInfoId,
|
||||
register_id: registerId,
|
||||
drugs: drugsPayload,
|
||||
question: question || '您是否曾使用过以上药品?',
|
||||
answer_status: answerStatus,
|
||||
};
|
||||
await sendToUserApi({
|
||||
room_id: roomId,
|
||||
sender_user_id: 'doctor_assistant',
|
||||
receiver_user_id: `doctor-${doctorId}`,
|
||||
message_type: 11,
|
||||
message_content: JSON.stringify(payload),
|
||||
duration: 0,
|
||||
});
|
||||
}
|
||||
|
||||
export function markFollowUpAssistSent(registerId) {
|
||||
if (registerId) {
|
||||
uni.setStorageSync(`followup_assist_sent_${registerId}`, '1');
|
||||
}
|
||||
}
|
||||
|
||||
export function wasFollowUpAssistSent(registerId) {
|
||||
return !!(registerId && uni.getStorageSync(`followup_assist_sent_${registerId}`));
|
||||
}
|
||||
Reference in New Issue
Block a user