Skip to content

药品订单与云函数(一) ​

本页对应 药品订单与云函数,共 8 段视频,约 1 小时 18 分。

学习目标 ​

  • 能够封装药品订单收货地址、预付订单、待付款订单、支付及订单详情等接口,并在页面中调用后按 code 判断成败、接收数据并给出错误提示。
  • 能够从路由地址中正确读取处方 ID、订单 ID、地址 ID 等参数,并按要求传给对应接口,避免参数名或订单类型混淆。
  • 能够完成预付订单信息与待付款订单的生成流程,并在点击“立即支付”后打开支付组件、传入订单 ID 和支付金额。
  • 能够使用支付渠道组件监听 close 与 confirm 事件,根据支付方式索引调用支付接口,并配置支付成功后的回调地址。
  • 能够根据支付接口返回结果完成 H5 跳转,并能说明小程序支付需调用 wx.requestPayment 等支付能力。
  • 能够获取并渲染支付结果页和药品订单详情页数据,完成金额展示、订单状态映射及“查看订单”“返回诊室”等页面跳转。

知识点 ​

一、先理清药品订单的业务链路 ​

为什么需要:药品订单不是孤立页面,它从问诊、开处方、选药、预付确认一路走来。如果不先理清链路,就容易把“预付订单”“待付款订单”“支付订单”混在一起。

怎么想:本节的核心链路是:问诊室拿到处方 ID → 进入药品订单页 → 获取收货地址 → 生成预付订单并展示金额/药品 → 点击“立即支付”生成待付款订单 → 打开支付渠道组件 → 选择支付宝/微信 → 调支付接口 → 跳支付结果页 → 支付结果页再查订单详情。

怎么做:在药品分包下的 payment 页面操作,注意不要走到问诊订单的 payment。支付组件沿用之前封装好的全局组件,通过 open 打开、close 关闭、confirm 接收确认支付事件。

易错点:两个 payment 页面容易走错;问诊订单 ID 和药品订单 ID 都叫“订单 ID”,但含义不同,传参和接收参数名必须对齐。

二、封装地址接口并渲染收货地址 ​

为什么需要:药品是线上购买后邮寄,页面顶部必须展示收货人、手机号、省市区和详细地址。项目没有独立地址管理模块,但后端提供了默认地址接口。

怎么想:这是一个普通接口调用套路:先封装 API,再在页面中 await 调用,判断 code,把 data 存入 ref,最后模板渲染。

怎么做:新建 address.js,封装地址列表接口,转写中接口路径为 patient/order/address;页面中定义 getAddressList,调用 addressListAPI,成功后将地址数据放入 addressList。转写中特别提醒:接口返回的是数组,且只有一条测试地址,为了使用方便可取 data[0] 接收为 addressInfo,再渲染 receiver、mobile、city、country、addressDetail 等字段。

易错点:忘记导入 ref;把数组直接当对象用;字段名写错,如 receiver 不是 receivever,addressDetail 不要拼错。

三、生成预付订单:先拿处方 ID ​

为什么需要:预付订单用于让用户确认“要买哪些药、实际支付多少钱”,所以必须根据处方 ID 查询药品信息。

怎么想:页面跳转时要携带处方 ID。转写中是在问诊室 room 页面跳转药品组件时拼接 ?id=处方ID,处方 ID 在 prescriptionInfo.id 下,而药品自身 ID 在 medicine 下面,不能混。

怎么做:在药品 payment 页面用 defineProps 接收地址参数,参数名必须和地址栏一致;再封装 medicine.js 中的预付订单 API。调用时用 await,判断 code !== 10000 就提示并 return;成功后把返回数据存入 preOrderInfo,模板渲染药品、实际支付金额、优惠券折扣、运费等。

易错点:转写中第一次调试就报“处方 ID 没有传”,原因是只取了 ID 却没传给接口;另外调用方法时忘记 return,会导致接口通了但拿不到结果,出现 code 未定义。

四、接入支付组件与待付款订单 ​

为什么需要:用户点“立即支付”时不能直接假定支付成功,而要先形成待付款订单,再弹出支付渠道。这样即使用户取消支付,订单状态也已经生成。

怎么想:支付组件需要 orderId 和 amount。但此时还没有待付款订单 ID,所以先给组件一个临时值保证正常加载;等点击按钮生成待付款订单后,再用真实 ID 打开组件。

怎么做:在“立即支付”按钮上绑定方法,调用 createOrderAPI,参数为处方 ID 和地址 ID。处方 ID 来自地址参数,地址 ID 来自 addressInfo.value.id。成功后通过支付组件 ref 调 open() 打开支付渠道。

