页面加载中
博客快捷键
按住 Shift 键查看可用快捷键
ShiftK
开启/关闭快捷键功能
ShiftA
打开/关闭中控台
ShiftD
深色/浅色显示模式
ShiftS
站内搜索
ShiftR
随机访问
ShiftH
返回首页
ShiftL
友链页面
ShiftP
关于本站
ShiftI
原版/本站右键菜单
松开 Shift 键或点击外部区域关闭
互动
最近评论
暂无评论
标签
寻找感兴趣的领域
暂无标签
    0
    文章
    0
    标签
    8
    分类
    10
    评论
    128
    功能
    深色模式
    标签
    JavaScript12TypeScript8React15Next.js6Vue10Node.js7CSS5前端20
    互动
    最近评论
    暂无评论
    标签
    寻找感兴趣的领域
    暂无标签
      0
      文章
      0
      标签
      8
      分类
      10
      评论
      128
      功能
      深色模式
      标签
      JavaScript12TypeScript8React15Next.js6Vue10Node.js7CSS5前端20
      未知歌曲
      未播放
      ♪ 暂无歌词 ♪
      随便逛逛
      博客分类
      文章标签
      复制地址
      深色模式
      AnHeYuAnHeYu
      Search⌘K
      博客
        暂无其他文档

        uni-app(优医咨询)项目实战 - 第7天

        本文介绍uni-app优医咨询项目第7天实战内容,主要包括:基于WebSocket实现问诊全流程对话功能,支持文字和图片消息;使用uniCloud云存储完成文件上传;实现电子处方查看及医生评价功能。重点讲解问诊室页面搭建、Socket.IO连接配置及消息交互实现。

        April 17, 202441 分钟 阅读2 次阅读

        uni-app(优医咨询)项目实战 - 第7天

        学习目标:

        • 能够基于 WebSocket 完成问诊全流程
        • 能够使用 uniCloud 云存储上传文件
        • 能够完成查看电子处方的功能
        • 能够完成医生评价的功能

        一、问诊室

        以对话聊天的方式向医生介绍病情并获取诊断方案,聊天的内容支持文字和图片两种形式。

        首先新建一个页面并完成分包的配置:

        {
            "subPackages": [
            {
              "root": "subpkg_consult",
              "pages": [
                {
                  "path": "room/index",
                  "style": {
                    "navigationBarTitleText": "问诊室"
                  }
                }
              ]
            },
          ]
        }
        

        该页面的内容特别多我们分段来数据模板代码移到项目当中:

        <!-- subpkg_consult/room/index.vue -->
        <script setup></script>
        
        <template>
          <view class="room-page">
        
            <scroll-view
              refresher-enabled
              refresher-background="#f2f2f2"
              scroll-y
              style="flex: 1; overflow: hidden"
            >
              <view class="message-container">
                <!-- 此处将来填充更多代码... -->
              </view>
            </scroll-view>
        
            <!-- 发送消息 -->
            <view class="message-bar">
              <template v-if="true">
                <uni-easyinput
                  disabled
                  :clearable="false"
                  :input-border="false"
                  placeholder-style="font-size: 32rpx; color: #c3c3c5;"
                  placeholder="问医生"
                />
                <view class="image-button">
                  <uni-icons size="40" color="#979797" type="image"></uni-icons>
                </view>
              </template>
              <button v-else class="uni-button">咨询其它医生</button>
            </view>
          </view>
        </template>
        
        <style lang="scss">
          @import './index.scss';
        </style>
        
        // subpkg_consult/room/index.scss
        .room-page {
          display: flex;
          flex-direction: column;
          height: 100vh;
          /* #ifdef H5 */
          height: calc(100vh - 44px);
          /* #endif */
          overflow: hidden;
          box-sizing: border-box;
          background-color: #f2f2f2;
        }
        
        .message-container {
          padding: 0 30rpx 60rpx;
          overflow: hidden;
        }
        
        .message-bar {
          background-color: red;
          display: flex;
          padding: 30rpx 30rpx calc(env(safe-area-inset-bottom) + 40rpx);
          background-color: #fff;
        
          :deep(.is-disabled) {
            background-color: transparent !important;
          }
        
          :deep(.uni-easyinput__content-input) {
            height: 88rpx;
            padding: 0 44rpx !important;
            border-radius: 88rpx;
            color: #3c3e42;
            font-size: 32rpx;
            background-color: #f6f6f6;
          }
        
          .image-button {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 88rpx;
            width: 88rpx;
            margin-left: 30rpx;
          }
        
          .uni-button {
            flex: 1;
          }
        }
        

        1.1 WebSocket 连接

        首先安装 Socket.IO

        npm install socket.io-client
        

        然后建立连接,在建立连接进需要传入参数和登录信息:

        • auth 登录状态信息,即 token
        • query 建立连接时传递的参数
        • transports 建立连接时使用的协议
        • timeout 超时设置
        <!-- subpkg_consult/room/index.vue -->
        <script setup>
          import { ref } from 'vue'
        
          import { io } from 'socket.io-client'
          import { useUserStore } from '@/stores/user'
        
          // 用户登录信息(不具有响应式)
          const { token } = useUserStore()
        
          // 获取地址中的参数
          const props = defineProps({
            orderId: String,
          })
        
          // 建立 socket 连接
          const socket = io('https://consult-api.itheima.net', {
            auth: { token: 'Bearer ' + token },
            query: { orderId: props.orderId },
            transports: ['websocket', 'polling'],
            timeout: 5000,
          })
        </script>
        

        1.2 接收消息

        Socket.IO 是基于事件来实现数据通信的,事件的名称是由前后端商定好的,详见接口文档说明,消息的获取分成两种情况:

        • 历史消息,事件名称为 chatMsgList
        • 即时消息,事件名称为 receiveChatMsg
        1.2.1 消息列表

        在建立连接时服务端会通过 chatMsgList 传递历史数据,通过 on 方法进行监听来获取这些数据:

        <!-- subpkg_consult/room/index.vue -->
        <script setup>
          import { ref } from 'vue'
          import { io } from 'socket.io-client'
          import { useUserStore } from '@/stores/user'
        
            // 省略前面小节的代码...
        
          // 消息列表
          const messageList = ref([])
        
          // 获取历史消息
          socket.on('chatMsgList', ({ code, data }) => {
            // 没有返回数据
            if (code !== 10000) return
            // 提取列表数据
            data.forEach(({ items }) => {
              // 追加到消息列表中
              messageList.value.push(...items)
            })
          })
        </script>
        

        在消息列表数据中包含了不同类型的消息且展示的方式也不相同,因此在对数据进行遍历的过程中需要通过 v-if 来渲染不同的模板,不同的类型对应了一个数值:

        消息类型 说明 备注
        21 患者信息
        22 处方信息
        23 未提交评价
        24 已提交评价
        31 普通通知 白底黑字
        32 温馨提示
        33 取消订单 灰底黑字
        4 图片消息
        1 文字消息

        首次进入问诊室返回的 3 条件的类型分别为患者信息(21)、普通通知(31)、温馨提示(32),我们逐个进行渲染。

        1.2.2 患者消息

        首先创建患者消息组件,组件的模板布局如下:

        <!-- subpkg_consult/room/components/patient-info.vue -->
        <script setup></script>
        <template>
          <!-- 患者信息(21) -->
          <view class="patient-info">
            <view class="header">
              <view class="title">李富贵 男 31岁</view>
              <view class="note">一周内 | 未去医院就诊</view>
            </view>
            <view class="content">
              <view class="list-item">
                <text class="label">病情描述</text>
                <text class="note">头痛、头晕、恶心</text>
              </view>
              <view class="list-item">
                <text class="label">图片</text>
                <text class="note">点击查看</text>
              </view>
            </view>
          </view>
        </template>
        
        <style lang="scss">
          .patient-info {
            padding: 30rpx;
            margin-top: 60rpx;
            border-radius: 20rpx;
            box-sizing: border-box;
            background-color: #fff;
        
            .header {
              padding-bottom: 20rpx;
              border-bottom: 1rpx solid #ededed;
        
              .title {
                font-size: 32rpx;
                color: #121826;
                margin-bottom: 10rpx;
              }
        
              .note {
                font-size: 26rpx;
                color: #848484;
              }
            }
        
            .content {
              margin-top: 20rpx;
              font-size: 26rpx;
        
              .list-item {
                display: flex;
                margin-top: 10rpx;
              }
        
              .label {
                width: 130rpx;
                color: #3c3e42;
              }
        
              .note {
                flex: 1;
                line-height: 1.4;
                color: #848484;
              }
            }
          }
        </style>
        

        接下来分成3个步骤来实现:

        1. 自定义组件的相关逻辑,要求组件能接收外部传入的数据
        <!-- subpkg_consult/room/components/patient-info.vue -->
        <script setup>
          // 定义属性接收外部传入的数据
          const props = defineProps({
            info: {
              type: Object,
              default: {},
            },
          })
        
          // 患病时长
          const illnessTimes = {
            1: '一周内',
            2: '一个月内',
            3: '半年内',
            4: '半年以上',
          }
          // 是否就诊过
          const consultFlags = {
            1: '就诊过',
            0: '没有就诊过',
          }
        </script>
        <template>
            ...
        </template>
        
        1. 在页面应用组件并传入数据
        <!-- subpkg_consult/room/index.vue -->
        <script setup>
          import { ref } from 'vue'
          import { io } from 'socket.io-client'
          import { useUserStore } from '@/stores/user'
          // 引入患者信息组件
          import patientInfo from './components/patient-info.vue'
        
          // 省略前面小节的代码
        </script>
        <template>
          <view class="room-page">
            <!-- 此处将来填充更多代码... -->
            <scroll-view
              refresher-enabled
              refresher-background="#f2f2f2"
              scroll-y
              style="flex: 1; overflow: hidden"
            >
              <view class="message-container">
                <template v-for="message in messageList" :key="message.id">
        
                  <!-- 患者信息(21) -->
                  <patient-info
                    v-if="message.msgType === 21"
                    :info="message.msg.consultRecord"
                  />
        
                  <!-- 此处将来填充更多代码... -->
                </template>
              </view>
                </scroll-view>
        
            <!-- 发送消息 -->
            <view class="message-bar">
              ...
            </view>
          </view>
        </template>
        
        1. 在组件内部接收并渲染数据
        <!-- subpkg_consult/room/components/patient-info.vue -->
        <script setup>
            // 省略前面小节的代码...
        </script>
        
        <template>
          <!-- 患者信息(21) -->
          <view class="patient-info">
            <view class="header">
              <view class="title">
                {{ props.info.patientInfo.name }}
                {{ props.info.patientInfo.genderValue }}
                {{ props.info.patientInfo.age }}岁
              </view>
              <view class="note">
                {{ illnessTimes[props.info.illnessTime] }}
                |
                {{ consultFlags[props.info.illnessType] }}
              </view>
            </view>
            <view class="content">
              <view class="list-item">
                <text class="label">病情描述</text>
                <text class="note">{{ props.info.illnessDesc }}</text>
              </view>
              <view class="list-item">
                <text class="label">图片</text>
                <text v-if="props.info.pictures?.length" class="note"> 点击查看 </text>
                <text v-else class="note">暂无图片</text>
              </view>
            </view>
          </view>
        </template>
        
        1. 大图查看患者病情图片,uni-app 提供了大图查看图片的 API uni.previewImage
        <script setup>
            // 省略前面小节的代码...
        
          // 点击查看病情介绍图片
          async function onPreviewClick(urls) {
            uni.previewImage({
              urls: urls.map((item) => item.url),
            })
          }
        </script>
        
        <template>
          <!-- 患者信息(21) -->
          <view class="patient-info">
            <view class="header">
              ...
            </view>
            <view class="content">
              <view class="list-item">
                <text class="label">病情描述</text>
                <text class="note">{{ props.info.illnessDesc }}</text>
              </view>
              <view class="list-item">
                <text class="label">图片</text>
                <text
                  v-if="props.info.pictures?.length"
                  @click="onPreviewClick(props.info.pictures)"
                  class="note"
                >
                  点击查看
                </text>
                <text v-else class="note">暂无图片</text>
              </view>
            </view>
          </view>
        </template>
        
        1.2.3 通知消息

        通知消息分为3种,分别为:

        消息类型 说明 备注
        31 普通通知 白底黑字
        32 温馨提示
        33 取消订单 灰底黑字

        首先创建消息通知组伯,通知消息的模板如下:

        <!-- subpkg_consult/room/components/notify-info.vue -->
        <script setup></script>
        
        <template>
          <!-- 普通通知(31) -->
          <view class="message-tips">
            <view class="wrapper">医护人员正在赶来,请耐心等候</view>
          </view>
        
          <!-- 温馨提示(32) -->
          <view class="message-tips">
            <view class="wrapper">
              <text class="label">温馨提示:</text>
              在线咨询不能代替面诊,医护人员建议仅供参考
            </view>
          </view>
        </template>
        
        <style lang="scss">
          .message-tips {
            display: flex;
            justify-content: center;
            margin-top: 60rpx;
        
            &:first-child {
              margin-top: 30rpx;
            }
          }
        
          .wrapper {
            line-height: 1;
            text-align: center;
            padding: 20rpx 30rpx;
            // margin-top: 60rpx;
            font-size: 24rpx;
            border-radius: 70rpx;
            color: #848484;
            background-color: #fff;
        
            .label {
              color: #16c2a3;
            }
          }
        </style>
        

        接下来分成3个步骤来实现:

        1. 定义组件的逻辑,要求能区分通知的类型并通过插槽来展示内容
        <!-- subpkg_consult/room/components/notify-info.vue -->
        <script setup>
          // 接收外部传入的数据
          const props = defineProps({
            type: {
              type: Number,
              default: 31,
            },
          })
        </script>
        <template>
          <!-- 温馨提示(32) -->
          <view class="message-tips">
            <text class="label">温馨提示:</text>
            <slot />
          </view>
        </template>
        
        1. 在页面应用通知消息组件并传入数据
        <!-- subpkg_consult/room/index.vue -->
        <script setup>
          import { ref } from 'vue'
          import { io } from 'socket.io-client'
          import { useUserStore } from '@/stores/user'
          // 引入通知消息组件
          import notifyInfo from './components/notify-info.vue'
        
          // 省略前面小节的代码
        </script>
        <template>
          <view class="room-page">
                <!-- 此处将来填充更多代码... -->
            <scroll-view
              refresher-enabled
              refresher-background="#f2f2f2"
              scroll-y
              style="flex: 1; overflow: hidden"
            >
              <view class="message-container">
                <template v-for="message in messageList" :key="message.id">
        
                  <!-- 消息通知 -->
                  <notify-info v-if="message.msgType >= 31" :type="message.msgType">
                    {{ message.msg.content }}
                  </notify-info>
        
                  <!-- 此处将来填充更多代码... -->
                </template>
              </view>
                </scroll-view>
        
            <!-- 发送消息 -->
            <view class="message-bar">
              ...
            </view>
          </view>
        </template>
        
        1. 接收并渲染组件数据
        <!-- subpkg_consult/room/components/notify-info.vue -->
        <script setup>
          // 省略前面小节的代码...
        </script>
        <template>
          <!-- 温馨提示(32) -->
          <view class="message-tips">
            <text v-if="props.type === 32" class="label">温馨提示:</text>
            <slot />
          </view>
        </template>
        
        1.2.4 文字/图片消息

        实时接收到医生发送过来的消息,包括文字消息和图片消息两种类型,使用超级医生来模拟医生端发送消息,根据订单 ID 来打通医生端和患者端的聊天连接。

        首先接收医生端的回复的消息需要监听的事件为 receiveChatMsg

        <!-- subpkg_consult/room/index.vue -->
        <script setup>
          import { ref } from 'vue'
          import { io } from 'socket.io-client'
          import { useUserStore } from '@/stores/user'
        
          // 省略前面小节的代码...
        
          // 接收消息
          socket.on('receiveChatMsg', (message) => {
            // 修改消息为已读
            socket.emit('updateMsgStatus', message.id)
            // 接收到的消息追加到消息列表中
            messageList.value.push(message)
          })
        </script>
        

        然后创建文字消息组件,组件模板如下:

        <!-- subpkg_consult/room/components/message-info.vue -->
        <script setup></script>
        
        <template>
          <!-- 文字/图片消息 -->
          <view class="message-item reverse">
            <image class="room-avatar" src="/static/uploads/doctor-avatar-2.png" />
            <view class="room-message">
              <view class="time">14:13</view>
              <view class="text">
                您好,我是医师王医生,已收到您的问诊信息,我会尽量及时、准确、负责的回复您的问题,请您稍等。
              </view>
              <image
                v-if="false"
                class="image"
                src="/static/uploads/feed-1.jpeg"
                mode="widthFix"
              />
            </view>
          </view>
        </template>
        
        <style lang="scss">
          .message-item {
            display: flex;
            align-self: flex-start;
            margin-top: 60rpx;
        
            .room-avatar {
              width: 80rpx;
              height: 80rpx;
              border-radius: 50%;
            }
        
            .room-message {
              margin-left: 20rpx;
            }
        
            .time {
              font-size: 26rpx;
              color: #979797;
            }
        
            .image {
              max-width: 420rpx;
              margin-top: 10rpx;
            }
        
            .text {
              max-width: 420rpx;
              line-height: 1.75;
              padding: 30rpx 40rpx;
              margin-top: 16rpx;
              border-radius: 20rpx;
              font-size: 30rpx;
              color: #3c3e42;
              background-color: #fff;
              position: relative;
        
              &::after {
                content: '';
                position: absolute;
                top: 0;
                left: -25rpx;
                width: 26rpx;
                height: 52rpx;
                background-image: url(https://consult-patient.oss-cn-hangzhou.aliyuncs.com/static/images/im-arrow-1.png);
                background-size: contain;
              }
            }
        
            &.reverse {
              flex-direction: row-reverse;
              align-self: flex-end;
        
              .room-message {
                margin-left: 0;
                margin-right: 20rpx;
              }
        
              .time {
                text-align: right;
              }
        
              .text {
                background-color: #16c2a3;
                color: #fff;
        
                &::after {
                  left: auto;
                  right: -25rpx;
                  background-image: url(https://consult-patient.oss-cn-hangzhou.aliyuncs.com/static/images/im-arrow-2.png);
                }
              }
            }
          }
        </style>
        

        接下来分成3个步骤来实现:

        1. 定义组件的逻辑,要求能接收外部传入的数据
        <!-- subpkg_consult/room/components/message-info.vue -->
        <script setup>
          // 接收外部传入的数据
          const props = defineProps({
            info: {
              type: Object,
              default: {},
            },
            type: {
              type: Number,
              default: 1,
            },
          })
        </script>
        
        1. 到页面中应用组件并传入数据
        <!-- subpkg_consult/room/index.vue -->
        <script setup>
          import { ref } from 'vue'
          import { io } from 'socket.io-client'
          import { useUserStore } from '@/stores/user'
          // 引入通知消息组件
          import messageInfo from './components/message-info.vue'
        
          // 省略前面小节的代码
        </script>
        <template>
          <view class="room-page">
                <!-- 此处将来填充更多代码... -->
            <scroll-view
              refresher-enabled
              refresher-background="#f2f2f2"
              scroll-y
              style="flex: 1; overflow: hidden"
            >
              <view class="message-container">
                <template v-for="message in messageList" :key="message.id">
        
                  <!-- 文字图片消息 -->
                  <message-info
                    v-if="message.msgType <= 4"
                    :info="message"
                    :type="message.msgType"
                  />
        
                  <!-- 此处将来填充更多代码... -->
                </template>
              </view>
                </scroll-view>
        
            <!-- 发送消息 -->
            <view class="message-bar">
              ...
            </view>
          </view>
        </template>
        
        1. 到组件是接收并渲染数据
        <!-- subpkg_consult/room/components/message-info.vue -->
        <script setup>
          // ...
        </script>
        
        <template>
          <!-- 文字/图片消息 -->
          <view class="message-item">
            <image class="room-avatar" :src="props.info.fromAvatar" />
            <view class="room-message">
              <view class="time">{{ props.info.createTime }}</view>
              <!-- 文字消息 -->
              <view v-if="props.type === 1" class="text">
                {{ props.info.msg.content }}
              </view>
              <!-- 图片消息 -->
              <image
                v-if="props.type === 4"
                class="image"
                :src="props.info.msg.picture.url"
                mode="widthFix"
              />
            </view>
          </view>
        </template>
        
        1. 处理消息的时间,安装 dayjs
        npm install dayjs
        
        <!-- subpkg_consult/room/index.vue -->
        <script setup>
          import dayjs from 'dayjs'
        
          // 省略前面小节的代码...
        
          // 格式化显示时间
          function dateFormat(date) {
            return dayjs(date).format('hh:mm:ss')
          }
        </script>
        
        <template>
          <!-- 文字/图片消息 -->
          <view class="message-item">
            <image class="room-avatar" :src="props.info.fromAvatar" />
            <view class="room-message">
              <view class="time">{{ dateFormat(props.info.createTime) }}</view>
              <view v-if="props.type === 1" class="text">
                {{ props.info.msg.content }}
              </view>
              <image
                v-if="props.type === 4"
                class="image"
                :src="props.info.msg.picture.url"
                mode="widthFix"
              />
            </view>
          </view>
        </template>
        
        1.2.5 处方消息

        医生根据问诊的情况开具诊断结果即为处方消息,到消息的类型值为 22,首先创建组件,布局模板如下所示:

        <!-- subpkg_consult/room/components/prescription-info.vue -->
        <script setup></script>
        
        <template>
          <!-- 处方消息(22) -->
          <view class="e-prescription">
            <view class="prescription-content">
              <view class="list-title">
                <view class="label">电子处方</view>
                <view class="extra">
                  原始处方
                  <uni-icons size="16" color="#848484" type="right" />
                </view>
              </view>
              <view class="list-item">李富贵 男 31岁 血管性头痛</view>
              <view class="list-item">开方时间:2022-01-15 14:21:42</view>
        
              <view class="dividing-line"></view>
        
              <view class="list-title">
                <view class="label">
                  <text class="name">优赛明 维生素E乳</text>
                  <text class="unit">85ml</text>
                  <text class="quantity">x1</text>
                </view>
              </view>
              <view class="list-item">口服,每次1袋,每天3次,用药3天</view>
            </view>
            <navigator
              class="uni-link"
              hover-class="none"
              url="/subpkg_medicine/payment/index"
            >
              购买药品
            </navigator>
          </view>
        </template>
        
        <style lang="scss">
          .e-prescription {
            width: 100%;
            margin-top: 60rpx;
            border-radius: 20rpx;
            background-color: #eaf8f6;
        
            .prescription-content {
              padding: 30rpx;
              border-radius: 20rpx;
              background-color: #fff;
            }
        
            .list-title {
              display: flex;
              align-items: center;
              justify-content: space-between;
              margin-bottom: 10rpx;
            }
        
            .list-item {
              margin-bottom: 10rpx;
              font-size: 26rpx;
              color: #848484;
            }
        
            .label {
              display: flex;
              justify-content: space-between;
              flex: 1;
              font-size: 32rpx;
              color: #121826;
        
              &.medicine {
                font-size: 30rpx;
                color: #666;
              }
            }
        
            .unit {
              width: 200rpx;
            }
        
            .extra {
              display: flex;
              align-items: center;
              font-size: 26rpx;
              color: #848484;
            }
        
            :deep(.uniui-right) {
              margin-top: 4rpx;
            }
        
            .dividing-line {
              padding-bottom: 20rpx;
              margin-bottom: 20rpx;
              border-bottom: 1rpx solid #ededed;
            }
        
            .uni-link {
              text-align: center;
              line-height: 1;
              padding: 30rpx 0;
              color: #16c2a3;
              font-size: 32rpx;
            }
          }
        </style>
        

        接下来分成3个步骤来实现:

        1. 定义组件逻辑,要求能接收组件外部传入的数据
        <!-- subpkg_consult/room/components/prescription-info.vue -->
        <script setup>
          // 接收组件外部传入的数据
          const props = defineProps({
            info: {
              type: Object,
              default: {},
            },
          })
        </script>
        
        1. 在页面中应用组件并传入数据
        <!-- subpkg_consult/room/index.vue -->
        <script setup>
          import { ref } from 'vue'
          import { io } from 'socket.io-client'
          import { useUserStore } from '@/stores/user'
        
          // 引入处方消息组件
          import prescriptionInfo from './components/prescription-info.vue'
        
          // 省略前面小节的代码
        </script>
        <template>
          <view class="room-page">
                <!-- 此处将来填充更多代码... -->
            <scroll-view
              refresher-enabled
              refresher-background="#f2f2f2"
              scroll-y
              style="flex: 1; overflow: hidden"
            >    
              <view class="message-container">
                <template v-for="message in messageList" :key="message.id">
        
                  <!-- 电子处方 -->
                  <prescription-info
                    v-if="message.msgType === 22"
                    :info="message.msg.prescription"
                  />
        
                  <!-- 此处将来填充更多代码... -->
                </template>
              </view>
                </scroll-view>
        
            <!-- 发送消息 -->
            <view class="message-bar">
              ...
            </view>
          </view>
        </template>
        
        1. 在组件中接收并渲染数据
        <!-- subpkg_consult/room/components/prescription-info.vue -->
        <script setup>
            // ...
        </script>
        
        <template>
          <!-- 处方消息(22)-->
          <view class="e-prescription">
            <view class="prescription-content">
              <view class="list-title">
                <view class="label">电子处方</view>
                <view class="extra">
                  原始处方
                  <uni-icons size="16" color="#848484" type="right" />
                </view>
              </view>
              <view class="list-item">
                {{ props.info.name }}
                {{ props.info.genderValue }}
                {{ props.info.age }}岁
                {{ props.info.diagnosis }}
              </view>
              <view class="list-item">开方时间:{{ props.info.createTime }}</view>
        
              <view class="dividing-line"></view>
        
              <template v-for="medicine in props.info.medicines" :key="medicine.id">
                <view class="list-title">
                  <view class="label">
                    <text class="name">{{ medicine.name }}</text>
                    <text class="unit">85ml</text>
                    <text class="quantity">x{{ medicine.quantity }}</text>
                  </view>
                </view>
                <view class="list-item">{{ medicine.usageDosag }}</view>
              </template>
            </view>
            <navigator
              class="uni-link"
              hover-class="none"
              url="/subpkg_medicine/payment/index"
            >
              购买药品
            </navigator>
          </view>
        </template>
        
        1.2.6 原始处方

        在医生开完处方后会生成电子版的处方,通过调用接口进行查看。

        1.2.7 医生评价

        在医生端结束问诊后,患者可以对医生进行评价,医生评价的布局模板为:

        <!-- subpkg_consult/room/components/rate-info.vue -->
        <script setup></script>
        <template>
          <!-- 医生评价 -->
          <view class="doctor-rating">
            <view class="title">医生服务评价</view>
            <view class="subtitle">本次在线问诊服务您还满意吗?</view>
            <view class="rating">
              <uni-rate :size="28" margin="12" :value="0" />
            </view>
            <view class="text">
              <uni-easyinput
                type="textarea"
                maxlength="150"
                :input-border="false"
                :styles="{ backgroundColor: '#f6f6f6' }"
                placeholder-style="font-size: 28rpx; color: #979797"
                placeholder="请描述您对医生的评价或是在医生看诊过程中遇到的问题"
              />
              <text class="word-count">0/150</text>
            </view>
            <view class="anonymous">
              <uni-icons v-if="true" size="16" color="#16C2A3" type="checkbox-filled" />
              <uni-icons v-else size="16" color="#d1d1d1" type="circle" />
              <text class="label">匿名评价</text>
            </view>
            <button disabled class="uni-button">提交</button>
          </view>
        </template>
        
        <script>
          export default {
            options: {
              styleIsolation: 'shared',
            },
          }
        </script>
        <style lang="scss">
          .doctor-rating {
            padding: 30rpx 30rpx 40rpx;
            border-radius: 20rpx;
            background-color: #fff;
            margin-top: 60rpx;
        
            .title {
              text-align: center;
              font-size: 30rpx;
              color: #121826;
            }
        
            .subtitle {
              text-align: center;
              font-size: 24rpx;
              color: #6f6f6f;
              margin: 10rpx 0 20rpx;
            }
        
            .rating {
              display: flex;
              justify-content: center;
            }
        
            .text {
              padding: 20rpx 30rpx;
              margin-top: 20rpx;
              background-color: #f6f6f6;
              border-radius: 20rpx;
              position: relative;
            }
        
            :deep(.uni-easyinput__content-textarea) {
              font-size: 28rpx;
            }
        
            .word-count {
              position: absolute;
              bottom: 20rpx;
              right: 30rpx;
              line-height: 1;
              font-size: 24rpx;
              color: #6f6f6f;
            }
        
            .anonymous {
              display: flex;
              align-items: center;
              justify-content: center;
              margin: 30rpx 0;
              color: #6f6f6f;
              font-size: 24rpx;
        
              .label {
                margin-left: 6rpx;
              }
            }
        
            .uni-button[disabled] {
              color: #a6dbd5;
              background-color: #eaf8f6;
            }
          }
        </style>
        

        接下来分成5个步骤来实现:

        1. 到页面中应用该组件,消息的类型值是 23
        <!-- subpkg_consult/room/index.vue -->
        <script setup>
          import { ref } from 'vue'
          import { io } from 'socket.io-client'
          import { useUserStore } from '@/stores/user'
        
          // 引入处方消息组件
          import rateInfo from './components/rate-info.vue'
        
          // 省略前面小节的代码
        </script>
        <template>
          <view class="room-page">
                <!-- 此处将来填充更多代码... -->
            <scroll-view
              refresher-enabled
              refresher-background="#f2f2f2"
              scroll-y
              style="flex: 1; overflow: hidden"
            >
              <view class="message-container">
                <template v-for="message in messageList" :key="message.id">
        
                  <!-- 医生评价 -->
                  <rate-info v-if="message.msgType === 23"></rate-info>
        
                  <!-- 此处将来填充更多代码... -->
                </template>
              </view>
                </scroll-view>
        
            <!-- 发送消息 -->
            <view class="message-bar">
              ...
            </view>
          </view>
        </template>
        
        1. 获取评价数据并对数据进行验证:
          • v-model 获取数据
          • 字数统计使用计算属性
          • 控制字数使用 maxlength
        <!-- subpkg_consult/room/components/rate-info.vue -->
        <script setup>
          import { computed, ref } from 'vue'
          // 评价内容
          const formData = ref({
            score: 0,
            content: '',
            anonymousFlag: 0,
          })
          // 统计字数
          const wordCount = computed(() => {
            return formData.value.content.length
          })
          // 是否允许提交
          const buttonEnable = computed(() => {
            return formData.value.score
          })
          // 是否匿名评价
          function onAnonymousClick() {
            formData.value.anonymousFlag = Math.abs(formData.value.anonymousFlag - 1)
          }
        </script>
        
        <template>
          <!-- 医生评价 -->
          <view class="doctor-rating">
            <view class="title">医生服务评价</view>
            <view class="subtitle">本次在线问诊服务您还满意吗?</view>
            <view class="rating">
              <uni-rate v-model="formData.score" :size="28" margin="12" />
            </view>
            <view class="text">
              <uni-easyinput
                type="textarea"
                maxlength="150"
                v-model="formData.content"
                :input-border="false"
                :styles="{ backgroundColor: '#f6f6f6' }"
                placeholder-style="font-size: 28rpx; color: #979797"
                placeholder="请描述您对医生的评价或是在医生看诊过程中遇到的问题"
              />
              <text class="word-count">{{ wordCount }}/150</text>
            </view>
            <view @click="onAnonymousClick" class="anonymous">
              <uni-icons
                v-if="formData.anonymousFlag"
                size="16"
                color="#16C2A3"
                type="checkbox-filled"
              />
              <uni-icons v-else size="16" color="#d1d1d1" type="circle" />
              <text class="label">匿名评价</text>
            </view>
            <button :disabled="!buttonEnable" class="uni-button">提交</button>
          </view>
        </template>
        
        1. 在提交评价时,需要获取问诊订单详情,在问诊订单详情中包含了医生的 ID,接口文档在这里
        // services/consult.js
        import { http } from '@/utils/http'
        
        // 省略前面小节的代码...
        
        /**
         * 问诊订单详情
         */
        export const orderDetailApi = (orderId) => {
          return http.get('/patient/consult/order/detail', { params: { orderId } })
        }
        

        将订单 ID 和医生 ID 传入组件

        <!-- subpkg_consult/room/index.vue -->
        <script setup>
          import { ref } from 'vue'
          import { io } from 'socket.io-client'
          import { useUserStore } from '@/stores/user'
          import { orderDetailApi } from '@/services/consult'
        
          // 省略前面小节的代码...
        
          // 问诊订单详情
          const orderDetail = ref({})
        
          // 省略前面小节的代码...
        
          // 获取问诊订单详情
          async function getOrderDetail() {
            // 调用接口
            const { code, data, message } = await orderDetailApi(props.orderId)
            // 检测接口是否调用成功
            if (code !== 10000) return uni.utils.toast(message)
            // 渲染问诊订单数据
            orderDetail.value = data
          }
        
          getOrderDetail()
        </script>
        
        <template>
          <view class="room-page">
                <!-- 此处将来填充更多代码... -->
            <scroll-view
              refresher-enabled
              refresher-background="#f2f2f2"
              scroll-y
              style="flex: 1; overflow: hidden"
            >
              <view class="message-container">
                <template v-for="message in messageList" :key="message.id">
        
                  <!-- 医生评价 -->
                  <rate-info
                    :order-id="props.orderId"
                    :doctor-id="orderDetail.docInfo?.id"
                    v-if="message.msgType === 23"
                  />
        
                  <!-- 此处将来填充更多代码... -->
                </template>
              </view>
                </scroll-view>
        
            <!-- 发送消息 -->
            <view class="message-bar">
                    ...
            </view>
          </view>
        </template>
        
        1. 调用接口提交评价的数据,接口文档在这里
        // services/doctor.js
        import { http } from '@/utils/http'
        
        // 省略了前面小节的代码...
        
        /**
         * 评价医生
         */
        export const evaluateDoctorApi = (data) => {
          return http.post('/patient/order/evaluate', data)
        }
        
        <!-- subpkg_consult/room/components/rate-info.vue -->
        <script setup>
          import { computed, ref } from 'vue'
          import { evaluateDoctorApi } from '@/services/doctor'
        
          // 接收组件外部的数据
          const props = defineProps({
            orderId: String,
            doctorId: String,
          })
        
          // 提交表单
          async function onFormSubmit() {
            // 调用接口
            const { code, data, message } = await evaluateDoctorApi({
              docId: props.doctorId,
              orderId: props.orderId,
              ...formData.value,
            })
            // 检测接口是否调用成功
            if (code !== 10000) return uni.utils.toast(message)
            uni.utils.toast('感谢您的评价!')
            // 标记已经评价过
            hasEvaluate.value = true
          }
        </script>
        
        <template>
          <!-- 医生评价 -->
          <view class="doctor-rating">
            <view class="title">医生服务评价</view>
            <view class="subtitle">本次在线问诊服务您还满意吗?</view>
            <view class="rating">
              <uni-rate v-model="formData.score" :size="28" margin="12" />
            </view>
            <view class="text">
              <uni-easyinput
                type="textarea"
                maxlength="150"
                v-model="formData.content"
                :input-border="false"
                :styles="{ backgroundColor: '#f6f6f6' }"
                placeholder-style="font-size: 28rpx; color: #979797"
                placeholder="请描述您对医生的评价或是在医生看诊过程中遇到的问题"
              />
              <text class="word-count">{{ wordCount }}/150</text>
            </view>
            <view @click="onAnonymousClick" v-if="!hasEvaluate" class="anonymous">
              <uni-icons
                v-if="formData.anonymousFlag"
                size="16"
                color="#16C2A3"
                type="checkbox-filled"
              />
              <uni-icons v-else size="16" color="#d1d1d1" type="circle" />
              <text class="label">匿名评价</text>
            </view>
            <button
              v-if="!hasEvaluate"
              :disabled="!buttonEnable"
              @click="onFormSubmit"
              class="uni-button"
            >
              提交
            </button>
          </view>
        </template>
        
        1. 已评价状态,消息类型值 为 24
        <!-- subpkg_consult/room/components/rate-info.vue -->
        <script setup>
          import { ref, computed } from 'vue'
          import { evaluateDoctorApi } from '@/services/doctor'
        
          // 接收组件外部的数据
          const props = defineProps({
            orderId: String,
            doctorId: String,
        
            // 是否已评价过
            hasEvaluate: {
              type: Boolean,
              default: false,
            },
            // 评价的内容
            evaluateDoc: {
              type: Object,
              default: {},
            },
          })
        
          // 评价内容
          const formData = ref({
            score: props.evaluateDoc.score,
            content: props.evaluateDoc.content,
            // 注意要指定一个默认值为 0
            anonymousFlag: 0,
          })
          // 是否已经评价过
          const hasEvaluate = ref(props.hasEvaluate)
        
          // 统计字数
          const wordCount = computed(() => {
            // 通过 ? 来避免初始数据中 content 不存在的情况
            return formData.value.content?.length || 0
          })
        </script>
        
        <!-- subpkg_consult/room/index.vue -->
        <script setup>
          import { ref } from 'vue'
          import { io } from 'socket.io-client'
          import { useUserStore } from '@/stores/user'
          import { orderDetailApi } from '@/services/consult'
        
          // 省略前面小节的代码...
        </script>
        
        <template>
          <view class="room-page">
                <!-- 此处将来填充更多代码... -->
            <scroll-view
              refresher-enabled
              refresher-background="#f2f2f2"
              scroll-y
              style="flex: 1; overflow: hidden"
            >
              <view class="message-container">
                <template v-for="message in messageList" :key="message.id">
        
                  <!-- 医生评价(已评价) -->
                  <rate-info
                    :evaluateDoc="message.msg.evaluateDoc"
                    has-evaluate
                    v-if="message.msgType === 24"
                  />
        
                  <!-- 此处将来填充更多代码... -->
                </template>
              </view>
                </scroll-view>
        
            <!-- 发送消息 -->
            <view class="message-bar">
        
            </view>
          </view>
        </template>
        

        1.3 发送消息

        患者向医生告之病情及询问诊断方法,分为文字图片消息两种类型,且只有问诊订单状态处理咨询中时才以发送消息,问诊订单的状态包含在订单详情数据中。

        <!-- subpkg_consult/room/index.vue -->
        <script setup>
          // 省略前面小节的代码...
        
          // 订单状态为3时,表示 问诊中...
        
          // 监听订单状态变化
          socket.on('statusChange', getOrderDetail)
        
          // 省略前面小节的代码...
        </script>
        <template>
          <view class="room-page">
                <!-- 此处将来填充更多代码... -->
            <scroll-view
              refresher-enabled
              refresher-background="#f2f2f2"
              scroll-y
              style="flex: 1; overflow: hidden"
            >
              <view class="message-container">
                <!-- 省略前面小节的代码... -->
              </view>
            </scroll-view>
        
            <!-- 发送消息 -->
            <view class="message-bar">
              <template v-if="true">
                <uni-easyinput
                  :disabled="orderDetail.status !== 3"
                  :clearable="false"
                  :input-border="false"
                  placeholder-style="font-size: 32rpx; color: #c3c3c5;"
                  placeholder="问医生"
                />
                <view class="image-button">
                  <uni-icons size="40" color="#979797" type="image"></uni-icons>
                </view>
              </template>
              <button v-else class="uni-button">咨询其它医生</button>
            </view>
          </view>
        </template>
        
        1.3.1 文字消息

        发送文字消息分3个步骤来实现:

        1. 监听 uni-easyinput 组件的 confirm 事件并使用 v-model 获取表单的内容
        <!-- subpkg_consult/room/index.vue -->
        <script setup>
          // 省略前面小节的代码...
        
          // 文字消息
          const textMessage = ref('')
        
          // 省略前面小节的代码...
        
          // 发送文字消息
          function onInputConfirm() {
            console.log(textMessage.value)
          }
        
          // 省略前面小节的代码...
        </script>
        
        <template>
          <view class="room-page">
                <!-- 此处将来填充更多代码... -->
            <scroll-view
              refresher-enabled
              refresher-background="#f2f2f2"
              scroll-y
              style="flex: 1; overflow: hidden"
            >
              <view class="message-container">
                ...
              </view>
            </scroll-view>
        
            <!-- 发送消息 -->
            <view class="message-bar">
              <template v-if="true">
                <uni-easyinput
                  v-model="textMessage"
                  @confirm="onInputConfirm"
                  :disabled="orderDetail.status !== 3"
                  :clearable="false"
                  :input-border="false"
                  placeholder-style="font-size: 32rpx; color: #c3c3c5;"
                  placeholder="问医生"
                />
                <view class="image-button">
                  <uni-icons size="40" color="#979797" type="image"></uni-icons>
                </view>
              </template>
              <button v-else class="uni-button">咨询其它医生</button>
            </view>
          </view>
        </template>
        
        1. 触发服务端正在监听的事件类型,文档地址在这里
        <script setup>
          // 省略前面小节的代码...
        
          // 用户登录信息(不具有响应式)
          const { token, userId } = useUserStore()
          // 问诊订单详情
          const orderDetail = ref({})
          // 文字消息
          const textMessage = ref('')
        
          // 省略前面小节的代码...
        
          // 发送文字消息
          function onInputConfirm() {
            // 发送消息
            socket.emit('sendChatMsg', {
              // 当前登录用户的ID
              from: userId,
              to: orderDetail.value?.docInfo?.id,
              msgType: 1,
              msg: {
                content: textMessage.value,
              },
            })
            // 清空表单
            textMessage.value = ''
          }
        
          // 省略前面小节的代码...
        </script>
        

        在用户登录成功时,只记录了用户的 token 在患者向医生发送消息时还需要传递用户的 ID,在 Pinia 中添加数据来记录登录用户的 ID

        // stores/user.js
        import { ref } from 'vue'
        import { defineStore } from 'pinia'
        
        export const useUserStore = defineStore(
          'user',
          () => {
            // 记录用户登录状态
            const token = ref('')
            // 记录登录成功后要路转的地址(默认值为首页)
            const redirectURL = ref('/pages/index/index')
            // 跳转地址时采用的 API 名称
            const openType = ref('switchTab')
        
            // 用户ID
            const userId = ref('')
        
            return { token, userId, redirectURL, openType }
          },
          {
            persist: {
              paths: ['token', 'userId', 'redirectURL', 'openType'],
            },
          }
        )
        
        <!-- pages/login/index.vue -->
        <script setup>
          async function onFormSubmit() {
            // 判断是否勾选协议
            if (!isAgree.value) return uni.utils.toast('请先同意协议!')
            // 调用 uniForms 组件验证数据的方法
            try {
        
              // 省略前面小节的代码...
        
              // 持久化存储 token
              userStore.token = data.token
              // 存储登录用户的 ID
              userStore.userId = data.id
        
            } catch (error) {
              console.log(error)
            }
          }
        </script>
        
        1. 调整消息的对齐方式,患者消息靠右显示

        在消息中包含的属性 from 是消息发送者的 ID,如果与登录用户的 ID 一致,则表示是患者发送的消息,消息的内容要靠右显示,类名 reverse 可以控制靠右对齐。

        <!-- subpkg_consult/room/components/message-info.vue -->
        <script setup>
          import dayjs from 'dayjs'
          import { useUserStore } from '@/stores/user.js'
        
          // 登录用户 ID
          const { userId } = useUserStore()
        
          // 省略前面小节的代码...
        </script>
        
        <template>
          <!-- 文字/图片消息 -->
          <view :class="{ reverse: props.info.from === userId }" class="message-item">
            <image class="room-avatar" :src="props.info.fromAvatar" />
            <view class="room-message">
              <view class="time">{{ dateFormat(props.info.createTime) }}</view>
              <view v-if="props.type === 1" class="text">
                {{ props.info.msg.content }}
              </view>
              <image
                v-if="props.type === 4"
                class="image"
                :src="props.info.msg.picture.url"
                mode="widthFix"
              />
            </view>
          </view>
        </template>
        
        1.3.2 图片消息

        发送图片消息需要将图片上传到云空间,需要调用 uniCloud 提供的 API chooseAndUploadFile,我们分x步来实现:

        1. 判断问诊订单状态是否为问诊中
        <!-- subpkg_consult/room/index.vue -->
        <script setup>
            // 省略前面小节代码...
        
          // 发送图片消息
          function onImageButtonClick() {
            // 是否在问诊状态中...
            if (orderDetail.value.status !== 3) {
              return uni.utils.toast('医生当前不在线!')
            }
          }
        
          // 省略前面小节代码...
        </script>
        
        <template>
          <view class="room-page">
                <!-- 此处将来填充更多代码... -->
            <scroll-view
              refresher-enabled
              refresher-background="#f2f2f2"
              scroll-y
              style="flex: 1; overflow: hidden"
            >
              <view class="message-container">
                ...
              </view>
            </scroll-view>
        
            <!-- 发送消息 -->
            <view class="message-bar">
              <template v-if="true">
                <uni-easyinput
                  v-model="textMessage"
                  @confirm="onInputConfirm"
                  :disabled="orderDetail.status !== 3"
                  :clearable="false"
                  :input-border="false"
                  placeholder-style="font-size: 32rpx; color: #c3c3c5;"
                  placeholder="问医生"
                />
                <view @click="onImageButtonClick" class="image-button">
                  <uni-icons size="40" color="#979797" type="image"></uni-icons>
                </view>
              </template>
              <button v-else class="uni-button">咨询其它医生</button>
            </view>
          </view>
        </template>
        
        1. 调用 API 上传到 uniCloud 存储空间
        <!-- subpkg_consult/room/index.vue -->
        <script setup>
            // 省略前面小节代码...
        
          // 发送图片消息
          function onImageButtonClick() {
            // 是否在问诊状态中...
            if (orderDetail.value.status !== 3) {
              return uni.utils.toast('医生当前不在线!')
            }
        
            // 上传图片到 uniCloud
            uniCloud.chooseAndUploadFile({
              type: 'image',
              count: 1,
              extension: ['.jpg', '.png', '.gif'],
              success: ({ tempFiles }) => {
                console.log(tempFiles)
              },
            })
          }
        
          // 省略前面小节代码...
        </script>
        
        <template>
          ...
        </template>
        
        1. 向医生发送图片消息,文档地址在这里
        <!-- subpkg_consult/room/index.vue -->
        <script setup>
            // 省略前面小节代码...
        
          // 用户登录信息(不具有响应式)
          const { token, userId } = useUserStore()
          // 发送图片消息
          function onImageButtonClick() {
            // 是否在问诊状态中...
            if (orderDetail.value.status !== 3) {
              return uni.utils.toast('医生当前不在线!')
            }
        
            // 上传图片到 uniCloud
            uniCloud.chooseAndUploadFile({
              type: 'image',
              count: 1,
              extension: ['.jpg', '.png', '.gif'],
              success: ({ tempFiles }) => {
                // 上传成功的图片
                const picture = {
                  id: tempFiles[0].lastModified,
                  url: tempFiles[0].url,
                }
                // 发送消息
                socket.emit('sendChatMsg', {
                  from: userId,
                  to: orderDetail.value?.docInfo?.id,
                  msgType: 4,
                  msg: { picture },
                })
              },
            })
          }
        
          // 省略前面小节代码...
        </script>
        

        1.4 问诊订单状态

        患者在与医生对话的过程中问诊订单状态会发生改变,包括待支付、待接诊、咨询中、已完成、已取消,在页面的顶部要根据订单的状态展示不同的内容。

        1. 将问诊状态的布局模板独立到组件中,要求组件能接收3个数据
          • status 问诊订单的状态值
          • statusValue 问诊订单的文字描述
          • countdown 倒计时剩余时长
        <!-- subpkg_consult/room/components/room-status.vue -->
        <script setup>
          // 接收组件外部传入的数据
          const props = defineProps({
            status: Number,
            statusValue: String,
            countdown: Number,
          })
        </script>
        <template>
          <!-- 咨询室状态 -->
          <view class="room-status">
            <view class="status countdown" v-if="false">
              <text class="label">咨询中</text>
              <view class="time">
                剩余时间:
                <uni-countdown
                  color="#3c3e42"
                  :font-size="14"
                  :show-day="false"
                  :second="0"
                />
              </view>
            </view>
            <view v-else-if="false" class="status waiting">
              已通知医生尽快接诊,24小时内医生未回复将自动退款
            </view>
            <view v-else class="status">
              <uni-icons size="20" color="#121826" type="checkbox-filled" />
              已结束
            </view>
          </view>
        </template>
        
        <style lang="scss">
          .room-status {
            font-size: 26rpx;
            position: sticky;
            top: 0;
            z-index: 99;
        
            .status {
              display: flex;
              padding: 30rpx;
              background-color: #fff;
            }
        
            .waiting {
              color: #16c2a3;
              background-color: #eaf8f6;
            }
        
            .countdown {
              justify-content: space-between;
            }
        
            .label {
              color: #16c2a3;
            }
        
            .icon-done {
              color: #121826;
              font-size: 28rpx;
              margin-right: 5rpx;
            }
        
            .time {
              display: flex;
              color: #3c3e42;
            }
        
            :deep(.uni-countdown) {
              margin-left: 6rpx;
            }
          }
        </style>
        
        1. 在页面中应用组件并传入数据,查询订单状态的的 API 在前面小节中已经调用了,即 getOrderDetail
        <!-- subpkg_consult/room/index.vue -->
        <script setup>
          // 省略前面小节的代码...
        
          // 问诊订单详情
          const orderDetail = ref({})
        
          // 获取问诊订单详情
          async function getOrderDetail() {
            // 调用接口
            const { code, data, message } = await orderDetailApi(props.orderId)
            // 检测接口是否调用成功
            if (code !== 10000) return uni.utils.toast(message)
            // 渲染问诊订单数据
            orderDetail.value = data
          }
        
          // 省略前面小节的代码...
        </script>
        <template>
          <view class="room-page">
            <!-- 问诊订单状态 -->
            <room-status
              :status-value="orderDetail.statusValue"
              :countdown="orderDetail.countdown"
              :status="orderDetail.status"
            />
        
            <!-- 省略前面小节的代码 -->
          </view>
        </template>
        
        1. 根据传入组件的订单状态展示数据
        <!-- subpkg_consult/room/components/room-status.vue -->
        <template>
          <!-- 咨询室状态 -->
          <view class="room-status">
            <!-- 待接诊(status: 2) -->
            <view v-if="props.status === 2" class="status waiting">
              {{ props.statusValue }}
            </view>
        
            <!-- 咨询中(status: 3) -->
            <view class="status" v-if="props.status === 3">
              <text class="label">{{ props.statusValue }}</text>
              <view class="time">
                剩余时间:
                <uni-countdown
                  color="#3c3e42"
                  :font-size="14"
                  :show-day="false"
                  :second="props.countdown"
                />
              </view>
            </view>
        
            <!-- 已完成(status: 4) -->
            <view v-if="props.status === 4" class="status">
              <view class="wrap">
                <uni-icons size="20" color="#121826" type="checkbox-filled" />
                {{ props.statusValue }}
              </view>
            </view>
          </view>
        </template>
        

        1.5 消息分段

        每次重新建立 Socket 连接后(刷新页面),后端都会对数据进行分组,前端在进行展示时也相应的需要展示分段的时间节点,这个时间节点按通知消息类型处理。

        <!-- subpkg_consult/room/index.vue -->
        <script setup>
          // 省略前面小节的代码...
        
          // 接收消息列表
          socket.on('chatMsgList', ({ code, data }) => {
            // 没有返回数据
            if (code !== 10000) return
            // 提取列表数据
            const tempList = []
            data.forEach(({ createTime, items }) => {
              // 追加到消息列表中
              tempList.push(
                // 构造一条数据,显示时间节点
                {
                  msgType: 31,
                  msg: { content: createTime },
                  id: createTime,
                },
                ...items
              )
            })
        
            // 追加到消息列表中
            messageList.value.unshift(...tempList)
          })
        
          // 省略后面小节的代码...
        </script>
        

        在返回的数据中 data 是一个数组,每个单元是一个消息的分组,在对该数组遍历时前端构造一条数据放到数组单元中,被构告的这条件数据仅仅是要显示一个时间节点。

        1.6 历史消息

        用户下拉操作时分页获取聊天记录,按以下几个步骤来实现:

        1. 启动下拉刷新并监听下拉操作
        <!-- subpkg_consult/room/index.vue -->
        <script setup>
            // 省略前面小节的代码...
        
          // 关闭下拉动画交互
          const refreshTrigger = ref(false)
        
          // 省略前面小节的代码...
        
          // 下拉获取历史消息
          function onPullDownRefresh() {
            // 开启下拉交互动画
            refreshTrigger.value = true
        
            setTimeout(() => {
              // 关闭下拉交互动画
              refreshTrigger.value = false
            }, 1000)
          }
        
          // 省略前面小节的代码...
        </script>
        
        <template>
          <view class="room-page">
            <!-- 省略前面小节的代码... -->
        
            <scroll-view
              @refresherrefresh="onPullDownRefresh"
              refresher-enabled
              :refresher-triggered="refreshTrigger"
              background-color="#f2f2f2"
            >
              ...
            </scroll-view>
        
            <!-- 省略前面小节的代码... -->
          </view>
        </template>
        
        1. 触发后端定义的事件类型获取历史消息,文档地址在这里。
        <!-- subpkg_consult/room/index.vue -->
        <script setup>
            // 省略前面小节的代码...
        
          // 关闭下拉动画交互
          const refreshTrigger = ref(false)
          // 上次获取历史消息节点
          const lastTime = ref(dayjs().format('YYYY-MM-DD HH:mm:ss'))
        
          // 省略前面小节的代码...
        
          // 下拉获取历史消息
          function onPullDownRefresh() {
            // 开启下拉交互动画
            refreshTrigger.value = true
                // 获取历史消息
            socket.emit('getChatMsgList', 20, lastTime.value, props.orderId)
          }
        
          // 省略前面小节的代码...
        </script>
        
        <template>
          <view class="room-page">
            <!-- 省略前面小节的代码... -->
        
            <scroll-page
              @refresherrefresh="onPullDownRefresh"
              refresher-enabled
              :refresher-triggered="refreshTrigger"
              background-color="#f2f2f2"
            >
              ...
            </scroll-page>
        
            <!-- 省略前面小节的代码... -->
          </view>
        </template>
        
        1. 更新时间节点,获取的历史消息会返回给客户端
        <!-- subpkg_consult/room/index.vue -->
        <script setup>
          // 省略前面小节的代码...
        
          // 接收消息列表
          socket.on('chatMsgList', ({ code, data }) => {
            // 关闭下拉交互动画
            refreshTrigger.value = false
        
            // 没有返回数据
            if (code !== 10000) return
        
            // 提取列表数据
            const tempList = []
            data.forEach(({ createTime, items }, index) => {
              // 获取消息的时间节点
              if (index === 0) lastTime.value = createTime
              // 追加到消息列表中
              tempList.push(
                {
                  msgType: 31,
                  msg: { content: createTime },
                  id: createTime,
                },
                ...items
              )
            })
        
            // 是否获取到新数据
            if (tempList.length === 0) return uni.utils.toast('没有更多聊天记录了')
            // 追加到消息列表中
            messageList.value.unshift(...tempList)
          })
        
          // 省略前面小节的代码...
        </script>
        

        注意事项:

        • 历史消息是以从后往前的顺序获取,将历史消息中第1个分组的时间节点做为下一次获取历史消息的起始点
        • 获取数据即表示请求结束,要关闭下拉交互的动画
        • 判断是否还存在更多的历史消息

        支付宝支付账号,密码为 111111

        scobys4865@sandbox.com

        askgxl8276@sandbox.com

        最后更新于 April 17, 2024
        On this page
        暂无目录