Skip to content
On this page

开药门诊模块

开药门诊-需求分析

理解开药门诊阶段流程分析

开药门诊流程

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

步骤

  1. 准备基础结构代码
  2. 路由配置
  3. 首页跳转
  4. cv静态结构

基础结构代码views/Consult/ConsultMedicine.vue

vue
<script setup lang="ts"></script>

<template>
  <div class="consult-medicine-page">
    <h1>consult-medicine</h1>
  </div>
</template>

<style scoped lang="scss"></style>

1
2
3
4
5
6
7
8
9
10

路由配置router/index.ts

ts
{
    path: '/consult/medicine',
    component: () => import('@/views/Consult/ConsultMedicine.vue'),
    meta: { title: '开药门诊' }
}
1
2
3
4
5

首页跳转Home/index.vue

vue
<router-link
    to="/consult/medicine"
    class="nav"
    @click="store.setType(ConsultType.Medication)"
>
    <cp-icon name="home-prescribe"></cp-icon>
    <p class="title">开药门诊</p>
    <p class="desc">线上买药更方便</p>
</router-link>
1
2
3
4
5
6
7
8
9

静态结构ConsultMedicine.vue

vue
<script setup lang="ts"></script>

<template>
  <div class="consult-medicine-page">
    <cp-nav-bar
      title="开药门诊"
      right-text="问诊记录"
      @click-right="$router.push('/user/consult')"
    ></cp-nav-bar>
    <van-notice-bar text="请如实填写资料以便医生了解您的病情和用药需求" />
    <div class="illness-form">
      <div class="adm-list-header">症状描述</div>
      <van-field
        type="textarea"
        rows="3"
        placeholder="请输入所患疾病名称"
      ></van-field>
      <div class="adm-list-header">用药人身体情况</div>
      <div class="item">
        <p>肝功能</p>
      </div>
      <div class="item">
        <p>肾功能</p>
      </div>
      <div class="item">
        <p>过敏史</p>
      </div>
      <div class="item">
        <p>生育状态及计划</p>
      </div>
      <div class="adm-list-header">补充病例信息</div>
      <!-- 上传组件 -->
      <div class="illness-img"></div>
      <!-- 下一步 -->
      <!-- <van-button type="primary" block round> 下一步 </van-button> -->
    </div>
  </div>
</template>

<style scoped lang="scss">
.consult-medicine-page {
  padding-top: 46px;
  .van-button {
    font-size: 16px;
    margin-bottom: 30px;
    &.disabled {
      opacity: 1;
      background: #fafafa;
      color: #d9dbde;
      border: #fafafa;
    }
  }
  .illness-form {
    padding: 0 15px 15px 15px;
    .adm-list-header {
      padding-bottom: 5px;
      font-size: 16px;
      font-weight: 500;
      color: #121826;
      border-bottom: none;
      margin-top: 30px;
    }
    .van-field {
      padding: 0;
      &::after {
        border-bottom: none;
      }
    }
    .item {
      > p {
        color: var(--cp-text3);
        padding: 15px 0;
      }
    }
  }
}
</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

开药门诊-表单单选框处理

需求:

完成表单双向数据绑定

步骤:

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

定义枚举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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

定义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 }
]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

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

ts
import {
  liverFunctionOptions,
  allergicHistoryOptions,
  fertilityStatusOptions,
  renalFunctionOptions
} from '@/services/constants'
1
2
3
4
5
6
vue
      <div class="item">
        <p>肝功能</p>
        <cp-radio-btn :options="liverFunctionOptions"></cp-radio-btn>
      </div>
      <div class="item">
        <p>肾功能</p>
        <cp-radio-btn :options="renalFunctionOptions"></cp-radio-btn>
      </div>
      <div class="item">
        <p>过敏史</p>
        <cp-radio-btn :options="allergicHistoryOptions"></cp-radio-btn>
      </div>
      <div class="item">
        <p>生育状态及计划</p>
        <cp-radio-btn :options="fertilityStatusOptions"></cp-radio-btn>
      </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

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

consult.d.ts

ts
import 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'
>    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

src/stores/modules/consult.ts

ts
import 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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

双向数据绑定ConsultMedicine.vue

