Skip to content

病情描述与问诊室布局(二) ​

本页对应 病情描述与问诊室布局,共 7 段视频,约 1 小时 30 分。

学习目标 ​

  1. 能够基于预支付信息和患者信息完成问诊支付页的条件渲染,并在数据未加载完成时展示骨架屏占位效果。
  2. 能够在预支付信息加载后记录优惠券 ID,并在生成订单成功后清理本地问诊信息、记录当前订单 ID。
  3. 能够使用动作面板实现支付抽屉,完成支付协议校验、支付方式选择、金额展示、按钮 loading,并调用生成订单 API。
  4. 能够实现支付页用户引导:阻止已生成订单后的路由回退、阻止关闭支付抽屉并弹出确认提示、刷新后校验问诊信息完整性并提示重新填写。
  5. 能够定义获取支付地址的 API,校验支付方式后发起请求并跳转第三方支付平台,同时根据回调结果处理支付成功进入问诊室、支付失败重定向到问诊记录页。
  6. 能够拆分问诊室的状态栏、操作栏和消息卡片组件,并组装到问诊室页面中完成基础布局展示。

知识点 ​

本节要完成的三件事 ​

本节在“极速问诊-问诊支付”页面完成三件事:基础渲染、记录优惠券 ID、加载骨架屏。转写稿中反复强调一个前提:页面里的金额、患者、病情描述都来自异步接口,不能一进页面就直接渲染。为什么需要先处理这个前提?因为 payInfo 和 patient 未加载完时直接取字段,轻则页面出现 undefined,重则模板报错。怎么想?把“数据是否就绪”作为渲染开关。怎么做?在外层容器上加条件渲染,两个数据都存在时才展示真实内容,否则展示骨架屏。易错点:v-if 与 v-else 必须相邻;不要把 v-if 只加在某个金额上,否则页面结构会闪。

vue
<div v-if="payInfo && patient" class="consult-pay">
  <!-- 真实支付信息 -->
</div>
<ConsultPaySkeleton v-else />

基础渲染:金额、患者与病情描述 ​

为什么需要逐字段映射?因为后台返回的是预支付信息,不是页面文案。怎么想?先对照接口字段,再决定展示位置;金额要注意单位。转写稿里提到应付款、优惠券抵扣、积分抵扣、实付款,以及患者姓名、性别、年龄、病情描述。怎么做?应付款取 payInfo.payment;优惠券抵扣取 payInfo 中 coupon 开头字段;积分抵扣取 p 开头字段;实付款取 a 开头字段。患者信息取 patient.name、patient.genderValue、patient.age;病情描述从本地仓库 store.consult.illnessDesc 取。易错点:转写稿说“实付金额单位是分,2900 展示 29 元”,又说“乘以 100 转成分”,这里要纠正:若后台实付款以“分”返回,页面展示应除以 100;只有提交给后台需要“分”时,才把“元”乘以 100。不要在同一处既乘又除。

vue
<p>应付款:{{ payInfo.payment }}</p>
<p>优惠券抵扣:{{ payInfo.couponDeduction }}</p>
<p>积分抵扣:{{ payInfo.pointsDeduction }}</p>
<p>实付款:{{ payInfo.actualPayment }}</p>

<p>患者:{{ patient.name }} {{ patient.genderValue }} {{ patient.age }}岁</p>
<p>病情描述:{{ store.consult.illnessDesc }}</p>
<p>合计:{{ (payInfo.actualPayment / 100).toFixed(2) }} 元</p>

复选框也需要数据支撑。转写稿中新增 agree,用 ref(false) 创建,模板绑定 v-model="agree",默认不勾选。易错点:忘记声明 agree 会导致模板编译或运行异常。

记录优惠券 ID ​

为什么需要记录?因为后台可能在预支付信息里返回优惠券 ID,将来提交生成订单时要把 consult 里的问诊信息整体提交,带优惠券的订单才能生效。怎么想?优惠券 ID 不属于页面展示,而属于“本次问诊的本地仓库状态”,所以应放在 store/modules/consult.ts 中。怎么做?在仓库提供 setCouponId(id: string),把 consult.value.couponId 设为传入 ID,并 return 出去;在组件获取预支付信息成功后调用它。易错点:字段名不要写成转写稿同音的 componId,应统一为 couponId;调用时机要在预支付信息返回之后。

