主题
药品订单与云函数(一)
本页对应 药品订单与云函数,共 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(),取消则弹层保持打开。
- 验收:点关闭 → 弹确认 → 取消,支付弹层仍在;再点关闭 → 确认,弹层消失。
课堂画面






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