ts
import type { MedicineIllness } from '@/types/consult'
import { ref } from 'vue'
const form = ref<MedicineIllness>({
  illnessDesc: '',
  liverFunction: undefined,
  renalFunction: undefined,
  allergicHistory: undefined,
  fertilityStatus: undefined,
  pictures: []
})
1
2
3
4
5
6
7
8
9
10
vue
<div class="illness-form">
      <div class="adm-list-header">症状描述</div>
      <van-field
        type="textarea"
        rows="3"
        placeholder="请输入所患疾病名称"
        v-model="form.illnessDesc"
      ></van-field>
      <div class="adm-list-header">用药人身体情况</div>
      <div class="item">
        <p>肝功能</p>
        <cp-radio-btn
          :options="liverFunctionOptions"
          v-model="form.liverFunction"
        ></cp-radio-btn>
      </div>
      <div class="item">
        <p>肾功能</p>
        <cp-radio-btn
          :options="renalFunctionOptions"
          v-model="form.renalFunction"
        ></cp-radio-btn>
      </div>
      <div class="item">
        <p>过敏史</p>
        <cp-radio-btn
          :options="allergicHistoryOptions"
          v-model="form.allergicHistory"
        ></cp-radio-btn>
      </div>
      <div class="item">
        <p>生育状态及计划</p>
        <cp-radio-btn
          :options="fertilityStatusOptions"
          v-model="form.fertilityStatus"
        ></cp-radio-btn>
      </div>
      <div class="adm-list-header">补充病例信息</div>
      <!-- 上传组件 -->
      <div class="illness-img"></div>
      <!-- 下一步 -->
      <!-- <van-button type="primary" block round> 下一步 </van-button> -->
    </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

开药门诊-封装上传组件

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

步骤:

  1. 封装组件CpUpload
  2. CpUpload组件提供类型
  3. ConsultIllness使用上传组件
  4. ConsultMedicine使用上传组件

封装组件components/CpUpload.vue

vue
<script setup lang="ts">
import { uploadImage } from '@/services/consult'
import type { UploaderFileListItem } from 'vant'
import type { UploaderAfterRead } from 'vant/lib/uploader/types'
import { ref } from 'vue'
import type { Image } from '@/types/consult'

const emit = defineEmits<{
  (e: 'uploadSuccess', img: Image): void
  (e: 'deleteSuccess', item: UploaderFileListItem): void
}>()

// 上传图片
const fileList = ref<Image[]>([])
// 图片上传
const onAfterRead: UploaderAfterRead = (item) => {
  if (Array.isArray(item)) return
  if (!item.file) return

  item.status = 'uploading'
  item.message = '上传中...'
  uploadImage(item.file)
    .then((res) => {
      item.status = 'done'
      item.message = undefined
      item.url = res.data.url
      emit('uploadSuccess', res.data)
    })
    .catch(() => {
      item.status = 'failed'
      item.message = '上传失败'
    })
}
const onDeleteImg = (item: UploaderFileListItem) => {
  emit('deleteSuccess', item)
}

const setFileList = (val: Image[]) => {
  fileList.value = val
}
defineExpose({
  setFileList
})
</script>

<template>
  <div class="illness-img">
    <van-uploader
      upload-icon="photo-o"
      upload-text="上传图片"
      max-count="9"
      :max-size="5 * 1024 * 1024"
      v-model="fileList"
      :after-read="onAfterRead"
      @delete="onDeleteImg"
    ></van-uploader>
    <p class="tip" v-if="!fileList.length">
      上传内容仅医生可见,最多9张图,最大5MB
    </p>
  </div>
</template>

<style scoped lang="scss">
.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);
      }
    }
  }
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

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

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

declare module 'vue' {
  interface GlobalComponents {
    // 添加组件类型
    ...
    CpUpload: typeof CpUpload
  }
}

1
2
3
4
5
6
7
8
9
10
11

ConsultIllness.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
  )
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
vue
<cp-upload
  ref="cpUploadRef"
  @upload-success="onUploadSuccess"
  @delete-success="onDeleteSuccess"
></cp-upload>
1
2
3
4
5

ConsultMedicine.vue使用上传组件

ts
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
  )
}
1
2
3
4
5
6
7
8
9
vue
<cp-upload
  ref="cpUploadRef"
  @upload-success="onUploadSuccess"
  @delete-success="onDeleteSuccess"
></cp-upload>
1
2
3
4
5

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

ConsultMedicine

步骤:

  1. 控制按钮样式
  2. 校验逻辑
  3. 下一步跳转逻辑

控制按钮样式

ts
const disabled = computed(
  () =>
    !form.value.illnessDesc ||
    form.value.liverFunction === undefined ||
    form.value.renalFunction === undefined ||
    form.value.allergicHistory === undefined ||
    form.value.fertilityStatus === undefined
)
1
2
3
4
5
6
7
8
vue
<van-button
  :class="{ disabled }"
  type="primary"
  block
  round
  @click="next"
>
  下一步
</van-button>
1
2
3
4
5
6
7
8
9

校验逻辑

ts
const 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('请选择生育状态及计划')
  
}
1
2
3
4
5
6
7
8
9
10
11
12

下一步跳转逻辑

