当前位置: 首页 > news >正文

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-viewrefresher-enabledrefresher-background="#f2f2f2"scroll-ystyle="flex: 1; overflow: hidden"><view class="message-container"><!-- 此处将来填充更多代码... --></view></scroll-view>
​<!-- 发送消息 --><view class="message-bar"><template v-if="true"><uni-easyinputdisabled: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-viewrefresher-enabledrefresher-background="#f2f2f2"scroll-ystyle="flex: 1; overflow: hidden"><view class="message-container"><template v-for="message in messageList" :key="message.id"><!-- 患者信息(21) --><patient-infov-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><textv-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-viewrefresher-enabledrefresher-background="#f2f2f2"scroll-ystyle="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><imagev-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-viewrefresher-enabledrefresher-background="#f2f2f2"scroll-ystyle="flex: 1; overflow: hidden"><view class="message-container"><template v-for="message in messageList" :key="message.id"><!-- 文字图片消息 --><message-infov-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><!-- 图片消息 --><imagev-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><imagev-if="props.type === 4"class="image":src="props.info.msg.picture.url"mode="widthFix"/></view></view>
</template>
1.2.5 处方消息

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

1

接下来分成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-viewrefresher-enabledrefresher-background="#f2f2f2"scroll-ystyle="flex: 1; overflow: hidden">    <view class="message-container"><template v-for="message in messageList" :key="message.id"><!-- 电子处方 --><prescription-infov-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><navigatorclass="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-easyinputtype="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-viewrefresher-enabledrefresher-background="#f2f2f2"scroll-ystyle="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-easyinputtype="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-iconsv-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-viewrefresher-enabledrefresher-background="#f2f2f2"scroll-ystyle="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-easyinputtype="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-iconsv-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><buttonv-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,// 注意要指定一个默认值为 0anonymousFlag: 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-viewrefresher-enabledrefresher-background="#f2f2f2"scroll-ystyle="flex: 1; overflow: hidden"><view class="message-container"><template v-for="message in messageList" :key="message.id"><!-- 医生评价(已评价) --><rate-info:evaluateDoc="message.msg.evaluateDoc"has-evaluatev-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-viewrefresher-enabledrefresher-background="#f2f2f2"scroll-ystyle="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-viewrefresher-enabledrefresher-background="#f2f2f2"scroll-ystyle="flex: 1; overflow: hidden"><view class="message-container">...</view></scroll-view><!-- 发送消息 --><view class="message-bar"><template v-if="true"><uni-easyinputv-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', {// 当前登录用户的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 'pinia'export 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 setup>async 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>
  1. 调整消息的对齐方式,患者消息靠右显示

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

<!-- subpkg_consult/room/components/message-info.vue -->
<script setup>import dayjs from 'dayjs'import { useUserStore } from '@/stores/user.js'// 登录用户 IDconst { 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><imagev-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-viewrefresher-enabledrefresher-background="#f2f2f2"scroll-ystyle="flex: 1; overflow: hidden"><view class="message-container">...</view></scroll-view><!-- 发送消息 --><view class="message-bar"><template v-if="true"><uni-easyinputv-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('医生当前不在线!')}// 上传图片到 uniClouduniCloud.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('医生当前不在线!')}// 上传图片到 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 问诊订单状态

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

  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-countdowncolor="#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-countdowncolor="#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 = truesetTimeout(() => {// 关闭下拉交互动画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

相关文章:

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

学习目标&#xff1a; 能够基于 WebSocket 完成问诊全流程 能够使用 uniCloud 云存储上传文件 能够完成查看电子处方的功能 能够完成医生评价的功能 一、问诊室 以对话聊天的方式向医生介绍病情并获取诊断方案&#xff0c;聊天的内容支持文字和图片两种形式。 首先新建一…...

今日总结 2024-12-28