易错点:地址 ID 不要漏 value;createOrderAPI 有两个同名/近名 API 时,要确认选的是 medicine 下的;待付款订单成功后才打开支付组件,顺序不能反。

五、支付参数与跳转 ​

为什么需要:确认支付时要告诉后端支付方式、订单 ID 和支付成功后的回跳地址。

怎么想:支付组件 confirm 事件会带回渠道索引 index,0 表示微信、1 表示支付宝、2 表示云闪付。转写中当前项目不支持微信支付,所以 index === 0 要提示“暂不支持微信支付”。

怎么做:监听 @close 调组件 close();监听 @confirm 接收 index,调用 orderPayAPI,传 paymentMethod、orderId、payCallback。orderId 来自生成待付款订单后的 data.id。成功后判断环境:H5 用 window.location.href = data.payUrl 跳转;小程序仅示意 wx.requestPayment。

易错点:orderId 要先接收待付款订单返回的 ID;支付回调地址要指向 payResult 页面;不要编造微信支付可用,本节明确当前不支持。

六、支付结果页与订单详情 ​

为什么需要:支付成功后要展示实付金额,并提供“查看订单”“返回诊室”。

怎么想:支付结果页通过地址参数拿药品订单 ID,再调用药品订单详情接口 orderDetailAPI。转写中该接口需要拼 ID,封装为 GET 请求。

怎么做:在 medicine.js 增加 orderDetailAPI,页面用 defineProps 接收 orderId,await 调用后判断 code,成功把数据存入 orderDetail,模板渲染 actual 金额。返回诊室要拼 roomId,但接收页面参数名是 orderId,所以地址栏参数名要写 orderId。

易错点:转写中曾因 ref 未导入报错;返回诊室时两个“订单 ID”容易混,传参名必须按接收方写;订单详情页同样要确认调用的是药品订单详情 API,不是问诊订单的。

七、调试与易错点复盘 ​

本节调试反复出现三类问题:一是接口参数漏传,如处方 ID;二是方法忘记 return,导致拿不到接口结果;三是 ref 未导入或变量名拼错。排查时先看 Network 请求是否发出、参数是否正确,再看控制台报错和接口返回的 code/message。地址接口返回数组只有一条时,用 data[0] 简化渲染,但要在心里明确它仍是数组。支付流程则牢记:先生成待付款订单,再打开支付组件,最后调支付接口并跳转结果页。

一、获取收货地址 ​

为什么需要: 药品是线上购买、邮寄到家的,所以订单页顶部必须展示收货地址。当前项目没有独立的地址管理模块,但后端提供了默认地址接口,前端直接调用即可。

怎么想: 这就是一次普通接口调用:先封装 API,再在页面里调用,拿到数据后渲染。注意地址接口返回的是数组,且通常只有一条测试地址,为了模板使用方便,直接取第一项。

怎么做: 新建 service/address.js,封装地址列表接口;在药品分包下的 payment 页面中定义 getAddressList,调用接口并把 res.data[0] 存入 addressList;模板中渲染收货人、手机号、省市区和详细地址。

js
// service/address.js
export const addressListAPI = () => request.get('/patient/order/address')

// 页面中
const addressList = ref([])
const getAddressList = async () => {
  const res = await addressListAPI()
  if (res.code !== 10000) return uni.$showMsg(res.message)
  addressList.value = res.data[0]
}

易错点: 忘记手动导入 ref;接口失败没有提示;地址是数组却直接当对象用;走错 payment 分包——问诊订单和药品订单都有 payment,这里必须是药品分包下的页面。

二、生成预付订单信息 ​

为什么需要: 预付订单是让用户确认“买哪些药、实付多少钱”的环节,确认后再进入支付。要生成它,必须先拿到处方 ID,因为处方决定了药品清单。

怎么想: 处方 ID 从问诊室跳转时通过地址参数带过来;在药品订单页面用 onLoad 接收;再封装药品模块的预付订单接口,调用后接收 preOrderInfo 并渲染。

怎么做: 跳转时拼 ?id=${proInfo.id};页面中 const pro = ref(null),onLoad 里取 options.id;封装 createPreOrderAPI,注意 get 请求参数通过 params 传递;调用成功后 preOrderInfo.value = res.data;模板渲染药品、实付金额、优惠券折扣和运费。

js
// service/medicine.js
export const createPreOrderAPI = (id) =>
  request.get('/medicine/preOrder', { params: { id } })