ts
const store = useConsultStore()
const router = useRouter()
const next = () => {
  ....
  //  记录病情
  store.setMedicineIlness(form.value)
  // 跳转,携带标识
  router.push('/user/patient?isChange=1&from=medicineConsult')
}
1
2
3
4
5
6
7
8
9

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

步骤:

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

开药门诊选择患者下一步跳转选择药品,根据标识跳转views/User/PatientPage.vue

ts
const 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')
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

新建页面结构ConsultChoose.vue

vue
<script setup lang="ts"></script>

<template>
  <div class="consult-choose-page">
    <h1>consult-choose</h1>
  </div>
</template>

<style scoped lang="scss"></style>

1
2
3
4
5
6
7
8
9
10

路由规则配置router/index.ts

ts
{
  path: '/consult/choose',
  component: () => import('@/views/Consult/ConsultChoose.vue'),
  meta: { title: '选择药品' }
}
1
2
3
4
5

选择药品-页面结构

步骤:

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

静态结构ConsultChoose.vue

vue
<script setup lang="ts">
import { showToast } from 'vant'
import { ref } from 'vue'

const searchValue = ref('')
const onSearch = (val: string) => showToast(val)
const onCancel = () => showToast('取消')

const step = ref(1)
</script>

<template>
  <div class="consult-choose-page">
    <cp-nav-bar title="选择药品"></cp-nav-bar>
    <van-search
      v-model="searchValue"
      show-action
      placeholder="搜一搜: 药品名称"
      @search="onSearch"
      @cancel="onCancel"
    />
    <!-- 药品列表 -->
    <div class="medicine-list">
      <div class="item van-hairline--top" v-for="i in 20" :key="i">
        <img class="img" src="@/assets/ad.png" alt="" />
        <div class="info">
          <p class="name">
            <span>优赛明 维生素E乳 {{ i }}</span>
            <span>
              <van-stepper v-model="step" />
            </span>
          </p>
          <p class="size">
            <van-tag>处方药</van-tag>
            <span>80ml</span>
          </p>
          <p class="price">¥25.00</p>
        </div>
      </div>
    </div>
    <van-action-bar>
      <van-action-bar-icon icon="cart-o" badge="0" />
      <div class="total-price">¥ 1000</div>
      <van-action-bar-button type="primary" text="申请开方" />
    </van-action-bar>
  </div>
</template>

<style scoped lang="scss">
.consult-choose-page {
  padding-top: 46px;
  .van-search {
    position: sticky;
    top: 46px;
    z-index: 10;
    background-color: #fff;
  }
  .van-action-bar {
    border-top: 1px solid rgba(237, 237, 237, 0.9);
    .total-price {
      width: 200px;
      font-size: 24px;
      line-height: 18px;
      font-weight: 700;
      color: #121826;
    }
  }
  .medicine-list {
    background-color: #fff;
    padding: 0 15px 45px;
    .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;
            // width: 300px;
            width: 40vw;
          }
          > 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);
      }
    }
  }
}
</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127

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

ts
const step = ref(0)
1
vue
<van-stepper
    v-model="step"
    min="0"
    :class="{ hide: step === 0 }"
/>
1
2
3
4
5
scss
.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;
        }
      }
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

选择药品-抽离列表卡片组件

步骤:

  1. 抽离卡片组件
  2. 抽离列表组件
  3. 使用列表组件

抽离卡片组件views/Consult/components/MedicineCard.vue

vue
<script setup lang="ts">
import { ref } from 'vue'

const step = ref(0)
</script>

<template>
  <div class="item van-hairline--top">
    <img class="img" src="@/assets/ad.png" alt="" />
    <div class="info">
      <p class="name">
        <span>优赛明 维生素E乳</span>
        <span>
          <van-stepper v-model="step" min="0" :class="{ hide: step === 0 }" />
        </span>
      </p>
      <p class="size">
        <van-tag>处方药</van-tag>
        <span>80ml</span>
      </p>
      <p class="price">¥25.00</p>
    </div>
  </div>
</template>

<style scoped lang="scss">
.item {
  display: flex;
  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;
        // width: 300px;
        width: 40vw;
      }
      > 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-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;
      }
    }
  }
}
</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

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

vue
<script setup lang="ts">
import MedicineCard from './MedicineCard.vue'
</script>

<template>
  <div class="medicine-list">
    <medicine-card v-for="i in 100" :key="i"></medicine-card>
  </div>
</template>

<style scoped lang="scss">
.medicine-list {
  background-color: #fff;
  padding: 0 15px 45px;
}
</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

使用列表组件ConsultChoose.vue

vue
import MedicineList from './components/MedicineList.vue'
...
<!-- 药品列表 -->
<medicine-list></medicine-list>
...
1
2
3
4
5

选择药品-list组件渲染药品列表

