电子处方
原始处方
{{ msg.prescription?.name }} {{ msg.prescription?.genderValue }} {{ msg.prescription?.age }}岁 {{ msg.prescription?.diagnosis }}
开方时间:{{ msg.prescription?.createTime }}
{{ med.name }} {{ med.specs }}
{{ med.usageDosag }}
实现:获取接诊前后默认聊天消息并渲染
需求❓:ws连接建立后,通过监听chatMsgList
事件,服务器会返回患者和提示消息
步骤:
代码:
Room/index.vue
import { MsgType } from '@/enums'
import type { Message, TimeMessages } from '@/types/room'
const list = ref<Message[]>([])
// 聊天记录 ({ data }: { data: TimeMessages[] }) 给解构的数据指定类型
socket.on('chatMsgList', ({ data }: { data: TimeMessages[] }) => {
// 准备转换常规消息列表
const arr: Message[] = []
data.forEach((item, i) => {
// 1. 处理消息时间
arr.push({
msgType: MsgType.Notify,
msg: { content: item.createTime },
createTime: item.createTime,
id: item.createTime
})
// 2. 其它消息
arr.push(...item.items)
})
// 追加到聊天消息列表
list.value.unshift(...arr)
})
Room/index.vue
<room-message :list="list" />
Room/components/RoomMessage.vue
import { ConsultTime } from '@/enums'
import type { Message } from '@/types/room'
defineProps<{ list: Message[] }>()
<template v-for="{ msgType, msg, id } in list" :key="id">
<div class="msg msg-illness" v-if="msgType === MsgType.CardPat">
<div class="patient van-hairline--bottom" v-if="msg.consultRecord">
<p>
{{ msg.consultRecord.patientInfo.name }}
{{ msg.consultRecord.patientInfo.genderValue }}
{{ msg.consultRecord.patientInfo.age }}岁
p>
<p>
{{ msg.consultRecord.illnessTime }} |
{{ msg.consultRecord.consultFlag }}
p>
div>
<van-row>
<van-col span="6">病情描述van-col>
<van-col span="18">{{ msg.consultRecord?.illnessDesc }}van-col>
<van-col span="6">图片van-col>
<van-col span="18"> 点击查看 van-col>
van-row>
div>
<div class="msg msg-tip" v-if="msgType === MsgType.NotifyTip">
<div class="content">
<span class="green">温馨提示:span>
<span>{{ msg.content }}span>
div>
div>
<div class="msg msg-tip" v-if="msgType === MsgType.Notify">
<div class="content">
<span>{{ msg.content }}span>
div>
div>
template>
抽取常量数据 api/constants.ts
import { ConsultTime } from '@/enums'
// 患病时间
export const timeOptions = [
{ label: '一周内', value: ConsultTime.Week },
{ label: '一月内', value: ConsultTime.Month },
{ label: '半年内', value: ConsultTime.HalfYear },
{ label: '大于半年', value: ConsultTime.More }
]
// 是否就诊过
export const flagOptions = [
{ label: '就诊过', value: 0 },
{ label: '没就诊过', value: 1 }
]
病情描述消息卡片使用
import { flagOptions, timeOptions } from '@/api/constants'
// 获取患病时间label信息
const getIllnessTimeText = (time: ConsultTime) =>
timeOptions.find((item) => item.value === time)?.label
// 获取是否就诊label信息
const getConsultFlagText = (flag: 0 | 1) =>
flagOptions.find((item) => item.value === flag)?.label
{{ msg.consultRecord.patientInfo.name }}
{{ msg.consultRecord.patientInfo.genderValue }}
{{ msg.consultRecord.patientInfo.age }}岁
+ {{ getIllnessTimeText(msg.consultRecord.illnessTime) }} |
+ {{ getConsultFlagText(msg.consultRecord.consultFlag) }}
病情描述
{{ msg.consultRecord?.illnessDesc }}
图片
点击查看
Room/components/RoomMessage.vue
病情描述
{{ msg.consultRecord?.illnessDesc }}
图片
+ 点击查看
import { ImagePreview } from 'vant'
const previewImg = (pictures?: Image[]) => {
if (pictures && pictures.length) ImagePreview(pictures.map((item) => item.url))
}
注意❓:ImagePreview函数形式,需要在入口手动引入 ImagePreview 组件的样式
main.ts
import 'vant/es/image-preview/style';
实现:可以发送文字消息,可以接收文字消息
需求分析❓:
RoomAction.vue
操作组件,可以输入文字,触发 send-text
事件传出文字send-text
事件接收文字socket.emit
的 sendChatMsg
事件发送文字给服务器,发送消息需要字段:from发送人(患者)、to收消息人(医生)、msgType消息类型、msg消息内容socket.on
的 receiveChatMsg
事件接收发送成功或者医生发来的消息,追加到聊天列表list中,并处理滚动RoomMessage.vue
组件展示渲染聊天消息RoomAction.vue
中处理医生未接诊,禁用消息输入框代码:
1)底部操作组件,可以输入文字,触发 send-text
事件传出文字
Room/components/RoomAction.vue
import { ref } from 'vue'
const emit = defineEmits<{
(e: 'send-text', text: string): void
}>()
const text = ref('')
const sendText = () => {
emit('send-text', text.value)
text.value = ''
}
2)问诊室组件,监听 send-text
事件接收文字
Room/index.vue
<room-action @send-text="sendText" />
const sendText = (text: string) => {
// 发送消息
}
3)获取订单详情1息人的标识
types/consult.d.ts
// 问诊订单详情类型
export type ConsultOrderItem = Consult & {
createTime: string
docInfo?: Doctor
patientInfo: Patient
orderNo: string
statusValue: string
typeValue: string
status: OrderType
countdown: number
prescriptionId?: string
evaluateId: number
payment: number
couponDeduction: number
pointDeduction: number
actualPayment: number
}
api/consult.ts
export const getConsultOrderDetail = (orderId: string) =>
request.get<ConsultOrderItem>('/patient/consult/order/detail', { params:{orderId} })
4)通过 socket.emit
的 sendChatMsg
发送文字给服务器
Room/index.vue
import type { ConsultOrderItem } from '@/types/consult'
import { getConsultOrderDetail } from '@/services/consult'
// 1. 获取订单详情
const consult = ref<ConsultOrderItem>()
onMounted(async () => {
const res = await getConsultOrderDetail(route.query.orderId as string)
consult.value = res.data
})
// 2. 发送消息
const sendText = (text: string) => {
// 发送信息需要数据:发送人、收消息人、消息类型、消息内容
socket.emit('sendChatMsg', {
from: store.user?.id, // 发送人
to: consult.value?.docInfo?.id, // 收消息人
msgType: MsgType.MsgText, // 消息类型
msg: { content: text } // 消息内容
})
}
5)通过 socket.on
的 receiveChatMsg
接收发送成功或者医生发来的消息
// 接收消息
socket.on('receiveChatMsg', async (msg) => {
list.value.push(msg)
await nextTick()
window.scrollTo(0, document.body.scrollHeight) // 收到新消息后滚动到底部
})
6)展示消息 Room/components/RoomMessage.vue
pnpm i dayjs
import dayjs from 'dayjs'
const formatTime = (time: string) => dayjs(time).format('HH:mm')
const store = useUserStore()
<div class="msg msg-to" v-if="msgType === MsgType.MsgText && store.user?.id === from">
<div class="content">
<div class="time">{{ formatTime(createTime) }}div>
<div class="pao">{{ msg.content }}div>
div>
<van-image :src="store.user?.avatar" />
div>
<div class="msg msg-from" v-if="msgType === MsgType.MsgText && store.user?.id !== from">
<van-image :src="fromAvatar" />
<div class="content">
<div class="time">{{ formatTime(createTime) }}div>
<div class="pao">{{ msg.content }}div>
div>
div>
说明❓:
store.user?.id === from
store.user?.id !== from
7)医生未接诊,不是咨询中状态禁用消息输入框
Room/index.vue
Room/components/RoomAction.vue
// 发消息输入框是否可用
defineProps<{
disabled: boolean
}>()
使用医生端app接单
步骤:
抢单前需要,监听超级医生订单状态变更
1)医生接单后,会触发ws的statusChange事件,更新订单状态
Room/index.vue
onMounted(async () => {
// ...
// 3. 监听超级医生订单状态变更,更新订单状态(必须)
socket.on('statusChange', async () => {
const res = await getConsultOrderDetail(route.query.orderId as string)
consult.value = res.data
console.log('订单状态更新了:', consult.value)
})
})
2)处理问诊订单状态
Room/index.vue
Room/components/RoomStatus.vue
已通知医生尽快接诊,24小时内医生未回复将自动退款
咨询中
剩余时间:
已结束
在抢单中心,输入订单号抢单
抢单成功后,进入医生问诊室,可以和患者端沟通
实现:点击图片icon上传图片,成功后发送图片消息
需求分析❓:
底部操作组件,可以调用后台api函数上传图片,触发 send-image
事件传出图片对象
问诊室组件,监听 send-image
事件接收图片对象
通过 socket.emit
的 sendChatMsg
发送图片给服务器
展示图片消息
代码:
1)底部操作组件,可以上传图片,触发 send-image
事件传出图片对象
Room/components/RoomAction.vue
import { uploadImage } from '@/api/consult'
import type { Image } from '@/types/consult'
import { Toast } from 'vant'
import type { UploaderAfterRead } from 'vant/lib/uploader/types'
const emit = defineEmits<{
(e: 'send-text', text: string): void
+ (e: 'send-image', img: Image): void
}>()
const sendImage: UploaderAfterRead = async (data) => {
// 排除多图上传数组情况
if (Array.isArray(data)) return
// 排除不存在情况
if (!data.file) return
const t = Toast.loading('正在上传')
const res = await uploadImage(data.file)
// 关闭提示
t.clear()
emit('send-image', res.data)
}
2)问诊室组件,监听 send-image
事件接收图片对象,通过 socket.emit
的 sendChatMsg
发送图片给服务器
Room/index.vue
import type { Image } from '@/types/consult'
const sendImage = (img: Image) => {
socket.emit('sendChatMsg', {
from: store.user?.id,
to: consult.value?.docInfo?.id,
msgType: MsgType.MsgImage,
msg: { picture: img }
})
}
3)展示消息
Room/components/RoomMessage.vue
<div class="msg msg-to" v-if="msgType === MsgType.MsgImage && store.user?.id === from">
<div class="content">
<div class="time">{{ formatTime(createTime) }}div>
<van-image @load="loadSuccess()" fit="contain" :src="msg.picture?.url" />
div>
<van-image :src="store.user?.avatar" />
div>
<div class="msg msg-from" v-if="msgType === MsgType.MsgImage && store.user?.id !== from">
<van-image :src="fromAvatar" />
<div class="content">
<div class="time">{{ formatTime(createTime) }}div>
<van-image @load="loadSuccess()" fit="contain" :src="msg.picture?.url" />
div>
div>
说明❓:解决图片加载滚动没有到最底部问题
// 图片加载成功=> 执行滚动
const loadSuccess = () => {
window.scrollTo(0, document.body.scrollHeight)
}
实现:在医生端app,点击开处方,患者端app展示处方消息并支持查看
步骤:
代码:
1)展示处方消息
Room/components/RoomMessage.vue
电子处方
原始处方
{{ msg.prescription?.name }}
{{ msg.prescription?.genderValue }}
{{ msg.prescription?.age }}岁
{{ msg.prescription?.diagnosis }}
开方时间:{{ msg.prescription?.createTime }}
{{ med.name }} {{ med.specs }}
{{ med.usageDosag }}
x{{ med.quantity }}
购买药品
2)定义查看处方API
api/consult.ts
// 查看处方
export const getPrescriptionPic = (id: string) =>
request.get<{ url: string }>(`/patient/consult/prescription/${id}`)
3)点击查看处方预览处方图片
Room/components/RoomMessage.vue
电子处方
原始处方
{{ msg.prescription?.name }}
{{ msg.prescription?.genderValue }}
{{ msg.prescription?.age }}岁
{{ msg.prescription?.diagnosis }}
开方时间:{{ msg.prescription?.createTime }}
import { getPrescriptionPic } from '@/api/consult'
const showPrescription = async (id?: string) => {
if (id) {
const res = await getPrescriptionPic(id)
ImagePreview([res.data.url])
}
}
说明❓:后台处方图片没实现
实现:点击处方的购买药品,进行处方订单的支付跳转
步骤:
代码:
Room/components/RoomMessage.vue
{{ med.name }} {{ med.specs }}
{{ med.usageDosag }}
x{{ med.quantity }}
+ 购买药品
import { useRouter } from 'vue-router'
import { PrescriptionStatus } from '@/enums'
import { Toast } from 'vant'
// 点击处方的跳转
const router = useRouter()
const buy = (pre?: Prescription) => {
if (pre) {
// 1. 如果处方失效:提示即可
if (pre.status === PrescriptionStatus.Invalid) return Toast('处方已失效')
// 2. 如果没付款且没订单ID:去药品预支付页面
if (pre.status === PrescriptionStatus.NotPayment)
return router.push(`/medicine/pay?id=${pre.id}`)
}
}
实现:医生端app,点击问诊结束,给医生做评价
步骤:
代码:
1)展示问诊室状态和评价组件
Room/components/RoomMessage.vue
<div class="msg msg-tip msg-tip-cancel" v-if="msgType === MsgType.NotifyCancel">
<div class="content">
<span>{{ msg.content }}span>
div>
div>
<div class="msg" v-if="msgType === MsgType.CardEva || msgType === MsgType.CardEvaForm">
<evaluate-card :evaluateDoc="msg.evaluateDoc">evaluate-card>
div>
Room/components/evaluateCard.vue
import type { EvaluateDoc } from '@/types/room'
defineProps<{
// 接收评价数据:存在说明评价过,相反没有
evaluateDoc?: EvaluateDoc
}>()
-
+
医生服务评价
我们会更加努力提升服务质量
-
感谢您的评价
本次在线问诊服务您还满意吗?
2)评价表单数据绑定和校验
import { Toast } from 'vant'
import { computed, inject, ref } from 'vue'
const score = ref(0)
const anonymousFlag = ref(false)
const content = ref('')
const disabled = computed(() => !score.value || !content.value)
const onSubmit = async () => {
}
-
感谢您的评价
本次在线问诊服务您还满意吗?
4)提交评价
api/consult.ts
// 评价问诊
export const evaluateConsultOrder = (data: {
docId: string // 医生ID
orderId: string // 订单ID
score: number
content: string
anonymousFlag: 0 | 1
}) => request.post<{ id: string }>('/patient/order/evaluate', data)
Room/index.vue
import { provide } from 'vue'
// ...
provide('consult', consult)
Room/components/EvaluateCard.vue
import { inject, type Ref } from 'vue'
import type { ConsultOrderItem } from '@/types/consult'
// 说明❓:Ref 指定类型为Ref,提交时才可以使用.value访问变量
const consult = inject<Ref<ConsultOrderItem>>('consult')
+ import { evaluateConsultOrder } from '@/api/consult'
const onSubmit = async () => {
if (!score.value) return Toast('请选择评分')
if (!content.value) return Toast('请输入评价')
+ if (!consult?.value) return Toast('未找到订单')
+ if (consult.value.docInfo?.id) {
+ await evaluateConsultOrder({
+ docId: consult.value?.docInfo?.id,
+ orderId: consult.value?.id,
+ score: score.value,
+ content: content.value,
+ anonymousFlag: anonymousFlag.value ? 1 : 0
+ })
+ }
// 修改消息:评价请求成功,改成已评价
}
5)成功后修改消息,切换显示已评价
Room/index.vue
// 评价成功,修改评价消息状态和数据,切换卡片展示
const completeEva = (score: number) => {
// 获取评价信息数据
const item = list.value.find((item) => item.msgType === MsgType.CardEvaForm)
if (item) {
item.msg.evaluateDoc = { score }
item.msgType = MsgType.CardEva
}
}
provide('completeEva', completeEva)
Room/components/EvaluateCard.vue
+ const completeEva = inject<(score: number) => void>('completeEva')
const onSubmit = async () => {
if (!consult?.value) return Toast('未找到订单')
if (consult.value.docInfo?.id) {
await evaluateConsultOrder({
docId: consult.value.docInfo?.id,
orderId: consult.value?.id,
score: score.value,
content: content.value,
anonymousFlag: anonymousFlag.value ? 1 : 0
})
}
- // 修改消息:评价请求成功,改成已评价
+ completeEva && completeEva(score.value)
}