Skip to content Skip to footer

开药门诊模块#

开药门诊模块 #开药门诊-需求分析 #理解开药门诊阶段流程分析

开药门诊-表单页面路由搭建 #步骤

准备基础结构代码路由配置首页跳转cv静态结构基础结构代码views/Consult/ConsultMedicine.vue

vue

12345678910路由配置router/index.ts

ts{

path: '/consult/medicine',

component: () => import('@/views/Consult/ConsultMedicine.vue'),

meta: { title: '开药门诊' }

}

12345首页跳转Home/index.vue

vue

to="/consult/medicine"

class="nav"

@click="store.setType(ConsultType.Medication)"

>

开药门诊

线上买药更方便

123456789静态结构ConsultMedicine.vue

vue

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778开药门诊-表单单选框处理 #需求:

完成表单双向数据绑定

步骤:

定义枚举定义选项options开药门诊页面中使用cp-radio-btn扩展Consult类型添加字段,store中添加方法保存字段双向数据绑定定义枚举enums/index.ts

ts// 肝功能

export enum LiverFunction {

/** 正常 */

Normal,

/** 异常 */

Abnormal,

/** 不清楚 */

Unclear

}

// 肾功能

export enum RenalFunction {

/** 正常 */

Normal,

/** 异常 */

Abnormal,

/** 不清楚 */

Unclear

}

// 过敏史

export enum AllergicHistory {

/** 正常 */

Normal,

/** 异常 */

Abnormal,

/** 不清楚 */

Unclear

}

// 生育状态及计划

export enum FertilityStatus {

/** 无 */

No,

/** 备孕中 */

TryingToConceive,

/** 已怀孕 */

AlreadyPregnant,

/** 哺乳期 */

Breastfeeding

}

1234567891011121314151617181920212223242526272829303132333435363738定义options选项services/constant

ts// 肝功能

export const liverFunctionOptions = [

{ label: '正常', value: LiverFunction.Normal },

{ label: '异常', value: LiverFunction.Abnormal },

{ label: '不清楚', value: LiverFunction.Unclear }

]

// 肾功能

export const renalFunctionOptions = [

{ label: '正常', value: RenalFunction.Normal },

{ label: '异常', value: RenalFunction.Abnormal },

{ label: '不清楚', value: RenalFunction.Unclear }

]

// 过敏史

export const allergicHistoryOptions = [

{ label: '正常', value: AllergicHistory.Normal },

{ label: '异常', value: AllergicHistory.Abnormal },

{ label: '不清楚', value: AllergicHistory.Unclear }

]

// 生育状态及计划

export const fertilityStatusOptions = [

{ label: '无', value: FertilityStatus.No },

{ label: '备孕中', value: FertilityStatus.TryingToConceive },

{ label: '已怀孕', value: FertilityStatus.AlreadyPregnant },

{ label: '哺乳期中', value: FertilityStatus.Breastfeeding }

]

1234567891011121314151617181920212223242526使用cp-radio-btn在开药门诊页面中ConsultMedicine.vue

tsimport {

liverFunctionOptions,

allergicHistoryOptions,

fertilityStatusOptions,

renalFunctionOptions

} from '@/services/constants'

123456vue

肝功能

肾功能

过敏史

生育状态及计划

12345678910111213141516扩展Consult类型添加字段,store中添加方法保存字段

consult.d.ts

tsimport type {

ConsultType,

IllnessTime,

OrderType,

LiverFunction,

RenalFunction,

AllergicHistory,

FertilityStatus

} from '@/enums'

...

export type Consult = {

...

/** 肝功能 */

liverFunction: LiverFunction

/** 肾功能 */

renalFunction: RenalFunction

/** 过敏史 */

allergicHistory: AllergicHistory

/** 生育状态及计划 */

fertilityStatus: FertilityStatus

}

export type MedicineIllness = Pick<

PartialConsult,

| 'illnessDesc'

| 'liverFunction'

| 'renalFunction'