步骤:

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

van-list初步使用MedicineList.vue

vue

<script setup lang="ts">
import { ref } from 'vue'
import MedicineCard from './MedicineCard.vue'
const list = ref<number[]>([])
const loading = ref(false)
const finished = ref(false)

const onLoad = () => {
  // 异步更新数据
  // setTimeout 仅做示例,真实场景中一般为 ajax 请求
  setTimeout(() => {
    for (let i = 0; i < 10; i++) {
      list.value.push(list.value.length + 1)
    }

    // 加载状态结束
    loading.value = false

    // 数据全部加载完成
    if (list.value.length >= 40) {
      finished.value = true
    }
  }, 1000)
}
</script>

<template>
  <div class="medicine-list">
    <van-list
      v-model:loading="loading"
      :finished="finished"
      finished-text="没有更多了"
      @load="onLoad"
    >
      <medicine-card v-for="i in list" :key="i"></medicine-card>
    </van-list>
  </div>
</template>

<style scoped lang="scss">
.medicine-list {
  background-color: #fff;
  padding: 0 15px 45px;
}
</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

封装并调用请求

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

ts
import type { Medical } from './room'

// 药品列表查询参数
export type MedicineParams = PageParams & {
  keyword: string
}


export type BasePage<T = any> = {
  pageTotal: number
  total: number
  rows: T
}

// 医生列表
export type DoctorList = Doctor[]

// 医生分页
export type DoctorPage = BasePage<DoctorList>

// 文章列表
export type KnowledgeList = Knowledge[]

// 文章列表带分页
export type KnowledgePage = BasePage<KnowledgeList>

// 药品列表
export type MedicineList = Medical[]
// 药品列表带分页
export type MedicinePage = BasePage<MedicineList>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

封装接口service/consult.ts

ts
export const getMedicinePage = (params: MedicineParams) => {
  return request<MedicinePage>('patient/medicine', 'GET', params)
}
1
2
3

调用接口MedicineList.vue

vue
<script setup lang="ts">
import { ref } from 'vue'
import MedicineCard from './MedicineCard.vue'
import type { MedicineList, MedicineParams } from '@/types/consult'
import { getMedicinePage } from '@/services/consult'
const list = ref<MedicineList>([])
const loading = ref(false)
const finished = ref(false)

const params = ref<MedicineParams>({
  keyword: '',
  pageSize: 10,
  current: 1
})

const onLoad = async () => {
  const { data } = await getMedicinePage(params.value)
  list.value.push(...data.rows)
  loading.value = false
  if (params.value.current >= data.pageTotal) {
    finished.value = true
  } else {
    finished.value = false
    params.value.current++
  }
}
</script>

<template>
  <div class="medicine-list">
    <van-list
      v-model:loading="loading"
      :finished="finished"
      finished-text="没有更多了"
      @load="onLoad"
    >
      <medicine-card v-for="item in list" :key="item.id"></medicine-card>
    </van-list>
  </div>
</template>

<style scoped lang="scss">
.medicine-list {
  background-color: #fff;
  padding: 0 15px 45px;
}
</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

渲染药品卡片

父传子MedicineList.vue

vue
<medicine-card
        v-for="item in list"
        :key="item.id"
        :item="item"
></medicine-card>
1
2
3
4
5

子组件接收数据并渲染MedicineCard

vue
<script setup lang="ts">
import type { Medical } from '@/types/room'
import { ref } from 'vue'

defineProps<{
  item: Medical
}>()

const step = ref(0)
</script>

<template>
  <div class="item van-hairline--top">
    <img class="img" :src="item.avatar" alt="" />
    <div class="info">
      <p class="name">
        <span>{{ item.name }}</span>
        <span>
          <van-stepper v-model="step" min="0" :class="{ hide: step === 0 }" />
        </span>
      </p>
      <p class="size">
        <van-tag v-if="item.prescriptionFlag === 1">处方药</van-tag>
        <span>{{ item.specs }}</span>
      </p>
      <p class="price">{{ item.amount }}</p>
    </div>
  </div>
</template>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

选择药品-搜索功能

步骤:

  1. 搜索框双向数据绑定字段searchValuekeyword查询参数字段,回车后赋值该字段
  2. 父传子
  3. 监听字段,渲染页面

ConsultChoose搜索框双向数据绑定字段searchValue,keyword查询参数字段,回车后赋值该字段

ts
const searchValue = ref('')
const keyword = ref('')
const onSearch = () => {
  keyword.value = searchValue.value
}
const onCancel = () => {
  keyword.value = ''
}
1
2
3
4
5
6
7
8

父传子ConsultChoose

vue
<!-- 药品列表 -->
<medicine-list :keyword="keyword"></medicine-list>
1
2

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

