Skip to content

订单支付与问诊室通知(四) ​

本页对应 订单支付与问诊室通知,共 6 段视频,约 49 分钟。

学习目标 ​

  • 能够将接口返回的嵌套 items 数据展开并合并到消息列表中,使用 unshift 保持聊天消息倒序。
  • 能够拆分患者信息组件,并在问诊室页面按消息类型 21 条件渲染,通过 props 将 patientInfo 传入子组件。
  • 能够渲染患者姓名、性别、年龄、病情描述等字段,并将患病时长、就诊状态等数值通过数组映射为对应文字。
  • 能够根据 pictures 数量条件显示“暂无图片”或“查看图片”,点击后提取图片 URL 数组并调用 uni.previewImage 预览大图。
  • 能够说明长轮询的实现思路与适用场景,区分其与 HTTP、WebSocket 在聊天场景中的差异。
  • 能够封装通知消息组件,使用插槽展示消息内容,接收 type 参数并按 31/32/33 及 >=31 条件控制温馨提示和列表过滤。

知识点 ​

目标与数据形状:先把深层列表拍平 ​

为什么需要:问诊室消息返回的数据层级较深,真正要渲染的消息在 data[].items 里;如果直接把整个响应塞给列表,模板取值会多一层,后续判断类型和传参都别扭。
怎么想:先打开网络面板看请求,找到 messageList 相关响应,展开后确认真正数据在 items;转写里的“艾特姆斯”就是 items。
怎么做:遍历 data,把每个 items 取出,用 unshift 展开放入新数组。转写里的“un shift”就是 unshift,“微杠报”是 v-for,“微杠意”是 v-if。

js
// 转写思路:真正数据在 data[].items
const list = []
res.data.forEach(item => {
  list.unshift(...item.items)
})
this.messageList = list

易错点:不要直接要 data,而要 data 下的 items;字段名和层级要按接口核对,unshift 会改变顺序,聊天列表要确认倒序是否符合预期。

患者信息:用条件渲染并抽成组件 ​

为什么需要:患者信息类型是 21,页面里如果直接堆模板,代码会乱,所以抽成 patient-info 组件。
怎么想:先 v-for 遍历 messageList,再判断 messageType === 21 才显示;组件通过 props 接收 info。转写中先出现“显示三次”的调试现象,就是因为只遍历没加条件。
怎么做:页面引入组件,用 template 包住条件;组件内声明 info 对象,默认空对象。

html
<template v-for="message in messageList" :key="message.id">
  <template v-if="message.messageType === 21">
    <patient-info :info="message.patientInfo" />
  </template>
</template>
js
props: {
  info: { type: Object, default: () => ({}) }
}

易错点:组件不是全局的,必须导入;v-for 不能替代 v-if;props 默认值用函数返回对象,避免引用共享。

患者基本信息与病情描述渲染 ​

为什么需要:患者信息里还有 patientInfo 层级,姓名、性别、年龄等字段要逐项取值。
怎么想:先渲染简单字段,复杂枚举后处理;转写里“李富贵、性别、年龄”就是按接口字段插值。
怎么做:在组件模板中用 info.patientInfo.name、genderValue、age 等字段;病情描述用对应字段插值。

html
<view>{{ info.patientInfo.name }}</view>
<view>{{ info.patientInfo.genderValue }}</view>
<view>{{ info.patientInfo.age }}</view>

易错点:字段名以接口为准;转写里“音否”是 info,“普拉普斯”是 props;不要漏掉 patientInfo 这一层。

数字转文字:用数组下标做映射 ​

为什么需要:患病时长、是否就诊返回的是数值,页面要显示“一周内”“未去医院就诊”等文字。
怎么想:数值当数组下标,定义文字数组,模板里用 数组[数值] 取文案。
怎么做:在组件 data 中按接口枚举定义文案数组,模板中取值。

html
<view>{{ durationText[info.patientInfo.duration] }}</view>
<view>{{ visitText[info.patientInfo.flags] }}</view>