ts
// store/modules/consult.ts
const setCouponId = (id: string) => {
  consult.value.couponId = id
}

return { consult, setCouponId }
ts
// consult-pay.vue 获取预支付信息后
store.setCouponId(res.data.couponId)

骨架屏:降低等待焦虑 ​

为什么需要?网络慢时页面会空白,用户不知道系统是否在工作。怎么想?在真实数据未就绪时展示一个与页面结构相近的灰色占位,而不是白屏。怎么做?使用 Wot Design Uni 提供的骨架屏组件,转写稿中把行数 row 设得大一些,并加顶部间距;外层仍保留顶部导航和“支付”标题,内容区用骨架屏占位。易错点:骨架屏要放在 v-else 分支;margin-top 建议用 18rpx 这类项目统一单位;不要给骨架屏再套 v-if="payInfo",否则它永远不会在数据未就绪时出现。

vue
<template v-else>
  <wd-navbar title="支付" />
  <wd-skeleton :row="1" custom-class="pay-skeleton" />
</template>

<style scoped>
.pay-skeleton {
  margin-top: 18rpx;
}
</style>

调试时,转写稿先正常刷新,发现骨架屏“一闪而过”,因为网络较快;随后把浏览器网络调成低速 3G,再走“选择患者→下一步→支付页”,就能明显看到骨架屏先出现、数据回来后切换为真实内容。这个调试过程说明:骨架屏不是静态装饰,而是条件渲染的另一分支。最后检查三个易错点:金额单位是否统一、couponId 是否写入仓库、v-if/v-else 是否覆盖加载前与加载后两种状态。

一、支付页渲染与优惠券记录 ​

为什么需要:支付页依赖预支付信息 payInfo 和患者信息 patient。如果数据没回来就渲染,网络慢时会出现 undefined,甚至报错;同时后台可能返回优惠券 ID,必须记录到本地 consult,生成订单时才能带上优惠券。

怎么想:先做条件渲染,等两份数据都到位再展示;再把优惠券 ID 存进仓库,供提交订单使用;最后用骨架屏兜住等待期。

怎么做:模板外层加 v-if="payInfo && patient";应付款、优惠券抵扣、积分抵扣、实付款从 payInfo 取;实付金额展示时按分转换,转写稿中写的是 实付款 * 100;患者信息绑定 patient.name、patient.genderValue、patient.age;病情描述取 store.consult.illnessDesc;协议勾选用 agree。优惠券则在仓库 consult.ts 增加 setCouponId(id: string),把 consult.value.couponId = id,获取预支付信息后调用。骨架屏用 van-skeleton,放在 v-else 中,并加 margin-top 拉开间距。

ts
const agree = ref(false)
// 获取预支付信息后
store.setCouponId(res.data.couponId)

易错点:不要提前渲染金额和患者信息;实付金额单位是分,展示前要转换;优惠券 ID 要存进 consult,不是只放在组件里;骨架屏应覆盖“数据未完成”的状态。调试时把网络调成低速 3G,可以看到骨架屏从一闪而过变得明显。

二、支付流程与生成订单 ​

为什么需要:前端在支付流程里主要做两件事:先生成问诊订单,再拿订单 ID、支付方式、回调地址去换加密支付地址。没有订单 ID,就无法生成支付地址。

怎么想:点击“立即支付”时先校验是否勾选协议,再请求生成订单;成功后清空本地问诊信息并记录 orderId,然后打开支付抽屉选择微信/支付宝。

怎么做:用 action-sheet 控制支付抽屉,show 控制显隐;agree 为 false 时用轻提示“请勾选我同意支付协议”并阻止;生成订单 API 接收 store.consult,成功后 store.clear() 清空本地问诊信息,并 orderId.value = res.data.id。支付方式按接口约定:0 微信、1 支付宝,类型写 ref<0 | 1>();微信/支付宝的 checkbox 用 checked 控制选中,不用 v-model,因为这里只需展示选中状态。

ts
const paymentMethod = ref<0 | 1>()
const orderId = ref('')
const loading = ref(false)