vue
const props = defineProps<{
  keyword: string
}>()

const params = ref<MedicineParams>({
  keyword: props.keyword || '',
  pageSize: 10,
  current: 1
})

watch(
  () => props.keyword,
  (val) => {
    list.value = []
    params.value.keyword = val
    params.value.current = 1
    onLoad()
  }
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

选择药品-购物车底部操作栏

步骤:

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

Consult类型新增medicines字段

consult.d.ts

ts
export type Consult = {
  ...
  /** 药品 */
  medicines: Medical[]
}
1
2
3
4
5

store新增方法,存medicines字段

ts
const setMedicines = (val: Medical[]) => (consult.value.medicines = val)
return {
    ...
    setMedicines
    ...
}
1
2
3
4
5
6

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

vue
<script setup lang="ts">
import { useConsultStore } from '@/stores'
import type { Medical } from '@/types/room'
import { ref, watch } from 'vue'
    
const props = defineProps<{
  item: Medical
}>()

const step = ref(0)

const consultStore = useConsultStore()
const onChange = (value: string, detail: { name: string }) => {
  // 不要忘记在组件上name属性绑定id值
  const medicines = consultStore.consult.medicines || []
  const medicine = medicines?.find((item) => item.id === detail.name)
  if (medicine) {
    medicine.quantity = value
  } else {
    medicines.push({
      ...props.item,
      quantity: value
    })
  }
  consultStore.setMedicines(medicines.filter((item) => +item.quantity > 0))
}

watch(
  () =>
    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 }
)
</script>

<template>
  <div class="item van-hairline--top">
    <img class="img" :src="item.avatar" alt="" />
    <div class="info">
      <p class="name">
        <span>{{ item.name }}</span>
        <span>
          <van-stepper
            :name="item.id"
            v-model="step"
            min="0"
            :class="{ hide: step === 0 }"
            @change="onChange"
          />
        </span>
      </p>
      <p class="size">
        <van-tag v-if="item.prescriptionFlag === 1">处方药</van-tag>
        <span>{{ item.specs }}</span>
      </p>
      <p class="price">{{ item.amount }}</p>
    </div>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

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

vue
<script setup lang="ts">
....

const consultStore = useConsultStore()
const totalPrice = computed(() => {
  return consultStore.consult.medicines
    ?.reduce((sum, item) => {
      return (sum += +item.amount * +item.quantity)
    }, 0)
    .toFixed(2)
})
const cartLength = computed(
  () =>
    consultStore.consult.medicines?.length || 0
)
</script>

<template>
  <div class="consult-choose-page">
    <cp-nav-bar title="选择药品"></cp-nav-bar>
    <van-search
      v-model="searchValue"
      show-action
      placeholder="搜一搜: 药品名称"
      @search="onSearch"
      @cancel="onCancel"
    />
    <!-- 药品列表 -->
    <medicine-list :keyword="keyword"></medicine-list>
    <van-action-bar>
      <van-action-bar-icon
        icon="cart-o"
        :color="cartLength > 0 ? '#323233' : '#eee'"
        :badge="cartLength === 0 ? '' : cartLength"
      />
      <div class="total-price">{{ totalPrice }}</div>
      <van-action-bar-button type="primary" text="申请开方" />
    </van-action-bar>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

选择药品-购物车抽屉

步骤:

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

抽屉结构样式ConsultChoose.vue

vue
<van-action-sheet v-model:show="show">
  <div class="content">
    <div class="content-header">
      <div class="content-header-left">
        <span>药品清单</span><span>共{{ cartLength }}件商品</span>
      </div>
      <div class="content-header-right" @click="clear">
        <van-icon name="delete-o" />
        <span>清空</span>
      </div>
    </div>
  </div>
  <van-action-bar>
    <van-action-bar-icon
      icon="cart-o"
      :color="cartLength > 0 ? '#323233' : '#eee'"
      :badge="cartLength === 0 ? '' : cartLength"
    />
    <div class="total-price">¥ {{ totalPrice }}</div>
    <van-action-bar-button type="primary" text="申请开方" />
  </van-action-bar>
</van-action-sheet>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
scss
  .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;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

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

ts
const show = ref(false)
// 底部操作栏注册事件,注意不是抽屉内容的底部操作栏
const openCart = () => {
  if (cartLength.value === 0) return showToast('请选择药品')
  show.value = true
}

const clear = () => {
  // console.log('clear')
  show.value = false
}
1
2
3
4
5
6
7
8
9
10
11

药品列表渲染

vue
import MedicineCard from './components/MedicineCard.vue'

<!-- 列表 -->
<div class="medicine-list">
  <medicine-card
    v-for="item in consultStore.consult.medicines"
    :key="item.id"
    :item="item"
  ></medicine-card>