今天全身心投入到鸿蒙系统下 TCPSocket 的学习中。从最基础的 TCP 协议三次握手、四次挥手原理重新梳理&#xff0c;深刻理解其可靠连接建立与断开机制&#xff0c;这是后续运用 TCPSocket 无误通信的根基。在深入鸿蒙体系时&#xff0c;仔细研读了其为 TCPSocket 封装的 API&a…...

ip归属地怎么判定?如何查看自己ip属地

在当今数字化时代&#xff0c;IP地址作为互联网通信的基础&#xff0c;扮演着至关重要的角色。而IP归属地的判定与查看&#xff0c;不仅关乎网络安全、隐私保护&#xff0c;还直接影响到社交平台的信任机制与信息传播的真实性。本文将深入探讨IP归属地的判定原理以及如何查看自…...

4.采用锁操作并支持等待功能的线程安全队列

分析 书接上文 修改push()似乎并不困难:在函数末尾加上对data_cond.notify_one()的调用即可&#xff0c;与代码清单1(第一篇文章)一样。事情其实没那么简单&#xff0c;我们之所以采用精细粒度的锁&#xff0c;目的是尽可能提高并发操作的数量。如果在notify_one()调用期间&a…...

螺杆支撑座在运用中会出现哪些问题?

螺杆支撑座是一种用于支撑滚珠螺杆的零件&#xff0c;通常用于机床、数控机床、自动化生产线等高精度机械设备中。在运用中可能会出现多种问题&#xff0c;这些问题源于多个方面&#xff0c;以下是对可能出现的问题简单了解下&#xff1a; 1、安装不当&#xff1a;安装过程中没…...

OSI 七层模型 | TCP/IP 四层模型

注&#xff1a;本文为 “OSI 七层模型 | TCP/IP 四层模型” 相关文章合辑。 未整理去重。 OSI 参考模型&#xff08;七层模型&#xff09; BeretSEC 于 2020-04-02 15:54:37 发布 OSI 的概念 七层模型&#xff0c;亦称 OSI&#xff08;Open System Interconnection&#xf…...

秒鲨后端之MyBatis【2】默认的类型别名、MyBatis的增删改查、idea中设置文件的配置模板、MyBatis获取参数值的两种方式、特殊SQL的执行

别忘了请点个赞收藏关注支持一下博主喵&#xff01;&#xff01;&#xff01;! ! ! 下篇更新&#xff1a; 秒鲨后端之MyBatis【3】自定义映射resultMap、动态SQL、MyBatis的缓存、MyBatis的逆向工程、分页插件。 默认的类型别名 MyBatis的增删改查 添加 <!--int insertUs…...

快云服务器小助手getdetail存在任意文件文件读取漏洞

免责声明: 本文旨在提供有关特定漏洞的深入信息,帮助用户充分了解潜在的安全风险。发布此信息的目的在于提升网络安全意识和推动技术进步,未经授权访问系统、网络或应用程序,可能会导致法律责任或严重后果。因此,作者不对读者基于本文内容所采取的任何行为承担责任。读者在…...

尚硅谷Vue3入门到实战 —— 02 编写 App 组件

根目录下的 index.html 是项目的入口文件&#xff0c;默认的具体内容如下&#xff1a; src 文件夹分析 <!DOCTYPE html> <html lang""><head><meta charset"UTF-8"><link rel"icon" href"/favicon.ico"&…...

Java - 日志体系_Simple Logging Facade for Java (SLF4J)日志门面_SLF4J实现原理分析

文章目录 官网SLF4J 简单使用案例分析SLF4J 获取 Logger 的原理获取 ILoggerFactory 的过程获取 Logger 的过程SLF4J 与底层日志框架的集成 小结 官网 https://slf4j.org/ Simple Logging Facade for Java &#xff08;SLF4J&#xff09; 用作各种日志记录框架&#xff08;e.g…...

Flutter 调试环境下浏览器网络请求跨域问题解决方案