易错点:数组顺序必须和接口枚举一致;字段名按实际接口核对;不要固定写死 0 或 1,要拿返回数值。

查看大图:previewImage 需要纯 URL 数组 ​

为什么需要:有图片才显示“查看图片”,无图片显示“暂无图片”;点击后要能预览。
怎么想:先判断 pictures.length > 0,再绑定点击;uni.previewImage 的 urls 要纯图片地址数组,而接口里是对象数组,所以用 map 取 url。
怎么做:

html
<view v-if="info.pictures.length > 0" @click="onPreview(info.pictures)">
  查看图片
</view>
<view v-else>暂无图片</view>
js
onPreview(pictures) {
  uni.previewImage({
    urls: pictures.map(item => item.url)
  })
}

易错点:previewImage 不是直接传对象数组;map 会返回新数组;转写里的“迈普”是 map,“优阿拉艾斯”是 urls。

通知消息:31/32/33 合用一个组件 ​

为什么需要:普通消息、温馨提示、已取消状态样式接近,可以合为一个 notify-info,减少重复。
怎么想:用 type 区分,默认 31;只有 32 才显示“温馨提示”;文字内容用插槽。
怎么做:父级用 messageType >= 31 过滤通知消息,传 type,插槽放 content。

html
<template v-for="message in messageList" :key="message.id">
  <template v-if="message.messageType >= 31">
    <notify-info :type="message.messageType">
      {{ message.content }}
    </notify-info>
  </template>
</template>
html
<view class="notify">
  <text v-if="type === 32">温馨提示:</text>
  <slot />
</view>
js
props: {
  type: { type: Number, default: 31 }
}

易错点:不要漏掉 21 的过滤;>= 31 可覆盖 31、32、33;插槽内容由父级传入,组件只负责样式和类型判断。

长轮询:作为了解 ​

为什么需要:聊天通常用 WebSocket;若浏览器不支持,才考虑轮询。
怎么想:HTTP 一次请求一次响应,聊天要持续获取消息,就用定时器反复发请求。
怎么做:转写中仅作概念说明,setInterval 定时发 HTTP 请求即可。

js
setInterval(() => {
  // 定时发送 HTTP 请求
}, 500)

易错点:长轮询不是首选,性能不如 WebSocket;转写里的“常轮循/长轮巡”应纠正为“长轮询”。

先处理接口数据:把层级压平 ​

为什么需要:问诊室消息列表接口返回的数据并不是直接一个数组,真正要用的消息在 data.items 里。如果直接把整个响应存到 messageList,后面模板取数据就会多一层 items,层级越深越容易写错。
怎么想:先确认“我真正要渲染的是哪一段数据”,只把 items 取出来,再放进页面使用的列表里。
怎么做:遍历返回的 data,把每个 items 取出来,用 unshift 放进 messageList。聊天消息通常新消息在前,所以这里用 unshift 而不是 push。
易错点:不要直接把整个响应塞进列表;unshift 和 push 的顺序相反;变量名 items 不要写成 iteoms 这类同音错字。

js
// 思路示例
data.forEach(item => {
  messageList.unshift(...item.items)
})

用 v-for 和 v-if 只显示患者信息 ​

为什么需要:列表里可能有多条消息,而当前只处理类型为 21 的患者信息。如果直接把组件写进页面,它会被渲染多次。
怎么想:先遍历消息列表,再判断消息类型,只有 messageType === 21 时才渲染患者信息组件。
怎么做:外层用 template 做 v-for,内层用 v-if 控制。调试时先看列表长度,再看条件是否命中。转写稿里一开始出现“显示三次”的现象,就是因为只写了遍历,没有加类型判断。
易错点:v-if 条件不要写成赋值;key 建议用消息 id;条件数字 21 要和接口字段一致。

html
<template v-for="item in messageList" :key="item.id">
  <patient-info
    v-if="item.messageType === 21"
    :info="item"
  />
</template>