易错点:要在打开抽屉前生成订单;生成成功后必须清空 consult,否则重复提交;必须记录 orderId;请求期间给按钮加 loading;抽屉关闭按钮可先隐藏,但灰色遮罩仍能关闭,后面要用 before-close 拦。调试时打开应用本地存储,能看到生成订单后 consult 被清空。

三、用户引导:阻止回退与关闭抽屉 ​

为什么需要:用户生成订单后如果直接回退或关闭抽屉,可能放弃支付。运营上希望尽量让用户留在当前页完成交易。

怎么想:回退由路由控制,用路由守卫拦;关闭抽屉由组件控制,用 before-close 拦;如果用户坚持离开,再引导到问诊记录页继续支付。

怎么做:使用 onBeforeRouteLeave,有 orderId 时返回 false;抽屉加 close-on-popstate="false",避免历史切换时自动关闭;加 closeable="false" 隐藏关闭按钮;绑定 before-close,返回 showConfirmDialog。确认框“继续支付”时返回 false 阻止关闭;“狠心离开”时先清空 orderId,再跳 /user/consult,最后返回 true 允许关闭。

ts
onBeforeRouteLeave(() => {
  if (orderId.value) return false
})

易错点:跳转问诊记录前一定要清空 orderId,否则路由守卫会永远拦截;before-close 可以返回 Promise;确认框按钮文案要区分“继续支付”和“狠心离开”。调试时点击灰色遮罩,确认框出现;点继续支付抽屉不关,点狠心离开跳到 user/consult。

四、刷新校验与完成支付 ​

为什么需要:生成订单后本地 consult 已清空,用户刷新页面时组件初始化会拿空信息请求预支付信息,必然失败。支付完成后还要根据回调参数判断成功/失败。

怎么想:刷新时先校验生成订单所需字段是否完整;不完整就提示并回首页。支付时判断是否选择支付方式,拿支付地址后跳转;回调到 /room,失败则重定向到问诊记录。

怎么做:准备 validKeys: (keyof PatientConsult)[],包含问诊类型、问诊级别、科室、疾病描述、患病时间、是否就诊过、患者 ID;用 every(k => store.consult[k] !== undefined) 校验。不完整时弹确认框,确认后跳 /。完成支付时判断 paymentMethod 是否为空,showLoadingToast({ duration: 0 }),调用 getConsultOrderPayUrl(orderId, paymentMethod, payCallback),成功后 window.location.href = res.data.payUrl。回调地址设为 /room,路由 beforeEnter 中判断 to.query.payResult === 'false',重定向 /user/consult。

ts
beforeEnter: (to) => {
  if (to.query.payResult === 'false') return '/user/consult'
}

易错点:query 参数是字符串,比较时用 'false';支付失败要能回到问诊记录继续支付;loading 的 duration 设为 0 才能持续显示到跳转;刷新校验不通过时直接 return,不要再发预支付请求。调试时支付失败直接返回,回跳地址会进入 user/consult,说明拦截生效。

本节目标与前置理解 ​

本节承接“问诊支付流程”的梳理,只做两件事中的第一件:生成问诊订单。转写稿中明确,支付流程前端要做“生成订单”和“生成支付地址”两步;本节先完成生成订单,为后续根据订单 ID、支付方式、回调地址生成支付地址做准备。转写稿未提供可插入的图片资源,以下不编造图片;代码按转写稿中的关键片段整理,接口地址以接口文档为准。

控制支付抽屉的显示与隐藏 ​

为什么需要:点击“立即支付”后,需要让用户选择微信或支付宝,不能直接跳走。
怎么想:用 action-sheet 做支付抽屉,用 show 控制显示隐藏;打开前必须校验“我同意支付协议”。
怎么做:在页面底部放支付抽屉,绑定 v-model:show="show",标题改为“选择支付方式”;在 submit 中先判断 agree,未勾选则轻提示并 return,勾选后再打开抽屉。
易错点:未勾选协议不能打开抽屉;不要只改样式不拦截逻辑;show 默认应为 false。
调试时转写稿先不勾选点“立即支付”,出现“请勾选我同意支付协议”;勾选后可打开、隐藏抽屉。

ts
const show = ref(false)
const agree = ref(false)

