前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >uni-app(优医咨询)项目实战 - 第7天

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

作者头像
陶然同学
发布2024-06-03 08:48:25
1520
发布2024-06-03 08:48:25
举报
文章被收录于专栏:陶然同学博客陶然同学博客

学习目标:

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

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

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

代码语言:javascript
复制
{
    "subPackages": [
    {
      "root": "subpkg_consult",
      "pages": [
        {
          "path": "room/index",
          "style": {
            "navigationBarTitleText": "问诊室"
          }
        }
      ]
    },
  ]
}

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

代码语言:javascript
复制
<!-- 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>
代码语言:javascript
复制
// 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

代码语言:javascript
复制
npm install socket.io-client

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

  • auth 登录状态信息,即 token
  • query 建立连接时传递的参数
  • transports 建立连接时使用的协议
  • timeout 超时设置
代码语言:javascript
复制
<!-- 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 方法进行监听来获取这些数据:

代码语言:javascript
复制
<!-- 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 患者消息

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

代码语言:javascript
复制
<!-- 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. 自定义组件的相关逻辑,要求组件能接收外部传入的数据
代码语言:javascript
复制
<!-- 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. 在页面应用组件并传入数据
代码语言:javascript
复制
<!-- 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. 在组件内部接收并渲染数据
代码语言:javascript
复制
<!-- 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
代码语言:javascript
复制
<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

取消订单

灰底黑字

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

代码语言:javascript
复制
<!-- 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. 定义组件的逻辑,要求能区分通知的类型并通过插槽来展示内容
代码语言:javascript
复制
<!-- 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. 在页面应用通知消息组件并传入数据
代码语言:javascript
复制
<!-- 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. 接收并渲染组件数据
代码语言:javascript
复制
<!-- 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

代码语言:javascript
复制
<!-- 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>

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

代码语言:javascript
复制
<!-- 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. 定义组件的逻辑,要求能接收外部传入的数据
代码语言:javascript
复制
<!-- subpkg_consult/room/components/message-info.vue -->
<script setup>
  // 接收外部传入的数据
  const props = defineProps({
    info: {
      type: Object,
      default: {},
    },
    type: {
      type: Number,
      default: 1,
    },
  })
</script>
  1. 到页面中应用组件并传入数据
代码语言:javascript
复制
<!-- 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. 到组件是接收并渲染数据
代码语言:javascript
复制
<!-- 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
代码语言:javascript
复制
npm install dayjs
代码语言:javascript
复制
<!-- 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,首先创建组件,布局模板如下所示:

代码语言:javascript
复制
1

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

  1. 定义组件逻辑,要求能接收组件外部传入的数据
代码语言:javascript
复制
<!-- subpkg_consult/room/components/prescription-info.vue -->
<script setup>
  // 接收组件外部传入的数据
  const props = defineProps({
    info: {
      type: Object,
      default: {},
    },
  })
</script>
  1. 在页面中应用组件并传入数据
代码语言:javascript
复制
<!-- 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. 在组件中接收并渲染数据
代码语言:javascript
复制
<!-- 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 医生评价

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

代码语言:javascript
复制
<!-- 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
代码语言:javascript
复制
<!-- 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
代码语言:javascript
复制
<!-- 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接口文档在这里
代码语言:javascript
复制
// services/consult.js
import { http } from '@/utils/http'

// 省略前面小节的代码...

/**
 * 问诊订单详情
 */
export const orderDetailApi = (orderId) => {
  return http.get('/patient/consult/order/detail', { params: { orderId } })
}

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

代码语言:javascript
复制
<!-- 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. 调用接口提交评价的数据,接口文档在这里
代码语言:javascript
复制
// services/doctor.js
import { http } from '@/utils/http'

// 省略了前面小节的代码...

/**
 * 评价医生
 */
export const evaluateDoctorApi = (data) => {
  return http.post('/patient/order/evaluate', data)
}
代码语言:javascript
复制
<!-- 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
代码语言:javascript
复制
<!-- 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>
代码语言:javascript
复制
<!-- 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 发送消息

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

代码语言:javascript
复制
<!-- 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 获取表单的内容
代码语言:javascript
复制
<!-- 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. 触发服务端正在监听的事件类型,文档地址在这里
代码语言:javascript
复制
<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

代码语言:javascript
复制
// 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'],
    },
  }
)
代码语言:javascript
复制
<!-- 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 可以控制靠右对齐。

代码语言:javascript
复制
<!-- 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. 判断问诊订单状态是否为问诊中
代码语言:javascript
复制
<!-- 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 存储空间
代码语言:javascript
复制
<!-- 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. 向医生发送图片消息,文档地址在这里
代码语言:javascript
复制
<!-- 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 倒计时剩余时长
代码语言:javascript
复制
<!-- 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
代码语言:javascript
复制
<!-- 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. 根据传入组件的订单状态展示数据
代码语言:javascript
复制
<!-- 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 连接后(刷新页面),后端都会对数据进行分组,前端在进行展示时也相应的需要展示分段的时间节点,这个时间节点按通知消息类型处理

代码语言:javascript
复制
<!-- 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. 启动下拉刷新并监听下拉操作
代码语言:javascript
复制
<!-- 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. 触发后端定义的事件类型获取历史消息,文档地址在这里。
代码语言:javascript
复制
<!-- 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. 更新时间节点,获取的历史消息会返回给客户端
代码语言:javascript
复制
<!-- 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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-06-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 学习目标:
    • 一、问诊室
      • 1.1 WebSocket 连接
      • 1.2 接收消息
      • 1.3 发送消息
      • 1.4 问诊订单状态
      • 1.5 消息分段
      • 1.6 历史消息
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档