把患者信息抽成独立组件 ​

为什么需要:一个页面里同时写患者信息、通知消息等模板,代码会越来越乱。患者信息适合单独放到 components/patient-info.vue。
怎么想:先建组件文件,再把模板和样式搬进去;组件不是全局注册,使用时必须导入。
怎么做:在页面中 import PatientInfo from './components/patient-info.vue',注册后在模板里使用。父组件把消息对象传给子组件,子组件用 props 接收。转写稿中 info 默认给空对象,是为了避免初始渲染时报错。
易错点:组件名和导入名要一致;props 名字要和父组件传参一致;默认值写空对象,不要直接写 null 后马上取属性。

js
props: {
  info: {
    type: Object,
    default: () => ({})
  }
}

渲染字段:注意数据层级 ​

为什么需要:患者信息里包含姓名、性别、年龄、病情描述、图片等字段,字段不一定都在第一层。
怎么想:先在控制台展开 props.info,确认 patientInfo、name、genderValue、age 等字段的实际位置,再写模板。转写稿里强调“患者信息下面又多了一个层级”,所以取姓名时要注意 props.info.patientInfo.name 这种路径,具体以你传入的是消息对象还是患者对象为准。
怎么做:模板里用插值表达式取字段,长表达式可以换行。
易错点:不要凭感觉写字段;性别、年龄等字段名要看接口;传参层级不同,模板路径也要跟着变。

html
<view>{{ info.patientInfo.name }}</view>
<view>{{ info.patientInfo.genderValue }}</view>
<view>{{ info.patientInfo.age }}</view>

数字状态转文字与查看图片 ​

为什么需要:患病时长、是否就诊这类字段接口给的是数字,但页面要显示“一周内”“一个月内”“就诊过”等文字;图片字段给的是对象数组,不能直接交给预览 API。
怎么想:数字转文字可以定义一个文字数组,用接口数值当下标取;图片先判断有没有,再决定显示“暂无图片”还是“点击查看图片”。
怎么做:模板中用 v-if="pictures.length > 0" 判断;点击时调用 uni.previewImage,但它的 urls 需要纯图片地址数组,所以用 map 把对象数组转成 url 数组。
易错点:previewImage 不能直接传对象数组;map 要返回 item.url;没有图片时不要绑定点击事件或不要显示“查看图片”。

html
<view v-if="info.patientInfo.pictures.length > 0" @click="onPreview">
  点击查看图片
</view>
<view v-else>暂无图片</view>
js
onPreview() {
  const urls = this.info.patientInfo.pictures.map(item => item.url)
  uni.previewImage({ urls })
}

调试时可以先 console.log(urls),确认输出的是 ["xxx.jpg", "xxx.png"] 这样的纯地址数组,再调用预览 API。这样患者信息的数据展开、条件渲染、组件传参、字段渲染和图片预览就串起来了。

本节要解决什么 ​

这一节围绕“问诊室患者信息”做两件事:先把接口返回的深层数据展开成更好用的 messageList,再把类型为 21 的患者信息交给独立组件渲染。随后补充了图片预览、长轮询概念,以及通知类消息组件的复用。学习时不要只盯模板,要先理解数据层级和组件边界。

本节笔记没有独立配图,以下按转写稿整理关键代码;字段名以实际接口返回为准,不能凭记忆硬写。

先展开数据:别让层级拖累模板 ​

为什么需要:接口返回的数据层级较深,真正要用的消息在 date 下的 items 里。如果直接把整段数据塞进列表,模板里就要层层点下去,容易写错,也不方便复用。

怎么想:先遍历 date,把每个 items 拿出来,再展开到 messageList。转写稿里强调用 unshift,因为聊天消息顺序需要倒过来。

怎么做:按转写稿可整理为:

js
// 示意:把 date 中每个 items 展开到 messageList
date.forEach(item => {
  messageList.unshift(...item.items)
})

