9.vue3医疗在线问诊项目 - _问诊室模块-聊天问诊功能实现 ==> 默认消息渲染、文字聊天功能、图片聊天功能、医生接单、查看处方、购买药品、评价医生

9.vue3医疗在线问诊项目 - _问诊室模块-聊天问诊功能实现 ==> 默认消息渲染、文字聊天功能、图片聊天功能、医生接单、查看处方、购买药品、评价医生

问诊室-默认消息

实现:获取接诊前后默认聊天消息并渲染

9.vue3医疗在线问诊项目 - _问诊室模块-聊天问诊功能实现 ==> 默认消息渲染、文字聊天功能、图片聊天功能、医生接单、查看处方、购买药品、评价医生_第1张图片

需求❓:ws连接建立后,通过监听chatMsgList事件,服务器会返回患者和提示消息

步骤:

  • 监听默认聊天消息,并且处理成消息列表
    9.vue3医疗在线问诊项目 - _问诊室模块-聊天问诊功能实现 ==> 默认消息渲染、文字聊天功能、图片聊天功能、医生接单、查看处方、购买药品、评价医生_第2张图片
  • 提取常量数据
  • 进行渲染服务器返回的患者信息和提示消息
  • 预览病情图片

代码:

  1. 监听默认聊天记录,并且处理成消息列表

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)
  })
  1. 进行渲染

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>    
  1. 格式化病情描述数据

抽取常量数据 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 }} 图片 点击查看
  1. 预览病情图片 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';

问诊室-文字聊天

实现:可以发送文字消息,可以接收文字消息

9.vue3医疗在线问诊项目 - _问诊室模块-聊天问诊功能实现 ==> 默认消息渲染、文字聊天功能、图片聊天功能、医生接单、查看处方、购买药品、评价医生_第3张图片

需求分析❓:

  1. 底部RoomAction.vue操作组件,可以输入文字,触发 send-text 事件传出文字
  2. 问诊室组件,监听 send-text 事件接收文字
  3. 定义订单详情api接口和数据类型,需要使用医生ID,作为收信息人的标识
  4. 获取订单详情,然后通过 socket.emitsendChatMsg 事件发送文字给服务器,发送消息需要字段:from发送人(患者)、to收消息人(医生)、msgType消息类型、msg消息内容
  5. 通过 socket.onreceiveChatMsg 事件接收发送成功或者医生发来的消息,追加到聊天列表list中,并处理滚动
  6. RoomMessage.vue组件展示渲染聊天消息
  7. 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.emitsendChatMsg 发送文字给服务器

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.onreceiveChatMsg 接收发送成功或者医生发来的消息

 // 接收消息
  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>

说明❓:

  1. 患者:store.user?.id === from
  2. 医生:store.user?.id !== from
  3. 医生未接单,发送消息无响应

7)医生未接诊,不是咨询中状态禁用消息输入框

Room/index.vue

    

Room/components/RoomAction.vue

// 发消息输入框是否可用
defineProps<{
  disabled: boolean
}>()
  

问诊室-医生接单

使用医生端app接单

9.vue3医疗在线问诊项目 - _问诊室模块-聊天问诊功能实现 ==> 默认消息渲染、文字聊天功能、图片聊天功能、医生接单、查看处方、购买药品、评价医生_第4张图片

步骤:

  1. 抢单前需要,监听超级医生订单状态变更

    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

    
    
    
    
  2. 在抢单中心,输入订单号抢单

  3. 抢单成功后,进入医生问诊室,可以和患者端沟通

9.vue3医疗在线问诊项目 - _问诊室模块-聊天问诊功能实现 ==> 默认消息渲染、文字聊天功能、图片聊天功能、医生接单、查看处方、购买药品、评价医生_第5张图片

问诊室-图片聊天

实现:点击图片icon上传图片,成功后发送图片消息

9.vue3医疗在线问诊项目 - _问诊室模块-聊天问诊功能实现 ==> 默认消息渲染、文字聊天功能、图片聊天功能、医生接单、查看处方、购买药品、评价医生_第6张图片

需求分析❓:

  1. 底部操作组件,可以调用后台api函数上传图片,触发 send-image 事件传出图片对象

  2. 问诊室组件,监听 send-image 事件接收图片对象

  3. 通过 socket.emitsendChatMsg 发送图片给服务器

  4. 展示图片消息

代码:

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)
}
 
      
    

9.vue3医疗在线问诊项目 - _问诊室模块-聊天问诊功能实现 ==> 默认消息渲染、文字聊天功能、图片聊天功能、医生接单、查看处方、购买药品、评价医生_第7张图片

2)问诊室组件,监听 send-image 事件接收图片对象,通过 socket.emitsendChatMsg 发送图片给服务器

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)
}

9.vue3医疗在线问诊项目 - _问诊室模块-聊天问诊功能实现 ==> 默认消息渲染、文字聊天功能、图片聊天功能、医生接单、查看处方、购买药品、评价医生_第8张图片

问诊室-查看处方

实现:在医生端app,点击开处方,患者端app展示处方消息并支持查看

  • 医生端app:
9.vue3医疗在线问诊项目 - _问诊室模块-聊天问诊功能实现 ==> 默认消息渲染、文字聊天功能、图片聊天功能、医生接单、查看处方、购买药品、评价医生_第9张图片
  • 患者端app:

9.vue3医疗在线问诊项目 - _问诊室模块-聊天问诊功能实现 ==> 默认消息渲染、文字聊天功能、图片聊天功能、医生接单、查看处方、购买药品、评价医生_第10张图片

步骤:

  • 展示处方消息
  • 定义查看处方API函数
  • 点击查看处方预览处方图片

代码:

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])
    }
  }

9.vue3医疗在线问诊项目 - _问诊室模块-聊天问诊功能实现 ==> 默认消息渲染、文字聊天功能、图片聊天功能、医生接单、查看处方、购买药品、评价医生_第11张图片

说明❓:后台处方图片没实现

问诊室-购买药品

实现:点击处方的购买药品,进行处方订单的支付跳转

9.vue3医疗在线问诊项目 - _问诊室模块-聊天问诊功能实现 ==> 默认消息渲染、文字聊天功能、图片聊天功能、医生接单、查看处方、购买药品、评价医生_第12张图片

步骤:

  • 处方状态不同此按钮操作不同:
    • 如果处方失效:提示即可
    • 如果没付款:去药品预支付页面

代码:

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,点击问诊结束,给医生做评价

9.vue3医疗在线问诊项目 - _问诊室模块-聊天问诊功能实现 ==> 默认消息渲染、文字聊天功能、图片聊天功能、医生接单、查看处方、购买药品、评价医生_第13张图片

步骤:

  • 展示问诊室状态和评价组件
  • 评价表单数据绑定和校验
  • 提交评价
  • 修改消息

代码:

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)提交评价

  1. 定义评价api函数

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)
  1. 注入订单信息:提供医生ID和订单ID

Room/index.vue

import { provide } from 'vue'
// ...
provide('consult', consult)
  1. 评价组件接收注入的数据

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')
  1. 提交评价
+ 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)
}

9.vue3医疗在线问诊项目 - _问诊室模块-聊天问诊功能实现 ==> 默认消息渲染、文字聊天功能、图片聊天功能、医生接单、查看处方、购买药品、评价医生_第14张图片

你可能感兴趣的:(前端,typescript,vue.js,websocket)