const submit = () => {
  if (!agree.value) {
    // 轻提示:请勾选我同意支付协议
    return
  }
  show.value = true
}

选择支付方式并记录 paymentMethod ​

为什么需要:后续生成支付地址要提交支付方式,后台约定 0 代表微信、1 代表支付宝。
怎么想:用一个数据记录当前选择,点击微信记 0,点击支付宝记 1;复选框只展示选中态,不需要 v-model 收集。
怎么做:定义 paymentMethod = ref<0 | 1>();微信项点击赋 0,支付宝项赋 1;checkbox 用 :checked="paymentMethod === 0"、:checked="paymentMethod === 1" 控制。
易错点:不要把 0/1 写反;不要用布尔值代替后台约定;类型应为 0 | 1 联合类型。转写稿调试时点击微信、支付宝,选中态能来回切换。

定义生成订单 API ​

为什么需要:打开抽屉前要先拿到订单 ID,后续支付地址需要它。
怎么想:提交本地 store.consult 问诊信息,后台返回订单 ID。
怎么做:在 consult.ts 中定义 createConsultOrder,参数类型用 PatientConsult,request.post 提交 data,响应中取 id: string。
易错点:接口地址不要手写猜测,按接口文档复制;请求方式为 POST;返回的是对象里的 id,不是整个响应。

ts
export const createConsultOrder = (data: PatientConsult) => {
  return request.post('/按接口文档复制的地址', data)
}

请求时序、loading 与打开抽屉 ​

为什么需要:生成订单有网络耗时,若先打开抽屉再请求,用户可能在没有订单 ID 时继续操作。
怎么想:点击“立即支付”先发请求,成功后再打开抽屉;请求期间按钮 loading。
怎么做:loading.value = true 后 await createConsultOrder(store.consult),成功后记录 orderId.value = res.data.id,清理 store.consult,再 show.value = true;最后 loading.value = false。
易错点:loading 要在请求前开启、结束后关闭;不要漏掉清理本地问诊信息;不要漏记订单 ID。转写稿用低速 3G 调试,确认先出现 loading,请求结束后抽屉才打开。

清理 consult 与记录 orderId ​

为什么需要:问诊信息已提交后台并归入订单,本地不再需要保存;后续生成支付地址又必须使用订单 ID。
怎么想:仓库提供 clean 函数,把 consult 置为空对象;组件用 orderId 保存返回的 ID。
怎么做:store.clean() 清理;orderId = ref(''),成功后赋 res.data.id。调试时在开发者工具中观察 consult 被清空、组件上 orderId 有值。
易错点:清理和记录顺序不要乱;后续跳转问诊记录前要注意 orderId 会影响路由拦截,这属于后续用户引导内容,本节先保证订单 ID 被正确记录。

代码示例 ​

1. 支付页渲染 + 骨架屏 + 支付抽屉(consult-pay.vue 模板) ​

vue
<template>
  <div class="consult-pay-page">
    <cp-nav-bar title="支付" />

    <!-- 预支付信息:必须等 payInfo 与 patient 都加载完成才渲染 -->
    <div v-if="payInfo && patient" class="pay-info">
      <p class="amount">
        <span class="symbol">¥</span><span class="num">{{ payInfo.payment }}</span>
      </p>
      <p class="tips">问诊费用</p>

      <div class="prepay">
        <p class="item">
          <span>优惠券抵扣</span><span>-¥{{ payInfo.couponAmount }}</span>
        </p>
        <p class="item">
          <span>积分抵扣</span><span>-¥{{ payInfo.pointsAmount }}</span>
        </p>
        <p class="item">
          <span>实付款</span><span>¥{{ payInfo.actualAmount }}</span>
        </p>
      </div>
    </div>

    <!-- 骨架屏:两份数据任意一份没就绪就展示占位,降低等待焦虑 -->
    <div v-else class="pay-skeleton">
      <van-skeleton :row="1" style="margin-top: 18px" />
    </div>

    <div v-if="payInfo && patient" class="patient-card">
      <p>{{ patient.name }} {{ patient.genderValue }} {{ patient.age }}岁</p>
      <p>病情描述:{{ store.consult.illnessDesc }}</p>
    </div>

    <div class="pay-footer">
      <van-checkbox v-model="agree">同意支付协议</van-checkbox>
      <p>合计:¥{{ payInfo?.actualAmount }}</p>
      <van-button type="primary" :loading="loading" @click="submit">立即支付</van-button>
    </div>

    <!-- 支付方式抽屉 -->
    <van-action-sheet
      v-model:show="show"
      title="选择支付方式"
      :closeable="false"
      :close-on-popstate="false"
      :before-close="onClose"
    >
      <div class="pay-sheet">
        <p class="amount">¥{{ payInfo?.actualAmount.toFixed(2) }}</p>
        <div class="pay-type" @click="paymentMethod = 0">
          <span>微信支付</span>
          <van-checkbox :model-value="paymentMethod === 0" />
        </div>
        <div class="pay-type" @click="paymentMethod = 1">
          <span>支付宝支付</span>
          <van-checkbox :model-value="paymentMethod === 1" />
        </div>
        <van-button type="primary" block @click="pay">立即支付</van-button>
      </div>
    </van-action-sheet>
  </div>