易错点:items 和 item 别混;unshift 会改变原数组;展开运算符 ... 不能漏。处理后调试面板里 messageList 应直接看到消息本身,而不是还包一层 items。

患者信息组件:先过滤 21,再传 props ​

为什么需要:页面里有很多类型消息,患者信息只对应 21。如果每个消息都渲染患者组件,就会出现多次重复。

怎么想:用 v-for 遍历 messageList,再用 v-if 判断 messageType === 21。转写稿中用 template 包住循环,因为它解析后不产生额外标签。

怎么做:

html
<template v-for="msg in messageList" :key="msg.id">
  <patient-info
    v-if="msg.messageType === 21"
    :info="msg.patientInfo"
  />
</template>

组件内接收:

js
props: {
  info: {
    type: Object,
    default: () => ({})
  }
}

易错点:messageType 不要写成 message.type;props 默认对象要用函数返回;子组件使用前必须 import 并注册。

渲染患者字段:简单字段直接取,数字字段做映射 ​

为什么需要:姓名、性别、年龄可直接渲染,但“一周内”“未去医院就诊”这类内容接口给的是数字,不能直接把数字显示给用户。

怎么想:简单字段从 props.info 往下取;数字字段定义文字数组,用数字当下标映射。

怎么做:

html
<text>{{ info.name }}</text>
<text>{{ info.genderValue }}</text>
<text>{{ info.age }}岁</text>

数字转文字示意:

js
// 以实际字段名为准
const timeText = ['一周内', '一个月内', '三个月内']

模板中按转写稿思路写:

html
<text>{{ timeText[info.illnessTime] }}</text>

易错点:info.patientInfo.name 这类层级要看清;数组下标从 0 开始;不要固定写死 0 或 1,要拿真实数据。

图片有无判断与 previewImage 预览 ​

为什么需要:没有图片时点“查看图片”没有意义,所以要先判断 pictures 是否为空。

怎么想:pictures.length > 0 显示“点击查看图片”,否则显示“暂无图片”;点击时调用 uni.previewImage,但它要求的是纯图片地址数组。

怎么做:

html
<text v-if="info.pictures && info.pictures.length > 0"
      @click="onPreview(info.pictures)">
  点击查看图片
</text>
<text v-else>暂无图片</text>
js
onPreview(pictures) {
  const urls = pictures.map(item => item.url)
  uni.previewImage({ urls })
}

转写稿还演示了可继续简化为:

js
const urls = pictures.map(({ url }) => url)

易错点:previewImage 的 urls 是纯路径数组,不是对象数组;map 要 return,箭头函数简写时别加大括号;字段是 url,不是 uil。

长轮询:作为了解,别和 WebSocket 混用 ​

为什么需要:聊天需要服务器持续给结果,普通 HTTP 一次请求一次响应,不适合实时聊天。

怎么想:优先用 WebSocket;浏览器不支持时,才用定时器不断发请求,这就是长轮询。

怎么做:转写稿用 setInterval 每隔一段时间发请求来保持联系。

易错点:长轮询不是新协议;它性能不如 WebSocket;面试可能问“没有 WebSocket 怎么做聊天”,要能答出定时轮询。

通知消息组件:31/32/33 复用,插槽放内容 ​

为什么需要:普通消息、温馨提示、已取消外观相近,没必要写三个组件。

怎么想:内容用插槽传入;是否显示“温馨提示”由 type 决定,32 才显示;通知类消息类型都 >= 31,可用一个条件过滤。

怎么做:

html
<notify-info
  v-if="msg.messageType >= 31"
  :type="msg.messageType"
>
  {{ msg.content }}
</notify-info>

组件内:

html
<view>
  <text v-if="props.type === 32">温馨提示</text>
  <slot></slot>
</view>
js
props: {
  type: {
    type: Number,
    default: 31
  }
}

易错点:>= 31 能覆盖 31、32、33;插槽内容别忘了在使用组件时写;type 默认值写 31;判断条件建议放模板前部,便于阅读。

代码示例 ​