</div>
1
2
3
4
5
6
7
8
9
10

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

增加减少药品功能 MedicineCard.vue

vue
watch(
  () => 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 }
)
1
2
3
4
5
6
7
8
9
10
11

清空购物车ConsultChoose

ts
const clear = () => {
  // console.log('clear')
  consultStore.setMedicines([])
  show.value = false
}
1
2
3
4
5

药品详情-跳转详情页

步骤:

  1. 基本结构,路由配置
  2. 跳转方法调用
  3. 静态结构

基本结构ConsultMedicineDetail

vue
<script setup lang="ts"></script>

<template>
  <div class="medicine-detail-page">
    <h1>medicine-detail</h1>
  </div>
</template>

<style scoped lang="scss"></style>

1
2
3
4
5
6
7
8
9
10

路由配置router/index.ts

ts
{
  path: '/medicineDetail/:id',
  component: () => import('@/views/Consult/ConsultMedicineDetail.vue'),
  meta: { title: '药品详情' }
},
1
2
3
4
5

跳转方法调用MedicineCard.vue

vue
<div class="item van-hairline--top" @click="$router.push(`/medicineDetail/${item.id}`)">
  <img class="img" :src="item.avatar" alt="" />
  <div class="info">
    <p class="name">
      <span>{{ item.name }}</span>
      <span>
        <van-stepper
          :name="item.id"
          v-model="step"
          min="0"
          :class="{ hide: step === 0 }"
          @change="onChange"
          @click.stop
        />
      </span>
    </p>
    <p class="size">
      <van-tag v-if="item.prescriptionFlag === 1">处方药</van-tag>
      <span>{{ item.specs }}</span>
    </p>
    <p class="price">¥{{ item.amount }}</p>
  </div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

静态结构ConsultMedicineDetail.vue

vue
<script setup lang="ts"></script>

<template>
  <div class="medicine-detail-page">
    <cp-nav-bar title="双蚁祛湿通络胶囊(双蚁)"></cp-nav-bar>
    <!-- 主图片 -->
    <van-swipe indicator-color="#fff">
      <van-swipe-item>
        <img src="http://img.39.net/yp/360/s/863578.jpg" alt="" />
      </van-swipe-item>
    </van-swipe>

    <div class="detail-top">
      <div class="info">
        <p class="name">
          <span>双蚁祛湿通络胶囊(双蚁)</span>
        </p>
        <p class="size">
          <van-tag>处方药</van-tag>
          <span>每粒装0.4克</span>
        </p>
        <p class="price">¥186.00</p>
      </div>
    </div>

    <div class="pay-space"></div>

    <div class="detail-bottom">
      <div class="info-item">
        <div class="info-title">药品名称</div>
        <div class="info-desc">双蚁祛湿通络胶囊(双蚁)</div>
      </div>
      <div class="info-item">
        <div class="info-title">品牌</div>
        <div class="info-desc"></div>
      </div>
      <div class="info-item">
        <div class="info-title">功能主治</div>
        <div class="info-desc">
          补肝肾、益气血、活血通络、祛风除湿的功能,临床用于改善肝肾两亏、气血不足引起的腰膝冷痛、肢气肿胀、麻木不仁、酸软乏力、屈伸不利等痹证症状。
        </div>
      </div>
      <div class="info-item">
        <div class="info-title">不良反应</div>
        <div class="info-desc">尚不明确。</div>
      </div>
      <div class="info-item">
        <div class="info-title">禁忌</div>
        <div class="info-desc">尚不明确。</div>
      </div>
      <div class="info-item">
        <div class="info-title">注意事项</div>
        <div class="info-desc">1.忌与茶、牛奶同服。2.本品含有制川乌,应严格按照规定用量服用。</div>
      </div>
      <div class="info-item">
        <div class="info-title">有效期</div>
        <div class="info-desc">24 月</div>
      </div>
      <div class="info-item">
        <div class="info-title">执行标准</div>
        <div class="info-desc">国药准字J20030056</div>
      </div>
      <div class="info-item">
        <div class="info-title">批准文号</div>
        <div class="info-desc">国药准字B20021059</div>
      </div>
      <div class="info-item">
        <div class="info-title">生产企业</div>
        <div class="info-desc">企业名称:广西双蚁药业有限公司企业简称:双蚁药业</div>
      </div>
    </div>

    <medicine-action></medicine-action>
  </div>
</template>