</template>

要点 / 易错点

  • v-if="payInfo && patient" 是核心:两份数据缺一份就渲染会报错或出现 undefined,所以外层用 v-if、骨架屏用 v-else,二者互斥。
  • 金额单位易错:接口返回的是「元」,模板直接展示;提交/展示「分」时才 * 100,别在渲染时又乘一次。
  • paymentMethod 取值必须按接口约定:0 微信、1 支付宝,不要用 'wx' / 'ali'。
  • 复选框用 :model-value 而非 v-model:这里只需展示选中态,不需要收集勾选值。
  • :close-on-popstate="false" 必须加,否则用户点浏览器回退时抽屉会被自动关闭。

2. store 记录优惠券 / 清理数据 + 生成订单、支付地址 API ​

ts
// stores/modules/consult.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { PartialConsult } from '@/types/consult'

export const useConsultStore = defineStore('consult', () => {
  const consult = ref<PartialConsult>({})

  // 记录优惠券 ID(预支付信息返回后调用)
  const setCouponId = (id: string) => {
    consult.value.couponId = id
  }

  // 生成订单成功后清理本地问诊信息
  const clear = () => {
    consult.value = {}
  }

  return { consult, setCouponId, clear }
})
ts
// api/consult.ts
import request from '@/utils/request'
import type { PartialConsult } from '@/types/consult'

/** 生成问诊订单 */
export const createConsultOrder = (data: PartialConsult) => {
  return request.post<{ id: string }>('/patient/consult/order', data)
}

/** 获取问诊订单支付地址 */
export const getConsultOrderPayUrl = (data: {
  orderId: string
  paymentMethod: 0 | 1
  payCallback: string
}) => {
  return request.post<{ payUrl: string }>('/patient/consult/pay', data)
}

要点 / 易错点

  • consult 用 ref 包一层,clear() 里赋 {} 即可;直接改 consult.value = null 会让模板里 store.consult.illnessDesc 报错。
  • 优惠券 ID 必须先落到 store,生成订单时整个 consult 一起提交,才能生成带优惠券的订单。
  • 生成订单成功之后才 clear(),顺序反了会导致提交空数据。
  • paymentMethod 类型写成 0 | 1 联合类型,避免传错值。

3. 生成订单 + 用户引导 + 完成支付(consult-pay.vue 脚本) ​

vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter, onBeforeRouteLeave } from 'vue-router'
import { showToast, showDialog, showConfirmDialog, showLoadingToast } from 'vant'
import { useConsultStore } from '@/stores'
import type { PartialConsult } from '@/types/consult'
import { getConsultOrderPre, getPatientDetail, createConsultOrder, getConsultOrderPayUrl } from '@/api/consult'

const store = useConsultStore()
const router = useRouter()

const payInfo = ref<PayInfo>()
const patient = ref<Patient>()
const agree = ref(false)
const show = ref(false)
const loading = ref(false)
const paymentMethod = ref<0 | 1>()
const orderId = ref('')

// 1. 生成订单 → 清理本地数据 → 记录订单 ID → 打开抽屉
const submit = async () => {
  if (!agree.value) return showToast('请勾选我同意支付协议')
  loading.value = true
  try {
    const res = await createConsultOrder(store.consult)
    store.clear()
    orderId.value = res.data.id
    show.value = true
  } finally {
    loading.value = false
  }
}

