问诊室模块
问诊室-路由与组件
1)路由与组件
- 组件:
views/Room/index.vue
<script setup lang="ts">
import RoomStatus from "./components/RoomStatus.vue";
import RoomAction from "./components/RoomAction.vue";
import RoomMessage from "./components/RoomMessage.vue";
</script>
<template>
<div class="room-page">
<cp-nav-bar title="问诊室" />
<room-status />
<room-message />
<room-action />
</div>
</template>
<style lang="scss" scoped>
.room-page {
padding-top: 90px;
padding-bottom: 60px;
min-height: 100vh;
box-sizing: border-box;
background-color: var(--cp-bg);
.van-pull-refresh {
width: 100%;
min-height: calc(100vh - 150px);
}
}
</style>
- 路由
{
path: '/room',
component: () => import('@/views/Room/index.vue'),
meta: { title: '问诊室' }
},
2)准备组件:素材文件夹中复制
src\views\Room\components\RoomAction.vue
src\views\Room\components\RoomMessage.vue
src\views\Room\components\RoomStatus.vue
3)准备样式文件:素材文件夹中复制
src\styles\room.scss
4)解读各小组件功能
问诊室-websocket 介绍
目的:认识 websocket
什么是 websocket ? https://websocket.org/
- 是一种网络通信协议,和 HTTP 协议 一样。
为什么需要 websocket ?
- 因为 HTTP 协议有一个缺陷:通信只能由客户端发起。
理解 websokect 通讯过程
了解 websocket api 含义
// 创建ws实例,建立连接 (ws://121.40.165.18:8800 有广告)
var ws = new WebSocket("wss://javascript.info/article/websocket/demo/hello");
// 连接成功事件
ws.onopen = function (evt) {
console.log("Connection open ...");
// 发送消息
ws.send("Hello WebSockets!");
};
// 接受消息事件
ws.onmessage = function (evt) {
console.log("Received Message: " + evt.data);
// 关闭连接
ws.close();
};
// 关闭连接事件
ws.onclose = function (evt) {
console.log("Connection closed.");
};
我们项目中使用 socket.io-client 来实现客户端代码,它是基于 websocket 的库。
问诊室-socket.io 使用
目的:掌握 socket.io 的基本使用
socket.io 什么?
- socket.io 是一个基于 WebSocket 的 CS(客户端-服务端)的实时通信库
- 使用它可以在后端提供一个即时通讯服务
- 它也提供一个 js 库,在前端可以去链接后端的 socket.io 创建的服务
- 总结:它是一套基于 websocket 前后端即时通讯解决方案
socket.io 如何使用?
- 大家可以体验下这个 官方 Demo
我们需要掌握的客户端几个 api 的基本使用
如何使用客户端 js 库?
pnpm i socket.io-client
如何建立连接?
import io from "socket.io-client";
// 参数1:不传默认是当前服务域名,开发中传入服务器地址
// 参数2:配置参数,根据需要再来介绍
const socket = io();
如何确定连接成功?
socket.on("connect", () => {
// 建立连接成功
});
如何发送消息?
// chat message 发送消息事件,由后台约定,可变
socket.emit("chat message", "消息内容");
如何接收消息?
// chat message 接收消息事件,由后台约定,可变
socket.on("chat message", (ev) => {
// ev 是服务器发送的消息
});
如何关闭连接?
// 离开组件需要使用
socket.close();
小结:
- sockt.io 在前端使用的 js 库需要知道哪些内容?
- 如何建立链接
io('地址')
- 连接成功的事件
connect
- 如何发消息
emit
+ 事件 - 如何收消息
on
+ 事件 - 如果关闭连接
close()
- 如何建立链接
问诊室-建立连接
步骤:
- 安装 sokect.io-client 包
- 在组件挂载完毕,进行 socket 连接
- 监听连接成功
- 组件卸载关闭连接
代码:
- 安装 sokect.io-client 包
pnpm i socket.io-client
import type { Socket } from "socket.io-client";
import { io } from "socket.io-client";
import { onMounted, onUnmounted } from "vue";
import { baseURL } from "@/utils/request";
import { useUserStore } from "@/stores";
import { useRoute } from "vue-router";
const store = useUserStore();
const route = useRoute();
let socket: Socket;
onUnmounted(() => {
socket.close();
});
onMounted( () => {
// 建立链接,创建 socket.io 实例
socket = io(baseURL, {
auth: {
token: `Bearer ${store.userInfo?.token}`,
},
query: {
orderId: route.query.orderId,
},
});
socket.on("connect", () => {
// 建立连接成功
console.log("connect");
});
});
问诊室-通讯规则
知道前后以及定义数据的类型
- 通讯的一些事件
chatMsgList
接收聊天记录
sendChatMsg
发送消息
receiveChatMsg
接收消息
updateMsgStatus
消息已读
getChatMsgList
获取聊天记录
statusChange
接收订单状态改变
- 消息数据的类型
src/enums/index.ts
/** 消息类型 */
export enum MsgType {
/** 文字聊天 */
MsgText = 1,
/** 图片消息 */
MsgImage = 4,
/** 患者信息 */
CardPat = 21,
/** 处方信息 */
CardPre = 22,
/** 未评价信息 */
CardEvaForm = 23,
/** 已评价信息 */
CardEva = 24,
/** 通用通知 */
Notify = 31,
/** 温馨提示 */
NotifyTip = 32,
/** 取消提示 */
NotifyCancel = 33,
}
/** 处方状态 */
export enum PrescriptionStatus {
/** 未付款 */
NotPayment = 1,
/** 已付款 */
Payment = 2,
/** 已失效 */
Invalid = 3,
}
src/types/room.d.ts
import { MsgType, PrescriptionStatus } from "@/enums";
import type { Consult, Image } from "./consult";
import type { Patient } from "./user";
export type Medical = {
/** 药品ID */
id: string;
/** 药品名称 */
name: string;
/** 金额 */
amount: string;
/** 药品图片 */
avatar: string;
/** 规格信息 */
specs: string;
/** 用法用量 */
usageDosag: string;
/** 数量 */
quantity: string;
/** 是否处方,0 不是 1 是 */
prescriptionFlag: 0 | 1;
};
export type Prescription = {
/** 处方ID */
id: string;
/** 药品订单ID */
orderId: string;
/** 创建时间 */
createTime: string;
/** 患者名称 */
name: string;
/** 问诊记录ID */
recordId: string;
/** 性别 0 女 1 男 */
gender: 0 | 1;
/** 性别文字 */
genderValue: "";
/** 年龄 */
age: number;
/** 诊断信息 */
diagnosis: string;
/** 处方状态 */
status: PrescriptionStatus;
/** 药品清单 */
medicines: Medical[];
};
export type EvaluateDoc = {
/** 评价ID */
id?: string;
/** 评分 */
score?: number;
/** 内容 */
content?: string;
/** 创建时间 */
createTime?: string;
/** 创建人 */
creator?: string;
};
// 消息类型
export type Message = {
/** 消息ID */
id: string;
/** 消息类型 */
msgType: MsgType;
/** 发信人 */
from?: string;
/** 发信人ID */
fromAvatar?: string;
/** 收信人 */
to?: string;
/** 收信人头像 */
toAvatar?: string;
/** 创建时间 */
createTime: string;
/** 消息主体 */
msg: {
/** 文本内容 */
content?: string;
/** 图片对象 */
picture?: Image;
/** 问诊记录,患者信息 */
consultRecord?: Consult & {
patientInfo: Patient;
};
/** 处方信息 */
prescription?: Prescription;
/** 评价信息 */
evaluateDoc?: EvaluateDoc;
};
};
/** 消息分组列表 */
export type TimeMessages = {
/** 分组消息最早时间 */
createTime: string;
/** 消息数组 */
items: Message[];
/** 订单ID */
orderId: string;
/** 会话ID */
sid: string;
};
以上是类型
问诊室-默认消息
步骤:
- 监听默认聊天记录,并且处理处理成消息列表
- 提取常量数据
- 进行渲染
- 预览病情图片
代码:
1)监听默认聊天记录,并且处理处理成消息列表 Room/index.vue
import { MsgType } from "@/enums";
import type { Message, TimeMessages } from "@/types/room";
const list = ref<Message[]>([]);
// 聊天记录
socket.on("chatMsgList", ({ data }: { data: TimeMessages[] }) => {
list.value.push(...data[0].items)
});
<room-message :list="list" />
3)进行渲染 Room/components/RoomMessage.vue
defineProps<{ list: Message[] }>();
const timeOptions = [
{ label: '一周内', value: IllnessTime.Week },
{ label: '一月内', value: IllnessTime.Month },
{ label: '半年内', value: IllnessTime.HalfYear },
{ label: '大于半年', value: IllnessTime.More }
]
const flagOptions = [
{ label: '就诊过', value: 0 },
{ label: '没就诊过', value: 1 }
]
const getIllnessTimeText = (time: IllnessTime) => {
return timeOptions.find((item) => item.value === time)?.label
}
const getConsultFlagText = (flag: 0 | 1) => {
return flagOptions.find((item) => item.value === flag)?.label
}
<template>
<div v-for="{ msgType, msg, createTime, from } in list" :key="id">
<!-- 病情描述 - 💥💥注意不能用v-show -->
<div class="msg msg-illness" v-if="msgType === MsgType.CardPat">
<div class="patient van-hairline--bottom" v-if="msg.consultRecord">
<p>
{{ msg.consultRecord?.patientInfo.name }} {{
msg.consultRecord?.patientInfo.genderValue }} {{
msg.consultRecord?.patientInfo.age }}岁
</p>
<p>
{{ getIllnessTimeText(msg.consultRecord!.illnessTime) }} | {{
getConsultFlagText(msg.consultRecord!.consultFlag) }}
</p>
</div>
<van-row>
<van-col span="6">病情描述</van-col>
<van-col span="18">{{ msg.consultRecord?.illnessDesc }}</van-col>
<van-col span="6">图片</van-col>
<van-col span="18" @click="onPreviewImg(msg.consultRecord?.pictures)">
点击查看
</van-col>
</van-row>
</div>
<!-- 温馨提示 -->
<div class="msg msg-tip" v-if="msgType === MsgType.NotifyTip">
<div class="content">
<span class="green">温馨提示:</span>
<span>{{ msg.content }}</span>
</div>
</div>
<!-- 通用通知 -->
<div class="msg msg-tip" v-if="msgType === 31">
<div class="content">
<span>{{ msg.content }}</span>
</div>
</div>
</div>
</template>
4)预览病情图片 Room/components/RoomMessage.vue
import { showImagePreview } from "vant";
const onPreview = (pics?: Image[]) => {
if (!pics || !pics.length) return
const urls = pics.map((item) => item.url)
showImagePreview(urls)
}
问诊室-接诊状态
步骤:
- 初始化需要订单信息,订单状态发送变化,需要更新订单信息
- 状态组件,根据状态展示对应信息
- 待接诊,绿色文字提示
- 问诊中,倒计时显示
- 已结束 or 已取消,显示问诊结束
- 底部操作组件,禁用和启用
代码:
- 订单状态发送变化,需要更新组件
Room/index.vue
enums/index.ts
/** 问诊订单的状态 */
export enum OrderType {
/** 待支付 */
ConsultPay = 1,
/** 待接诊 */
ConsultWait = 2,
/** 问诊中 */
ConsultChat = 3,
/** 问诊完成 */
ConsultComplete = 4,
/** 取消问诊 */
ConsultCancel = 5,
/** 药品订单 */
/** 待支付 */
MedicinePay = 10,
/** 待发货 */
MedicineSend = 11,
/** 待收货 */
MedicineTake = 12,
/** 已完成 */
MedicineComplete = 13,
/** 取消订单 */
MedicineCancel = 14,
}
types/consult.d.ts
/** 问诊订单单项信息 */
export type ConsultOrderItem = Consult & {
/** 创建时间 */
createTime: string;
/** 医生信息 */
docInfo?: Doctor;
/** 患者信息 */
patientInfo: Patient;
/** 订单编号 */
orderNo: string;
/** 订单状态 */
status: OrderType;
/** 状态文字 */
statusValue: string;
/** 类型问诊文字 */
typeValue: string;
/** 倒计时时间 */
countdown: number;
/** 处方ID */
prescriptionId?: string;
/** 评价ID */
evaluateId: number;
/** 应付款 */
payment: number;
/** 优惠券抵扣 */
couponDeduction: number;
/** 积分抵扣 */
pointDeduction: number;
/** 实付款 */
actualPayment: number;
};
services/consult.ts
/** 获取订单详情API */
export const getOrderDetailAPI = (orderId: string) => {
return request({ url: '/patient/consult/order/detail', params: { orderId } })
}
import type { ConsultOrderItem } from "@/types/consult";
import { getOrderDetailAPI } from "@/services/consult";
const consult = ref<ConsultOrderItem>();
const loadDetailData = async () => {
const res = await getOrderDetailAPI(route.query.orderId as string);
consult.value = res.data;
};
onMounted(loadDetailData);
// 订单状态 在onMounted注册
socket.on("statusChange", loadDetailData);
2)传入 订单状态 倒计时时间 给状态组件 Room/index.vue
<room-status :status="consult?.status" :countdown="consult?.countdown" />
Room/components/RoomStatus.vue
import { OrderType } from "@/enums";
interface Props {
status?: OrderType;
countdown?: number;
}
const { status, countdown = 0 } = defineProps<Props>();
开启解构 Props 响应式转换功能,vite.config.ts
vue({ reactivityTransform: true }),
3)根据状态展示对应信息 Room/components/RoomStatus.vue
<div class="room-status">
<div class="wait" v-if="status === OrderType.ConsultWait">
已通知医生尽快接诊,24小时内医生未回复将自动退款
</div>
<div class="chat" v-if="status === OrderType.ConsultChat">
<span>咨询中</span>
<span class="time"
>剩余时间:<van-count-down :time="countdown * 1000"
/></span>
</div>
<div
class="end"
v-if="status === OrderType.ConsultComplete || status === OrderType.ConsultCancel"
>
<van-icon name="passed" /> 已结束
</div>
</div>
- 根据状态禁用状态栏
Room/components/RoomAction.vue
<room-action
:disabled="consult?.status !== OrderType.ConsultChat"
></room-action>
defineProps<{ disabled: boolean }>();
<van-field
+ :disabled="disabled"
type="text"
class="input"
:border="false"
placeholder="问医生"
autocomplete="off"
></van-field>
<!-- 不预览,使用小图标作为上传按钮 -->
+ <van-uploader :preview-image="false" :disabled="disabled">
<cp-icon name="consult-img" />
</van-uploader>
问诊室-文字聊天
可以发送文字消息,可以接收文字消息
步骤:
- 声明数据 text,双向绑定输入框
- 点击发送,子传父,触发
send-text
事件 - 问诊室组件,监听
send-text
事件接收文字 - 获取订单详情,需要使用医生 ID,作为收信息人的标识
- 通过
socket.emit
的sendChatMsg
发送文字给服务器 - 通过
socket.on
的receiveChatMsg
接收发送成功或者医生发来的消息 - 展示消息
代码:
1)声明数据 text,双向绑定输入框
2)点击发送,子传父,触发 send-text
事件
Room/components/RoomAction.vue
import { ref } from "vue";
const text = ref("");
const emit = defineEmits<{
(e: "send-text", text: string): void;
}>();
const onSendText = () => {
emit("send-text", text.value);
text.value = "";
};
<van-field
:disabled="disabled"
+ v-model="text"
type="text"
class="input"
:border="false"
placeholder="问医生"
autocomplete="off"
+ @keyup.enter="onSendText"
/>
2)问诊室组件,监听 send-text
事件接收文字 Room/index.vue
<room-action @send-text="sendText" />
const sendText = (text: string) => {
// 发送消息
};
3)通过 socket.emit
的 sendChatMsg
发送文字给服务器
const sendText = (text: string) => {
// 发送信息需要 发送人 收消息人 消息类型 消息内容
socket.emit("sendChatMsg", {
from: store.userInfo?.id,
to: consult.value?.docInfo?.id,
msgType: MsgType.MsgText,
msg: { content: text },
});
};
5)通过 socket.on
的 receiveChatMsg
接收发送成功或者医生发来的消息
// 接收消息 在onMounted注册
socket.on("receiveChatMsg", async (event) => {
list.value.push(event);
nextTick(() => {
window.scrollTo(0, document.body.scrollHeight);
});
});
- 展示消息
Room/components/RoomMessage.vue
pnpm i dayjs
import dayjs from "dayjs";
const formatTime = (time: string) => dayjs(time).format("HH:mm");
<!-- 我发的消息 -->
<div
class="msg msg-to"
v-if="msgType === MsgType.MsgText && store.userInfo?.id === from"
>
<div class="content">
<div class="time">{{ formatTime(createTime) }}</div>
<div class="pao">{{ msg.content }}</div>
</div>
<van-image :src="store.userInfo?.avatar" />
</div>
<!-- 医生发的消息 -->
<div
class="msg msg-from"
v-if="msgType === MsgType.MsgText && store.user?.id !== from"
>
<van-image :src="fromAvatar" />
<div class="content">
<div class="time">{{ formatTime(createTime) }}</div>
<div class="pao">{{ msg.content }}</div>
</div>
</div>
问诊室-图片聊天
步骤:
- 底部操作组件,可以上传图片,触发
send-image
事件传出图片对象 - 问诊室组件,监听
send-image
事件接收图片对象 - 通过
socket.emit
的sendChatMsg
发送图片给服务器 - 展示消息
代码:
1)底部操作组件,可以上传图片,触发 send-image
事件传出图片对象
import { uploadImageAPI } from "@/services/consult";
import type { Image } from "@/types/consult";
import type { UploaderAfterRead } from "vant/lib/uploader/types";
interface Emits {
(e: 'send-text', text: string): void
+ (e: 'send-image', img: Image): void
}
const onSendImage: UploaderAfterRead = async (data) => {
if (Array.isArray(data)) return;
if (!data.file) return;
const t = showLoadingToast("正在上传");
const res = await uploadImageAPI(data.file);
t.close();
emit("send-image", res.data);
};
<van-uploader
:preview-image="false"
:disabled="disabled"
+ :after-read="onSendImage"
>
<cp-icon name="consult-img" />
</van-uploader>
2)问诊室组件,监听 send-image
事件接收图片对象,通过 socket.emit
的 sendChatMsg
发送图片给服务器
<room-action
:disabled="consult?.status !== OrderType.ConsultChat"
@send-text="sendText"
+ @send-image="onSendImage"
/>
import type { Image } from "@/types/consult";
const onSendImage = (img: Image) => {
socket.emit("sendChatMsg", {
from: store.userInfo?.id,
to: consult.value?.docInfo?.id,
msgType: MsgType.MsgImage,
msg: { picture: img },
});
};
3)展示消息
<!-- 发消息-图片 -->
<div
class="msg msg-to"
v-if="msgType === MsgType.MsgImage && store.userInfo?.id === from"
>
<div class="content">
<div class="time">{{ formatTime(createTime) }}</div>
<van-image fit="contain" :src="msg.picture?.url" />
</div>
<van-image :src="store.userInfo?.avatar" />
</div>
<!-- 收消息-图片 -->
<div
class="msg msg-from"
v-if="msgType === MsgType.MsgImage && store.userInfo?.id !== from"
>
<van-image :src="fromAvatar" />
<div class="content">
<div class="time">{{ formatTime(createTime) }}</div>
<van-image fit="contain" :src="msg.picture?.url" />
</div>
</div>
问诊室-聊天记录
流程:
步骤:
- 实现下拉刷新效果
- 记录每段消息的开始时间,作为下一次请求的开始时间
- 触发刷新,发送获取聊天记录消息
- 在接收聊天记录事件中
- 关闭刷新中效果
- 判断是否有数据?没有提示
没有聊天记录了
- 如果是初始化获取的聊天,需要滚动到最底部
- 如果是第二,三...次获取消息,不需要滚动到底部
- 如果断开连接后再次连接,需要清空聊天记录
代码:
- 实现下拉刷新效果
<van-pull-refresh v-model="loading" @refresh="onRefresh">
<room-message :list="list" />
</van-pull-refresh>
const loading = ref(false);
const onRefresh = () => {
// 触发下拉
};
- 记录每段消息的开始时间,作为下一次请求的开始时间
const nextTime = ref('');
socket.on('chatMsgList', ({ data }: { data: TimeMessages[] }) => {
+ loading.value = false
+ nextTime.value = data[0].items[0].createTime
list.value.push(...data[0].items)
})
- 触发刷新,发送获取聊天记录消息
const onRefresh = () => {
socket.emit("getChatMsgList", 20, time.value, route.query.orderId);
};
- 在接收聊天记录事件中
- 关闭刷新中效果
- 判断是否有数据?没有提示
没有聊天记录了
- 如果是初始化获取的聊天,需要滚动到最底部
const finished = ref(false)
socket.on('chatMsgList', ({ data }: { data: TimeMessages[] }) => {
loading.value = false
+ if (!data.length) {
+ finished.value = true
+ return showToast('没有更多数据了')
+ }
nextTime.value = data[0].items[0].createTime
+ if (list.value.length) {
+ list.value.unshift(...data[0].items)
+ } else {
+ list.value.push(...data[0].items)
+ nextTick(() => {
+ // 2. 第一次加载后滚动到底部
+ window.scrollTo(0, document.body.scrollHeight)
+ })
+ }
})
- 如果断开连接后再次连接,需要清空聊天记录
// 建立连接成功
socket.on("connect", () => {
list.value = [];
});
问诊室-查看处方
步骤:
- 定义参考处方 API
- 点击查看处方预览处方图片
TIP
需要去辅助-超级医生开处方
代码:
- 渲染处方消息
<!-- 处方 -->
<div class="msg msg-recipe" v-if="msgType === MsgType.CardPre">
<div class="content" v-if="msg.prescription">
<div class="head van-hairline--bottom">
<div class="head-tit">
<h3>电子处方</h3>
<p >
原始处方 <van-icon name="arrow"></van-icon>
</p>
</div>
<p>
{{ msg.prescription.name }} {{ msg.prescription.genderValue }} {{
msg.prescription.age }}岁 {{ msg.prescription.diagnosis }}
</p>
<p>开方时间:{{ msg.prescription.createTime }}</p>
</div>
<div class="body">
<div
class="body-item"
v-for="med in msg.prescription.medicines"
:key="med.id"
>
<div class="drug">
<p>{{ med.name }} {{ med.specs }}</p>
<p>{{ med.usageDosag }}</p>
</div>
<div class="num">x{{ med.quantity }}</div>
</div>
</div>
<div class="foot">
<span>购买药品</span>
</div>
</div>
</div>
2)定义参考处方 API
services/consult.ts
/** 查看处方API */
export const getPrescriptionPicAPI = (id: string) => {
return request(`/patient/consult/prescription/${id}`)
}
3)点击查看处方预览处方图片
<div class="head-tit">
<h3>电子处方</h3>
+ <p @click="showPrescription(msg.prescription?.id)">
原始处方 <van-icon name="arrow"></van-icon>
</p>
</div>
import { getPrescriptionPicAPI } from "@/services/consult";
import { showImagePreview } from "vant";
const showPrescription = async (id?: string) => {
if (id) {
const res = await getPrescriptionPicAPI(id);
showImagePreview([res.data.url]);
}
};
问诊室-购买药品
步骤:
- 处方状态不同此,按钮操作不同:
- 如果处方失效:提示即可
- 如果没付款且有订单 ID,代表已经生成订单没付款:去订单详情付款
- 如果没付款且没订单 ID:去预支付页面
代码:
按钮事件绑定
<div class="foot"><span @click="onBuy(msg.prescription!)">购买药品</span></div>
跳转逻辑处理
import { useRouter } from "vue-router";
import { PrescriptionStatus } from "@/enums";
// 点击处方的跳转
const router = useRouter();
const onBuy = (pre: Prescription) => {
if (pre.status === PrescriptionStatus.Invalid) return showToast("处方已失效");
if (pre.status === PrescriptionStatus.NotPayment && !pre.orderId)
return router.push(`/order/pay?id=${pre.id}`);
router.push(`/order/${pre.orderId}`);
};
问诊室-评价医生
步骤:
- 准备评价组件:素材复制
- 展示评价组件
- 传入 评价信息对象 条件展示
- 评价表单数据绑定和校验
- 提交评价
- 修改消息
代码:
1)准备评价组件 Room/components/EvaluateCard.vue
2)展示评价组件
Room/components/RoomMessage.vue
<div
class="msg msg-comment"
+ v-if="msgType === MsgType.CardEva || msgType === MsgType.CardEvaForm"
>
+ <evaluate-card :evaluateDoc="msg.evaluateDoc" />
</div>
Room/components/evaluateCard.vue
import type { EvaluateDoc } from "@/types/room";
defineProps<{
evaluateDoc?: EvaluateDoc;
}>();
+ <div class="evaluate-card" v-if="evaluateDoc">
<p class="title">医生服务评价</p>
<p class="desc">我们会更加努力提升服务质量</p>
<van-rate
- :modelValue="3"
+ :modelValue="evaluateDoc.score"
size="7vw"
gutter="3vw"
color="#FADB14"
void-icon="star"
void-color="rgba(0,0,0,0.04)"
/>
</div>
+ <div class="evaluate-card" v-else>
3)评价表单数据绑定和校验
import { computed, inject, ref } from "vue";
const score = ref(0);
const anonymousFlag = ref(false);
const content = ref("");
const disabled = computed(() => !score.value || !content.value);
const onSubmit = async () => {
if (!score.value) return showToast("请选择评分");
if (!content.value) return showToast("请输入评价");
};
<van-rate
+ v-model="score"
size="7vw"
gutter="3vw"
color="#FADB14"
void-icon="star"
void-color="rgba(0,0,0,0.04)"
/>
<van-field
+ v-model="content"
type="textarea"
maxlength="150"
show-word-limit
rows="3"
placeholder="请描述您对医生的评价或是在医生看诊过程中遇到的问题"
/>
<div class="footer">
<van-checkbox
+ v-model="anonymousFlag"
>匿名评价</van-checkbox>
<van-button
+ @click="onSubmit"
type="primary"
size="small"
+ :class="{ disabled }"
round
>
提交评价
</van-button>
</div>
4)提交评价
api 函数
type EvaluateOrderParams = {
docId: string;
orderId: string;
score: number;
content: string;
anonymousFlag: 0 | 1;
};
// 评价问诊
export const evaluateOrderAPI = (data: EvaluateOrderParams) => {
return request({ url: "/patient/order/evaluate", method: "POST", data });
};
注入订单信息:提供医生 ID 和订单 ID Room/index.vue
import { provide } from "vue";
// ...
provide("consult", consult);
Room/components/EvaluateCard.vue
import { inject, type Ref } from "vue";
import type { ConsultOrderItem } from "@/types/consult";
const consult = inject<Ref<ConsultOrderItem>>("consult");
提交评价
const onSubmit = async () => {
if (!score.value) return showToast('请选择评分')
if (!content.value) return showToast('请输入评价')
+ if (!consult?.value) return showToast('未找到订单')
+ if (consult.value.docInfo?.id) {
+ await evaluateOrderAPI({
+ docId: consult.value?.docInfo?.id,
+ orderId: consult.value?.id,
+ score: score.value,
+ content: content.value,
+ anonymousFlag: anonymousFlag.value ? 1 : 0
+ })
+ }
// 修改消息
}
5)成功后修改消息 Room/index.vue
const completeEva = (score: number) => {
const item = list.value.find((item) => item.msgType === MsgType.CardEvaForm);
if (item) {
item.msg.evaluateDoc = { score };
item.msgType = MsgType.CardEva;
}
};
provide("completeEva", completeEva);
Room/components/EvaluateCard.vue
+const completeEva = inject<(score: number) => void>('completeEva')
const onSubmit = async () => {
if (!score.value) return showToast('请选择评分')
if (!content.value) return showToast('请输入评价')
if (!consult?.value) return showToast('未找到订单')
if (consult.value.docInfo?.id) {
await evaluateOrderAPI({
docId: consult.value.docInfo?.id,
orderId: consult.value?.id,
score: score.value,
content: content.value,
anonymousFlag: anonymousFlag.value ? 1 : 0
})
}
+ completeEva && completeEva(score.value)
}
结束问诊消息:
Room/components/RoomMessage.vue
<div class="msg msg-tip msg-tip-cancel" v-if="msgType === MsgType.NotifyCancel">
<div class="content">
<span>{{ msg.content }}</span>
</div>
</div>
问诊室-支付失败
处理问诊室支付失败情况
思考:
怎么处理?
- 地址栏上的 payResult 是否是 false,是代表失败
何时处理?
- 组件挂载完毕,太晚,页面已渲染
- 进入路由前处理即可
{
path: '/room',
component: () => import('@/views/Room/index.vue'),
meta: { title: '问诊室' },
+ beforeEnter(to) {
+ if (to.query.payResult === 'false') return '/user/consult'
+ }
},