// 页面中
const pro = ref(null)
onLoad((options) => { pro.value = options.id })
const preOrderInfo = ref(null)
const createPreOrder = async () => {
  const res = await createPreOrderAPI(pro.value)
  if (res.code !== 10000) return uni.$showMsg(res.message)
  preOrderInfo.value = res.data
}

易错点: get 参数不要写到第二个参数上,必须放 params;两个 payment 分包不要选错;两个预付订单 API 不要选错,要选 medicine 下的;处方 ID 和药品 ID 要区分;调用接口忘记传参,后端会直接报“处方 ID 没有传”。

三、支付渠道组件与待付款订单 ​

为什么需要: 用户点“立即支付”时,不能直接付款,而是先生成待付款订单,拿到订单 ID 后才能调支付接口;同时要弹出支付渠道让用户选择微信或支付宝。

怎么想: 支付组件之前已经封装成全局组件,符合 easycom 规范,直接引入即可。它暴露了 open 和 close 方法,并支持 close、confirm 事件。点击“立即支付”先调生成待付款订单接口,成功后再打开弹层。

怎么做: 封装 createOrderAPI,传处方 ID 和地址 ID;点击按钮时调用,成功后保存返回的订单 ID,再 paymentRef.value.open();监听 close 调用 paymentRef.value.close(),监听 confirm 处理支付。

js
const paymentRef = ref(null)
const orderId = ref('')
const createOrder = async () => {
  const res = await createOrderAPI(pro.value, addressInfo.value.id)
  if (res.code !== 10000) return uni.$showMsg(res.message)
  orderId.value = res.data.id
  paymentRef.value.open()
}

易错点: 两个 createOrderAPI 不要选错,要选 medicine 下的;地址 ID 要从 addressInfo.value.id 取;先有订单再开弹层;open 是打开,close 是关闭,不要混。

四、支付与支付结果 ​

为什么需要: 用户确认支付后要调后端支付接口,成功后跳转结果页展示实付金额,并支持查看订单和返回诊室。

怎么想: 支付接口需要三个参数:支付方式、订单 ID、支付成功回调地址。H5 用 window.location.href 跳转,小程序调 wx.requestPayment;当前项目不支持微信支付,只做提示。结果页再调订单详情接口拿实付金额。

怎么做: confirm 事件解构 index 作为 paymentMethod;微信支付提示暂不支持;支付宝支付成功后跳 payUrl;结果页封装 orderDetailAPI,用地址参数 orderId 调接口,渲染 actualPay;查看订单拼药品订单 ID,返回诊室拼问诊订单 ID。

js
const onConfirm = async (index) => {
  if (index === 0) return uni.$showMsg('暂不支持微信支付')
  const res = await orderPayAPI({
    paymentMethod: index,
    orderId: orderId.value,
    payCallback: '支付成功后的结果页地址'
  })
  if (res.code !== 10000) return uni.$showMsg(res.message)
  window.location.href = res.data.payUrl
}

易错点: 调用方法忘记 return 结果,导致 code 未定义;两个订单 ID 要区分——药品订单 ID 和问诊订单 ID;地址参数名要和接收时一致,否则取不到;支付结果页地址参数叫 orderId,跳转拼参时不要写错。

先确认页面:药品支付页在 medicine 分包 ​

为什么需要:问诊订单和药品订单都有 payment,页面长得像但业务不同,走错目录后面接口全错。怎么想:先按业务分包定位,药品支付在 medicine 下的 payment。怎么做:只在这个页面里写地址、预付订单、待付款订单和支付逻辑。易错点:转写稿反复提醒“两个 payment 不要走错”,不要凭文件名直接打开。

获取收货地址:封装接口并取数组第一项 ​

为什么需要:药品是线上购买后邮寄,必须展示收货人、手机号、省市区和详细地址。怎么想:地址模块虽未做,但后端提供 patient/order/address,页面调用后渲染即可。怎么做:新建 address.js 封装接口,页面用 ref 接收数据。转写稿里接口返回的是数组且只有一条测试地址,所以取 data[0] 方便模板使用。

js
// service/address.js
export const addressListApi = () => http.get('/patient/order/address')

// 页面
const addressList = ref([])
const addressInfo = ref()
const getAddressList = async () => {
  const res = await addressListApi()
  if (res.code !== 10000) return uni.$u.toast(res.message)
  addressList.value = res.data
  addressInfo.value = res.data[0]
}

易错点:ref 要手动导入;判断 code !== 10000 后要 return,否则继续执行;模板里不要把数组当对象用,优先用 addressInfo。

处方ID从问诊室路由带入 ​

