在线复诊选择药品、说明主诉
This commit is contained in:
@@ -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
|
||||
@@ -176,10 +77,10 @@ export default {
|
||||
if (!this.currentQuestion) {
|
||||
return ['是', '否'] // 默认答案
|
||||
}
|
||||
|
||||
|
||||
// 获取问题的answers字段
|
||||
let answers = this.currentQuestion.answers
|
||||
|
||||
|
||||
// 如果answers是字符串(JSON格式),解析为数组
|
||||
if (typeof answers === 'string') {
|
||||
try {
|
||||
@@ -189,19 +90,19 @@ export default {
|
||||
answers = ['是', '否']
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 如果answers不是数组或为空,使用默认值
|
||||
if (!Array.isArray(answers) || answers.length === 0) {
|
||||
answers = ['是', '否']
|
||||
}
|
||||
|
||||
|
||||
// 过滤空值
|
||||
return answers.filter(answer => answer && answer.trim().length > 0)
|
||||
},
|
||||
// 按时间顺序显示的消息列表
|
||||
displayedMessages() {
|
||||
const messages = []
|
||||
|
||||
|
||||
// 1. 欢迎消息
|
||||
if (this.showWelcome) {
|
||||
messages.push({
|
||||
@@ -210,7 +111,7 @@ export default {
|
||||
timestamp: 0
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 2. 问题和答案交替显示
|
||||
for (let i = 0; i <= this.currentQuestionIndex && i < this.questions.length; i++) {
|
||||
const question = this.questions[i]
|
||||
@@ -222,7 +123,7 @@ export default {
|
||||
content: question.question,
|
||||
timestamp: i * 2 + 1
|
||||
})
|
||||
|
||||
|
||||
// 如果有对应的答案,添加答案
|
||||
if (i < this.userAnswers.length) {
|
||||
messages.push({
|
||||
@@ -233,7 +134,7 @@ export default {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 3. 等待转接消息
|
||||
if (this.showWaiting) {
|
||||
messages.push({
|
||||
@@ -242,7 +143,7 @@ export default {
|
||||
timestamp: messages.length
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return messages
|
||||
}
|
||||
},
|
||||
@@ -307,8 +208,8 @@ export default {
|
||||
this.prescriptionInfo = transferData.drug_name_text
|
||||
} else if (transferData.content) {
|
||||
// 如果后端没有返回drug_name_text,前端自己解析
|
||||
const content = typeof transferData.content === 'string'
|
||||
? JSON.parse(transferData.content)
|
||||
const content = typeof transferData.content === 'string'
|
||||
? JSON.parse(transferData.content)
|
||||
: transferData.content
|
||||
this.prescriptionInfo = this.extractDrugName(content, transferData.prescription_type || 1)
|
||||
}
|
||||
@@ -321,9 +222,9 @@ export default {
|
||||
// 从处方内容中提取药品名称(前端备用方法)
|
||||
extractDrugName(content, prescriptionType) {
|
||||
if (!content) return '未知药品'
|
||||
|
||||
|
||||
const drugNames = []
|
||||
|
||||
|
||||
if (prescriptionType === 1) {
|
||||
// 中药处方
|
||||
if (content.repice && Array.isArray(content.repice)) {
|
||||
@@ -369,7 +270,7 @@ export default {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (drugNames.length === 0) return '未知药品'
|
||||
if (drugNames.length === 1) return drugNames[0]
|
||||
return drugNames[0] + '等共' + drugNames.length + '味'
|
||||
@@ -402,7 +303,7 @@ export default {
|
||||
}
|
||||
return a.id - b.id
|
||||
})
|
||||
|
||||
|
||||
if (this.questions.length === 0) {
|
||||
uni.showToast({
|
||||
title: '暂无咨询问题',
|
||||
@@ -455,7 +356,7 @@ export default {
|
||||
if (this.currentQuestionIndex < this.questions.length - 1) {
|
||||
// 显示"正在回复"提示
|
||||
this.isTyping = true
|
||||
|
||||
|
||||
// 延迟后显示下一个问题
|
||||
setTimeout(() => {
|
||||
this.isTyping = false
|
||||
@@ -514,7 +415,7 @@ export default {
|
||||
const doctorInfo = result.doctor_info || {}
|
||||
const doctorName = doctorInfo.name || '医生'
|
||||
const doctorAvatar = doctorInfo.avatar || ''
|
||||
|
||||
|
||||
// 构建跳转URL,包含所有必要参数
|
||||
let chatUrl = `/subPackages/chat/chat?user_id=${result.doctor_id}&room_id=${result.room_id}&nick_name=${encodeURIComponent(doctorName)}&room_status=0`
|
||||
if (registerId) {
|
||||
@@ -523,7 +424,7 @@ export default {
|
||||
if (doctorAvatar) {
|
||||
chatUrl += `&avatar=${encodeURIComponent(doctorAvatar)}`
|
||||
}
|
||||
|
||||
|
||||
uni.navigateTo({
|
||||
url: chatUrl
|
||||
})
|
||||
@@ -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({
|
||||
@@ -594,7 +474,7 @@ export default {
|
||||
const result = res.data.result || {}
|
||||
this.noticeText = result.notice_text || ''
|
||||
this.agreements = result.agreements || []
|
||||
|
||||
|
||||
// 解析文案,提取协议链接
|
||||
this.parseNoticeText()
|
||||
}
|
||||
@@ -630,7 +510,7 @@ export default {
|
||||
// 查找匹配的协议
|
||||
const agreementName = match[1]
|
||||
const agreement = this.agreements.find(ag => ag.name === agreementName || ag.name.includes(agreementName))
|
||||
|
||||
|
||||
if (agreement) {
|
||||
// 找到匹配的协议,添加为可点击的链接
|
||||
parts.push({
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user