// 2. 生成支付地址并跳转第三方
const pay = async () => {
  if (paymentMethod.value === undefined) return showToast('请选择支付方式')
  showLoadingToast({ message: '跳转支付', duration: 0, forbidClick: true })
  const res = await getConsultOrderPayUrl({
    orderId: orderId.value,
    paymentMethod: paymentMethod.value,
    payCallback: 'http://localhost:5173/room'
  })
  window.location.href = res.data.payUrl
}

// 3. 阻止返回上一页
onBeforeRouteLeave(() => {
  if (orderId.value) return false
})

// 4. 阻止关闭抽屉
const onClose = () => {
  return showConfirmDialog({
    title: '温馨提示',
    message: '取消支付将无法获取医生的回复,医生的接诊名额有限,您确认是否真的关闭?',
    cancelButtonText: '狠心离开',
    confirmButtonText: '继续支付'
  })
    .then(() => false) // 继续支付:阻止关闭
    .catch(() => {
      orderId.value = '' // 必须清空,否则下面的路由跳转会被 onBeforeRouteLeave 拦住
      router.push('/user/consult')
      return true
    })
}

// 5. 刷新后校验信息完整性
type Key = keyof PartialConsult
const validateKeys: Key[] = [
  'illnessType', 'illnessLevel', 'depId', 'illnessDesc',
  'illnessTime', 'consultFlag', 'patientId'
]

onMounted(async () => {
  const valid = validateKeys.every((key) => store.consult[key] !== undefined)
  if (!valid) {
    showDialog({
      title: '温馨提示',
      message: '问诊记录的信息不完整,请重新填写。如果您有未支付的订单,可以在问诊记录页面继续支付',
      closeOnPopstate: false
    }).then(() => router.push('/'))
    return
  }

  const res = await getConsultOrderPre(store.consult)
  payInfo.value = res.data
  // 预支付信息返回优惠券 ID 时记录下来
  if (res.data.couponId) store.setCouponId(res.data.couponId)

  const p = await getPatientDetail(store.consult.patientId!)
  patient.value = p.data
})
</script>

要点 / 易错点

  • loading 用 try/finally 复位,否则接口报错按钮会一直转圈。
  • onBeforeRouteLeave 里只有「已生成订单(orderId 有值)」时才 return false;跳转问诊记录前必须先清空 orderId,否则永远跳不出去。
  • onClose 返回 Promise:resolve(false) 阻止关闭,reject 才真正关闭;before-close 支持 Promise 是这里能弹确认框的前提。
  • 校验用 validateKeys.every(key => store.consult[key] !== undefined),Key = keyof PartialConsult 提供类型提示,防止字段名拼错。
  • showDialog 需配 closeOnPopstate: false,避免回退时提示框被关掉;校验不通过要 return,不要继续发请求。
  • showLoadingToast 的 duration: 0 表示常驻,跳转后自然消失;跳转用 window.location.href,不要用 router.push(第三方外链)。