<style scoped lang="scss">
.medicine-detail-page {
  padding: 46px 0px 45px;

  .van-swipe {
    img {
      width: 100%;
    }
  }

  .detail-top {
    padding: 15px;
    .info {
      padding-left: 15px;
      width: 250px;
      .name {
        display: flex;
        font-size: 15px;
        margin-bottom: 5px;
        > span:first-child {
          width: auto;
        }
        > span:last-child {
          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;
      }
    }
  }
  .detail-bottom {
    padding: 15px;
    .info-item {
      margin-bottom: 30px;
      .info-title {
        font-size: 18px;
        font-family: PingFang SC, PingFang SC-Medium;
        font-weight: 500;
        color: #000000;
      }
      .info-desc {
        margin-top: 10px;
        font-size: 14px;
        font-family: PingFang SC, PingFang SC-Regular;
        font-weight: 400;
        color: #3c3e42;
      }
    }
  }
  .pay-space {
    height: 12px;
    background-color: var(--cp-bg);
  }
}
</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146

药品详情-渲染页面

步骤:

  1. 定义类型
  2. 封装接口
  3. 调用请求渲染

定义类型consult.d.ts

ts
export 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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

封装接口services/consult.ts

ts
export const getMedicineDetail = (id: string) => {
  return request<MedicineDetail>(`patient/medicine/${id}`)
}
1
2
3

调用请求渲染ConsultMedicineDetail.vue

vue
<script setup lang="ts">
import { getMedicineDetail } from '@/services/consult'
import type { MedicineDetail } from '@/types/consult'
import { ref } from 'vue'
import { onMounted } from 'vue'
import { computed } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()
const id = computed(() => route.params.id)
const detail = ref<MedicineDetail>()
onMounted(() => {
  loadDetail()
})
const loadDetail = async () => {
  const { data } = await getMedicineDetail(id.value as string)
  detail.value = data
}
</script>

<template>
  <div class="medicine-detail-page" v-if="detail">
    <cp-nav-bar :title="detail.name"></cp-nav-bar>
    <!-- 主图片 -->
    <van-swipe indicator-color="#fff">
      <van-swipe-item v-for="(item, index) in detail.mainPictures" :key="index">
        <img :src="item" alt="" />
      </van-swipe-item>
    </van-swipe>

    <div class="detail-top">
      <div class="info">
        <p class="name">
          <span>{{ detail.name }}</span>
        </p>
        <p class="size">
          <van-tag v-if="detail.prescriptionFlag === 1">处方药</van-tag>
          <span>{{ detail.specs }}</span>
        </p>
        <p class="price">{{ detail.amount }}</p>
      </div>
    </div>

    <div class="pay-space"></div>

    <div class="detail-bottom">
      <div class="info-item">
        <div class="info-title">药品名称</div>
        <div class="info-desc">{{ detail.name }}</div>
      </div>
      <div class="info-item">
        <div class="info-title">品牌</div>
        <div class="info-desc">{{ detail.brand }}</div>
      </div>
      <div class="info-item">
        <div class="info-title">功能主治</div>
        <div class="info-desc">{{ detail.indicationsFunction }}</div>
      </div>
      <div class="info-item">
        <div class="info-title">不良反应</div>
        <div class="info-desc">{{ detail.untowardReaction }}</div>
      </div>
      <div class="info-item">
        <div class="info-title">禁忌</div>
        <div class="info-desc">{{ detail.contraindication }}</div>
      </div>
      <div class="info-item">
        <div class="info-title">注意事项</div>
        <div class="info-desc">{{ detail.preparation }}</div>
      </div>
      <div class="info-item">
        <div class="info-title">有效期</div>
        <div class="info-desc">{{ detail.expiration }}</div>
      </div>
      <div class="info-item">
        <div class="info-title">执行标准</div>
        <div class="info-desc">{{ detail.standard }}</div>
      </div>
      <div class="info-item">
        <div class="info-title">批准文号</div>
        <div class="info-desc">{{ detail.approvalNo }}</div>
      </div>
      <div class="info-item">
        <div class="info-title">生产企业</div>
        <div class="info-desc">{{ detail.manufacturer }}</div>
      </div>
    </div>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

药品详情-底部操作栏功能

列表页和详情页都有底部操作栏,所以我们可以抽离组件

步骤:

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

封装底部操作栏组件MedicineAction.vue

vue
<script setup lang="ts">
import { useConsultStore } from '@/stores'
import { computed, ref } from 'vue'
import MedicineCard from './MedicineCard.vue'

const consultStore = useConsultStore()
const totalPrice = computed(() => {
  return consultStore.consult.medicines
    ?.reduce((sum, item) => {
      return (sum += +item.amount * +item.quantity)
    }, 0)
    .toFixed(2)
})
const cartLength = computed(() => consultStore.consult.medicines?.length || 0)
const show = ref(false)
const openCart = () => {
  if (cartLength.value === 0) return
  show.value = true
}

const clear = () => {
  // console.log('clear')
  consultStore.setMedicines([])
  show.value = false
}
</script>

<template>
  <van-action-bar>
    <van-action-bar-icon
      icon="cart-o"
      :badge="cartLength === 0 ? '' : cartLength"
      :color="cartLength > 0 ? '#323233' : '#eee'"
      @click="openCart"
    />
    <div class="total-price">{{ totalPrice }}</div>
    <van-action-bar-button type="primary" text="申请开方" />
  </van-action-bar>

  <van-action-sheet v-model:show="show">
    <div class="content">
      <div class="content-header">
        <div class="content-header-left">
          <span>药品清单</span><span>{{ cartLength }}件商品</span>
        </div>
        <div class="content-header-right" @click="clear">
          <van-icon name="delete-o" />
          <span>清空</span>
        </div>
      </div>
      <!-- 列表 -->
      <div class="medicine-list">
        <medicine-card
          v-for="item in consultStore.consult.medicines"
          :key="item.id"
          :item="item"
        ></medicine-card>
      </div>
    </div>
    <van-action-bar>
      <van-action-bar-icon
        icon="cart-o"
        :color="cartLength > 0 ? '#323233' : '#eee'"
        :badge="cartLength === 0 ? '' : cartLength"
      />
      <div class="total-price">{{ totalPrice }}</div>
      <van-action-bar-button type="primary" text="申请开方" />
    </van-action-bar>
  </van-action-sheet>
</template>

<style scoped lang="scss">
.van-action-bar {
  border-top: 1px solid rgba(237, 237, 237, 0.9);
  .total-price {
    width: 200px;
    font-size: 24px;
    line-height: 18px;
    font-weight: 700;
    color: #121826;
  }
}

.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;
  }
}
</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121