| 'allergicHistory'

| 'fertilityStatus'

| 'pictures'

>

123456789101112131415161718192021222324252627282930313233src/stores/modules/consult.ts

tsimport type { ConsultIllness, PartialConsult, MedicineIllness } from '@/types/consult'

...

// 记录问药门诊病情

const setMedicineIlness = (illness: MedicineIllness) => {

consult.value.illnessDesc = illness.illnessDesc

consult.value.liverFunction = illness.liverFunction

consult.value.renalFunction = illness.renalFunction

consult.value.allergicHistory = illness.allergicHistory

consult.value.fertilityStatus = illness.fertilityStatus

consult.value.pictures = illness.pictures

}

return {

...,

setMedicineIlness

}

1234567891011121314151617双向数据绑定ConsultMedicine.vue

tsimport type { MedicineIllness } from '@/types/consult'

import { ref } from 'vue'

const form = ref({

illnessDesc: '',

liverFunction: undefined,

renalFunction: undefined,

allergicHistory: undefined,

fertilityStatus: undefined,

pictures: []

})

12345678910vue

症状描述

type="textarea"

rows="3"

placeholder="请输入所患疾病名称"

v-model="form.illnessDesc"

>

用药人身体情况

肝功能

:options="liverFunctionOptions"

v-model="form.liverFunction"

>

肾功能

:options="renalFunctionOptions"

v-model="form.renalFunction"

>

过敏史

:options="allergicHistoryOptions"

v-model="form.allergicHistory"

>

生育状态及计划

:options="fertilityStatusOptions"

v-model="form.fertilityStatus"

>

补充病例信息

12345678910111213141516171819202122232425262728293031323334353637383940414243开药门诊-封装上传组件 #极速问诊中图文问诊用到了上传图片功能,在开药门诊中我们也有上传图片功能,所以需要封装上传组件

步骤:

封装组件CpUpload为CpUpload组件提供类型ConsultIllness使用上传组件ConsultMedicine使用上传组件封装组件components/CpUpload.vue

vue

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101为CpUpload组件提供类型src/types/components.d.ts

ts...

import CpUpload from '@/components/CpUpload.vue'

declare module 'vue' {

interface GlobalComponents {

// 添加组件类型

...

CpUpload: typeof CpUpload

}

}

1234567891011ConsultIllness.vue使用上传组件

ts// 数据的回显

onMounted(() => {

if (store.consult.illnessDesc) {

showConfirmDialog({

title: '温馨提示',

message: '是否恢复之前填写的病情信息?',

closeOnPopstate: false

}).then(() => {

// 回显数据

const { illnessDesc, illnessTime, consultFlag, pictures } = store.consult

form.value = { illnessDesc, illnessTime, consultFlag, pictures }

cpUploadRef.value.setFileList(pictures || [])

})

}

})

const cpUploadRef = ref()

const onUploadSuccess = (image: Image) => {

form.value.pictures?.push(image)

}

const onDeleteSuccess = (item: UploaderFileListItem) => {

form.value.pictures = form.value.pictures?.filter(

(pic) => pic.url !== item.url

)

}

12345678910111213141516171819202122232425vue

ref="cpUploadRef"

@upload-success="onUploadSuccess"

@delete-success="onDeleteSuccess"

>

12345ConsultMedicine.vue使用上传组件

tsconst cpUploadRef = ref()

const onUploadSuccess = (image: Image) => {

form.value.pictures?.push(image)

}

const onDeleteSuccess = (item: UploaderFileListItem) => {

form.value.pictures = form.value.pictures?.filter(

(pic) => pic.url !== item.url

)

}

123456789vue

ref="cpUploadRef"

@upload-success="onUploadSuccess"

@delete-success="onDeleteSuccess"

>

12345开药门诊-表单页面下一步功能 #ConsultMedicine

步骤:

控制按钮样式校验逻辑下一步跳转逻辑控制按钮样式