常见错误 ​

  1. 现象:支付页出现 undefined、金额空白或直接报错。
    原因: payInfo、patient 未加载完成就渲染模板。
    修法: 用 v-if 同时判断两个数据源已就绪;必要时配合可选链和默认值,数据完成后再渲染主体。

  2. 现象:生成订单后优惠券没有带上。
    原因: 预支付信息返回后未把 couponId 写入 consult 仓库,或提交订单前未确认该字段。
    修法: 在 store 提供设置优惠券 ID 的方法,预支付信息成功后立即记录;生成订单前检查 consult 中是否包含该字段。

  3. 现象:连续点击“立即支付”产生重复请求或重复订单。
    原因: 请求期间按钮未禁用,loading 只置真未复位。
    修法: 请求前开启 loading,用 finally 复位;按钮绑定 loading/disabled,成功打开抽屉前避免重复提交。

  4. 现象:正常返回或跳转问诊记录时被拦截,页面像“卡死”。
    原因: 路由守卫只要发现 orderId 存在就 return false,离开前未清空订单 ID。
    修法: 只在需要阻止的场景拦截;跳转问诊记录前先清空/重置 orderId,或增加显式放行标志。

  5. 现象:点击遮罩关闭支付抽屉时没有提示,或提示后仍关不掉。
    原因: before-close 返回 Promise 时,对“继续支付”和“狠心离开”的返回值处理错误。
    修法: 继续支付返回 false 阻止关闭;狠心离开先清空 orderId、再跳转问诊记录,并返回 true 允许关闭。

  6. 现象:刷新支付页后仍发起预支付请求,或提示不准确。
    原因: 只判断 consult 是否为空,未校验生成订单所需字段是否完整;字段列表有遗漏。
    修法: 维护必填字段 key 列表,逐项判断是否为 undefined;不完整时直接提示并 return,阻断后续请求。

  7. 现象:支付失败后没有进入问诊记录页。
    原因: 回调地址 query 参数是字符串,却直接与布尔值 false 比较。
    修法: 按接口约定判断字符串值,如 payResult === 'false',再重定向到问诊记录页。

  8. 现象:问诊室消息卡片错位、内容展示不全。
    原因: 组件拆分后遗漏 room.scss 导入或父容器布局样式。
    修法: 补齐样式文件和父级布局样式,再逐类检查患者卡片、通知、文字/图片、处方等消息容器。

练习 ​

  1. 为“立即支付”按钮实现防重复提交:写出 loading 状态、按钮绑定方式,并说明请求异常时如何复位。

  2. 给定 consult 对象,设计一个必填字段完整性校验方案;要求信息不完整时提示用户并阻止预支付请求继续执行。

  3. 设计支付抽屉的 before-close 逻辑:分别处理“继续支付”和“狠心离开”,说明 orderId 清空与路由跳转的先后顺序。

  4. 支付回调地址携带 payResult=true/false,请写出进入问诊室路由前的失败拦截思路,并说明类型判断注意点。

  5. 问诊室页面拆分后出现消息卡片展示不全,请列出至少 4 个排查步骤。

课件图示 ​

课件第23页:极速问诊-问诊支付-用户引导 阻止返回上页 阻止关闭抽屉 刷新页面后提示 知识:

课件第17页:极速问诊-问诊支付-完成渲染 完成渲染 设置优惠券 骨架屏效果

课件第21页:极速问诊-问诊支付-生成订单 控制抽屉 生成订单API 实现生成订单 清理&记录

课件第19页:极速问诊-问诊支付-支付流程 问诊前台 问诊后端 问诊信息 订单ID 订单ID

课件第15页:极速问诊-问诊支付-获取数据 路由与组件 预支付类型 预支付API 患者详情AP

课件第25页:极速问诊-问诊支付-进行支付 支付地址API 获取支付地址&跳转支付 处理支付失

原视频对照 ​

#视频时长
1极速问诊-问诊支付-完成渲染12:19
2极速问诊-问诊支付-支付流程06:11
3极速问诊-问诊支付-生成订单17:13
4极速问诊-问诊支付-用户引导-01-阻止回退和关闭抽屉13:37
5极速问诊-问诊支付-用户引导-02-刷新后校验信息完整性12:30
6极速问诊-问诊支付-完成支付15:27
7问诊室-组件拆分页面布局13:28

小结 ​

  1. 支付页渲染必须以数据就绪为前提,骨架屏解决等待体验,不替代数据校验。
  2. 优惠券 ID 属于订单上下文,必须在预支付成功后进入本地仓库,并随订单提交。
  3. 生成订单与打开支付抽屉存在先后和防重关系,loading 与按钮状态是流程正确性的一部分。
  4. 用户引导必须保留退出路径:阻止回退、阻止关闭抽屉时,要同时处理继续支付和离开后的跳转。
  5. 刷新后的关键不是“弹提示”,而是校验生成订单所需字段完整性,并阻断无效预支付请求。
  6. 支付跳转依赖订单 ID、支付方式、回调地址三要素;回调结果决定成功进问诊室、失败进问诊记录。
  7. 问诊室页面应先拆状态栏、操作栏、消息卡片,再汇总到页面组件;样式和禁用态要随组件一起迁移。
  8. 代码提交应按功能粒度拆分,便于回滚、定位支付链路问题和后续联调。