示例 1:问诊室页面——数据扁平化 + 按 messageType 条件渲染 ​

vue
<!-- pages/room/index.vue -->
<template>
  <view class="room">
    <!-- v-for 用 block 包一层,v-if 不写在 v-for 的同一个标签上 -->
    <block v-for="msg in messageList" :key="msg.id">
      <!-- 患者信息:messageType === 21 -->
      <patient-info
        v-if="msg.messageType === 21"
        :info="msg.patientInfo"
      />

      <!-- 通知消息:31 普通消息 / 32 温馨提示 / 33 已取消,共用一个组件 -->
      <notify-info
        v-if="msg.messageType >= 31"
        :type="msg.messageType"
      >
        {{ msg.content }}
      </notify-info>
    </block>
  </view>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import patientInfo from './components/patient-info.vue'
import notifyInfo from './components/notify-info.vue'

const messageList = ref([])

const getMessageList = async () => {
  const res = await uni.request({ url: 'https://your-api/message/list' })
  // 返回结构形如:[{ items: [...] }, { items: [...] }]
  const list = []
  res.data.forEach((item) => {
    // 把每层的 items 展开,放进同一个数组
    list.unshift(...item.items)
  })
  messageList.value = list
}

onMounted(() => {
  getMessageList()
})
</script>

<style>
.room {
  padding: 24rpx;
  background: #f5f5f5;
}
</style>

要点:

  • 原来直接把整个 res.data 存进列表,层级深(data[i].items[j]);这里遍历 res.data,用 unshift(...item.items) 把真正的消息摊平成一维数组,渲染时只写 msg.xxx。
  • 用 unshift 而不是 push:聊天消息接口返回的顺序是倒的,靠前插入才能保证页面顺序正确。
  • v-for 与 v-if 不写在同一个标签上,用 <block> 包一层,避免两个指令优先级带来的判断错乱。
  • 31 / 32 / 33 有共同规律,用 >= 31 一个条件代替三个 ||,患者信息(21)就不会被误渲染出来。
  • 患者信息组件用 :info="msg.patientInfo" 把子对象传进去,父组件只传数据,不传结构。

示例 2:患者信息组件 patient-info.vue(含数字转文字、预览大图) ​

vue
<!-- pages/room/components/patient-info.vue -->
<template>
  <view class="patient-info">
    <view class="item">
      <text class="label">病情描述</text>
      <text class="value">{{ props.info.illnessDesc }}</text>
    </view>

    <view class="item">
      <text class="label">患者信息</text>
      <text class="value">
        {{ props.info.patientInfo.name }}
        {{ props.info.patientInfo.genderValue }}
        {{ props.info.patientInfo.age }}岁
      </text>
    </view>

    <!-- 接口给的是数字,用数组下标映射成文字 -->
    <view class="item">
      <text class="label">患病时长</text>
      <text class="value">{{ durationText[props.info.illnessTime] }}</text>
    </view>

    <view class="item">
      <text class="label">是否就诊过</text>
      <text class="value">{{ visitText[props.info.visited] }}</text>
    </view>

    <view class="item">
      <text class="label">病情图片</text>
      <text
        v-if="props.info.pictures.length > 0"
        class="value link"
        @click="onPreview(props.info.pictures)"
      >点击查看图片</text>
      <text v-else class="value">暂无图片</text>
    </view>
  </view>
</template>

<script setup>
const props = defineProps({
  info: {
    type: Object,
    // 对象类型的默认值必须写成函数
    default: () => ({})
  }
})

// 数字 -> 文字,下标就是接口返回的数值
const durationText = ['一周内', '一个月内', '半年内', '一年以上']
const visitText = ['未去医院就诊', '已去医院就诊']

const onPreview = (pictures) => {
  // pictures 是对象数组,previewImage 只认纯 url 字符串数组
  const urls = pictures.map(({ url }) => url)
  uni.previewImage({
    urls,
    current: urls[0]
  })
}
</script>