tsconst disabled = computed(

() =>

!form.value.illnessDesc ||

form.value.liverFunction === undefined ||

form.value.renalFunction === undefined ||

form.value.allergicHistory === undefined ||

form.value.fertilityStatus === undefined

)

12345678vue

:class="{ disabled }"

type="primary"

block

round

@click="next"

>

下一步

123456789校验逻辑

tsconst next = () => {

if (!form.value.illnessDesc) return showToast('请输入病情描述')

if (form.value.liverFunction === undefined)

return showToast('请选择肝功能情况')

if (form.value.renalFunction === undefined)

return showToast('请选择肾功能情况')

if (form.value.allergicHistory === undefined)

return showToast('请选择过敏史情况')

if (form.value.fertilityStatus === undefined)

return showToast('请选择生育状态及计划')

}

123456789101112下一步跳转逻辑

tsconst store = useConsultStore()

const router = useRouter()

const next = () => {

....

// 记录病情

store.setMedicineIlness(form.value)

// 跳转,携带标识

router.push('/user/patient?isChange=1&from=medicineConsult')

}

123456789开药门诊-选择患者页面下一步功能 #步骤:

开药门诊选择患者下一步跳转选择药品,根据标识跳转新建页面结构路由规则配置开药门诊选择患者下一步跳转选择药品,根据标识跳转views/User/PatientPage.vue

tsconst fromMedicineConsultFlag = computed(

() => route.query.from === 'medicineConsult'

)

const next = () => {

if (!patientId.value) return showToast('请选择患者')

store.setPatient(patientId.value)

if (fromMedicineConsultFlag.value) {

router.push('/consult/choose')

} else {

router.push('/consult/pay')

}

}

123456789101112新建页面结构ConsultChoose.vue

vue

12345678910路由规则配置router/index.ts

ts{

path: '/consult/choose',

component: () => import('@/views/Consult/ConsultChoose.vue'),

meta: { title: '选择药品' }

}

12345选择药品-页面结构 #步骤:

静态结构步进器组件学习及样式处理静态结构ConsultChoose.vue

vue

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127步进器组件学习及样式处理

tsconst step = ref(0)

1vue

v-model="step"

min="0"

:class="{ hide: step === 0 }"

/>

12345scss.van-stepper {

position: absolute;

right: 0;

bottom: 15px;

:deep() {

.van-stepper__input {

background: none;

}

.van-stepper__minus {

background-color: #fff;

border: 0.5px solid #16c2a3;

}

.van-stepper__plus {

background-color: #eaf8f6;

}

.van-stepper__minus,

.van-stepper__plus {

width: 20px;

height: 20px;

}

}

&.hide {

:deep() {

.van-stepper__minus,

.van-stepper__input {

visibility: hidden;

}

}

}

}

123456789101112131415161718192021222324252627282930选择药品-抽离列表卡片组件 #步骤:

抽离卡片组件抽离列表组件使用列表组件抽离卡片组件views/Consult/components/MedicineCard.vue

vue

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111抽离列表组件src/views/Consult/components/MedicineList.vue

vue

1234567891011121314151617使用列表组件ConsultChoose.vue

vueimport MedicineList from './components/MedicineList.vue'

...

...

12345选择药品-list组件渲染药品列表 #步骤:

van-list初步使用封装并调用请求渲染药品卡片van-list初步使用MedicineList.vue

vue

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647封装并调用请求

consult.d.ts编写类型,提取BasePage泛型别名

tsimport type { Medical } from './room'

// 药品列表查询参数

export type MedicineParams = PageParams & {

keyword: string

}

export type BasePage = {

pageTotal: number

total: number

rows: T

}

// 医生列表

export type DoctorList = Doctor[]

// 医生分页

export type DoctorPage = BasePage

// 文章列表

export type KnowledgeList = Knowledge[]

// 文章列表带分页

export type KnowledgePage = BasePage

// 药品列表

export type MedicineList = Medical[]

// 药品列表带分页

