Files
xk-client-wx/subPackages/transfer/transfer-consultation.vue
2026-05-06 16:41:19 +08:00

582 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<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,
submitTransferQuestionsApi,
getTransferAssistantConfigApi,
getTransferConsultationAgreementInfoApi,
getAgreementDetailApi
} from '@/request/api/transferPrescription.js'
export default {
name: 'TransferConsultation',
components: { AssistantConsultationShell },
data() {
return {
transferId: 0, // 转诊ID
questions: [], // 问题列表
currentQuestionIndex: 0, // 当前问题索引
userAnswers: [], // 用户答案列表
showWelcome: true, // 显示欢迎消息
showNotice: true, // 显示提示信息
showWaiting: false, // 显示等待转接消息
isSubmitting: false, // 是否正在提交
scrollIntoView: '', // 滚动到指定位置
assistantAvatar: '/static/images/assistant-avatar.png', // 医生助理头像(默认值,会从后端获取)
assistantName: '医生助理', // 医生助理名字(默认值,会从后端获取)
userAvatar: '/static/images/user-avatar.png', // 用户头像(需要替换为实际路径)
prescriptionInfo: '炒黄芪等共2味', // 处方信息(可以从转诊详情中获取)
replyDelay: 1.00, // 回复延迟时间(秒),从配置中获取
isTyping: false, // 是否显示"正在回复"提示
noticeText: '', // 提示文案
agreements: [], // 协议列表
parsedNoticeParts: [], // 解析后的文案部分(文本和协议链接)
showAgreement: false, // 控制协议抽屉显示
agreementContent: '', // 协议内容
currentAgreementId: 0, // 当前查看的协议ID
currentAgreementName: '' // 当前查看的协议名称
}
},
computed: {
// 当前问题
currentQuestion() {
if (this.currentQuestionIndex < this.questions.length) {
return this.questions[this.currentQuestionIndex]
}
return null
},
// 当前问题的答案选项
currentQuestionAnswers() {
if (!this.currentQuestion) {
return ['是', '否'] // 默认答案
}
// 获取问题的answers字段
let answers = this.currentQuestion.answers
// 如果answers是字符串JSON格式解析为数组
if (typeof answers === 'string') {
try {
answers = JSON.parse(answers)
} catch (e) {
console.error('解析答案选项失败:', e)
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({
type: 'welcome',
id: 'welcome-msg',
timestamp: 0
})
}
// 2. 问题和答案交替显示
for (let i = 0; i <= this.currentQuestionIndex && i < this.questions.length; i++) {
const question = this.questions[i]
// 添加问题
messages.push({
type: 'question',
id: `question-${question.id}`,
questionId: question.id,
content: question.question,
timestamp: i * 2 + 1
})
// 如果有对应的答案,添加答案
if (i < this.userAnswers.length) {
messages.push({
type: 'answer',
id: `answer-${i}`,
content: this.userAnswers[i],
timestamp: i * 2 + 2
})
}
}
// 3. 等待转接消息
if (this.showWaiting) {
messages.push({
type: 'waiting',
id: 'waiting-msg',
timestamp: messages.length
})
}
return messages
}
},
watch: {
// 监听当前问题索引变化,自动滚动到底部
currentQuestionIndex(newVal, oldVal) {
if (newVal !== oldVal && newVal >= 0) {
this.$nextTick(() => {
this.scrollToBottom()
})
}
},
// 监听用户答案变化,自动滚动到底部
userAnswers: {
handler(newVal, oldVal) {
if (newVal.length > (oldVal?.length || 0)) {
this.$nextTick(() => {
this.scrollToBottom()
})
}
},
deep: true
},
// 监听显示等待消息,自动滚动到底部
showWaiting(newVal) {
if (newVal) {
this.$nextTick(() => {
this.scrollToBottom()
})
}
}
},
onLoad(options) {
// 获取转诊ID
this.transferId = parseInt(options.transfer_id || 0)
if (!this.transferId) {
uni.showToast({
title: '转诊ID不能为空',
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
return
}
// 加载转诊详情、问题列表、助理配置和协议信息
this.loadTransferDetail()
this.loadQuestions()
this.loadAssistantConfig()
this.loadAgreementInfo()
},
methods: {
// 加载转诊详情(获取药品信息)
async loadTransferDetail() {
try {
const res = await getTransferPrescriptionDetailApi({ id: this.transferId })
if (res.data && res.data.code === 0) {
const transferData = res.data.result || {}
// 从转诊详情中提取药品名称
if (transferData.drug_name_text) {
this.prescriptionInfo = transferData.drug_name_text
} else if (transferData.content) {
// 如果后端没有返回drug_name_text前端自己解析
const content = typeof transferData.content === 'string'
? JSON.parse(transferData.content)
: transferData.content
this.prescriptionInfo = this.extractDrugName(content, transferData.prescription_type || 1)
}
}
} catch (error) {
console.error('加载转诊详情失败:', error)
// 失败不影响继续,使用默认值
}
},
// 从处方内容中提取药品名称(前端备用方法)
extractDrugName(content, prescriptionType) {
if (!content) return '未知药品'
const drugNames = []
if (prescriptionType === 1) {
// 中药处方
if (content.repice && Array.isArray(content.repice)) {
content.repice.forEach(recipe => {
if (recipe.content) {
let recipeContent = recipe.content
if (typeof recipeContent === 'string') {
try {
recipeContent = JSON.parse(recipeContent)
} catch {
recipeContent = []
}
}
if (Array.isArray(recipeContent)) {
recipeContent.forEach(drug => {
const name = drug.name || drug.drug_name
if (name && !drugNames.includes(name)) {
drugNames.push(name)
}
})
}
}
})
}
} else {
// 西药处方
if (content.repice && Array.isArray(content.repice)) {
content.repice.forEach(recipe => {
if (recipe.content) {
let recipeContent = recipe.content
if (typeof recipeContent === 'string') {
try {
recipeContent = JSON.parse(recipeContent)
} catch {
recipeContent = {}
}
}
const name = recipeContent.drug_name || recipeContent.name
if (name && !drugNames.includes(name)) {
drugNames.push(name)
}
}
})
}
}
if (drugNames.length === 0) return '未知药品'
if (drugNames.length === 1) return drugNames[0]
return drugNames[0] + '等共' + drugNames.length + '味'
},
// 加载助理配置
async loadAssistantConfig() {
try {
const res = await getTransferAssistantConfigApi()
if (res.data && res.data.code === 0) {
const config = res.data.result || {}
this.assistantName = config.name || '医生助理'
this.assistantAvatar = config.avatar || '/static/images/assistant-avatar.png'
this.replyDelay = parseFloat(config.reply_delay) || 1.00 // 获取延迟时间,支持小数
}
} catch (error) {
console.error('加载助理配置失败:', error)
// 失败不影响继续,使用默认值
}
},
// 加载问题列表
async loadQuestions() {
try {
const res = await getTransferQuestionsApi()
if (res.data && res.data.code === 0) {
// 确保问题按sort字段排序
const questions = res.data.result || []
this.questions = questions.sort((a, b) => {
if (a.sort !== b.sort) {
return a.sort - b.sort
}
return a.id - b.id
})
if (this.questions.length === 0) {
uni.showToast({
title: '暂无咨询问题',
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
} else {
// 延迟显示第一个问题(模拟真人回复)
this.isTyping = true
setTimeout(() => {
this.isTyping = false
// 显示第一个问题
this.currentQuestionIndex = 0
this.$nextTick(() => {
this.scrollToBottom()
})
}, this.replyDelay * 1000) // 延迟时间(毫秒)
}
} else {
throw new Error(res.data.message || '获取问题失败')
}
} catch (error) {
console.error('加载问题失败:', error)
uni.showToast({
title: error.message || '网络异常',
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
},
// 处理用户回答
handleAnswer(answer) {
if (!this.currentQuestion) {
return
}
// 保存答案
this.userAnswers.push(answer)
// 立即滚动到底部,显示答案
this.$nextTick(() => {
this.scrollToBottom()
})
// 如果还有下一个问题,延迟显示下一个问题(模拟真人回复)
if (this.currentQuestionIndex < this.questions.length - 1) {
// 显示"正在回复"提示
this.isTyping = true
// 延迟后显示下一个问题
setTimeout(() => {
this.isTyping = false
this.currentQuestionIndex++
// watch会自动触发滚动
}, this.replyDelay * 1000) // 延迟时间(毫秒)
} else {
// 所有问题已回答,提交答案
// 延迟提交,让用户看到最后的问题和答案
setTimeout(() => {
this.submitAnswers()
}, 500)
}
},
// 提交答案
async submitAnswers() {
if (this.isSubmitting) {
return
}
this.isSubmitting = true
this.showWaiting = true
// 滚动到底部watch会自动触发
try {
// 构建答案数组(按照问题的顺序,确保与后端保存顺序一致)
const answers = []
for (let i = 0; i < this.questions.length; i++) {
if (this.userAnswers[i]) {
answers.push({
question_id: this.questions[i].id,
answer: this.userAnswers[i]
})
}
}
const res = await submitTransferQuestionsApi({
transfer_id: this.transferId,
answers: answers
})
if (res.data && res.data.code === 0) {
const result = res.data.result || {}
uni.showToast({
title: '提交成功',
icon: 'success'
})
// 延迟跳转,让用户看到成功提示
setTimeout(() => {
// 跳转到聊天房间使用navigateTo而不是redirectTo确保页面生命周期正常执行
if (result.room_id && result.doctor_id) {
const registerId = result.register?.id || ''
// 获取医生信息优先使用返回的doctor_info否则使用默认值
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) {
chatUrl += `&register_id=${registerId}`
}
if (doctorAvatar) {
chatUrl += `&avatar=${encodeURIComponent(doctorAvatar)}`
}
uni.navigateTo({
url: chatUrl
})
} else {
// 如果没有房间信息,跳转到转诊消息页面
uni.navigateBack()
}
}, 1500)
} else {
throw new Error(res.data.message || '提交失败')
}
} catch (error) {
console.error('提交答案失败:', error)
this.isSubmitting = false
this.showWaiting = false
uni.showToast({
title: error.message || '提交失败,请重试',
icon: 'none'
})
}
},
// 返回
handleBack() {
uni.showModal({
title: '提示',
content: '确定要退出咨询吗?未完成的回答将不会保存。',
success: (res) => {
if (res.confirm) {
uni.navigateBack()
}
}
})
},
// 滚动到底部
scrollToBottom() {
this.scrollIntoView = 'bottom-anchor'
// 延迟一下确保滚动生效
setTimeout(() => {
this.scrollIntoView = ''
}, 100)
},
// 加载协议信息
async loadAgreementInfo() {
try {
const res = await getTransferConsultationAgreementInfoApi()
if (res.data && res.data.code === 0) {
const result = res.data.result || {}
this.noticeText = result.notice_text || ''
this.agreements = result.agreements || []
// 解析文案,提取协议链接
this.parseNoticeText()
}
} catch (error) {
console.error('加载协议信息失败:', error)
// 失败时使用默认文案
this.noticeText = '根据国家互联网医院管理办法要求,平台仅为复诊患者提供服务。为了保障您的用药安全,购买该药物需要经过医生问诊,请根据真实情况回答,并请仔细阅读《互联网医疗风险告知及知情同意书》,继续咨询即表示您已知悉相关规则与风险并同意相关条款。'
this.parseNoticeText()
}
},
// 解析提示文案,将《协议名称》替换为可点击的链接
parseNoticeText() {
if (!this.noticeText) {
this.parsedNoticeParts = []
return
}
const parts = []
// 使用正则表达式匹配《》中的协议名称
const regex = /《([^》]+)》/g
let lastIndex = 0
let match
while ((match = regex.exec(this.noticeText)) !== null) {
// 添加匹配前的文本
if (match.index > lastIndex) {
parts.push({
type: 'text',
text: this.noticeText.substring(lastIndex, match.index)
})
}
// 查找匹配的协议
const agreementName = match[1]
const agreement = this.agreements.find(ag => ag.name === agreementName || 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.noticeText.length) {
parts.push({
type: 'text',
text: this.noticeText.substring(lastIndex)
})
}
this.parsedNoticeParts = parts
},
// 查看协议
async viewAgreement(agreementId) {
if (!agreementId) {
uni.showToast({
title: '协议ID不能为空',
icon: 'none'
})
return
}
// 查找协议名称
const agreement = this.agreements.find(ag => ag.id === agreementId)
this.currentAgreementId = agreementId
this.currentAgreementName = agreement ? agreement.name : '协议详情'
this.showAgreement = true
try {
const res = await getAgreementDetailApi(agreementId)
if (res.data && res.data.code === 0) {
const result = res.data.result || {}
this.agreementContent = result.content || ''
// 如果API返回了协议名称使用API返回的名称
if (result.name) {
this.currentAgreementName = result.name
}
} else {
throw new Error(res.data.message || '获取协议详情失败')
}
} catch (error) {
console.error('获取协议详情失败:', error)
uni.showToast({
title: error.message || '获取协议详情失败',
icon: 'none'
})
this.showAgreement = false
}
}
}
}
</script>