为什么需要:预付订单要知道“买哪些药”,必须根据处方ID查询。怎么想:从问诊室 room 跳药品组件时,把处方ID拼到地址上。怎么做:跳转地址加 ?id=处方ID,支付页按同名参数接收。易错点:药品ID不是处方ID;地址参数叫什么,接收时就叫什么,不能随意改名。

js
// room 跳转
url: `/medicine/payment?id=${prescriptionInfo.id}`

// 支付页按项目写法接收
const props = defineProps({ id: String })

生成预付订单并渲染药品与金额 ​

为什么需要:预付订单让用户确认药品、实付金额、优惠和运费,确认后再支付。怎么想:用处方ID调 preOrderApi,成功后把 data 存到 preOrderInfo。怎么做:接口封装在 medicine.js,GET 参数通过 params 传。

js
const preOrderInfo = ref()
const getPreOrder = async () => {
  const res = await preOrderApi({ id: props.id })
  if (res.code !== 10000) return uni.$u.toast(res.message)
  preOrderInfo.value = res.data
}

易错点:GET 参数不要直接塞到第二个参数;preOrderApi 有问诊和药品两个,必须选 medicine 下的;金额字段按接口返回的实际支付金额渲染。

待付款订单:先成单,再打开支付弹层 ​

为什么需要:用户点“立即支付”时,要先形成待付款订单,拿到订单ID后才能支付。怎么想:按钮触发 createOrder,参数是处方ID和地址ID;成功后保存 res.data.id,再打开支付组件。怎么做:

js
const orderId = ref('')
const createOrder = async () => {
  const res = await createOrderApi({
    id: props.id,
    addressId: addressInfo.value.id
  })
  if (res.code !== 10000) return uni.$u.toast(res.message)
  orderId.value = res.data.id
  paymentRef.value.open()
}

易错点:地址ID在 addressInfo.value.id,不要漏 value;转写稿调试时曾忘记传处方ID,接口直接报“处方ID没有传”;顺序是先成单再开弹层。

支付组件:ref、金额和事件 ​

为什么需要:支付渠道组件已封装,药品支付复用它。怎么想:传订单ID和金额,监听关闭与确认事件。怎么做:

html
<payment
  ref="paymentRef"
  :order-id="orderId"
  :amount="preOrderInfo?.actualPayment"
  @close="onClose"
  @confirm="onConfirm"
/>

易错点:订单ID还没生成时,转写稿先给占位值保证组件加载;金额用预付订单里的实际支付金额;关闭时调用 paymentRef.value.close()。

支付接口:支付方式、订单ID、回调地址 ​

为什么需要:真正支付由后端接口返回跳转地址或小程序支付参数。怎么想:确认事件带回支付渠道索引,0微信、1支付宝、2云闪付;当前不支持微信支付要提示。怎么做:

js
const onConfirm = async (index) => {
  if (index === 0) return uni.$u.toast('暂不支持微信支付')
  const res = await orderPayApi({
    paymentMethod: index,
    orderId: orderId.value,
    payCallback: '支付结果页地址'
  })
  if (res.code !== 10000) return uni.$u.toast(res.message)
  // H5 跳转 res.data.payUrl;小程序走 requestPayment
}

易错点:参数名按接口文档;H5和小程序跳转方式不同;支付接口在 payment 下,方法名转写稿里是 orderPayApi,不要和备课名混掉。

支付结果与订单详情:用 return 补上调试坑 ​

为什么需要:支付成功后展示实付金额,并能查看订单、返回问诊室。怎么想:支付结果页调用药品订单详情接口,地址参数是订单ID。怎么做:

js
const getOrderDetail = async () => {
  const res = await orderDetailApi({ id: props.orderId })
  if (res.code !== 10000) return uni.$u.toast(res.message)
  orderDetail.value = res.data
}

调试过程:转写稿中接口已调通但金额不显示,控制台提示 code 未定义;原因是封装方法里 await http.get(...) 后没有 return res,页面拿不到返回值。补上 return 后金额正常。易错点:orderDetailApi 有问诊和药品两个,选 medicine;返回问诊室时传的是问诊订单ID,接收参数名仍要叫 orderId;地址参数名必须前后一致。

代码示例 ​

示例一:接口封装(service 层) ​

js
// src/service/address.js
import request from '@/utils/request'

// 获取收货地址列表(项目暂无地址管理模块,后端只返回测试地址)
export const getAddressListAPI = () => {
  return request.get('/patient/order/address')
}
js
// src/service/medicine.js
import request from '@/utils/request'

// 药品预付订单信息:需要处方 ID
export const preOrderAPI = (id) => {
  return request.get(`/patient/medicine/order/pre/${id}`)
}