本篇文章主要讲解&#xff0c;Flutter调试环境情况下&#xff0c;浏览器调试报错跨域问题的解决方法&#xff0c;通过本篇文章你可以快速掌握Flutter调试环境情况下的跨域问题。 日期&#xff1a;2024年12月28日 作者&#xff1a;任聪聪 报错现象&#xff1a; 报文信息&#xf…...

python编译为可执行文件

1.用py2exe生成可执行文件 目前&#xff0c;在py2exe 0.9.2版本已经支持python3.x&#xff0c;它可以将python程序打包为windows下独立的可执行文件。 要使用py2exe&#xff0c;首先要编写一个编译程序(例如编写一个名为setup.py的程序)&#xff0c;然后在python中运行…...

【机器学习(九)】分类和回归任务-多层感知机(Multilayer Perceptron,MLP)算法-Sentosa_DSML社区版 (1)111

文章目录 一、算法概念111二、算法原理&#xff08;一&#xff09;感知机&#xff08;二&#xff09;多层感知机1、隐藏层2、激活函数sigma函数tanh函数ReLU函数 3、反向传播算法 三、算法优缺点&#xff08;一&#xff09;优点&#xff08;二&#xff09;缺点 四、MLP分类任务…...

spring cloud微服务-OpenFeign的使用

OpenFeign的使用 openFeign的作用是服务间的远程调用 &#xff0c;比如通过OpenFeign可以实现调用远程服务。 已经有了LoadBalancer为什么还要用openFeign? 在微服务架构中&#xff0c;LoadBalancer和OpenFeign虽然都提供了服务间调用的能力&#xff0c;但它们的设计目的和…...

欢迪迈手机商城设计与实现基于(代码+数据库+LW)

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本欢迪迈手机商城就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息…...

GCP Cloud Architect exam - PASS

备考指南 推荐视频课程 https://www.udemy.com/course/google-cloud-architect-certifications/?couponCodeKEEPLEARNING 推荐题库 https://www.udemy.com/course/gcp-professional-cloud-architect-exam-practice-tests-2024​/?couponCodeKEEPLEARNING 错题集 http…...

通过 `@Configuration` 和 `WebMvcConfigurer` 配置 Spring MVC 中的静态资源映射

通过 Configuration 和 WebMvcConfigurer 配置 Spring MVC 中的静态资源映射 一、前言1. 了解静态资源的默认配置2. 使用 Configuration 和 WebMvcConfigurer 自定义资源映射示例&#xff1a;将 /upload/ 和 /img/ 映射到不同的文件系统目录解释&#xff1a;为什么使用 classpa…...

敏捷测试文化的转变

敏捷文化是敏捷测试转型的基础&#xff0c;只有具备敏捷文化的氛围&#xff0c;对组织架构、流程和相关测试实践的调整才能起作用。在前面的敏捷测试定义中&#xff0c;敏捷测试是遵从敏捷软件开发原则的一种测试实践&#xff0c;这意味着敏捷的价值观。 此外&#xff0c;从传…...

深度学习:从原理到搭建基础模型