<style>
.patient-info {
  background: #fff;
  border-radius: 12rpx;
  padding: 24rpx;
  margin-bottom: 20rpx;
}
.item {
  display: flex;
  margin-bottom: 16rpx;
}
.label {
  width: 160rpx;
  color: #999;
}
.value {
  flex: 1;
  color: #333;
}
.link {
  color: #1aad19;
}
</style>

要点:

  • props 里用 default: () => ({}),写成 default: {} 会让所有实例共享同一个对象,是常见坑。
  • patientInfo 是 info 下的二级对象,.name / .genderValue / .age 层级不要写漏;患病时长、是否就诊过与 patientInfo 平级,直接在 info 下取。
  • 数字转文字用“数组下标取值”:durationText[props.info.illnessTime],不要写死成 durationText[0]。
  • 预览图片前必须 map 出纯 url 数组:pictures.map(({ url }) => url),直接把对象数组交给 uni.previewImage 是无效的;map 返回新数组,不会改动原数据。
  • 图片判断用 props.info.pictures.length > 0,没图时走 v-else 显示“暂无图片”,否则会出现能点但点不出图的情况。

示例 3:通知消息组件 notify-info.vue(插槽 + 类型判断) ​

vue
<!-- pages/room/components/notify-info.vue -->
<template>
  <view class="notify-info">
    <view class="msg-box">
      <!-- 只有 32(温馨提示)才显示这个前缀 -->
      <text v-if="props.type === 32" class="tips">温馨提示:</text>
      <!-- 具体文字由父组件通过插槽传入 -->
      <slot></slot>
    </view>
  </view>
</template>

<script setup>
const props = defineProps({
  type: {
    type: Number,
    default: 31 // 默认按普通消息处理
  }
})
</script>

<style>
.notify-info {
  text-align: center;
  margin-bottom: 20rpx;
}
.msg-box {
  display: inline-block;
  background: #fff;
  color: #333;
  padding: 12rpx 24rpx;
  border-radius: 8rpx;
  font-size: 26rpx;
}
.tips {
  color: #1aad19;
}
</style>

