网站的设计特点有哪些,素材网站怎么推广,安卓软件制作网站,网站怎么做平台学习目标#xff1a; 能够基于 WebSocket 完成问诊全流程 能够使用 uniCloud 云存储上传文件 能够完成查看电子处方的功能 能够完成医生评价的功能
一、问诊室
以对话聊天的方式向医生介绍病情并获取诊断方案#xff0c;聊天的内容支持文字和图片两种形式。
首先新建一…学习目标 能够基于 WebSocket 完成问诊全流程 能够使用 uniCloud 云存储上传文件 能够完成查看电子处方的功能 能够完成医生评价的功能
一、问诊室
以对话聊天的方式向医生介绍病情并获取诊断方案聊天的内容支持文字和图片两种形式。
首先新建一个页面并完成分包的配置
{subPackages: [{root: subpkg_consult,pages: [{path: room/index,style: {navigationBarTitleText: 问诊室}}]},]
}
该页面的内容特别多我们分段来数据模板代码移到项目当中
!-- subpkg_consult/room/index.vue --
script setup/script
templateview classroom-page
scroll-viewrefresher-enabledrefresher-background#f2f2f2scroll-ystyleflex: 1; overflow: hiddenview classmessage-container!-- 此处将来填充更多代码... --/view/scroll-view
!-- 发送消息 --view classmessage-bartemplate v-iftrueuni-easyinputdisabled:clearablefalse:input-borderfalseplaceholder-stylefont-size: 32rpx; color: #c3c3c5;placeholder问医生/view classimage-buttonuni-icons size40 color#979797 typeimage/uni-icons/view/templatebutton v-else classuni-button咨询其它医生/button/view/view
/template
style langscssimport ./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 setupimport { ref } from vueimport { io } from socket.io-clientimport { 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 setupimport { ref } from vueimport { io } from socket.io-clientimport { 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 classpatient-infoview classheaderview classtitle李富贵 男 31岁/viewview classnote一周内 | 未去医院就诊/view/viewview classcontentview classlist-itemtext classlabel病情描述/texttext classnote头痛、头晕、恶心/text/viewview classlist-itemtext classlabel图片/texttext classnote点击查看/text/view/view/view
/template
style langscss.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个步骤来实现 自定义组件的相关逻辑要求组件能接收外部传入的数据
!-- 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 在页面应用组件并传入数据
!-- subpkg_consult/room/index.vue --
script setupimport { ref } from vueimport { io } from socket.io-clientimport { useUserStore } from /stores/user// 引入患者信息组件import patientInfo from ./components/patient-info.vue// 省略前面小节的代码
/script
templateview classroom-page!-- 此处将来填充更多代码... --scroll-viewrefresher-enabledrefresher-background#f2f2f2scroll-ystyleflex: 1; overflow: hiddenview classmessage-containertemplate v-formessage in messageList :keymessage.id!-- 患者信息21 --patient-infov-ifmessage.msgType 21:infomessage.msg.consultRecord/!-- 此处将来填充更多代码... --/template/view/scroll-view!-- 发送消息 --view classmessage-bar.../view/view
/template 在组件内部接收并渲染数据
!-- subpkg_consult/room/components/patient-info.vue --
script setup// 省略前面小节的代码...
/scripttemplate!-- 患者信息21 --view classpatient-infoview classheaderview classtitle{{ props.info.patientInfo.name }}{{ props.info.patientInfo.genderValue }}{{ props.info.patientInfo.age }}岁/viewview classnote{{ illnessTimes[props.info.illnessTime] }}|{{ consultFlags[props.info.illnessType] }}/view/viewview classcontentview classlist-itemtext classlabel病情描述/texttext classnote{{ props.info.illnessDesc }}/text/viewview classlist-itemtext classlabel图片/texttext v-ifprops.info.pictures?.length classnote 点击查看 /texttext v-else classnote暂无图片/text/view/view/view
/template 大图查看患者病情图片uni-app 提供了大图查看图片的 API uni.previewImage
script setup// 省略前面小节的代码...// 点击查看病情介绍图片async function onPreviewClick(urls) {uni.previewImage({urls: urls.map((item) item.url),})}
/scripttemplate!-- 患者信息21 --view classpatient-infoview classheader.../viewview classcontentview classlist-itemtext classlabel病情描述/texttext classnote{{ props.info.illnessDesc }}/text/viewview classlist-itemtext classlabel图片/texttextv-ifprops.info.pictures?.lengthclickonPreviewClick(props.info.pictures)classnote点击查看/texttext v-else classnote暂无图片/text/view/view/view
/template
1.2.3 通知消息
通知消息分为3种分别为:
消息类型说明备注31普通通知白底黑字32温馨提示33取消订单灰底黑字
首先创建消息通知组伯通知消息的模板如下
!-- subpkg_consult/room/components/notify-info.vue --
script setup/scripttemplate!-- 普通通知31 --view classmessage-tipsview classwrapper医护人员正在赶来请耐心等候/view/view!-- 温馨提示32 --view classmessage-tipsview classwrappertext classlabel温馨提示:/text在线咨询不能代替面诊医护人员建议仅供参考/view/view
/templatestyle langscss.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个步骤来实现 定义组件的逻辑要求能区分通知的类型并通过插槽来展示内容
!-- subpkg_consult/room/components/notify-info.vue --
script setup// 接收外部传入的数据const props defineProps({type: {type: Number,default: 31,},})
/script
template!-- 温馨提示32 --view classmessage-tipstext classlabel温馨提示:/textslot //view
/template 在页面应用通知消息组件并传入数据
!-- subpkg_consult/room/index.vue --
script setupimport { ref } from vueimport { io } from socket.io-clientimport { useUserStore } from /stores/user// 引入通知消息组件import notifyInfo from ./components/notify-info.vue// 省略前面小节的代码
/script
templateview classroom-page!-- 此处将来填充更多代码... --scroll-viewrefresher-enabledrefresher-background#f2f2f2scroll-ystyleflex: 1; overflow: hiddenview classmessage-containertemplate v-formessage in messageList :keymessage.id!-- 消息通知 --notify-info v-ifmessage.msgType 31 :typemessage.msgType{{ message.msg.content }}/notify-info!-- 此处将来填充更多代码... --/template/view/scroll-view!-- 发送消息 --view classmessage-bar.../view/view
/template 接收并渲染组件数据
!-- subpkg_consult/room/components/notify-info.vue --
script setup// 省略前面小节的代码...
/script
template!-- 温馨提示32 --view classmessage-tipstext v-ifprops.type 32 classlabel温馨提示:/textslot //view
/template
1.2.4 文字/图片消息
实时接收到医生发送过来的消息包括文字消息和图片消息两种类型使用超级医生来模拟医生端发送消息根据订单 ID 来打通医生端和患者端的聊天连接。
首先接收医生端的回复的消息需要监听的事件为 receiveChatMsg
!-- subpkg_consult/room/index.vue --
script setupimport { ref } from vueimport { io } from socket.io-clientimport { 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/scripttemplate!-- 文字/图片消息 --view classmessage-item reverseimage classroom-avatar src/static/uploads/doctor-avatar-2.png /view classroom-messageview classtime14:13/viewview classtext您好我是医师王医生已收到您的问诊信息我会尽量及时、准确、负责的回复您的问题请您稍等。/viewimagev-iffalseclassimagesrc/static/uploads/feed-1.jpegmodewidthFix//view/view
/templatestyle langscss.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个步骤来实现 定义组件的逻辑要求能接收外部传入的数据
!-- subpkg_consult/room/components/message-info.vue --
script setup// 接收外部传入的数据const props defineProps({info: {type: Object,default: {},},type: {type: Number,default: 1,},})
/script 到页面中应用组件并传入数据
!-- subpkg_consult/room/index.vue --
script setupimport { ref } from vueimport { io } from socket.io-clientimport { useUserStore } from /stores/user// 引入通知消息组件import messageInfo from ./components/message-info.vue// 省略前面小节的代码
/script
templateview classroom-page!-- 此处将来填充更多代码... --scroll-viewrefresher-enabledrefresher-background#f2f2f2scroll-ystyleflex: 1; overflow: hiddenview classmessage-containertemplate v-formessage in messageList :keymessage.id!-- 文字图片消息 --message-infov-ifmessage.msgType 4:infomessage:typemessage.msgType/!-- 此处将来填充更多代码... --/template/view/scroll-view!-- 发送消息 --view classmessage-bar.../view/view
/template 到组件是接收并渲染数据
!-- subpkg_consult/room/components/message-info.vue --
script setup// ...
/scripttemplate!-- 文字/图片消息 --view classmessage-itemimage classroom-avatar :srcprops.info.fromAvatar /view classroom-messageview classtime{{ props.info.createTime }}/view!-- 文字消息 --view v-ifprops.type 1 classtext{{ props.info.msg.content }}/view!-- 图片消息 --imagev-ifprops.type 4classimage:srcprops.info.msg.picture.urlmodewidthFix//view/view
/template 处理消息的时间安装 dayjs
npm install dayjs
!-- subpkg_consult/room/index.vue --
script setupimport dayjs from dayjs// 省略前面小节的代码...// 格式化显示时间function dateFormat(date) {return dayjs(date).format(hh:mm:ss)}
/scripttemplate!-- 文字/图片消息 --view classmessage-itemimage classroom-avatar :srcprops.info.fromAvatar /view classroom-messageview classtime{{ dateFormat(props.info.createTime) }}/viewview v-ifprops.type 1 classtext{{ props.info.msg.content }}/viewimagev-ifprops.type 4classimage:srcprops.info.msg.picture.urlmodewidthFix//view/view
/template
1.2.5 处方消息
医生根据问诊的情况开具诊断结果即为处方消息到消息的类型值为 22首先创建组件布局模板如下所示
1
接下来分成3个步骤来实现 定义组件逻辑要求能接收组件外部传入的数据
!-- subpkg_consult/room/components/prescription-info.vue --
script setup// 接收组件外部传入的数据const props defineProps({info: {type: Object,default: {},},})
/script 在页面中应用组件并传入数据
!-- subpkg_consult/room/index.vue --
script setupimport { ref } from vueimport { io } from socket.io-clientimport { useUserStore } from /stores/user// 引入处方消息组件import prescriptionInfo from ./components/prescription-info.vue// 省略前面小节的代码
/script
templateview classroom-page!-- 此处将来填充更多代码... --scroll-viewrefresher-enabledrefresher-background#f2f2f2scroll-ystyleflex: 1; overflow: hidden view classmessage-containertemplate v-formessage in messageList :keymessage.id!-- 电子处方 --prescription-infov-ifmessage.msgType 22:infomessage.msg.prescription/!-- 此处将来填充更多代码... --/template/view/scroll-view!-- 发送消息 --view classmessage-bar.../view/view
/template 在组件中接收并渲染数据
!-- subpkg_consult/room/components/prescription-info.vue --
script setup// ...
/scripttemplate!-- 处方消息22--view classe-prescriptionview classprescription-contentview classlist-titleview classlabel电子处方/viewview classextra原始处方uni-icons size16 color#848484 typeright //view/viewview classlist-item{{ props.info.name }}{{ props.info.genderValue }}{{ props.info.age }}岁{{ props.info.diagnosis }}/viewview classlist-item开方时间{{ props.info.createTime }}/viewview classdividing-line/viewtemplate v-formedicine in props.info.medicines :keymedicine.idview classlist-titleview classlabeltext classname{{ medicine.name }}/texttext classunit85ml/texttext classquantityx{{ medicine.quantity }}/text/view/viewview classlist-item{{ medicine.usageDosag }}/view/template/viewnavigatorclassuni-linkhover-classnoneurl/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 classdoctor-ratingview classtitle医生服务评价/viewview classsubtitle本次在线问诊服务您还满意吗/viewview classratinguni-rate :size28 margin12 :value0 //viewview classtextuni-easyinputtypetextareamaxlength150:input-borderfalse:styles{ backgroundColor: #f6f6f6 }placeholder-stylefont-size: 28rpx; color: #979797placeholder请描述您对医生的评价或是在医生看诊过程中遇到的问题/text classword-count0/150/text/viewview classanonymousuni-icons v-iftrue size16 color#16C2A3 typecheckbox-filled /uni-icons v-else size16 color#d1d1d1 typecircle /text classlabel匿名评价/text/viewbutton disabled classuni-button提交/button/view
/templatescriptexport default {options: {styleIsolation: shared,},}
/script
style langscss.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个步骤来实现 到页面中应用该组件消息的类型值是 23
!-- subpkg_consult/room/index.vue --
script setupimport { ref } from vueimport { io } from socket.io-clientimport { useUserStore } from /stores/user// 引入处方消息组件import rateInfo from ./components/rate-info.vue// 省略前面小节的代码
/script
templateview classroom-page!-- 此处将来填充更多代码... --scroll-viewrefresher-enabledrefresher-background#f2f2f2scroll-ystyleflex: 1; overflow: hiddenview classmessage-containertemplate v-formessage in messageList :keymessage.id!-- 医生评价 --rate-info v-ifmessage.msgType 23/rate-info!-- 此处将来填充更多代码... --/template/view/scroll-view!-- 发送消息 --view classmessage-bar.../view/view
/template 获取评价数据并对数据进行验证 v-model 获取数据 字数统计使用计算属性 控制字数使用 maxlength
!-- subpkg_consult/room/components/rate-info.vue --
script setupimport { 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)}
/scripttemplate!-- 医生评价 --view classdoctor-ratingview classtitle医生服务评价/viewview classsubtitle本次在线问诊服务您还满意吗/viewview classratinguni-rate v-modelformData.score :size28 margin12 //viewview classtextuni-easyinputtypetextareamaxlength150v-modelformData.content:input-borderfalse:styles{ backgroundColor: #f6f6f6 }placeholder-stylefont-size: 28rpx; color: #979797placeholder请描述您对医生的评价或是在医生看诊过程中遇到的问题/text classword-count{{ wordCount }}/150/text/viewview clickonAnonymousClick classanonymousuni-iconsv-ifformData.anonymousFlagsize16color#16C2A3typecheckbox-filled/uni-icons v-else size16 color#d1d1d1 typecircle /text classlabel匿名评价/text/viewbutton :disabled!buttonEnable classuni-button提交/button/view
/template 在提交评价时需要获取问诊订单详情在问诊订单详情中包含了医生的 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 setupimport { ref } from vueimport { io } from socket.io-clientimport { useUserStore } from /stores/userimport { 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()
/scripttemplateview classroom-page!-- 此处将来填充更多代码... --scroll-viewrefresher-enabledrefresher-background#f2f2f2scroll-ystyleflex: 1; overflow: hiddenview classmessage-containertemplate v-formessage in messageList :keymessage.id!-- 医生评价 --rate-info:order-idprops.orderId:doctor-idorderDetail.docInfo?.idv-ifmessage.msgType 23/!-- 此处将来填充更多代码... --/template/view/scroll-view!-- 发送消息 --view classmessage-bar.../view/view
/template 调用接口提交评价的数据接口文档在这里
// 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 setupimport { computed, ref } from vueimport { 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}
/scripttemplate!-- 医生评价 --view classdoctor-ratingview classtitle医生服务评价/viewview classsubtitle本次在线问诊服务您还满意吗/viewview classratinguni-rate v-modelformData.score :size28 margin12 //viewview classtextuni-easyinputtypetextareamaxlength150v-modelformData.content:input-borderfalse:styles{ backgroundColor: #f6f6f6 }placeholder-stylefont-size: 28rpx; color: #979797placeholder请描述您对医生的评价或是在医生看诊过程中遇到的问题/text classword-count{{ wordCount }}/150/text/viewview clickonAnonymousClick v-if!hasEvaluate classanonymousuni-iconsv-ifformData.anonymousFlagsize16color#16C2A3typecheckbox-filled/uni-icons v-else size16 color#d1d1d1 typecircle /text classlabel匿名评价/text/viewbuttonv-if!hasEvaluate:disabled!buttonEnableclickonFormSubmitclassuni-button提交/button/view
/template 已评价状态消息类型值 为 24
!-- subpkg_consult/room/components/rate-info.vue --
script setupimport { ref, computed } from vueimport { 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,// 注意要指定一个默认值为 0anonymousFlag: 0,})// 是否已经评价过const hasEvaluate ref(props.hasEvaluate)// 统计字数const wordCount computed(() {// 通过 ? 来避免初始数据中 content 不存在的情况return formData.value.content?.length || 0})
/script
!-- subpkg_consult/room/index.vue --
script setupimport { ref } from vueimport { io } from socket.io-clientimport { useUserStore } from /stores/userimport { orderDetailApi } from /services/consult// 省略前面小节的代码...
/scripttemplateview classroom-page!-- 此处将来填充更多代码... --scroll-viewrefresher-enabledrefresher-background#f2f2f2scroll-ystyleflex: 1; overflow: hiddenview classmessage-containertemplate v-formessage in messageList :keymessage.id!-- 医生评价已评价 --rate-info:evaluateDocmessage.msg.evaluateDochas-evaluatev-ifmessage.msgType 24/!-- 此处将来填充更多代码... --/template/view/scroll-view!-- 发送消息 --view classmessage-bar/view/view
/template
1.3 发送消息
患者向医生告之病情及询问诊断方法分为文字图片消息两种类型且只有问诊订单状态处理咨询中时才以发送消息问诊订单的状态包含在订单详情数据中。
!-- subpkg_consult/room/index.vue --
script setup// 省略前面小节的代码...// 订单状态为3时表示 问诊中...// 监听订单状态变化socket.on(statusChange, getOrderDetail)// 省略前面小节的代码...
/script
templateview classroom-page!-- 此处将来填充更多代码... --scroll-viewrefresher-enabledrefresher-background#f2f2f2scroll-ystyleflex: 1; overflow: hiddenview classmessage-container!-- 省略前面小节的代码... --/view/scroll-view!-- 发送消息 --view classmessage-bartemplate v-iftrueuni-easyinput:disabledorderDetail.status ! 3:clearablefalse:input-borderfalseplaceholder-stylefont-size: 32rpx; color: #c3c3c5;placeholder问医生/view classimage-buttonuni-icons size40 color#979797 typeimage/uni-icons/view/templatebutton v-else classuni-button咨询其它医生/button/view/view
/template
1.3.1 文字消息
发送文字消息分3个步骤来实现 监听 uni-easyinput 组件的 confirm 事件并使用 v-model 获取表单的内容
!-- subpkg_consult/room/index.vue --
script setup// 省略前面小节的代码...// 文字消息const textMessage ref()// 省略前面小节的代码...// 发送文字消息function onInputConfirm() {console.log(textMessage.value)}// 省略前面小节的代码...
/scripttemplateview classroom-page!-- 此处将来填充更多代码... --scroll-viewrefresher-enabledrefresher-background#f2f2f2scroll-ystyleflex: 1; overflow: hiddenview classmessage-container.../view/scroll-view!-- 发送消息 --view classmessage-bartemplate v-iftrueuni-easyinputv-modeltextMessageconfirmonInputConfirm:disabledorderDetail.status ! 3:clearablefalse:input-borderfalseplaceholder-stylefont-size: 32rpx; color: #c3c3c5;placeholder问医生/view classimage-buttonuni-icons size40 color#979797 typeimage/uni-icons/view/templatebutton v-else classuni-button咨询其它医生/button/view/view
/template 触发服务端正在监听的事件类型文档地址在这里
script setup// 省略前面小节的代码...// 用户登录信息不具有响应式const { token, userId } useUserStore()// 问诊订单详情const orderDetail ref({})// 文字消息const textMessage ref()// 省略前面小节的代码...// 发送文字消息function onInputConfirm() {// 发送消息socket.emit(sendChatMsg, {// 当前登录用户的IDfrom: 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 piniaexport const useUserStore defineStore(user,() {// 记录用户登录状态const token ref()// 记录登录成功后要路转的地址默认值为首页const redirectURL ref(/pages/index/index)// 跳转地址时采用的 API 名称const openType ref(switchTab)// 用户IDconst userId ref()return { token, userId, redirectURL, openType }},{persist: {paths: [token, userId, redirectURL, openType],},}
)
!-- pages/login/index.vue --
script setupasync function onFormSubmit() {// 判断是否勾选协议if (!isAgree.value) return uni.utils.toast(请先同意协议!)// 调用 uniForms 组件验证数据的方法try {// 省略前面小节的代码...// 持久化存储 tokenuserStore.token data.token// 存储登录用户的 IDuserStore.userId data.id} catch (error) {console.log(error)}}
/script 调整消息的对齐方式患者消息靠右显示
在消息中包含的属性 from 是消息发送者的 ID如果与登录用户的 ID 一致则表示是患者发送的消息消息的内容要靠右显示类名 reverse 可以控制靠右对齐。
!-- subpkg_consult/room/components/message-info.vue --
script setupimport dayjs from dayjsimport { useUserStore } from /stores/user.js// 登录用户 IDconst { userId } useUserStore()// 省略前面小节的代码...
/scripttemplate!-- 文字/图片消息 --view :class{ reverse: props.info.from userId } classmessage-itemimage classroom-avatar :srcprops.info.fromAvatar /view classroom-messageview classtime{{ dateFormat(props.info.createTime) }}/viewview v-ifprops.type 1 classtext{{ props.info.msg.content }}/viewimagev-ifprops.type 4classimage:srcprops.info.msg.picture.urlmodewidthFix//view/view
/template
1.3.2 图片消息
发送图片消息需要将图片上传到云空间需要调用 uniCloud 提供的 API chooseAndUploadFile我们分x步来实现 判断问诊订单状态是否为问诊中
!-- subpkg_consult/room/index.vue --
script setup// 省略前面小节代码...// 发送图片消息function onImageButtonClick() {// 是否在问诊状态中...if (orderDetail.value.status ! 3) {return uni.utils.toast(医生当前不在线!)}}// 省略前面小节代码...
/scripttemplateview classroom-page!-- 此处将来填充更多代码... --scroll-viewrefresher-enabledrefresher-background#f2f2f2scroll-ystyleflex: 1; overflow: hiddenview classmessage-container.../view/scroll-view!-- 发送消息 --view classmessage-bartemplate v-iftrueuni-easyinputv-modeltextMessageconfirmonInputConfirm:disabledorderDetail.status ! 3:clearablefalse:input-borderfalseplaceholder-stylefont-size: 32rpx; color: #c3c3c5;placeholder问医生/view clickonImageButtonClick classimage-buttonuni-icons size40 color#979797 typeimage/uni-icons/view/templatebutton v-else classuni-button咨询其它医生/button/view/view
/template 调用 API 上传到 uniCloud 存储空间
!-- subpkg_consult/room/index.vue --
script setup// 省略前面小节代码...// 发送图片消息function onImageButtonClick() {// 是否在问诊状态中...if (orderDetail.value.status ! 3) {return uni.utils.toast(医生当前不在线!)}// 上传图片到 uniClouduniCloud.chooseAndUploadFile({type: image,count: 1,extension: [.jpg, .png, .gif],success: ({ tempFiles }) {console.log(tempFiles)},})}// 省略前面小节代码...
/scripttemplate...
/template 向医生发送图片消息文档地址在这里
!-- subpkg_consult/room/index.vue --
script setup// 省略前面小节代码...// 用户登录信息不具有响应式const { token, userId } useUserStore()// 发送图片消息function onImageButtonClick() {// 是否在问诊状态中...if (orderDetail.value.status ! 3) {return uni.utils.toast(医生当前不在线!)}// 上传图片到 uniClouduniCloud.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 问诊订单状态
患者在与医生对话的过程中问诊订单状态会发生改变包括待支付、待接诊、咨询中、已完成、已取消在页面的顶部要根据订单的状态展示不同的内容。 将问诊状态的布局模板独立到组件中要求组件能接收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 classroom-statusview classstatus countdown v-iffalsetext classlabel咨询中/textview classtime剩余时间:uni-countdowncolor#3c3e42:font-size14:show-dayfalse:second0//view/viewview v-else-iffalse classstatus waiting已通知医生尽快接诊24小时内医生未回复将自动退款/viewview v-else classstatusuni-icons size20 color#121826 typecheckbox-filled /已结束/view/view
/templatestyle langscss.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 在页面中应用组件并传入数据查询订单状态的的 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
templateview classroom-page!-- 问诊订单状态 --room-status:status-valueorderDetail.statusValue:countdownorderDetail.countdown:statusorderDetail.status/!-- 省略前面小节的代码 --/view
/template 根据传入组件的订单状态展示数据
!-- subpkg_consult/room/components/room-status.vue --
template!-- 咨询室状态 --view classroom-status!-- 待接诊(status: 2) --view v-ifprops.status 2 classstatus waiting{{ props.statusValue }}/view!-- 咨询中(status: 3) --view classstatus v-ifprops.status 3text classlabel{{ props.statusValue }}/textview classtime剩余时间:uni-countdowncolor#3c3e42:font-size14:show-dayfalse:secondprops.countdown//view/view!-- 已完成(status: 4) --view v-ifprops.status 4 classstatusview classwrapuni-icons size20 color#121826 typecheckbox-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 历史消息
用户下拉操作时分页获取聊天记录按以下几个步骤来实现 启动下拉刷新并监听下拉操作
!-- subpkg_consult/room/index.vue --
script setup// 省略前面小节的代码...// 关闭下拉动画交互const refreshTrigger ref(false)// 省略前面小节的代码...// 下拉获取历史消息function onPullDownRefresh() {// 开启下拉交互动画refreshTrigger.value truesetTimeout(() {// 关闭下拉交互动画refreshTrigger.value false}, 1000)}// 省略前面小节的代码...
/scripttemplateview classroom-page!-- 省略前面小节的代码... --scroll-viewrefresherrefreshonPullDownRefreshrefresher-enabled:refresher-triggeredrefreshTriggerbackground-color#f2f2f2.../scroll-view!-- 省略前面小节的代码... --/view
/template 触发后端定义的事件类型获取历史消息文档地址在这里。
!-- 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)}// 省略前面小节的代码...
/scripttemplateview classroom-page!-- 省略前面小节的代码... --scroll-pagerefresherrefreshonPullDownRefreshrefresher-enabled:refresher-triggeredrefreshTriggerbackground-color#f2f2f2.../scroll-page!-- 省略前面小节的代码... --/view
/template 更新时间节点获取的历史消息会返回给客户端
!-- 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
scobys4865sandbox.com
askgxl8276sandbox.com