S11-12 Vue-项目:mr-vue3-ts-consult-patient2
[TOC]
Consult
TS类型
知识点:Enum
1、使用枚举类型定义问诊类型、问诊时间
// 问诊类型
export enum ConsultType {
/** 找医生 */
Doctor = 1,
/** 快速问诊 */
Fast = 2,
/** 开药问诊 */
Medication = 3
}
// 问诊时间,以1自增可以省略
export enum IllnessTime {
/** 一周内 */
Week = 1,
/** 一月内 */
Month,
/** 半年内 */
HalfYear,
/** 半年以上 */
More
}
2、定义问诊记录类型
import { ConsultType, IllnessTime } from '@/enums'
// 图片列表
export type Image = {
/** 图片ID */
id: string
/** 图片地址 */
url: string
}
// 问诊记录
export type Consult = {
/** 问诊记录ID */
id: string
/** 问诊类型 */
type: ConsultType
/** 快速问诊类型,0 普通 1 三甲 */
illnessType: 0 | 1
/** 科室ID */
depId: string
/** 疾病描述 */
illnessDesc: string
/** 疾病持续时间 */
illnessTime: IllnessTime
/** 是否就诊过,0 未就诊过 1 就诊过 */
consultFlag: 0 | 1
/** 图片数组 */
pictures: Image[]
/** 患者ID */
patientId: string
/** 优惠券ID */
couponId: string
}
// 问诊记录-全部可选
export type PartialConsult = Partial<Consult>
// Required 转换为全部必须 Partial 转换问全部可选 两个内置的泛型类型
问诊类型
定义问诊仓库
1、在 store/consult.ts 中定义 consultStore
2、在 store/index.ts 中统一导出consultStore
记录问诊类型
1、在 store/consult.ts 中定义 setType() 方法,记录问诊类型
2、在 home 组件调用 setType() 方法,记录问诊类型
问诊级别:consult-fast
路由规则
页面布局
1、HTML
<template>
<div class="consult-fast-page">
<cp-nav-bar title="极速问诊" right-text="问诊记录"></cp-nav-bar>
<div class="fast-logo">
<img class="img" src="@/assets/consult-fast.png" alt="" />
<p class="text"><span>20s</span> 快速匹配专业医生</p>
</div>
<div class="fast-type">
<router-link to="/consult/dep" class="item">
<cp-icon class="pic" name="consult-doctor"></cp-icon>
<div class="info">
<p>三甲图文问诊</p>
<p>三甲主治及以上级别医生</p>
</div>
<van-icon name="arrow"></van-icon>
</router-link>
<router-link to="/consult/dep" class="item">
<cp-icon class="pic" name="consult-message"></cp-icon>
<div class="info">
<p>普通图文问诊</p>
<p>二甲主治及以上级别医生</p>
</div>
<van-icon name="arrow"></van-icon>
</router-link>
</div>
</div>
</template>
2、样式
.consult-fast-page {
padding-top: 46px;
.fast-logo {
padding: 30px 0;
text-align: center;
.img {
width: 240px;
}
.text {
font-size: 16px;
margin-top: 10px;
> span {
color: var(--cp-primary);
}
}
}
.fast-type {
padding: 15px;
.item {
display: flex;
padding: 16px;
border-radius: 4px;
align-items: center;
margin-bottom: 16px;
border: 0.5px solid var(--cp-line);
}
.pic {
width: 40px;
height: 40px;
}
.info {
margin-left: 12px;
flex: 1;
> p:first-child {
font-size: 16px;
color: var(--cp-text1);
margin-bottom: 4px;
}
> p:last-child {
font-size: 13px;
color: var(--cp-tag);
}
}
.van-icon {
color: var(--cp-tip);
}
}
}
记录问诊级别
1、在 store/consult.ts 中定义 setIllnessType() 方法,记录问诊级别
2、在 consult-fast 组件调用 setIllnessType() 方法,记录问诊级别
问诊科室:consult-department
路由规则
页面布局
1、HTML
<template>
<div class="consult-dep-page">
<!-- 导航栏 -->
<cp-nav-bar title="选择科室" />
<div class="wrapper">
<!-- 一级科室 -->
<van-sidebar v-model="active">
<van-sidebar-item title="内科" />
<van-sidebar-item title="外科" />
<van-sidebar-item title="皮肤科" />
<van-sidebar-item title="骨科" />
</van-sidebar>
<!-- 二级科室 -->
<div class="sub-dep">
<router-link to="/consult/illness">科室一</router-link>
<router-link to="/consult/illness">科室二</router-link>
<router-link to="/consult/illness">科室三</router-link>
</div>
</div>
</div>
</template>
2、样式
.van-sidebar {
width: 114px;
&-item {
padding: 14px;
color: var(--cp-tag);
&--select {
color: var(--cp-main);
font-weight: normal;
&::before {
display: none;
}
}
}
}
.consult-dep-page {
padding-top: 46px;
.wrapper {
height: calc(100vh - 46px);
overflow: hidden;
display: flex;
.sub-dep {
flex: 1;
height: 100%;
overflow-y: auto;
> a {
display: block;
padding: 14px 30px;
color: var(--cp-dark);
}
}
}
}
渲染请求数据
TS类型
// 二级科室
export type SubDep = {
/** 科室ID */
id: string
/** 科室名称 */
name: string
}
// 一级科室
export type TopDep = SubDep & {
/** 二级科室数组 */
child: SubDep[]
}
接口
URL:
/dep/all
类型:
GET
token:携带
参数:无
返回数据:
渲染数据
1、在 services/consult.ts 中发送网络请求
2、在组件中调用请求方法,获取一级科室列表数据
3、渲染一级科室数据
4、根据 active 属性,获取二级科室列表数据
5、渲染二级科室数据
记录问诊科室
1、在 store/consult.ts 中定义 setDep() 方法,记录问诊科室
2、在 consult-department 组件调用 setDep() 方法,记录问诊科室
病情描述:consult-illness
路由规则
页面布局
1、HTML
<template>
<div class="consult-illness-page">
<cp-nav-bar title="图文问诊" />
<!-- 医生提示 -->
<div class="illness-tip van-hairline--bottom">
<img class="img" src="@/assets/avatar-doctor.svg" />
<div class="info">
<p class="tit">在线医生</p>
<p class="tip">
请描述你的疾病或症状、是否用药、就诊经历,需要我听过什么样的帮助
</p>
<p class="safe">
<cp-icon name="consult-safe" /><span>内容仅医生可见</span>
</p>
</div>
</div>
<!-- 收集信息 -->
<div class="illness-form">
<van-field
type="textarea"
rows="3"
placeholder="请详细描述您的病情,病情描述不能为空"
></van-field>
<div class="item">
<p>本次患病多久了?</p>
</div>
<div class="item">
<p>此次病情是否去医院就诊过?</p>
</div>
</div>
</div>
</template>
2、样式
.consult-illness-page {
padding-top: 46px;
}
.illness-tip {
display: flex;
padding: 15px;
.img {
width: 52px;
height: 52px;
border-radius: 4px;
overflow: hidden;
margin-top: 10px;
}
.info {
flex: 1;
padding-left: 12px;
.tit {
font-size: 16px;
margin-bottom: 5px;
}
.tip {
padding: 12px;
background: var(--cp-bg);
color: var(--cp-text3);
font-size: 13px;
margin-bottom: 10px;
}
.safe {
font-size: 10px;
color: var(--cp-text3);
display: flex;
align-items: center;
.cp-icon {
font-size: 12px;
margin-right: 2px;
}
}
}
}
.illness-form {
padding: 15px;
.van-field {
padding: 0;
&::after {
border-bottom: none;
}
}
.item {
> p {
color: var(--cp-text3);
padding: 15px 0;
}
}
}
表单数据
1、TS类型
export type ConsultIllness = Pick<
PartialConsult,
'illnessDesc' | 'illnessTime' | 'consultFlag' | 'pictures'
>
2、表单数据
import type { ConsultIllness } from '@/types/consult'
import { ref } from 'vue'
import { IllnessTime } from '@/enums'
const timeOptions = [
{ label: '一周内', value: IllnessTime.Week },
{ label: '一月内', value: IllnessTime.Month },
{ label: '半年内', value: IllnessTime.HalfYear },
{ label: '半年以上', value: IllnessTime.More }
]
const flagOptions = [
{ label: '就诊过', value: 1 },
{ label: '没就诊过', value: 0 }
]
const form = ref<ConsultIllness>({
illnessDesc: '',
illnessTime: undefined,
consultFlag: undefined,
pictures: []
})
渲染数据
上传图片@
页面布局
1、HTML
<div class="illness-img">
<van-uploader></van-uploader>
<p class="tip" >上传内容仅医生可见,最多9张图,最大5MB</p>
</div>
2、样式
.illness-img {
padding-top: 16px;
margin-bottom: 40px;
display: flex;
align-items: center;
.tip {
font-size: 12px;
color: var(--cp-tip);
}
::v-deep() {
.van-uploader {
&__preview {
&-delete {
left: -6px;
top: -6px;
border-radius: 50%;
background-color: var(--cp-primary);
width: 20px;
height: 20px;
&-icon {
transform: scale(0.9) translate(-22%, 22%);
}
}
&-image {
border-radius: 8px;
overflow: hidden;
}
}
&__upload {
border-radius: 8px;
}
&__upload-icon {
color: var(--cp-text3);
}
}
}
}
配置图片文字
限制数量和大小
收集图片数据
实现上传业务
接口
URL:
/upload
类型:
POST
token:携带
参数:
ts{ file: FormData // 上传的文件 }
返回数据:
上传图片
1、在 services/consult.ts 中发送网络请求
2、在组件中,通过after-read
绑定图片上传完毕后的回调函数
3、通过after-read
的item.status参数定义上传状态
4、同步上传数据到form.pictures中
3、在组件中,通过@delete
监听删除文件预览的处理函数
记录病情描述
按钮激活
1、默认情况下按钮有disabled类,此时按钮为灰色状态
2、样式默认为灰色
.van-button {
font-size: 16px;
margin-bottom: 30px;
&.disabled {
opacity: 1;
background: #fafafa;
color: #d9dbde;
border: #fafafa;
}
}
3、激活:当病情描述、时间、是否就诊选中时激活按钮
轻提示校验
记录病情描述
1、在 store/consult.ts 中定义记录病情描述的方法 setIllness()
2、在组件中调用 setIllness() 方法,记录病情描述,并跳转到选择患者页面
回显数据
1、当进入到病情描述页面时,判断是否已经存在病情描述数据,如果存在则回显该数据。
2、通过设置closeOnPopstate: false
处理从选择患者页面回退时,无法弹出弹框的问题
选择患者:patient
界面兼容
依据isChange=1
区分选择患者、家庭档案
选中患者效果
1、在患者项上绑定点击事件,设置选中患者id
2、阻止点击编辑按钮时的事件冒泡
默认选中
思路:进入页面后,如果有默认患者,则选中该患者;如果没有默认患者,则选中第一个患者。
记录患者ID
1、在 store/consult.ts 中,添加记录患者ID的方法 setPatient()
2、在组件中点击下一步时,调用 setPatient() 方法记录患者ID
问诊支付:consult-pay
路由规则
页面布局
1、HTML
<template>
<div class="consult-pay-page">
<cp-nav-bar title="支付" />
<div class="pay-info">
<p class="tit">图文问诊 49 元</p>
<img class="img" src="@/assets/avatar-doctor.svg" />
<p class="desc">
<span>极速问诊</span>
<span>自动分配医生</span>
</p>
</div>
<van-cell-group>
<van-cell title="优惠券" value="-¥10.00" />
<van-cell title="积分抵扣" value="-¥10.00" />
<van-cell title="实付款" value="¥29.00" class="pay-price" />
</van-cell-group>
<div class="pay-space"></div>
<van-cell-group>
<van-cell title="患者信息" value="李富贵 | 男 | 30岁"></van-cell>
<van-cell title="病情描述" label="头痛,头晕,恶心"></van-cell>
</van-cell-group>
<div class="pay-schema">
<van-checkbox>我已同意 <span class="text">支付协议</span></van-checkbox>
</div>
<van-submit-bar button-type="primary" :price="2900" button-text="立即支付" text-align="left" />
</div>
</template>
2、样式
.consult-pay-page {
padding: 46px 0 50px 0;
}
.pay-info {
display: flex;
padding: 15px;
flex-wrap: wrap;
align-items: center;
.tit {
width: 100%;
font-size: 16px;
margin-bottom: 10px;
}
.img {
margin-right: 10px;
width: 38px;
height: 38px;
border-radius: 4px;
overflow: hidden;
}
.desc {
flex: 1;
> span {
display: block;
color: var(--cp-tag);
&:first-child {
font-size: 16px;
color: var(--cp-text2);
}
}
}
}
.pay-price {
::v-deep() {
.vam-cell__title {
font-size: 16px;
}
.van-cell__value {
font-size: 16px;
color: var(--cp-price);
}
}
}
.pay-space {
height: 12px;
background-color: var(--cp-bg);
}
.pay-schema {
height: 56px;
display: flex;
align-items: center;
justify-content: center;
.text {
color: var(--cp-primary);
}
}
::v-deep() {
.van-submit-bar__button {
font-weight: normal;
width: 160px;
}
}
.pay-type {
.amount {
padding: 20px;
text-align: center;
font-size: 16px;
font-weight: bold;
}
.btn {
padding: 15px;
}
.van-cell {
align-items: center;
.cp-icon {
margin-right: 10px;
font-size: 18px;
}
.van-checkbox :deep(.van-checkbox__icon) {
font-size: 16px;
}
}
}
渲染请求数据
TS类型
// 问诊订单预支付传参
export type ConsultOrderPreParams = Pick<PartialConsult, 'type' | 'illnessType'>
// 问诊订单预支付信息
export type ConsultOrderPreData = {
/** 积分抵扣 */
pointDeduction: number
/** 优惠券抵扣 */
couponDeduction: number
/** 优惠券ID */
couponId: string
/** 需付款 */
payment: number
/** 实付款 */
actualPayment: number
}
接口-预支付信息
URL:
/patient/consult/order/pre
类型:
GET
token:携带
参数:
ts{ // 急速问诊只需要以下2个属性 type: string // 问诊类型:1 找医生,2 极速问诊,3 开药问诊 illnessType: string // 极速问诊级别:0 普通,1 三甲 useCoupon: string // 是否使用优惠券:0 不使用优惠券,1 使用优惠券(默认,且按最大优惠券使用)。 couponId: string // 可使用优惠券id docId: string // 医生id,当type为1时必传 }
返回数据:
接口-患者信息
URL:
/patient/info/:id
类型:
GET
token:携带
参数:
ts{ id: string // 患者id }
返回数据:
渲染数据
1、在 services/consult.ts 中发送网络请求
2、在 services/patient.ts 中发送网络请求
3、在组件中,调用请求方法
4、渲染数据
记录优惠券
1、在store中定义 setCoupon() 方法记录优惠券
2、在组件中,获取到预支付信息后,调用 setCoupon() 方法记录优惠券
骨架屏@
实现支付
支付流程
生成订单
支付抽屉面板
1、在 van-submit-bar 上绑定点击事件,打开支付抽屉面板
2、支付抽屉面板布局
接口
URL:
/patient/consult/order
类型:
POST
token:携带
参数:
tsConsult
返回数据:
实现生成订单
1、选择支付方式
2、在 services/consult.ts 中发送网络请求
3、在组件中调用请求方法,获取订单id,同时添加loading效果
清理问诊数据
1、在store中定义 clear() 方法,清理问诊数据
2、在组件中调用 clear() 方法,清理问诊数据
用户引导
阻止返回上页
阻止关闭抽屉
1、设置属性阻止关闭抽屉
2、监听用户点击灰色区域关闭抽屉,弹出确认弹框
刷新页面提示
思路:刷新页面后在onMounted钩子中校验所有需要的key对应的值是否为undefined,如果所有值都不是undefined,则校验通过,否则弹出提示框,提示数据不完整。
实现支付
接口
URL:
/patient/consult/pay
类型:
POST
token:携带
参数:
ts{ paymentMethod: string // 支付方式:0 微信支付,1 支付宝,2 云闪付 orderId: string // 订单id payCallback: string // 回跳地址,http://域名/回跳页面 }
返回数据:
实现支付
1、在 services/consult.ts 中发送网络请求
2、在组件中调用请求方法,获取支付地址
处理支付失败
思路:支付完成后等待几秒会跳转到指定地址,url参数中包括支付结果payResult,支付成功payResult为true,支付失败payResult为false。
在room路由的beforeEnter路由钩子中处理支付失败的跳转
问诊订单:consult-order
路由规则
页面布局
1、HTML
<script setup lang="ts">
import ConsultList from './components/ConsultList.vue'
</script>
<template>
<div class="consult-page">
<cp-nav-bar title="问诊记录" />
<van-tabs sticky>
<van-tab title="极速问诊"><consult-list /></van-tab>
<van-tab title="找医生"><consult-list /></van-tab>
<van-tab title="开药问诊"><consult-list /></van-tab>
</van-tabs>
</div>
</template>
2、样式
.consult-page {
padding-top: 46px;
background-color: var(--cp-bg);
min-height: calc(100vh - 46px);
}
功能
问诊记录入口
1、入口一:在consult-fast组件中,点击问诊记录跳转
2、入口二:在user组件中,点击问诊记录跳转
接口-问诊记录订单列表
URL:
/patient/consult/order/list
类型:
GET
token:携带
参数:
ts{ current: string // 当前页数,默认:1 pageSize: string // 每页记录数,默认:10 type: string // 问诊类型,1:问医生,2:急速问诊,3:开药问诊,默认:1 }
返回数据:
接口-TS类型
1、接口参数类型
export type ConsultOrderListParams = PageParams & {
type: ConsultType // 问诊记录类型
}
2、带分页问诊订单类型
export type ConsultOrderPage = {
pageTotal: number
total: number
rows: ConsultOrderItem[]
}
请求数据
1、在 services/consult.js 中,发送网络请求,获取问诊记录订单列表数据
2、在 consult-record-list 组件中,使用 van-list 组件触发加载更多,并在@load事件处理函数中调用请求方法
3、渲染请求到的问诊记录列表数据
组件:consult-order-list
页面布局
1、HTML
<script setup lang="ts">
import ConsultRecordItem from './ConsultRecordItem.vue'
</script>
<template>
<div class="consult-list">
<consult-record-item v-for="i in 5" :key="i" />
</div>
</template>
2、样式
.consult-list {
padding: 10px 15px;
}
接收问诊类型
1、在consult-record 组件中,传递问诊类型属性type
2、在 consult-record-list 组件中,接收属性
组件:consult-order-item
页面布局
1、HTML
<template>
<div class="consult-record-item">
<div class="head van-hairline--bottom">
<img class="img" src="@/assets/avatar-doctor.svg" />
<p>极速问诊(自动分配医生)</p>
<span>待支付</span>
</div>
<div class="body">
<div class="body-row">
<div class="body-label">病情描述</div>
<div class="body-value">腹痛腹泻 胃部有些痉挛</div>
</div>
<div class="body-row">
<div class="body-label">价格</div>
<div class="body-value">¥ 39.00</div>
</div>
<div class="body-row">
<div class="body-label">创建时间</div>
<div class="body-value tip">2019-07-08 09:55:54</div>
</div>
</div>
<div class="foot">
<van-button class="gray" plain size="small" round>取消问诊</van-button>
<van-button type="primary" plain size="small" round >去支付</van-button>
</div>
</div>
</template>
2、样式
.consult-record-item {
border-radius: 4px;
box-shadow: 0px 0px 22px 0px rgba(245, 245, 245, 0.1);
background-color: #fff;
margin-bottom: 10px;
.head {
display: flex;
align-items: center;
height: 50px;
padding: 0 15px;
.img {
width: 20px;
height: 20px;
}
> p {
flex: 1;
font-size: 15px;
padding-left: 10px;
}
> span {
color: var(--cp-tag);
&.orange {
color: #f2994a;
}
&.green {
color: var(--cp-primary);
}
}
}
.body {
padding: 15px 15px 8px 15px;
.body-row {
display: flex;
margin-bottom: 7px;
}
.body-label {
width: 62px;
font-size: 13px;
color: var(--cp-tip);
}
.body-value {
width: 250px;
&.tip {
color: var(--cp-tip);
}
}
}
.foot {
padding: 0 15px 15px 15px;
display: flex;
justify-content: flex-end;
align-items: center;
.van-button {
margin-left: 10px;
padding: 0 16px;
&.gray {
color: var(--cp-text3);
background-color: var(--cp-bg);
}
}
.more {
color: var(--cp-tag);
flex: 1;
font-size: 13px;
}
}
}
渲染组件
1、渲染卡片头部和主体
2、渲染卡片底部按钮
功能-更多操作
1、使用 van-popover 组件实现更多气泡
2、添加选择事件 @select
3、设置弹出位置为 top-start
功能-取消问诊
接口-取消问诊订单
URL:
/patient/order/cancel/${id}
类型:
PUT
token:携带
参数:
ts{ id: string // 订单id }
返回数据:
实现取消问诊
1、在 services/consult.js 中发送网络请求
2、在 consult-record-item 组件中,实现取消订单
3、为所有的 取消问诊 按钮绑定点击事件
功能-删除订单
接口-删除订单
URL:
/patient/order/${id}
类型:
DELETE
token:携带
参数:
ts{ id: string // 订单id }
返回数据:
实现删除订单
1、在 services/consult.js 中发送网络请求
2、在 consult-record-item 组件中,实现删除订单,并发射 on-delete 事件到父组件
3、为所有的 删除订单 按钮绑定点击事件
4、更多操作 中也有删除订单按钮,需要在onSelect()方法中调用deleteConsultOrder()方法实现删除订单
5、在父组件 consult-order-list 中,接收发射过来的事件 on-delete,并绑定到onDelete()中实现删除对应的item订单数据
功能-查看处方
1、封装 room-message 组件中的查看处方方法到hooks中
2、使用封装的方法
3、在更多操作的@select中使用封装的方法
问诊详情:consult-detail
路由规则
页面布局
1、HTML
<script setup lang="ts"></script>
<template>
<div class="consult-detail-page">
<cp-nav-bar title="问诊详情" />
<!-- 头部 -->
<div class="detail-head">
<div class="text">
<h3>图文问诊 39 元</h3>
<span class="sta green">待支付</span>
<p class="tip">服务医生信息</p>
</div>
<div class="card">
<img class="avatar" src="@/assets/avatar-doctor.svg" alt="" />
<p class="doc">
<span>极速问诊</span>
<span>自动分配医生</span>
</p>
<van-icon name="arrow" />
</div>
</div>
<!-- 患者信息 -->
<div class="detail-patient">
<van-cell-group :border="false">
<van-cell title="患者信息" value="李富贵 | 男 | 30岁" />
<van-cell title="患病时长" value="一周内" />
<van-cell title="就诊情况" value="未就诊过" />
<van-cell title="病情描述" label="头痛,头晕,恶心" />
</van-cell-group>
</div>
<!-- 订单信息 -->
<div class="detail-order">
<h3>订单信息</h3>
<van-cell-group :border="false">
<van-cell title="订单编号">
<template #value>
<span class="copy">复制</span>
202201127465
</template>
</van-cell>
<van-cell title="创建时间" value="2022-01-23 09:23:46" />
<van-cell title="应付款" value="¥39" />
<van-cell title="优惠券" value="-¥0" />
<van-cell title="积分抵扣" value="-¥0" />
<van-cell title="实付款" value="¥39" class="price" />
</van-cell-group>
</div>
<!-- <div class="detail-time">
请在 <van-count-down :time="10000 * 1000" /> 内完成支付,超时订单将取消
</div> -->
<!-- 支付 -->
<div class="detail-action van-hairline--top">
<div class="price">
<span>需付款</span>
<span>¥39.00</span>
</div>
<van-button type="default" round>取消问诊</van-button>
<van-button type="primary" round>继续支付</van-button>
</div>
</div>
</template>
2、样式
.consult-detail-page {
padding: 46px 0 110px 0;
}
.detail-head {
height: 140px;
position: relative;
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 135px;
background: linear-gradient(180deg, rgba(44, 181, 165, 0), rgba(44, 181, 165, 0.2));
border-bottom-left-radius: 150px 20px;
border-bottom-right-radius: 150px 20px;
}
padding: 15px;
.text {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 10px 3px;
.sta {
color: var(--cp-tag);
font-weight: 500;
font-size: 16px;
&.green {
color: var(--cp-primary);
}
&.orange {
color: #f2994a;
}
}
.tip {
width: 100%;
color: var(--cp-text3);
margin-top: 5px;
}
}
.card {
height: 74px;
background-color: #fff;
border-radius: 8px;
position: relative;
display: flex;
align-items: center;
padding: 0 15px;
box-shadow: 0px 0px 22px 0px rgba(229, 229, 229, 0.5);
.avatar {
width: 38px;
height: 38px;
}
.doc {
flex: 1;
padding-left: 15px;
> span {
display: block;
font-size: 16px;
&:last-child {
font-size: 13px;
color: var(--cp-text3);
}
}
}
.van-icon {
color: var(--cp-tip);
}
}
}
.detail-patient {
&::after {
content: '';
display: block;
height: 12px;
background-color: var(--cp-bg);
}
}
.detail-order {
> h3 {
padding: 10px 18px;
font-weight: normal;
}
.copy {
padding: 2px 10px;
border: 1px solid var(--cp-primary);
background-color: var(--cp-plain);
color: var(--cp-primary);
font-size: 12px;
border-radius: 12px;
margin-right: 10px;
}
:deep(.van-cell__title) {
width: 70px;
flex: none;
}
.price :deep(.van-cell__value) {
font-size: 16px;
color: var(--cp-price);
}
}
.detail-action {
height: 65px;
width: 100%;
position: fixed;
left: 0;
bottom: 0;
display: flex;
align-items: center;
background-color: #fff;
justify-content: flex-end;
padding: 0 15px;
box-sizing: border-box;
.price {
flex: 1;
> span:last-child {
font-size: 18px;
color: var(--cp-price);
padding-left: 5px;
}
}
.van-button {
margin-left: 10px;
padding-left: 17px;
padding-right: 17px;
}
:deep(.van-button--default) {
background-color: var(--cp-bg);
color: var(--cp-text3);
}
}
.van-cell {
padding-left: 18px;
padding-right: 18px;
}
.detail-time {
position: fixed;
left: 0;
bottom: 65px;
width: 100%;
height: 44px;
background-color: #fff7eb;
text-align: center;
line-height: 44px;
font-size: 13px;
color: #f2994a;
.van-count-down {
display: inline;
color: #f2994a;
}
}
骨架屏
<div class="consult-detail-page" v-if="item">
// ...
</div>
<div class="consult-detail-page" v-else>
<cp-nav-bar title="问诊详情" />
<van-skeleton title :row="4" style="margin-top: 30px" />
<van-skeleton title :row="4" style="margin-top: 30px" />
</div>
请求数据
1、在 consult-detail 组件中,调用 getConsultOrderDetail() 请求数据
2、渲染页面
3、封装患病时长、就诊情况的文本映射hooks
4、导入hooks并使用
封装:consult-more
页面布局
1、使用 consult-more 组件
2、HTML
3、popover逻辑
禁用功能
1、在使用组件处传入disabled属性
2、在组件中接收disabled属性,并通过它决定气泡项是否禁用
删除订单/查看处方
1、在组件中向外暴露 on-delete 和 on-preview事件
2、在父组件中监听暴露的事件,实现删除订单、查看处方
倒计时
操作按钮
不同status,对应不同的按钮
封装-取消订单
1、封装hooks
2、在 consult-order-item 组件中使用hook
3、在 consult-detail 组件中使用hook
封装-删除订单
1、封装hooks
2、在 consult-order-item 组件中使用hook,并向外暴露 on-delete 事件
3、在 consult-detail 组件中使用hook,并跳转到 consult-order 页面
4、查看处方
复制订单号@
1、绑定点击事件 onCopy 到 复制 处
2、在 onCopy 中实现复制订单号到剪切板
封装-支付抽屉
1、封装 cp-pay-sheet 支付抽屉全局组件
2、在 consult-pay 组件中使用 cp-pay-sheet 组件,并传入属性
3、在 consult-detail 组件中使用 cp-pay-sheet 组件,并传入属性
Room
路由规则
页面布局
问诊室
<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-status>
<room-message></room-message>
<room-action></room-action>
</div>
</template>
状态栏
<template>
<div class="room-status">
<!-- 待接诊 -->
<div class="wait">已通知医生尽快接诊,24小时内医生未回复将自动退款</div>
<!-- 咨询中 -->
<div class="chat">
<span>咨询中</span>
<span>剩余时间:23:10:34</span>
</div>
<!-- 已结束 -->
<div class="end"><van-icon name="passed" />已结束</div>
</div>
</template>
操作栏
注意:操作栏默认处于禁用状态
<template>
<div class="room-action">
<van-field
type="text"
class="input"
:border="false"
placeholder="问医生"
autocomplete="off"
:disabled="true"
></van-field>
<van-uploader :preview-image="false" :disabled="true">
<cp-icon name="consult-img" />
</van-uploader>
</div>
</template>
消息卡片
<template>
<!-- 患者卡片 -->
<div class="msg msg-illness">
<div class="patient van-hairline--bottom">
<p>李富贵 男 31岁</p>
<p>一周内 | 未去医院就诊</p>
</div>
<van-row>
<van-col span="6">病情描述</van-col>
<van-col span="18">头痛、头晕、恶心</van-col>
<van-col span="6">图片</van-col>
<van-col span="18">点击查看</van-col>
</van-row>
</div>
<!-- 通知-通用 -->
<div class="msg msg-tip">
<div class="content">
<span>医护人员正在赶来,请耐心等候</span>
</div>
</div>
<!-- 通知-温馨提示 -->
<div class="msg msg-tip">
<div class="content">
<span class="green">温馨提示:</span>
<span>在线咨询不能代替面诊,医护人员建议仅供参考</span>
</div>
</div>
<!-- 通知-结束 -->
<div class="msg msg-tip msg-tip-cancel">
<div class="content">
<span>订单取消</span>
</div>
</div>
<!-- 发送文字 -->
<div class="msg msg-to">
<div class="content">
<div class="time">20:12</div>
<div class="pao">大夫你好?</div>
</div>
<van-image src="https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/popular_3.jpg" />
</div>
<!-- 发送图片 -->
<div class="msg msg-to">
<div class="content">
<div class="time">20:12</div>
<van-image
fit="contain"
src="https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/popular_3.jpg"
/>
</div>
<van-image src="https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/popular_3.jpg" />
</div>
<!-- 接收文字 -->
<div class="msg msg-from">
<van-image src="https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/popular_3.jpg" />
<div class="content">
<div class="time">20:12</div>
<div class="pao">哪里不舒服</div>
</div>
</div>
<!-- 接收图片 -->
<div class="msg msg-from">
<van-image src="https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/popular_3.jpg" />
<div class="content">
<div class="time">20:12</div>
<van-image
fit="contain"
src="https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/popular_3.jpg"
/>
</div>
</div>
<!-- 处方卡片 -->
<div class="msg msg-recipe">
<div class="content">
<div class="head van-hairline--bottom">
<div class="head-tit">
<h3>电子处方</h3>
<p>原始处方 <van-icon name="arrow"></van-icon></p>
</div>
<p>李富贵 男 31岁 血管性头痛</p>
<p>开方时间:2022-01-15 14:21:42</p>
</div>
<div class="body">
<div class="body-item" v-for="i in 2" :key="i">
<div class="durg">
<p>优赛明 维生素E乳</p>
<p>口服,每次1袋,每天3次,用药3天</p>
</div>
<div class="num">x1</div>
</div>
</div>
<div class="foot"><span>购买药品</span></div>
</div>
</div>
<!-- 评价卡片,后期实现 -->
</template>
<style lang="scss" scoped>
@import '@/styles/room.scss';
</style>
TS类型
1、枚举类型
// 消息类型
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
}
2、问诊室类型
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
}
WebSocket
接口
建立连接
1、安装socket.io-client
pnpm i socket.io-client
2、在 onMounted/onUnmounted 钩子中,建立/关闭socket连接
聊天记录
获取数据
在 onMounted 钩子中,监听 chatMsgList
,获取聊天记录。
渲染聊天记录
1、在room组件中,传入获取到的聊天记录数据,并遍历传递到room-message组件中
2、在room-message组件中,渲染聊天记录消息
2.1、抽取常量数据
2.2、根据常量数据转换聊天记录数据类型
2.3、渲染页面
3、预览图片
渲染通用通知
渲染温馨提示
接诊状态
TS类型
1、问诊订单状态枚举类型
export enum OrderType {
// 问诊订单
/** 待支付 */
ConsultPay = 1,
/** 待接诊 */
ConsultWait = 2,
/** 问诊中 */
ConsultChat = 3,
/** 问诊完成 */
ConsultComplete = 4,
/** 取消问诊 */
ConsultCancel = 5,
// 药品订单
/** 待支付 */
MedicinePay = 10,
/** 待发货 */
MedicineSend = 11,
/** 待收货 */
MedicineTake = 12,
/** 已完成 */
MedicineComplete = 13,
/** 取消订单 */
MedicineCancel = 14
}
2、问诊订单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
}
接口
URL:
/patient/consult/order/detail
类型:
GET
token:携带
参数:
ts{ orderId: string // 订单id }
返回数据:ConsultOrderItem
请求数据
1、在 services/room 中发送网络请求
2、在 room 组件的 onMounted 钩子中,调用请求方法,获取数据
3、在 room 组件的订单状态变化时,重新获取数据
控制组件
控制状态栏
1、传递订单详情数据给 room-status 组件
2、在 room-status 组件中接收传递的数据
3、根据传递的数据渲染不同的状态
控制操作栏
1、根据获取的订单详情数据,传递disabled值给 room-action 组件
2、在 room-action 组件中接收 disabled 属性
3、根据 disabled 属性,控制操作栏是否可用
文字聊天
发送文字
1、在 room-action 组件中,监听 enter 键点击事件,发送 send-text 事件到父组件
2、在 room 组件中,监听 send-text 事件,实现socket发送文字消息
渲染文字
1、在 room 组件中通过socket接收聊天消息
2、在 room-message 组件中,渲染接收的聊天消息
3、接收消息后,在nextTick()后,滚动页面到底部
4、修改时间格式
4.1、安装 dayjs 插件:pnpm i dayjs
4.2、定义格式化时间方法
4.3、调用方法
图片聊天
1、上传图片:在 room-action 组件中,绑定 sendImage() 方法到 after-read 属性上
2、在 sendImage() 方法中,调用 uploadImage() 方法发送网络请求,上传图片,并向外发射 send-image 事件
3、发送图片socket:在 room 组件中,绑定onSendImage() 方法到 send-image 事件
4、在 onSendImage() 方法中,通过socket发送图片消息
5、渲染消息:
聊天记录
默认显示最新消息
下拉刷新获取聊天记录
1、使用 van-pull-refresh 组件包裹 room-message,实现下拉刷新
2、在下拉刷新处理函数 onRefresh() 中,通过socket获取历史聊天记录
3、记录每一段消息中最早的消息时间,并在加载完毕后设置loading为false,并在没有聊天记录数据时提示
消息已读
接口-所有未读消息数量
URL:
/patient/message/unRead/all
类型:
GET
token:携带
参数:无
返回数据:
渲染数据
1、在 services/room.js 中发送网络请求
2、在 layout 组件的 onMounted 中调用请求方法,获取数据
3、渲染数据到 layout 组件的消息通知项中
实现消息已读
1、在room 组件的 onMounted 中,在第一次进入聊天室时,设置消息已读
2、在聊天时,每收到一条消息就将其设置为已读
查看处方
渲染处方卡片
接口-获取处方图片
URL:
/patient/consult/prescription/:id
类型:
GET
token:携带
参数:
id: string // 处方id
返回数据:
渲染数据
1、在 services/room.js 中发送网络请求,获取处方图片地址
2、在 room-message 组件中,绑定点击事件 onShowPrescription()
3、在onShowPrescription()中,实现预览处方图片
评价医生:evaluate-card
展示结束问诊消息
展示评价卡片
1、HTML
<template>
<!-- 已评价 -->
<div class="evaluate-card">
<p class="title">医生服务评价</p>
<p class="desc">我们会更加努力提升服务质量</p>
<van-rate
:modelValue="3"
size="7vw"
gutter="3vw"
color="#FADB14"
void-icon="star"
void-color="rgba(0,0,0,0.04)"
/>
</div>
<!-- 未评价 -->
<div class="evaluate-card">
<p class="title">感谢您的评价</p>
<p class="desc">本次在线问诊服务您还满意吗?</p>
<van-rate
size="7vw"
gutter="3vw"
color="#FADB14"
void-icon="star"
void-color="rgba(0,0,0,0.04)"
/>
<van-field
type="textarea"
maxlength="150"
show-word-limit
rows="3"
placeholder="请描述您对医生的评价或是在医生看诊过程中遇到的问题"
></van-field>
<div class="footer">
<van-checkbox>匿名评价</van-checkbox>
<van-button type="primary" size="small" round> 提交评价 </van-button>
</div>
</div>
</template>
2、样式
.evaluate-card {
width: 100%;
background-color: #fff;
border-radius: 8px;
overflow: hidden;
text-align: center;
padding: 15px;
.title {
font-size: 15px;
margin-bottom: 5px;
}
.desc {
font-size: 12px;
margin-bottom: 15px;
color: var(--cp-tip);
}
.van-field {
background-color: var(--cp-bg);
margin: 15px 0;
border-radius: 8px;
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
:deep() {
.van-checkbox {
.van-icon {
font-size: 12px;
}
&__label {
font-size: 12px;
color: var(--cp-tip);
}
height: 16px;
}
.van-button {
padding: 0 16px;
&.disabled {
opacity: 1;
background: #fafafa;
color: #d9dbde;
border: #fafafa;
}
}
}
}
}
渲染数据
1、使用评价卡片组件
2、在评价卡片组件中,接收传递的数据
3、根据 evaluateDoc 是否有数据,展示不同的内容
收集评价信息
1、定义评价数据
2、绑定数据到表单中
3、绑定提交按钮点击方法 onSubmit,并在其中提交评价信息
4、提交评价前校验
接口-评价医生
URL:
/patient/order/evaluate
类型:
POST
token:携带
参数:
ts{ docId: string // 评价的医生id orderId: string // 订单id score: number // 分数 content: string // 评价内容 anonymousFlag: 0 | 1 // 匿名标志,0:不是匿名,1:是匿名 }
返回数据:
提交评价
1、在 services/room.js 中,发送网络请求
2、docId
和orderId
需要由祖父组件room的consult传递
3、在后代组件 evaluate-card 中,使用 inject() 注入provide() 提供的响应式数据
4、在 evaluate-card 组件的onSubmit中,调用请求方法实现评价医生
修改父组件中的数据
1、在祖父组件 room 中,定义completeEva()方法找打并修改评价消息数据,并将其提供给子孙组件
2、在子孙组件 evaluate-card 中,注入祖父组件提供的completeEva()方法,并调用它
3、渲染已评价卡片
Order
页面布局
页面入口
1、在 room 组件的处方卡片上绑定 buy 处理方法
药品支付:order-medicine-pay
页面布局
1、HTML
<script setup lang="ts"></script>
<template>
<div class="order-pay-page">
<cp-nav-bar title="药品支付" />
<div class="order-address">
<p class="area">
<van-icon name="location" />
<span>北京市昌平区</span>
</p>
<p class="detail">建材城西路金燕龙办公楼999号</p>
<p>李富贵 13211112222</p>
</div>
<div class="order-medical">
<div class="head">
<h3>优医药房</h3>
<small>优医质保 假一赔十</small>
</div>
<div class="item van-hairline--top" v-for="i in 2" :key="i">
<img class="img" src="@/assets/ad.png" alt="" />
<div class="info">
<p class="name">
<span>优赛明 维生素E乳</span>
<span>x1</span>
</p>
<p class="size">
<van-tag>处方药</van-tag>
<span>80ml</span>
</p>
<p class="price">¥25.00</p>
</div>
<div class="desc">用法用量:口服,每次1袋,每天3次,用药3天</div>
</div>
</div>
<div class="order-detail">
<van-cell-group>
<van-cell title="药品金额" value="¥50" />
<van-cell title="运费" value="¥4" />
<van-cell title="优惠券" value="-¥0" />
<van-cell title="实付款" value="¥54" class="price" />
</van-cell-group>
</div>
<div class="order-tip">
<p class="tip">
由于药品的特殊性,如非错发、漏发药品的情况,药品一经发出
不得退换,请核对药品信息无误后下单。
</p>
<van-checkbox>我已同意<a href="javascript:;">支付协议</a></van-checkbox>
</div>
<van-submit-bar
:price="50 * 100"
button-text="立即支付"
button-type="primary"
text-align="left"
></van-submit-bar>
</div>
</template>
2、样式
:deep(.van-nav-bar) {
background-color: var(--cp-primary);
.van-nav-bar__arrow,
.van-nav-bar__title {
color: #fff;
}
}
:deep(.van-cell) {
.van-cell__title {
font-size: 16px;
}
.van-cell__value {
font-size: 16px;
}
&.price {
.van-cell__value {
font-size: 18px;
color: var(--cp-price);
}
}
}
:deep(.van-submit-bar) {
box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);
.van-button {
width: 200px;
}
}
.order-pay-page {
padding: 46px 0 65px;
}
.order-address {
padding: 15px 15px 0 15px;
background-color: #fff;
font-size: 13px;
.area {
color: var(--cp-tag);
margin-bottom: 5px;
.van-icon-location {
color: #ff7702;
font-size: 14px;
}
}
.detail {
font-size: 17px;
margin-bottom: 5px;
}
&::after {
content: '';
display: block;
height: 12px;
background-color: var(--cp-bg);
margin: 0 -15px;
margin-top: 15px;
}
}
.order-medical {
background-color: #fff;
padding: 0 15px;
.head {
display: flex;
height: 54px;
align-items: center;
> h3 {
font-size: 16px;
font-weight: normal;
}
> small {
font-size: 13px;
color: var(--cp-tag);
margin-left: 10px;
}
}
.item {
display: flex;
flex-wrap: wrap;
padding: 15px 0;
.img {
width: 80px;
height: 70px;
border-radius: 2px;
overflow: hidden;
}
.info {
padding-left: 15px;
width: 250px;
.name {
display: flex;
font-size: 15px;
margin-bottom: 5px;
> span:first-child {
width: 200px;
}
> span:last-child {
width: 50px;
text-align: right;
}
}
.size {
margin-bottom: 5px;
.van-tag {
background-color: var(--cp-primary);
vertical-align: middle;
}
span:not(.van-tag) {
margin-left: 10px;
color: var(--cp-tag);
vertical-align: middle;
}
}
.price {
font-size: 16px;
color: #eb5757;
}
}
.desc {
width: 100%;
background-color: var(--cp-bg);
border-radius: 4px;
margin-top: 10px;
padding: 4px 10px;
color: var(--cp-tip);
}
}
}
.order-tip {
padding: 0 15px;
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-top: 10px;
.tip {
font-size: 12px;
color: var(--cp-tag);
width: 100%;
&::before {
content: '*';
color: var(--cp-price);
font-size: 14px;
}
margin-bottom: 30px;
}
.van-checkbox {
a {
color: var(--cp-primary);
}
}
}
路由规则
渲染数据
接口-药品预支付
URL:
/patient/medicine/order/pre
类型:
GET
token:携带
参数:
ts{ prescriptionId: string // 处方id couponId?: string // 优惠券id useCoupon?: string // 是否使用优惠券,1:使用,0:不使用,默认:1 }
返回数据:
TS类型:
ts/** 药品预支付 */ export type OrderPre = { /** 处方ID */ id: string /** 优惠券ID */ couponId: string /** 积分抵扣 */ pointDeduction: number /** 优惠券抵扣 */ couponDeduction: number /** 应付款 */ payment: number /** 邮费 */ expressFee: number /** 实付款 */ actualPayment: number /** 药品订单 */ medicines: Medical[] }
接口-收货地址
URL:
/patient/order/address
类型:
GET
token:携带
参数:NULL
返回数据:
TS类型:
tsexport type AddressItem = { /** 地址ID */ id: string /** 联系方式 */ mobile: string /** 收件人 */ receiver: string /** 省 */ province: string /** 市 */ city: string /** 区 */ county: string /** 详细地址 */ addressDetail: string /** 是否默认地址,0 不是 1 是 */ isDefault: 0 | 1 }
网络请求
1、在 services/order.ts 中发送网络请求 getMedicalOrderPre(),此处只需要传入参数prescriptionId
2、在 services/order.ts 中发送网络请求 getAddressList()
组件渲染
1、在 order-medicine-pay 组件中调用网络请求方法获取 预支付信息 数据
2、在 order-medicine-pay 组件中调用网络请求方法获取 收货地址 数据
3、在 onMounted 钩子中调用
4、骨架屏
5、渲染获取的数据
完成支付
接口-生成药品订单ID
URL:
/patient/medicine/order
类型:
POST
token:携带
参数:
ts{ id: string // 处方ID couponId?: string // 优惠券ID addressId: string // 地址信息ID }
返回数据:
TS类型:
ts
网络请求
在services/order.ts 中发送网络请求 getMedicalOrderId()
生成药品订单ID
1、在 order-medicine-pay 组件中绑定立即支付处理函数 onSubmit
2、在 onSubmit 中调用网络请求方法实现支付
重构:cp-pay-sheet
1、重构 cp-pay-sheet 支付抽屉组件,向外暴露 payCallback 跳转地址
2、在 order-medicine-pay 组件中使用 cp-pay-sheet 支付抽屉组件
检测受影响位置@
如果想知道在组件中修改了props之后,哪些位置会受到影响,可以通过以下方法:
# 在命令行中输入:
pnpm type-check
支付结果:order-medicine-pay-result
页面布局
1、HTML
<template>
<div class="order-pay-result-page">
<cp-nav-bar title="药品支付结果" />
<!-- 支付失败 -->
<div class="result">
<van-icon name="clear" />
<p class="price">¥ 100.00</p>
<p class="status">支付失败</p>
<p class="tip">
订单支付失败,可以点击查看订单继续支付,如有疑问联系客服~
</p>
</div>
<!-- 支付成功 -->
<div class="result">
<van-icon name="checked" />
<p class="price">¥ 100.00</p>
<p class="status">支付成功</p>
<p class="tip">订单支付成功,已通知药房尽快发出, 请耐心等待~</p>
</div>
<div class="btn">
<van-button type="primary" :to="`/order/10000`">查看订单</van-button>
<van-button :to="`/room?orderId=10000`">返回诊室</van-button>
</div>
</div>
</template>
2、样式
.order-pay-result-page {
padding-top: 46px;
.result {
display: flex;
flex-direction: column;
align-items: center;
.van-icon {
font-size: 75px;
margin-top: 60px;
}
.van-icon-clear {
color: var(--cp-price);
}
.van-icon-checked {
color: var(--cp-primary);
}
.price {
font-size: 22px;
margin-top: 10px;
}
.status {
color: var(--cp-text3);
}
.tip {
color: var(--cp-tip);
width: 240px;
text-align: center;
margin-top: 20px;
}
}
.btn {
margin-top: 60px;
display: flex;
justify-content: center;
.van-button--primary {
margin-right: 20px;
}
}
}
路由规则
渲染数据
接口-药品订单信息
URL:
/patient/medicine/order/detail/${id}
类型:
GET
token:携带
参数:
ts{ id: string // 药品订单ID }
返回数据:
TS类型:
tstype Address = Omit<AddressItem, 'isDefault'> export type OrderDetail = { /** 药品订单ID */ id: string /** 药品订单编号 */ orderNo: string /** 订单类型 */ type: number /** 创建时间 */ createTime: string /** 处方ID */ prescriptionId: string /** 订单状态 */ status: OrderType /** 订单状态说明 */ statusValue: string // 10待支付,11待发货,12待收货,13已完成,14已取消 /** 药品清单 */ medicines: Medical[] /** 支付倒计时时间 */ countDown: number /** 收货地址 */ addressInfo: Address /** 物流信息 */ expressInfo: { /** 物流最新位置 */ content: string /** 物流最新时间 */ time: string } /** 支付时间 */ payTime: string /** 支付方式 */ paymentMethod?: 0 | 1 /** 支付金额 */ payment: number /** 积分抵扣 */ pointDeduction: number /** 优惠券抵扣 */ couponDeduction: number /** 邮费 */ expressFee: number /** 实付金额 */ actualPayment: number /** 问诊室ID */ roomId: string }
网络请求
在 services/order.ts 中发送网络请求
组件渲染
1、在 order-medicine-pay-result 组件中获取数据
2、渲染获取的数据
订单详情:order-medicine-detail
页面布局
1、HTML
<template>
<div class="order-detail-page">
<cp-nav-bar title="药品订单详情" />
<div class="order-head">
<!-- <div class="address">
<p class="area">
<van-icon name="location" />
<span>北京市昌平区</span>
</p>
<p class="detail">建材城西路金燕龙办公楼999号</p>
<p>李富贵 13211112222</p>
</div> -->
<div class="card">
<div class="logistics">
<p>【东莞市】您的包裹已由物流公司揽收</p>
<p>2019-07-14 17:42:12</p>
</div>
<van-icon name="arrow" />
</div>
</div>
<div class="order-medical">
<div class="head">
<h3>优医药房</h3>
<small>优医质保 假一赔十</small>
</div>
<div class="item van-hairline--top" v-for="i in 2" :key="i">
<img class="img" src="@/assets/ad.png" alt="" />
<div class="info">
<p class="name">
<span>优赛明 维生素E乳</span>
<span>x1</span>
</p>
<p class="size">
<van-tag>处方药</van-tag>
<span>80ml</span>
</p>
<p class="price">¥25.00</p>
</div>
<div class="desc">用法用量:口服,每次1袋,每天3次,用药3天</div>
</div>
</div>
<div class="order-detail">
<van-cell-group>
<van-cell title="药品金额" value="¥50.00" />
<van-cell title="运费" value="¥4.00" />
<van-cell title="优惠券" value="-¥0.00" />
<van-cell title="实付款" value="¥54.00" class="price" />
<van-cell title="订单编号" value="202201127465" />
<van-cell title="创建时间" value="2022-01-23 09:23:46" />
<van-cell title="支付时间" value="2022-01-23 09:23:46" />
<van-cell title="支付方式" value="支付宝支付" />
</van-cell-group>
</div>
<!-- 已取消 -->
<!-- <van-action-bar>
<van-action-bar-icon icon="delete-o" text="删除" />
<van-action-bar-button type="primary" text="沟通记录" />
</van-action-bar> -->
<!-- 待收货 -->
<van-action-bar>
<van-action-bar-button type="primary" text="确认收货" />
</van-action-bar>
<!-- 待发货 -->
<!-- <van-action-bar>
<van-action-bar-button type="primary" text="提醒发货" />
</van-action-bar> -->
<!-- 待支付 -->
<!-- <van-action-bar>
<p class="price">需要支付:<span>¥60</span></p>
<van-action-bar-button color="#bbb" text="取消订单" />
<van-action-bar-button type="primary" text="继续支付" />
</van-action-bar> -->
<!-- 已完成 -->
<!-- <van-action-bar>
<van-action-bar-icon icon="delete-o" text="删除" />
<van-action-bar-button type="primary" text="再次购买" />
</van-action-bar> -->
</div>
</template>
2、样式
.order-detail-page {
padding-top: 46px;
padding-bottom: 65px;
}
.address {
padding: 15px;
background-color: #fff;
font-size: 13px;
position: relative;
box-shadow: 0px 0px 22px 0px rgba(229, 229, 229, 0.5);
border-radius: 8px;
.area {
color: var(--cp-tag);
margin-bottom: 5px;
.van-icon-location {
color: #ff7702;
font-size: 14px;
}
}
.detail {
font-size: 17px;
margin-bottom: 5px;
}
}
.order-head {
position: relative;
padding: 15px;
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 80px;
background: linear-gradient(180deg, rgba(44, 181, 165, 0), rgba(44, 181, 165, 0.2));
border-bottom-left-radius: 150px 20px;
border-bottom-right-radius: 150px 20px;
}
.card {
height: 74px;
background-color: #fff;
border-radius: 8px;
position: relative;
display: flex;
align-items: center;
padding: 0 15px;
box-shadow: 0px 0px 22px 0px rgba(229, 229, 229, 0.5);
.logistics {
flex: 1;
p {
&:first-child {
color: var(--cp-primary);
}
&:last-child {
color: var(--cp-tag);
font-size: 13px;
margin-top: 5px;
}
}
}
.van-icon {
color: var(--cp-tip);
}
}
}
:deep(.van-cell) {
.van-cell__title {
font-size: 14px;
flex: none;
width: 100px;
}
.van-cell__value {
font-size: 14px;
}
&.price {
.van-cell__value {
font-size: 18px;
color: var(--cp-price);
}
}
}
.order-medical {
background-color: #fff;
padding: 0 15px;
.head {
display: flex;
height: 54px;
align-items: center;
> h3 {
font-size: 16px;
font-weight: normal;
}
> small {
font-size: 13px;
color: var(--cp-tag);
margin-left: 10px;
}
}
.item {
display: flex;
flex-wrap: wrap;
padding: 15px 0;
.img {
width: 80px;
height: 70px;
border-radius: 2px;
overflow: hidden;
}
.info {
padding-left: 15px;
width: 250px;
.name {
display: flex;
font-size: 15px;
margin-bottom: 5px;
> span:first-child {
width: 200px;
}
> span:last-child {
width: 50px;
text-align: right;
}
}
.size {
margin-bottom: 5px;
.van-tag {
background-color: var(--cp-primary);
vertical-align: middle;
}
span:not(.van-tag) {
margin-left: 10px;
color: var(--cp-tag);
vertical-align: middle;
}
}
.price {
font-size: 16px;
color: #eb5757;
}
}
.desc {
width: 100%;
background-color: var(--cp-bg);
border-radius: 4px;
margin-top: 10px;
padding: 4px 10px;
color: var(--cp-tip);
}
}
}
.van-action-bar {
padding: 0 10px;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);
.price {
padding: 0 10px;
> span {
font-size: 18px;
color: var(--cp-price);
}
}
}
路由规则
渲染数据
接口-药品订单信息
网络请求
1、封装:获取订单详情的网络请求到hook中
2、在 order-medicine-pay-result 组件中使用 useOrderDetail() 钩子方法
药品清单:order-medicine
1、页面布局
2、在 order-medicine-pay 页面使用组件
组件渲染
1、在 order-medicine-detail 组件中调用 useOrderDetail() 钩子方法获取数据
2、渲染获取的数据
物流详情:order-logistics
页面布局
1、HTML
<template>
<div class="order-logistics-page">
<!-- 地图 -->
<div id="map">
<div class="title">
<van-icon name="arrow-left" @click="$router.back()" />
<span>配送中</span>
<van-icon name="service" />
</div>
<div class="current">
<p class="status">订单派送中 预计明天送达</p>
<p class="predict">
<span>申通快递</span>
<span>7511266366963366</span>
</p>
</div>
</div>
<!-- 物流详情 -->
<div class="logistics">
<p class="title">物流详情</p>
<van-steps direction="vertical" :active="0">
<van-step>
<p class="status">派送中</p>
<p class="content">您的订单正在派送中【深圳市】科技园派送员宋平正在为您派件</p>
<p class="time">今天天 17:25</p>
</van-step>
<van-step v-for="i in 5" :key="i">
<p class="status">运输中</p>
<p class="content">在广东深圳公司进行发出扫描</p>
<p class="time">昨天 10:25</p>
</van-step>
<van-step>
<p class="status">已发货</p>
<p class="content">卖家已发货</p>
<p class="time">2022-08-20 10:25</p>
</van-step>
</van-steps>
</div>
</div>
</template>
2、样式
.order-logistics-page {
--van-step-icon-size: 18px;
--van-step-circle-size: 10px;
}
#map {
height: 450px;
background-color: var(--cp-bg);
overflow: hidden;
position: relative;
.title {
background-color: #fff;
height: 46px;
width: 355px;
border-radius: 4px;
display: flex;
align-items: center;
padding: 0 15px;
font-size: 16px;
position: absolute;
left: 10px;
top: 10px;
box-sizing: border-box;
box-shadow: 0px 0px 22px 0px rgba(229, 229, 229, 0.5);
z-index: 999;
> span {
flex: 1;
text-align: center;
}
.van-icon {
font-size: 18px;
color: #666;
&:last-child {
color: var(--cp-primary);
}
}
}
.current {
height: 80px;
border-radius: 4px;
background-color: #fff;
height: 80px;
width: 355px;
box-sizing: border-box;
padding: 15px;
position: absolute;
left: 10px;
bottom: 10px;
box-shadow: 0px 0px 22px 0px rgba(229, 229, 229, 0.5);
z-index: 999;
.status {
font-size: 15px;
line-height: 26px;
}
.predict {
color: var(--cp-tip);
font-size: 13px;
margin-top: 5px;
> span {
padding-right: 10px;
}
}
}
}
.logistics {
padding: 0 10px 20px;
.title {
font-size: 16px;
padding: 15px 5px 5px;
}
.van-steps {
:deep(.van-step) {
&::after {
display: none;
}
}
.status {
font-size: 15px;
color: var(--cp-text3);
margin-bottom: 4px;
}
.content {
font-size: 13px;
color: var(--cp-tip);
margin-bottom: 4px;
}
.time {
font-size: 13px;
color: var(--cp-tag);
}
}
}
路由规则
渲染数据
接口-物流信息
URL:
/pa }/logistics
类型:
GET
token:携带
参数:
ts{ id: string // 订单ID }
返回数据:
TS类型:
tsexport enum ExpressStatus { /** 已发货 */ Delivered = 1, /** 已揽件 */ Received = 2, /** 运输中 */ Transit = 3, /** 派送中 */ Delivery = 4, /** 已签收 */ Signed = 5 }
tsexport type Express = { /** 物流信息ID */ id: string /** 物流内容 */ content: string /** 创建时间 */ createTime: string /** 物流状态 */ status: ExpressStatus /** 状态文章 */ statusValue: string } export type Location = { /** 经度 */ longitude: string /** 纬度 */ latitude: string } export type Logistics = { /** 预计送达时间 */ estimatedTime: string /** 物流公司名称 */ name: string /** 物流编号 */ awbNo: string /** 最新物流状态 */ status: ExpressStatus /** 最新物流状态文字 */ statusValue: string /** 物流信息数组 */ list: Express[] /** 轨迹信息数组 */ logisticsInfo: Location[] /** 当前运输位置 */ currentLocationInfo: Location }
网络请求
在 services/order.ts 中发送网络请求
组件渲染
1、在 order-logistics 组件中调用网络请求方法获取数据
2、渲染获取的数据
集成高德地图
开发文档
参考手册
注册并创建key
1、注册高度地图开放平台账号,并认证成为开发者
2、进入 控制台 -> 应用管理 -> 我的应用 -> 创建新应用
3、进入新建的应用 在线问诊 -> 添加Key
初始化
结合Vue
1、安装loader @amap/amap-jsapi-loader
pnpm add @amap/amap-jsapi-loader
2、在使用高德地图的组件中,配置安全密钥 securityJsCode
注意:2021年12月02日以后申请的key需要配合安全密钥一起使用。
window._AMapSecurityConfig = {
securityJsCode: '你申请的安全密钥'
}
3、扩展 Window 的类型
// types/global.d.ts
interface Window {
_AMapSecurityConfig: {
securityJsCode: string
}
}
4、在使用高德地图的组件中,初始化时加载高德地图需要的资源
import AMapLoader from '@amap/amap-jsapi-loader'
onMounted(async () => {
// ... 省略 ...
AMapLoader.load({
key: '你申请的key',
version: '2.0'
}).then((AMap) => {
// 使用 Amap 初始化地图
})
})
5、初始化地图
const map = new AMap.Map('container', {
mapStyle: 'amap://styles/whitesmoke', // 初始化地图主题样式
zoom: 11, // 初始化地图缩放级别
center: [116.397428, 39.90923], // 初始化地图中心点位置
})
插件
JS API 提供了众多的插件,需要引入之后才能使用这些插件的功能。需要通过插件使用的功能有:
服务类 | 地图控件 | 矢量图形编辑工具 | 工具类 |
---|---|---|---|
PlaceSearch POI 搜索 | ToolBar 缩放工具条 | PolylineEditor 折线编辑器 | MouseTool 鼠标绘制 |
AutoComplete 输入提示 | Scale 比例尺 | PolygonEditor 多边形编辑器 | RangingTool 测距 |
Driving/Transfer/Riding/Truck 路线规划 | ControlBar 控制罗盘 | RectangleEditor 矩形编辑器 | |
Geocoder 地理编码 | Geolocation 定位控件 | CircleEditor 圆形编辑器 | |
LineSearch 公交线路 | HawkEye 鹰眼控件 | EllipseEditor 椭圆编辑器 | |
StationSearch 公交站点 | MapType 图层切换控件 | BezierCurveEditor 贝塞尔曲线编辑器 |
1、初始化地图
2、引入插件
//异步加载工具条插件
AMap.plugin("AMap.Driving", function () {
//在回调函数中实例化插件,并使用插件功能
});
物流轨迹
基本使用
1、异步加载 AMap.Driving 插件,并在回调中 new AMap.Driving() 返回drving实例
2、经纬度可以有如下写法:
3、关闭交通拥堵情况
4、实际案例中画出物流轨迹
5、实际案例中画出途径点
自定义图标
1、关闭默认图标
2、通过 new AMap.Marker() 自定义图标
当前位置
最佳缩放视野
API
Map【
- new AMap.Map():
(div,opts)
,
LngLat【
- new AMap.LngLat():
(lng,lat,noWrap?)
,经纬度坐标。
Driving【
- new AMap.Driving():
(opts)
, - driving.search():
(origin,destination,opts?,callback?)
,
Marker【
- new AMap.Marker():
()
,
项目部署
项目打包
1、执行命令打包项目
pnpm build
2、