要点:

  • 31 / 32 / 33 三种状态共用这一个组件:结构完全一样,只是 32 多一段绿色文字,所以没必要拆成三个组件。
  • 文字内容用 <slot></slot> 交给父组件写(&#123;&#123; msg.content &#125;&#125;),组件只负责“长什么样”;父组件不传内容时插槽为空。
  • type 声明为 Number,默认 31;如果在父组件写成 type="32"(字符串),=== 32 就会判断失败,必须用 :type="msg.messageType" 动态绑定。
  • 模板里通过 props.type 判断而不是直接写 type,避免和保留字/局部变量混淆。
  • 样式上“白底黑字”是 31、32、33 的公共表现,绿色文字只加在 .tips 上,不要给整个盒子改颜色。

常见错误 ​

1. 患者信息渲染了三遍

  • 现象:页面上一模一样的患者卡片出现 3 次,或医生/患者消息混着渲染。
  • 原因:v-for 遍历 messageList 时只做了循环,没有做类型过滤,模板本身不判断 messageType。
  • 修法:用 <template v-for> 包一层,内部 v-if="msg.messageType === 21";同理通知消息用 >= 31 一次性覆盖 31/32/33。

2. 组件标签不报错但页面上什么都没有 / 控制台提示未知组件

  • 现象:<patient-info> 写在模板里,浏览器里既没内容也没报错,或者提示 component 未注册。
  • 原因:组件放在 components 目录下属于局部组件,只 import 了没注册,或者注册名与使用名大小写/短横线写法不一致。
  • 修法:import 后在 components: { PatientInfo } 中注册;使用时统一短横线写法 <patient-info>。找组件时用编辑器搜索 find components 定位文件。

3. 模板里取值为 undefined,插值全空

  • 现象:props.info.patientInfo.name 这类长链取值渲染空白,控制台报 Cannot read property of undefined。
  • 原因:后端返回体是 data[].items[],直接把整个返回体当成消息数组用,层级多了一层;或者 props 默认值给的是 null 而不是空对象。
  • 修法:取数据时遍历 data 拿每一条 items,用 unshift 展开成一个扁平数组;props 默认值统一给 {},模板取值前用 v-if 兜底。

4. 患病时长、是否就诊显示成数字 0 / 1 / 2

  • 现象:本该显示"一周内""未去医院就诊",页面上却是数字。
  • 原因:直接把接口返回的枚举数值插值到模板,没有做值到文案的转换。
  • 修法:定义文案数组,用返回的数值做下标取值;取值处加默认值或 || 兜底,避免下标越界拿到 undefined。

5. 点"查看图片"没反应,或只预览一张

  • 现象:调用 previewImage 后什么都没弹出,或只弹出第一张。
  • 原因:直接把 pictures 对象数组传给了 urls,而该 API 只接受纯图片路径字符串数组。
  • 修法:先 map 出 url 字段组成新数组再传入;没有图片时不要绑点击事件,用 v-if / v-else 切换成"暂无图片"文案。

6. 温馨提示的绿字永远不出现

  • 现象:31 和 32 两条通知样式一样,绿字标签丢失。
  • 原因:type 没传或写死成默认值 31,组件内 v-if="type === 32" 永远为假。
  • 修法:使用处动态绑定 :type="msg.messageType",props 里声明 type: { type: Number, default: 31 },改完清缓存刷新验证。

练习 ​

练习 1|组件拆分与条件渲染 把患者信息从问诊室页面抽成独立组件,在列表循环中通过 v-if 控制只渲染 messageType === 21 的那一条,并验证页面上卡片只出现一次。

练习 2|枚举值转文案 写一个工具函数,把患病时长(0/1/2)和是否就诊(0/1)的数值转成中文文案,要求传入非法值时不返回 undefined。

练习 3|图片预览全流程 实现"点击查看图片 → 大图预览",并在没有图片时显示"暂无图片"且不可点击。完成后自己创建一条带图片的问诊订单,验证有图分支也走通。

练习 4|通知消息组件复用 用一个组件承载 31/32/33 三种通知状态,正文用插槽传入,仅当类型为 32 时显示"温馨提示"标签,并在列表循环里用一条条件把 21 的患者信息排除掉。

练习 5|长轮询模拟 用 setInterval 每 500ms 发一次请求模拟长轮询,跑通后用自己的话说明:为什么聊天场景优先用 WebSocket 而不是它。

课堂画面 ​

课堂画面 01

课堂画面 02

课堂画面 03

课堂画面 04

课堂画面 05

课堂画面 06

原视频对照 ​

#视频时长
1优医咨询-问诊室患者信息组件模板06:13
2优医咨询-问诊室患者信息-108:46
3优医咨询-问诊室患者信息-208:32
4什么是长轮循04:47
5优医咨询-查看大图08:11
6优医咨询-问诊室通知消息13:05

小结 ​

  1. 接口返回层级越深,模板取值越容易出错;进组件前先拍平数据结构,是省事不是多事。
  2. v-for 和 v-if 必须成对出现——循环负责"有几条",条件负责"该不该显示",缺一个就会出现重复渲染。
  3. 组件拆分的第一判据是"这块内容会不会被复用、会不会把页面撑乱",患者信息和通知消息都属于该拆的。
  4. 父传子一律走 props,默认值给空对象而不是 null,能挡掉大半的 undefined 报错。
  5. 接口给的是枚举数字,界面要的是中文文案,中间这层转换必须显式做,别指望模板自动翻译。
  6. 调用第三方 API 前先看它要什么格式,previewImage 要的是纯路径数组,对象数组必须 map 一遍。
  7. 组件复用靠"插槽管内容、props 管状态",同类不同款的消息没必要写三个组件。
  8. WebSocket 与长轮询是场景选择问题:能用 WebSocket 就别用定时器轮询,性能差距是实打实的。