引言: 深度学习为什么火? 深度学习在处理复杂的感知和模式识别任务方面展现出了前所未有的能力。以图像识别为例,深度学习模型(如卷积神经网络 CNN)能够识别图像中的各种物体、场景和特征,准确率远超传统的计算机视觉方法。 当然这之中也还因为 大数据时代的推动(随着…...

MySQL和HBase的对比

Mysql &#xff1a;关系型数据库&#xff0c;主要面向 OLTP &#xff0c;支持事务&#xff0c;支持二级索引&#xff0c;支持 sql &#xff0c;支持主从、 Group Replication 架构模型&#xff08;此处以 Innodb 为例&#xff0c;不涉及别的存储引擎&#xff09;。 HBase &am…...

Gateway Timeout504 网关超时的完美解决方法

引言 在Web开发中&#xff0c;遇到HTTP状态码504&#xff08;Gateway Timeout&#xff09;是相当常见的。这个状态码表示前端服务器&#xff08;如负载均衡器或代理服务器&#xff09;作为网关工作时&#xff0c;在尝试访问后端服务器处理请求时未能及时得到响应。本文将探讨导…...

【探花交友】day03—MongoDB基础

目录 课程介绍 1、通用设置 1.1 需求分析 1.2 查询通用设置 1.2 陌生人问题 1.3 通知设置 1.4 黑名单管理 2、MongoDB简介 1.1、MongoDB简介 1.2、MongoDB的特点 1.3 数据类型 3、MongoDB入门 2.1、数据库以及表的操作 2.2、新增数据 2.3、更新数据 2.4、删除数…...

总结-常见缓存替换算法

缓存替换算法 1. 总结 1. 总结 常见的缓存替换算法除了FIFO、LRU和LFU还有下面几种&#xff1a; 算法优点缺点适用场景FIFO简单实现可能移除重要数据嵌入式系统&#xff0c;简单场景LRU局部性原理良好维护成本高&#xff0c;占用更多存储空间内存管理&#xff0c;浏览器缓存L…...

宏集eX710物联网工控屏在石油开采机械中的应用与优势

案例概况 客户&#xff1a;天津某石油机械公司 应用产品&#xff1a;宏集eX710物联网工控屏 应用场景&#xff1a;钻井平台设备控制系统 一、应用背景 石油开采和生产过程复杂&#xff0c;涵盖钻井平台、采油设备、压缩机、分离器、管道输送系统等多种机械设备。这些设备通…...

【社区投稿】自动特征auto trait的扩散规则

自动特征auto trait的扩散规则 公式化地概括&#xff0c;auto trait marker trait derived trait。其中&#xff0c;等号右侧的marker与derived是在Rustonomicon书中的引入的概念&#xff0c;鲜见于Rust References。所以&#xff0c;若略感生僻&#xff0c;不奇怪。 marker …...

【MySQL】第一弹----库的操作及数据类型

笔上得来终觉浅,绝知此事要躬行 &#x1f525; 个人主页&#xff1a;星云爱编程 &#x1f525; 所属专栏&#xff1a;MySQL &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 一、SQL 语句分类 DDL:数据定…...

【服务器主板】定制化:基于Intel至强平台的全新解决方案

随着数据处理需求不断增长&#xff0c;服务器硬件的发展也在持续推进。在这一背景下&#xff0c;为用户定制了一款全新的基于Intel至强平台的服务器主板&#xff0c;旨在提供强大的计算能力、优异的内存支持以及高速存储扩展能力。适用于需要高性能计算、大规模数据处理和高可用…...

Flutter路由工具类RouteUtils,可二次开发,拿来即用

一、RouteUtils路由核心类 /*** 路由封装*/ class RouteUtils {RouteUtils._();static final navigatorKey GlobalKey<NavigatorState>();// App 根节点Contextstatic BuildContext get context > navigatorKey.currentContext!;static NavigatorState get navigato…...

报错:No module named ‘pygeohash‘

如果你遇到这个错误&#xff1a; platform... using builtin-java classes where applicableTraceback (most recent call last):File "/home/spark-shell/AppLogDWD02.py", line 4, in <module>from pygeohash import encodeModuleNotFoundError: No module …...

SQL中的TRIM用法

TRIM 是 SQL 中用于去除字符串两端&#xff08;左侧和右侧&#xff09;的空格或特定字符的函数。这个函数常用于清理数据中的无效空白字符&#xff0c;尤其是在从外部系统导入数据时&#xff0c;常常会遇到数据两端有不必要的空格&#xff0c;使用 TRIM 可以去除这些多余的字符…...

AIGC在电影与影视制作中的应用:提高创作效率与创意的无限可能

云边有个稻草人-CSDN博客 目录 引言 一、AIGC在剧本创作中的应用 1.1 剧本创作的传统模式与挑战 1.2 AIGC如何协助剧本创作 1.3 未来的剧本创作&#xff1a;AI辅助的协同创作 二、AIGC在角色设计中的应用 2.1 传统角色设计的挑战 2.2 AIGC如何协助角色设计 三、AIGC在…...

【蓝桥杯——物联网设计与开发】拓展模块5 - 光敏/热释电模块

目录 一、光敏/热释电模块 &#xff08;1&#xff09;资源介绍 &#x1f505;原理图 &#x1f505;AS312 &#x1f319;简介 &#x1f319;特性 &#x1f505;LDR &#xff08;2&#xff09;STM32CubeMX 软件配置 &#xff08;3&#xff09;代码编写 &#xff08;4&#x…...

深入探索openEuler Kernel:操作系统的心脏

title: 深入探索openEuler Kernel&#xff1a;操作系统的心脏 date: ‘2024-12-28’ category: blog tags: openEulerLinux Kernel操作系统性能优化 sig: Kernel archives: ‘2024-12’ author:way_back summary: openEuler Kernel作为openEuler操作系统的核心&#xff0c;扮演…...

Unity3d UGUI如何优雅的实现Web框架(Vue/Rect)类似数据绑定功能(含源码)

前言 Unity3d的UGUI系统与Web前端开发中常见的数据绑定和属性绑定机制有所不同。UGUI是一个相对简单和基础的UI系统&#xff0c;并不内置像Web前端&#xff08;例如 Vue.js或React中&#xff09;那样的双向数据绑定或自动更新UI的机制。UGUI是一种比较传统的 UI 系统&#xff…...

【JavaEE进阶】@RequestMapping注解

目录 &#x1f4d5;前言 &#x1f334;项目准备 &#x1f332;建立连接 &#x1f6a9;RequestMapping注解 &#x1f6a9;RequestMapping 注解介绍 &#x1f384;RequestMapping是GET还是POST请求&#xff1f; &#x1f6a9;通过Fiddler查看 &#x1f6a9;Postman查看 …...

Vue.js组件开发-自定义文件上传

在Vue.js中开发自定义文件上传组件&#xff0c;创建一个独立的Vue组件来处理文件选择和上传的逻辑。这个组件可以包含文件选择器、上传进度条、上传状态提示等元素&#xff0c;并根据需要进行自定义。 示例&#xff1a; <template><div class"file-upload"…...

CES Asia 2025的低空经济展区有哪些亮点?

CES Asia 2025&#xff08;赛逸展&#xff09;的低空经济展区有以下亮点&#xff1a; • 前沿科技产品展示&#xff1a; 多款新型无人机将亮相&#xff0c;如固定翼无人机和系留无人机的最新型号&#xff0c;其在监测、救援和货物运输等方面功能强大。此外&#xff0c;还有可能…...

公路边坡安全监测中智能化+定制化+全面守护的应用方案

面对公路边坡的安全挑战&#xff0c;我们如何精准施策&#xff0c;有效应对风险&#xff1f;特别是在强降雨等极端天气下&#xff0c;如何防范滑坡、崩塌、路面塌陷等灾害&#xff0c;确保行车安全&#xff1f;国信华源公路边坡安全监测解决方案&#xff0c;以智能化、定制化为…...

Arduino 驱动GY-271(HMC5883L)三轴磁场模块

Arduino 驱动GY-271&#xff08;HMC5883L&#xff09;三轴磁场模块 简介特征参数原理图寄存器通信测量步骤接线主要代码结果 简介 HMC5883L 是一种表面贴装的高集成模块&#xff0c;并带有数字接口的弱磁传感器芯片&#xff0c;应用于低成本罗盘和磁场的检测领域。HMC5883L 包…...

ImportError: cannot import name ‘einsum‘ from ‘einops‘

报错&#xff1a; from einops import einsum ImportError: cannot import name einsum from einops 测试代码&#xff1a; python -c "from einops import einsum" 解决方法&#xff1a; pip uninstall einops pip install einops Successfully installed ein…...

GitLab安装及使用

目录 一、安装 1.创建一个目录用来放rpm包 2.检查防火墙状态 3.安装下载好的rpm包 4.修改配置文件 5.重新加载配置 6.查看版本 7.查看服务器状态 8.重启服务器 9.输网址 二、GitLab的使用 1.创建空白项目 2.配置ssh 首先生成公钥&#xff1a; 查看公钥 把上面的…...

攻防世界web第二题unseping

这是题目 <?php highlight_file(__FILE__);class ease{private $method;private $args;function __construct($method, $args) {$this->method $method;$this->args $args;}function __destruct(){if (in_array($this->method, array("ping"))) {cal…...

Bitmap(BMP)图像信息验证

Bitmap BMP图像信息验证 参考文章例程目的一、Bitmap图像结构二、获取文件大小三、获取应用程序路径四、获取目录中所有内容(包括子目录)五、Bitmap图像信息验证六、主函数测试七、测试结果 参考文章 在Windows下C语言获取当前应用程序运行路径并获取指定目录下所有文件Bitmap…...

Faster R-CNN

文章目录 摘要Abstract1. 引言2. 框架2.1 RPN2.1.1 网络结构2.1.2 损失函数2.1.3 训练细节 2.2 训练过程 3. 创新点和不足3.1 创新点3.2 不足 参考总结 摘要 Faster R-CNN是针对Fast R-CNN缺点改进的目标检测模型。为了解决候选区域生成耗时长的问题&#xff0c;Faster R-CNN提…...

MySQL数据库锁

MySQL中读写不互斥&#xff08;前提是没有使用串行化隔离级别&#xff09;&#xff0c;但是写写操作要互斥才行&#xff0c;MySQL中使用锁机制来实现写写互斥。 按照锁的粒度可以分为&#xff1a;全局锁、表锁、行锁以及其他位于二者之间的间隙锁。 全局锁 锁定整个数据库&…...

树莓派A+安装lnmp-第一步,安装mariadb

20:26 2024/12/27 第一件事情&#xff0c;当然是超频&#xff01;&#xff01;&#xff01; raspi-config 4 Performance Options&#xff0c;选择P1 Overclock&#xff0c;可配置超频 不要贪心&#xff0c;选择900就可以&#xff01;&#xff01;&#xff01; rootpia4:~#…...

C++:单例模式

创建自己的对象&#xff0c;同时确保对象的唯一性。 单例类只能有一个实例☞静态成员static☞静态成员 必须类外初始化 单例类必须自己创建自己的唯一实例 单例类必须给所有其他对象提供这一实例 静态成员类内部可以访问 构造函数私有化☞构造函数私有外部不能创建&#x…...

【数据结构】数据结构整体大纲

数据结构用来干什么的&#xff1f;很简单&#xff0c;存数据用的。 &#xff08;这篇文章仅介绍数据结构的大纲&#xff0c;详细讲解放在后面的每一个章节中&#xff0c;逐个击破&#xff09; 那为什么不直接使用数组、集合来存储呢 ——> 如果有成千上亿条数据呢&#xff…...

网页数据的解析提取之Beautiful Soup

前面博客介绍了正则表达式的相关用法&#xff0c;只是一旦正则表达式写得有问题&#xff0c;得到的结果就可能不是我们想要的了。而且每一个网页都有一定的特殊结构和层级关系&#xff0c;很多节点都用id或 class 作区分所以借助它们的结构和属性来提取不也可以吗? 本篇博客我…...

Ai写作人工智能官网模板源码

Mortal是响应式的Tailwind CSS 模板&#xff0c;适用于AI写作和文案智能生成网站。 可用于撰写博客内容、数字广告文案、技术写作、SEO内容、登陆页面文案、社交媒体内容、电子邮件营销、网站文案等。使用世界上流行的响应式CSS框架Tailwind CSS、HTML5、CSS3 和 Javascript构…...