export type MedicinePage = BasePage

12345678910111213141516171819202122232425262728293031封装接口service/consult.ts

tsexport const getMedicinePage = (params: MedicineParams) => {

return request('patient/medicine', 'GET', params)

}

123调用接口MedicineList.vue

vue

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748渲染药品卡片

父传子MedicineList.vue

vue

v-for="item in list"

:key="item.id"

:item="item"

>

12345子组件接收数据并渲染MedicineCard

vue

123456789101112131415161718192021222324252627282930选择药品-搜索功能 #步骤:

搜索框双向数据绑定字段searchValue,keyword查询参数字段,回车后赋值该字段父传子监听字段,渲染页面ConsultChoose搜索框双向数据绑定字段searchValue,keyword查询参数字段,回车后赋值该字段

tsconst searchValue = ref('')

const keyword = ref('')

const onSearch = () => {

keyword.value = searchValue.value

}

const onCancel = () => {

keyword.value = ''

}

12345678父传子ConsultChoose

vue

12监听字段,重新渲染页面ConsultList

vueconst props = defineProps<{

keyword: string

}>()

const params = ref({

keyword: props.keyword || '',

pageSize: 10,

current: 1

})

watch(

() => props.keyword,

(val) => {

list.value = []

params.value.keyword = val

params.value.current = 1

onLoad()

}

)

12345678910111213141516171819选择药品-购物车底部操作栏 #步骤:

Consult类型新增medicines字段store新增方法,存medicines字段处理药品增加数量减少数量逻辑计算属性处理数量和总金额Consult类型新增medicines字段

consult.d.ts

tsexport type Consult = {

...

/** 药品 */

medicines: Medical[]

}

12345store新增方法,存medicines字段

tsconst setMedicines = (val: Medical[]) => (consult.value.medicines = val)

return {

...

setMedicines

...

}

123456处理药品增加数量减少数量逻辑 MedicineCard

vue

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465计算属性处理数量和总金额 ConsultChoose

vue

12345678910111213141516171819202122232425262728293031323334353637383940选择药品-购物车抽屉 #步骤:

抽屉结构样式打开抽屉,清空药品方法定义药品列表渲染实现增加减少清空药品功能抽屉结构样式ConsultChoose.vue

vue

药品清单共{{ cartLength }}件商品

清空

icon="cart-o"

:color="cartLength > 0 ? '#323233' : '#eee'"

:badge="cartLength === 0 ? '' : cartLength"

/>

¥ {{ totalPrice }}

12345678910111213141516171819202122scss .content {

--content-height: 400px;

--content-header-height: 25px;

padding: 16px;

height: var(--content-height);

.content-header {

position: sticky;

top: 0px;

z-index: 10;

background-color: #fff;

display: flex;

justify-content: space-between;

align-items: center;

height: var(--content-header-height);

padding-bottom: 10px;

&-left {

span {

font-size: 16px;

color: #000000;

margin-right: 10px;

}

span + span {

font-size: 13px;

color: var(--cp-primary);

}

}

&-right {

span {

margin-left: 5px;

}

}

}

.medicine-list {

padding-bottom: 45px;

}

}

123456789101112131415161718192021222324252627282930313233343536打开抽屉,清空药品方法定义

tsconst show = ref(false)

// 底部操作栏注册事件,注意不是抽屉内容的底部操作栏

const openCart = () => {

if (cartLength.value === 0) return showToast('请选择药品')

show.value = true

}

const clear = () => {

// console.log('clear')

show.value = false

}

1234567891011药品列表渲染

vueimport MedicineCard from './components/MedicineCard.vue'

v-for="item in consultStore.consult.medicines"

:key="item.id"

:item="item"

>

12345678910实现增加减少清空药品功能

增加减少药品功能 MedicineCard.vue

vuewatch(

() => consultStore.consult.medicines?.find((item) => item.id === props.item.id),

(val) => {

if (val) {

step.value = +val.quantity

} else {

step.value = 0

}

},

{ deep: true, immediate: true }

)