ConsultMedicineDetail使用底部操作栏

vue
<medicine-action></medicine-action>
1

区分加入药箱逻辑MedicineAction

ts
<script setup lang="ts">
...

withDefaults(
  defineProps<{
    from?: 'list' | 'detail'
  }>(),
  {
    from: 'list'
  }
)

const onAskDocotor = () => {
  console.log('申请开方')
}

const onAddToCart = () => {
  console.log('加入药箱')
}
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

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

vue
<van-action-bar-button
  v-if="from === 'list'"
  type="primary"
  text="申请开方"
  @click="onAskDocotor"
/>
<van-action-bar-button
  v-else
  type="primary"
  text="加入药箱"
  @click="onAddToCart"
></van-action-bar-button>
1
2
3
4
5
6
7
8
9
10
11
12

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

MedicineAction

ts
const emits = defineEmits<{
  (e: 'addToCart'): void
}>()

const onAddToCart = () => {
  emits('addToCart')
}
1
2
3
4
5
6
7

ConsultMedicineDetail

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)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

申请开单跳转问诊室,需要先生成订单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<string, unknown> = {}
  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
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

此时再处理申请开单逻辑 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()
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

问诊室-开药门诊信息渲染

步骤:

  1. 渲染开药门诊信息
  2. 数据转中文

渲染开药门诊信息 RoomMessage

ts
// RoomMessage
const route = useRoute()
const fromPage = computed(() => route.query.from)
1
2
3
vue
<span v-if="fromPage === 'medicine'">
  肝功能 {{ item.msg.consultRecord.liverFunction }} | 肾功能
  {{ item.msg.consultRecord.renalFunction }} | 过敏史
  {{ item.msg.consultRecord.allergicHistory }} | 生育状态
  {{ item.msg.consultRecord.fertilityStatus }}
</span>
<span v-else>
  {{ getIllnessTimeText(item.msg.consultRecord?.illnessTime) }} |
  {{ getConsultFlagText(item.msg.consultRecord?.consultFlag) }}
</span>
1
2
3
4
5
6
7
8
9
10
vue
<van-col span="6" v-if="fromPage === 'medicine'">用药需求</van-col>
<van-col span="18" v-if="fromPage === 'medicine'">
  {{
    item.msg.consultRecord?.medicines
      .map(
        (item) => `${item.name}
  ${item.specs} X${item.quantity}`
      )
      .join(',')
  }}
</van-col>
1
2
3
4
5
6
7
8
9
10
11

数据转中文

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
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
vue
<span v-if="fromPage === 'medicine'">
  肝功能
  {{ getLiverFunctionText(item.msg.consultRecord.liverFunction) }} |
  肾功能
  {{ getRenalFunctionText(item.msg.consultRecord.renalFunction) }} |
  过敏史
  {{ getAllergicHistoryText(item.msg.consultRecord.allergicHistory) }} |
  生育状态
  {{ getFertilityStatusText(item.msg.consultRecord.fertilityStatus) }}
</span>
1
2
3
4
5
6
7
8
9
10

Released under the MIT License.