593 lines
19 KiB
Vue
593 lines
19 KiB
Vue
<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: 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/images/assistant-avatar.png', // 医生助理头像(默认值,会从后端获取)
|
||
assistantName: '医生助理', // 医生助理名字(默认值,会从后端获取)
|
||
userAvatar: 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/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()
|
||
},
|
||
onBackPress() {
|
||
if (this.showAgreement) {
|
||
this.showAgreement = false
|
||
return true
|
||
}
|
||
return false
|
||
},
|
||
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 || 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/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 += `®ister_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() {
|
||
if (this.showAgreement) {
|
||
this.showAgreement = false
|
||
return
|
||
}
|
||
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>
|