1234567891011清空购物车ConsultChoose

tsconst clear = () => {

// console.log('clear')

consultStore.setMedicines([])

show.value = false

}

12345药品详情-跳转详情页 #步骤:

基本结构,路由配置跳转方法调用静态结构基本结构ConsultMedicineDetail

vue

12345678910路由配置router/index.ts

ts{

path: '/medicineDetail/:id',

component: () => import('@/views/Consult/ConsultMedicineDetail.vue'),

meta: { title: '药品详情' }

},

12345跳转方法调用MedicineCard.vue

vue

{{ item.name }}

:name="item.id"

v-model="step"

min="0"

:class="{ hide: step === 0 }"

@change="onChange"

@click.stop

/>

处方药

{{ item.specs }}

¥{{ item.amount }}

1234567891011121314151617181920212223静态结构ConsultMedicineDetail.vue

vue

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146药品详情-渲染页面 #步骤:

定义类型封装接口调用请求渲染定义类型consult.d.ts

tsexport type MedicineDetail = Medical & {

/** 品牌 */

brand: string

brandId?: any

/** 生产企业 */

manufacturer: string

/** 批准文号 */

approvalNo: string

/** 有效期 */

expiration: string

classify: string

classifyId: string

/** 功能主治 */

indicationsFunction: string

/** 禁忌 */

contraindication: string

/** 不良反应 */

untowardReaction: string

/** 注意事项 */

preparation: string

/** 执行标准 */

standard: string

/** 药品图片 */

mainPictures: string[]

creator: string

updator: string

createTime: string

updateTime: string

deleteState: number

}

12345678910111213141516171819202122232425262728293031封装接口services/consult.ts

tsexport const getMedicineDetail = (id: string) => {

return request(`patient/medicine/${id}`)

}

123调用请求渲染ConsultMedicineDetail.vue

vue

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889药品详情-底部操作栏功能 #列表页和详情页都有底部操作栏,所以我们可以抽离组件

步骤:

封装底部操作栏组件区分加入药箱逻辑加入药箱逻辑实现申请开单跳转问诊室封装底部操作栏组件MedicineAction.vue

vue

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121ConsultMedicineDetail使用底部操作栏

vue

1区分加入药箱逻辑MedicineAction

ts

123456789101112131415161718192021底部操作栏按钮区分,注意抽屉内容都是申请开方

vue

v-if="from === 'list'"

type="primary"

text="申请开方"

@click="onAskDocotor"

/>

v-else

type="primary"

text="加入药箱"

@click="onAddToCart"

>

123456789101112加入药箱逻辑实现,子传父通知父组件处理数据

MedicineAction

tsconst emits = defineEmits<{

(e: 'addToCart'): void

}>()

const onAddToCart = () => {

emits('addToCart')

}

1234567ConsultMedicineDetail

ts// ConsultMedicineDetail

const consultStore = useConsultStore()

const onAddToCart = () => {

const medicines = consultStore.consult.medicines || []

const medicine = medicines?.find((item) => item.id === detail.value?.id)

if (medicine) {

medicine.quantity += 1

} else {

medicines.push({

amount: detail.value?.amount!,

avatar: detail.value?.avatar!,

id: detail.value?.id!,

name: detail.value?.name!,

prescriptionFlag: detail.value?.prescriptionFlag!,

specs: detail.value?.specs!,

usageDosag: detail.value?.usageDosag!,

quantity: '1'

})

}

consultStore.setMedicines(medicines)

}

123456789101112131415161718192021申请开单跳转问诊室,需要先生成订单id,订单id的参数,我们都存在pinia中,由于不同主线需要的参数不同,所以,我们要区分下

utils/createOrderParams

ts// utils/createOrderParams

import { ConsultType } from '@/enums'

import type { PartialConsult } from '@/types/consult'

type Key = keyof PartialConsult

// 三条线都有的字段