// 生成待付款订单:处方 ID + 收货地址 ID
export const createOrderAPI = (data) => {
  return request.post('/patient/medicine/order', data)
}

// 药品订单详情:路径参数拼 ID
export const orderDetailAPI = (id) => {
  return request.get(`/patient/medicine/order/detail/${id}`)
}
js
// src/service/payment.js(此前已封装,此处仅确认名字,勿重复定义)
import request from '@/utils/request'

// 订单支付:paymentMethod 0-微信 1-支付宝 2-银联
export const orderPayAPI = (data) => {
  return request.post('/patient/order/pay', data)
}

关键行与易错点

  • GET 参数必须走 params(或直接拼在 URL 上),绝不能把参数对象当 request.get(url, params) 的第二个参数直接传——这是最基本语法问题,转写稿里专门强调过。
  • 每个方法必须 return 请求结果。转写稿中曾因漏写 return,接口明明 200 通了,页面却报 code is not defined——调用处拿不到返回值就解构不出 code/data/message。
  • 同名 API 极易导入错:问诊订单和药品订单都有 preOrderAPI / createOrderAPI,导入时要确认是 service/medicine.js 里的那个。
  • 页面里 ref、onMounted 忘导入是最常见的低级错误,转写稿里就踩过两次。

示例二:药品订单支付页(获取地址 + 预付订单 + 待付款 + 支付组件) ​

vue
<!-- src/pages/medicine/payment/index.vue -->
<template>
  <div class="medicine-payment">
    <!-- 1. 收货地址:接口只返回一条,已取 data[0] -->
    <div class="address">
      <p class="region">
        {{ addressInfo.province }}{{ addressInfo.city }}{{ addressInfo.county }}
      </p>
      <p class="detail">{{ addressInfo.addressDetail }}</p>
      <p class="user">
        <span>收货人:{{ addressInfo.receiver }}</span>
        <span>{{ addressInfo.mobile }}</span>
      </p>
    </div>

    <!-- 2. 药品清单 -->
    <ul class="medicine-list">
      <li class="medicine-item" v-for="item in preOrderInfo.medicines" :key="item.id">
        <img class="avatar" :src="item.avatar" alt="" />
        <div class="info">
          <p>{{ item.name }}</p>
          <p class="specs">{{ item.specs }}</p>
        </div>
        <span class="price">¥{{ item.price }}</span>
      </li>
    </ul>

    <!-- 3. 金额明细 -->
    <div class="amount">
      <p><span>药品金额</span><span>¥{{ preOrderInfo.totalMoney }}</span></p>
      <p><span>优惠券折扣</span><span>-¥{{ preOrderInfo.couponMoney }}</span></p>
      <p><span>运费</span><span>¥{{ preOrderInfo.expressFee }}</span></p>
      <p class="real"><span>实付金额</span><span>¥{{ preOrderInfo.actualPayment }}</span></p>
    </div>

    <!-- 4. 底部操作栏 -->
    <div class="toolbar">
      <span class="total">合计:¥{{ preOrderInfo.actualPayment }}</span>
      <button class="pay-btn" @click="createOrder">立即支付</button>
    </div>

    <!-- 5. 支付渠道弹层(全局组件,已按 easycom 规范注册) -->
    <Payment
      ref="paymentRef"
      :order-id="orderId"
      :amount="preOrderInfo.actualPayment"
      @close="onClose"
      @confirm="onConfirm"
    />
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { getAddressListAPI } from '@/service/address'
import { preOrderAPI, createOrderAPI } from '@/service/medicine'
import { orderPayAPI } from '@/service/payment'

// 处方 ID:由问诊室跳转时拼在地址上带过来,名字必须和跳转处一致
const props = defineProps({
  id: { type: String, required: true }
})

const addressInfo = ref({})   // 收货地址
const preOrderInfo = ref({})  // 预付订单信息
const orderId = ref('')       // 待付款订单 ID
const paymentRef = ref(null)  // 支付组件引用

// 1. 获取收货地址
const getAddressList = async () => {
  const { data, code, message } = await getAddressListAPI()
  if (code !== 10000) {
    uni.showToast({ title: message, icon: 'none' })
    return
  }
  // 接口返回的是数组,取第 0 项,模板里就不用再写 addressList[0]
  addressInfo.value = data[0]
}

// 2. 生成预付订单信息
const createPreOrder = async () => {
  const { data, code, message } = await preOrderAPI(props.id)
  if (code !== 10000) {
    uni.showToast({ title: message, icon: 'none' })
    return
  }
  preOrderInfo.value = data
}

