uni-app(优医咨询)项目实战 – 第7天
学习目标:
能够基于 WebSocket 完成问诊全流程
能够使用 uniCloud 云存储上传文件
能够完成查看电子处方的功能
能够完成医生评价的功能
一、问诊室
以对话聊天的方式向医生介绍病情并获取诊断方案,聊天的内容支持文字和图片两种形式。
首先新建一个页面并完成分包的配置:
{ "subPackages": [ { "root": "subpkg_consult", "pages": [ { "path": "room/index", "style": { "navigationBarTitleText": "问诊室" } } ] }, ] }
该页面的内容特别多我们分段来数据模板代码移到项目当中:
@import './index.scss';
// 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
超时设置
1.2 接收消息
Socket.IO 是基于事件来实现数据通信的,事件的名称是由前后端商定好的,详见接口文档说明,消息的获取分成两种情况:
历史消息,事件名称为
chatMsgList
即时消息,事件名称为
receiveChatMsg
1.2.1 消息列表
在建立连接时服务端会通过 chatMsgList
传递历史数据,通过 on
方法进行监听来获取这些数据:
在消息列表数据中包含了不同类型的消息且展示的方式也不相同,因此在对数据进行遍历的过程中需要通过 v-if
来渲染不同的模板,不同的类型对应了一个数值:
消息类型 | 说明 | 备注 |
---|---|---|
21 | 患者信息 | |
22 | 处方信息 | |
23 | 未提交评价 | |
24 | 已提交评价 | |
31 | 普通通知 | 白底黑字 |
32 | 温馨提示 | |
33 | 取消订单 | 灰底黑字 |
4 | 图片消息 | |
1 | 文字消息 |
首次进入问诊室返回的 3 条件的类型分别为患者信息(21)、普通通知(31)、温馨提示(32),我们逐个进行渲染。
1.2.2 患者消息
首先创建患者消息组件,组件的模板布局如下:
李富贵 男 31岁 一周内 | 未去医院就诊 病情描述 头痛、头晕、恶心 图片 点击查看 .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; } } }
接下来分成3个步骤来实现:
自定义组件的相关逻辑,要求组件能接收外部传入的数据
...
在页面应用组件并传入数据
...
在组件内部接收并渲染数据
{{ props.info.patientInfo.name }} {{ props.info.patientInfo.genderValue }} {{ props.info.patientInfo.age }}岁 {{ illnessTimes[props.info.illnessTime] }} | {{ consultFlags[props.info.illnessType] }} 病情描述 {{ props.info.illnessDesc }} 图片 点击查看 暂无图片
大图查看患者病情图片,uni-app 提供了大图查看图片的 API
uni.previewImage
... 病情描述 {{ props.info.illnessDesc }} 图片 点击查看 暂无图片
1.2.3 通知消息
通知消息分为3种,分别为:
消息类型 | 说明 | 备注 |
---|---|---|
31 | 普通通知 | 白底黑字 |
32 | 温馨提示 | |
33 | 取消订单 | 灰底黑字 |
首先创建消息通知组伯,通知消息的模板如下:
医护人员正在赶来,请耐心等候 温馨提示: 在线咨询不能代替面诊,医护人员建议仅供参考 .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; } }
接下来分成3个步骤来实现:
定义组件的逻辑,要求能区分通知的类型并通过插槽来展示内容
温馨提示:
在页面应用通知消息组件并传入数据
= 31" :type="message.msgType"> {{ message.msg.content }} ...
接收并渲染组件数据
温馨提示:
1.2.4 文字/图片消息
实时接收到医生发送过来的消息,包括文字消息和图片消息两种类型,使用超级医生来模拟医生端发送消息,根据订单 ID 来打通医生端和患者端的聊天连接。
首先接收医生端的回复的消息需要监听的事件为 receiveChatMsg
然后创建文字消息组件,组件模板如下:
14:13 您好,我是医师王医生,已收到您的问诊信息,我会尽量及时、准确、负责的回复您的问题,请您稍等。 .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(/images/2024/0608/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(/images/2024/0608/im-arrow-2.png); } } } }
接下来分成3个步骤来实现:
定义组件的逻辑,要求能接收外部传入的数据
到页面中应用组件并传入数据
<message-info v-if="message.msgType ...
到组件是接收并渲染数据
{{ props.info.createTime }} {{ props.info.msg.content }}
处理消息的时间,安装
dayjs
npm install dayjs
{{ dateFormat(props.info.createTime) }} {{ props.info.msg.content }}
1.2.5 处方消息
医生根据问诊的情况开具诊断结果即为处方消息,到消息的类型值为 22
,首先创建组件,布局模板如下所示:
1
接下来分成3个步骤来实现:
定义组件逻辑,要求能接收组件外部传入的数据
在页面中应用组件并传入数据
...
在组件中接收并渲染数据
电子处方 原始处方 {{ props.info.name }} {{ props.info.genderValue }} {{ props.info.age }}岁 {{ props.info.diagnosis }} 开方时间:{{ props.info.createTime }} {{ medicine.name }} 85ml x{{ medicine.quantity }} {{ medicine.usageDosag }} 购买药品
1.2.6 原始处方
在医生开完处方后会生成电子版的处方,通过调用接口进行查看。
1.2.7 医生评价
在医生端结束问诊后,患者可以对医生进行评价,医生评价的布局模板为:
医生服务评价 本次在线问诊服务您还满意吗? 0/150 匿名评价 .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; } }
接下来分成5个步骤来实现:
到页面中应用该组件,消息的类型值是
23
...
获取评价数据并对数据进行验证:
v-model
获取数据字数统计使用计算属性
控制字数使用
maxlength
医生服务评价 本次在线问诊服务您还满意吗? {{ wordCount }}/150 匿名评价
在提交评价时,需要获取问诊订单详情,在问诊订单详情中包含了医生的 ID,接口文档在这里
// services/consult.js import { http } from '@/utils/http' // 省略前面小节的代码... /** * 问诊订单详情 */ export const orderDetailApi = (orderId) => { return http.get('/patient/consult/order/detail', { params: { orderId } }) }
将订单 ID 和医生 ID 传入组件
...
调用接口提交评价的数据,接口文档在这里
// services/doctor.js import { http } from '@/utils/http' // 省略了前面小节的代码... /** * 评价医生 */ export const evaluateDoctorApi = (data) => { return http.post('/patient/order/evaluate', data) }
医生服务评价 本次在线问诊服务您还满意吗? {{ wordCount }}/150 匿名评价
已评价状态,消息类型值 为 24
1.3 发送消息
患者向医生告之病情及询问诊断方法,分为文字图片消息两种类型,且只有问诊订单状态处理咨询中时才以发送消息,问诊订单的状态包含在订单详情数据中。
1.3.1 文字消息
发送文字消息分3个步骤来实现:
监听
uni-easyinput
组件的confirm
事件并使用v-model
获取表单的内容
...
触发服务端正在监听的事件类型,文档地址在这里
在用户登录成功时,只记录了用户的 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'], }, } )
调整消息的对齐方式,患者消息靠右显示
在消息中包含的属性 from
是消息发送者的 ID,如果与登录用户的 ID 一致,则表示是患者发送的消息,消息的内容要靠右显示,类名 reverse
可以控制靠右对齐。
{{ dateFormat(props.info.createTime) }} {{ props.info.msg.content }}
1.3.2 图片消息
发送图片消息需要将图片上传到云空间,需要调用 uniCloud
提供的 API chooseAndUploadFile
,我们分x步来实现:
判断问诊订单状态是否为问诊中
...
调用 API 上传到 uniCloud 存储空间
...
向医生发送图片消息,文档地址在这里
1.4 问诊订单状态
患者在与医生对话的过程中问诊订单状态会发生改变,包括待支付、待接诊、咨询中、已完成、已取消,在页面的顶部要根据订单的状态展示不同的内容。
将问诊状态的布局模板独立到组件中,要求组件能接收3个数据
status
问诊订单的状态值statusValue
问诊订单的文字描述countdown
倒计时剩余时长
咨询中 剩余时间: 已通知医生尽快接诊,24小时内医生未回复将自动退款 已结束 .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; } }
在页面中应用组件并传入数据,查询订单状态的的 API 在前面小节中已经调用了,即
getOrderDetail
根据传入组件的订单状态展示数据
{{ props.statusValue }} {{ props.statusValue }} 剩余时间: {{ props.statusValue }}
1.5 消息分段
每次重新建立 Socket 连接后(刷新页面),后端都会对数据进行分组,前端在进行展示时也相应的需要展示分段的时间节点,这个时间节点按通知消息类型处理。
在返回的数据中 data
是一个数组,每个单元是一个消息的分组,在对该数组遍历时前端构造一条数据放到数组单元中,被构告的这条件数据仅仅是要显示一个时间节点。
1.6 历史消息
用户下拉操作时分页获取聊天记录,按以下几个步骤来实现:
启动下拉刷新并监听下拉操作
...
触发后端定义的事件类型获取历史消息,文档地址在这里。
...
更新时间节点,获取的历史消息会返回给客户端
注意事项:
历史消息是以从后往前的顺序获取,将历史消息中第1个分组的时间节点做为下一次获取历史消息的起始点
获取数据即表示请求结束,要关闭下拉交互的动画
判断是否还存在更多的历史消息
支付宝支付账号,密码为 111111
scobys4865@sandbox.com
askgxl8276@sandbox.com