export const commonKeys: Key[] = [

'type', // 问医生,极速问诊,开药门诊

'illnessDesc', // 病情描述

'patientId', // 患者Id

'pictures' // 图片

]

// 极速问诊字段

export const fastKeys: Key[] = [

...commonKeys,

'depId', // 科室

'illnessTime', // 患病时长

'consultFlag' // 是否就诊过

]

// 问医生字段,比极速问诊多一个docId 医生Id

// export const doctorKeys: Key[] = [...fastKeys, 'docId']

// 开药门诊字段

export const medicineKeys: Key[] = [

...commonKeys,

'allergicHistory', // 过敏史

'fertilityStatus', // 生育状态及计划

'liverFunction', // 肝功能

'renalFunction', // 肾功能

'medicines' // 药品

]

export const getCreateOrderParams = (

consult: PartialConsult,

type: ConsultType = ConsultType.Fast

) => {

const params: Record = {}

switch (type) {

case ConsultType.Doctor:

// for (const key of doctorKeys) {

// params[key] = consult[key]

// }

break

case ConsultType.Fast:

for (const key of fastKeys) {

params[key] = consult[key]

}

break

case ConsultType.Medication:

for (const key of medicineKeys) {

params[key] = consult[key]

}

break

}

return params

}

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960此时再处理申请开单逻辑 MedicineAction

ts// MedicineAction

const onAskDocotor = async () => {

const medicines = consultStore.consult.medicines || []

if (medicines?.length === 0) return showToast('请先选药')

const params = getCreateOrderParams(

consultStore.consult,

ConsultType.Medication

)

// console.log(params)

try {

const { data } = await createConsultOrder(params)

router.push(`/room?orderId=${data.id}&from=medicine`)

} catch (e) {

return showDialog({

title: '温馨提示',

message: '问诊信息不完整请重新填写',

closeOnPopstate: false

}).then(() => {

router.push('/')

})

} finally {

consultStore.clear()

}

}

123456789101112131415161718192021222324问诊室-开药门诊信息渲染 #步骤:

渲染开药门诊信息数据转中文渲染开药门诊信息 RoomMessage

ts// RoomMessage

const route = useRoute()

const fromPage = computed(() => route.query.from)

123vue

肝功能 {{ item.msg.consultRecord.liverFunction }} | 肾功能

{{ item.msg.consultRecord.renalFunction }} | 过敏史

{{ item.msg.consultRecord.allergicHistory }} | 生育状态

{{ item.msg.consultRecord.fertilityStatus }}

{{ getIllnessTimeText(item.msg.consultRecord?.illnessTime) }} |

{{ getConsultFlagText(item.msg.consultRecord?.consultFlag) }}

12345678910vue用药需求

{{

item.msg.consultRecord?.medicines

.map(

(item) => `${item.name}

${item.specs} X${item.quantity}`

)

.join(',')

}}

1234567891011数据转中文

filter.ts

ts// filter.ts

...

export const getLiverFunctionText = (val: LiverFunction) => {

return liverFunctionOptions.find((item) => item.value === val)?.label

}

export const getAllergicHistoryText = (val: AllergicHistory) => {

return allergicHistoryOptions.find((item) => item.value === val)?.label

}

export const getFertilityStatusText = (val: FertilityStatus) => {

return fertilityStatusOptions.find((item) => item.value === val)?.label

}

export const getRenalFunctionText = (val: RenalFunction) => {

return renalFunctionOptions.find((item) => item.value === val)?.label

}

1234567891011121314151617181920vue

肝功能

{{ getLiverFunctionText(item.msg.consultRecord.liverFunction) }} |

肾功能

{{ getRenalFunctionText(item.msg.consultRecord.renalFunction) }} |

过敏史

{{ getAllergicHistoryText(item.msg.consultRecord.allergicHistory) }} |

生育状态

{{ getFertilityStatusText(item.msg.consultRecord.fertilityStatus) }}

12345678910