// 3. 点击立即支付:先生成待付款订单,成功后再弹出支付渠道
const createOrder = async () => {
  const { data, code, message } = await createOrderAPI({
    id: props.id,                       // 处方 ID
    addressId: addressInfo.value.id     // 收货地址 ID,别丢 .value
  })
  if (code !== 10000) {
    uni.showToast({ title: message, icon: 'none' })
    return
  }
  orderId.value = data.id   // 待付款订单 ID,后面支付要用
  paymentRef.value.open()   // 打开支付组件
}

// 4. 关闭支付组件
const onClose = () => {
  paymentRef.value.close()
}

// 5. 确认支付:index 即支付方式下标
const onConfirm = async (index) => {
  if (index === 0) {
    uni.showToast({ title: '暂不支持微信支付', icon: 'none' })
    return
  }
  const { data, code, message } = await orderPayAPI({
    paymentMethod: index,
    orderId: orderId.value,
    payCallback: 'http://localhost:5173/#/medicine/pay-result'
  })
  if (code !== 10000) {
    uni.showToast({ title: message, icon: 'none' })
    return
  }
  // H5 直接跳转后端返回的支付地址
  window.location.href = data.payUrl
}

onMounted(() => {
  getAddressList()
  createPreOrder()
})
</script>

<style lang="scss" scoped>
.medicine-payment { padding-bottom: 60px; background: #f5f6f7; }
.address { padding: 12px; background: #fff; margin-bottom: 8px; }
.address .region { font-size: 16px; font-weight: 600; }
.address .detail { margin: 6px 0; color: #666; font-size: 13px; }
.address .user { display: flex; justify-content: space-between; font-size: 13px; color: #999; }
.medicine-list { list-style: none; padding: 0 12px; background: #fff; }
.medicine-item { display: flex; align-items: center; padding: 10px 0; border-bottom: 1px solid #eee; }
.medicine-item .avatar { width: 60px; height: 60px; border-radius: 6px; object-fit: cover; }
.medicine-item .info { flex: 1; margin-left: 10px; }
.medicine-item .specs { font-size: 12px; color: #999; }
.amount { margin-top: 8px; padding: 12px; background: #fff; }
.amount p { display: flex; justify-content: space-between; font-size: 13px; line-height: 26px; color: #666; }
.amount .real { color: #ff5b3a; font-weight: 600; }
.toolbar { position: fixed; left: 0; right: 0; bottom: 0; height: 50px; display: flex;
  align-items: center; justify-content: flex-end; padding: 0 12px; background: #fff; }
.toolbar .total { color: #ff5b3a; font-weight: 600; margin-right: 12px; }
.toolbar .pay-btn { padding: 8px 20px; border: none; border-radius: 20px; background: #16c2a3; color: #fff; }
</style>

关键行与易错点

  • 地址接口返回的是数组,且只有一条。必须 addressInfo.value = data[0],否则模板里到处写 addressList[0].xxx,很别扭也容易漏判空。
  • 处方 ID 靠地址参数传递。跳转时要写成 `/medicine/payment?id=${preOrderInfo.id}`,接收端 defineProps 的名字必须是 id——转写稿反复强调"地址里叫啥名,这儿就写啥名"。
  • 调接口忘传参:转写稿第一次跑 createPreOrder 报"处方ID没有传",原因就是只 props.id 读了却没传进去,必须 preOrderAPI(props.id)。
  • addressInfo.value.id 的 .value 不能丢,ref 忘了 .value 传给后端就是 undefined。
  • 顺序不能反:必须先 createOrderAPI 成功拿到 data.id,再 paymentRef.value.open()。预付款只是让用户确认信息,点"立即支付"这一步才生成待付款订单。
  • 支付方式映射:0 微信 / 1 支付宝 / 2 银联,本项目不支持微信支付,所以要提前拦截 index === 0。
  • payCallback 是支付成功后的跳转地址,三个参数都是必传。

示例三:支付结果页(金额展示 + 返回诊室 / 查看订单) ​

vue
<!-- src/pages/medicine/pay-result/index.vue -->
<template>
  <div class="pay-result">
    <div class="result-icon">✔</div>
    <p class="result-text">支付成功</p>
    <p class="result-amount">¥{{ orderDetail.actualPayment }}</p>

    <div class="btn-group">
      <button class="btn ghost" @click="goRoom">返回诊室</button>
      <button class="btn primary" @click="goDetail">查看订单</button>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { orderDetailAPI } from '@/service/medicine'

// 支付回调地址上带的订单 ID,参数名必须与跳转时一致
const props = defineProps({
  orderId: { type: String, required: true }
})

const orderDetail = ref({})

// 获取药品订单详情
const getOrderDetail = async () => {
  const { data, code, message } = await orderDetailAPI(props.orderId)
  if (code !== 10000) {
    uni.showToast({ title: message, icon: 'none' })
    return
  }
  orderDetail.value = data
}

// 返回诊室:参数名叫 orderId,但值要传 roomId(问诊订单 ID)
const goRoom = () => {
  window.location.href = `/room?orderId=${orderDetail.value.roomId}`
}

// 查看订单:这里传的才是药品订单 ID
const goDetail = () => {
  window.location.href = `/medicine/order-detail?id=${orderDetail.value.id}`
}

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

<style lang="scss" scoped>
.pay-result { padding: 60px 20px; text-align: center; background: #f5f6f7; min-height: 100vh; }
.result-icon { width: 64px; height: 64px; line-height: 64px; margin: 0 auto 16px;
  border-radius: 50%; background: #16c2a3; color: #fff; font-size: 32px; }
.result-text { font-size: 18px; font-weight: 600; }
.result-amount { margin: 12px 0 40px; font-size: 26px; color: #ff5b3a; font-weight: 700; }
.btn-group { display: flex; gap: 12px; justify-content: center; }
.btn { flex: 1; max-width: 140px; padding: 10px 0; border-radius: 22px; font-size: 14px; }
.btn.ghost { border: 1px solid #16c2a3; background: #fff; color: #16c2a3; }
.btn.primary { border: none; background: #16c2a3; color: #fff; }
</style>

关键行与易错点

  • defineProps 里的 orderId 必须和支付回调地址上的参数名一致,写错就取不到值,接口会报"订单 ID 不存在"。
  • 两个 orderId 含义不同,最容易踩坑:返回诊室时地址参数仍叫 orderId,但值必须传 orderDetail.roomId(问诊订单 ID);查看订单传的才是 orderDetail.id(药品订单 ID)。转写稿里就因为这个"同名不同义"掉进坑里。
  • 金额取 actualPayment(实付金额),不要取药品总金额。
  • 该页同样有 ref 未导入导致报错的坑,import { ref, onMounted } from 'vue' 别漏。
  • 订单状态文案可用映射对象处理(10 待支付、11 待发货……),不要写一长串 v-if。

常见错误 ​

1. 支付结果页金额空白,控制台报 code is not defined

  • 现象:网络面板里订单详情接口 200 返回正常,但页面取不到数据、模板报变量未定义。
  • 原因:封装函数里 await orderDetailAPI(id) 前漏写 return,调用方 const { code, data } = await getOrderDetail(id) 解构到的是 undefined。
  • 修法:凡是会被页面 await 的封装函数必须有返回值(return await ... 或先存 const res = await ...; return res);页面侧解构前先确认封装层有 return。

2. 生成预付订单接口报"处方ID不能为空"

  • 现象:接口能发出,但后端返回参数校验失败。
  • 原因:已经从路由里 const props = route.query 拿到了 id,调用 API 时却没把 props.id 作为实参传进去(只写了 createPreOrderAPI())。
  • 修法:调用处逐个对照接口文档的必填参数清单勾选;封装函数签名里保留形参位,传参与形参数量对不上时立刻警觉。

3. 调到了同名的另一个接口,返回数据莫名其妙

  • 现象:参数、URL 都对,但返回结构对不上预期。
  • 原因:项目里问诊订单与药品订单各有一套 preOrderAPI / createOrderAPI / payOrderAPI,编辑器补全默认选中了第一个。
  • 修法:导入时确认路径在 medicine 分包下;关键 API 用别名导入(如 createMedicineOrderAPI)避免歧义。

4. 支付弹层右上角关闭按钮点了没反应

  • 现象:弹层能弹出,无法关闭。
  • 原因:只调了子组件暴露的 open(),没有监听子组件 emit 的 close 事件,自然也没有在回调里调 close()。
  • 修法:模板上 @close="onClose",回调里 payRef.value.close();如需"放弃支付"二次确认,在这个回调里加。

5. 地址渲染模板写得很别扭或直接报错

  • 现象:模板里要写 v-for 或取 .value[0] 才能出数据。
  • 原因:接口返回的是数组(且仅一条),赋值时没取 [0],变量语义与数据形状不匹配。
  • 修法:赋值处 addressInfo.value = data[0],模板里按对象使用;变量命名与数据结构保持一致,别在模板里做形状转换。

6. 从支付结果页"返回诊室"后聊天页拿不到 ID

  • 现象:跳转成功,但目标页 route.query 取不到值或取到 undefined。
  • 原因:拼参时用的名字和目标页接收时用的名字不一致(药品订单叫 orderId,问诊订单叫 roomId),两个都是"订单ID"但来源不同。
  • 修法:拼参前先去目标页确认取值名,两边严格一致;不同来源的订单 ID 用不同变量名区分,不要都叫 orderId。

7. H5 下点微信支付毫无反应

  • 现象:选微信支付后不跳转、不报错。
  • 原因:当前项目未接入微信支付,仍按正常流程发了支付请求。
  • 修法:在确认支付回调里先按 index === 0 拦截并 toast 提示"暂不支持微信支付"后 return,不要发请求。

练习 ​

练习 1|封装层补全 在 service 下补全 getAddressList、createPreOrder、createOrder、payOrder、getOrderDetail 五个封装。写完后自查两件事:每个函数的形参是否与接口文档必填项一一对应;每个函数是否有返回值。

  • 验收:随便挑一个在页面里 await 并解构 { code, data, message },能正常打印。

练习 2|支付组件三处接线 在药品订单支付页引入支付渠道组件,完成 open / close / confirm 三处接线。

  • 验收:① 点"立即支付"生成待付款订单成功后弹层出现;② 点右上角能关闭;③ 选支付宝点确认能发出支付请求并拿到跳转地址。

练习 3|排错训练 下面这段代码"接口通了但页面没数据",请找出至少 3 处问题并说明为什么:

js
async function getOrderDetail(id) {
  const res = await orderDetailAPI(id)
}
const { code, data } = await getOrderDetail()
orderDetail.value = data
  • 验收:说出"漏 return""漏传参""未判断 code 就赋值"分别会导致什么现象。

练习 4|参数名对齐 支付结果页有两个跳转:查看订单、返回诊室。分别写出:拼什么参数名、值从返回数据的哪个字段取、目标页 route.query 里用什么名字接收。

  • 验收:两条跳转都能正常落地,目标页能取到 ID 并发出对应请求。

练习 5|交互增强(选做) 在支付组件的 close 回调里加"确认放弃支付?"二次确认弹窗,用户确认后才真正 close(),取消则弹层保持打开。

  • 验收:点关闭 → 弹确认 → 取消,支付弹层仍在;再点关闭 → 确认,弹层消失。

课堂画面 ​

课堂画面 01

课堂画面 02

课堂画面 03

课堂画面 04

课堂画面 05

课堂画面 06

原视频对照 ​

#视频时长
1优医咨询-药品订单获取收货地址07:07
2优医咨询-生成预付订单信息10:10
3优医咨询-药品订单支付组件06:38
4优医咨询-药品订单待付款09:10
5优医咨询-支付组件配置09:19
6优医咨询-药品订单支付宝支付08:38
7优医咨询-药品订单支付结果17:24
8优医咨询-药品订单详情数据获取10:30

小结 ​

  1. 这一串页面的核心套路只有一条:封装接口 → 页面 await 调用 → 判 code → 赋值给 ref → 模板渲染。任何一步"看起来做了但没效果",先按这五步回查。
  2. 封装函数是"有返回值"的约定方,return 漏写是本次最容易踩且最难肉眼发现的坑——接口通不代表数据到得了页面。
  3. 参数传递有三道关口,缺一不可:路由跳转时拼参 → 目标页 route.query 接收(名字必须与拼参一致)→ 调接口时作为实参传入。本次的两个"处方ID没传"问题都出在第二、三道关口。
  4. 同名 API 是这个项目的结构性风险(问诊/药品各一套),选错不会有明显报错,只会"数据不对"。靠路径确认 + 别名导入来兜底。
  5. 订单存在三种形态且顺序固定:预付订单(确认信息)→ 待付款订单(拿到真实 orderId)→ 支付。支付组件所需 orderId 必须在待付款订单生成之后才有值,之前只能用占位值保证组件可渲染。
  6. 支付本身是后端的事,前端只做三件事:传对三个参数(支付方式、订单ID、回调地址)、成功后按端类型分流(H5 跳 location.href,小程序走 requestPayment)、不支持的渠道提前拦截提示。
  7. 两个"订单ID"必须分清:问诊订单 ID 用于回诊室聊天,药品订单 ID 用于查订单详情。混用不会报错,只会跳到错误的地方或查不到数据。
  8. 交互组件(弹层)的使用闭环是 open + 事件监听 + close,只做 open 不做事件监听,用户就被困在弹层里了。