小程序开发日记①:快递与外卖下单流程

1215 字
6 分钟
小程序开发日记①:快递与外卖下单流程

小程序最核心的动作是下单。项目支持快递和外卖两类配送,它们共享收货人、船舶和位置数据,但订单来源、货品字段、预计时间与图片凭证不同。

1. 两个页面,一套订单状态仓库#

页面分别位于:

  • pages/order/courier-form.vue:快递订单;
  • pages/order/takeaway-form.vue:外卖订单;
  • store/modules/order.js:两类订单的表单状态与 payload 构造;
  • store/modules/recipient.js:收货人与船舶地址。

没有强行把两张表单做成几十个条件分支的“万能页面”,而是共享数据模型和组件。页面保留各自业务表达,仓库负责复用。

2. 快递订单模型#

快递侧包含快递公司、快递单号、重量、体积和备注:

function buildCourierPayload(recipient) {
return normalizeOrderPayload({
...(editingOrderId.value ? { orderId: editingOrderId.value } : {}),
serviceType: 'EXPRESS',
expressCompanyName: courierCompany.value,
expressNo: courierNo.value,
cargoWeightKg: packageWeight.value,
cargoVolume: includeVolume.value ? Number(packageVolume.value) : null,
cargoRemark: courierRemark.value,
vesselName: recipient.shipName,
mmsi: recipient.shipMmsi,
receiverName: recipient.recipientName,
receiverPhone: recipient.recipientPhone,
regionId: recipient.regionId,
shipLongitude: recipient.longitude,
shipLatitude: recipient.latitude
})
}

重量必须大于 0,长宽高只有填写时才成组校验。可选字段不要用空字符串冒充有效数字,否则后端可能得到 0、NaN 或错误体积。

3. 外卖订单模型#

外卖侧包含平台、平台订单号、骑手电话、预计到达时间和备注:

function buildTakeawayPayload(recipient) {
return normalizeOrderPayload({
serviceType: 'TAKEOUT',
sourcePlatformName: takeawayPlatform.value,
sourceOrderNo: takeawayOrderNo.value,
riderPhone: riderPhone.value,
expectedArriveTime: buildTodayDateTime(arriveTime.value),
cargoRemark: takeawayRemark.value,
...buildRecipientFields(recipient)
})
}

预计时间选择器会限制最早可选时间,并把页面的时、分组合成后端需要的完整日期时间。不能只提交 13:40,否则服务端无法判断是哪一天。

4. 收货地址与服务范围#

下单页面优先读取默认地址,并把地址簿记录填入 recipientStore。提交前除了校验姓名、手机号、MMSI 和经纬度,还会检查坐标是否在服务区域内。

选择/读取默认地址
↓
校验收货信息完整性
↓
检查经纬度是否落在服务范围
↓
构造订单 payload

服务范围校验应该使用经纬度,而不是地址文本。两个名字相似的港区可能相距很远,文字无法替代空间关系。

5. 图片凭证上传#

订单截图和货品照片使用不同凭证类型:

await DeliveryApi.uploadProof(filePath, {
orderId,
proofType: DELIVERY_PROOF_TYPES.ORDER_PHOTO,
sortNo: index + 1
})

提交顺序是先创建订单,拿到 orderId 后再上传图片。上传任务可以并行,但只有全部完成才提示成功:

const response = await OrderApi.createOrder(payload)
const orderId = response.data?.orderId || response.data
await uploadProofFiles(orderId)

6. 新增、修改与重新提交#

同一表单支持三种模式:

模式判断条件接口
新增无 editingOrderIdcreate
修改有订单 IDupdate
驳回重提resubmit=1resubmit

编辑时通过 fillFromOrder 回显数据,并下载已有凭证为临时地址。仓库额外记录 existingOrderPhotoFiles 和 existingGoodsPhotoFiles,避免未变更的远程图片被重复上传。

const shouldUpload = (filePath, existingFiles) => {
return isResubmitting.value || !existingFiles.includes(filePath)
}

删除已有图片时,则调用删除凭证接口并重新加载当前凭证集合,保持服务端和页面一致。

7. 防重复提交#

按钮的 loading 与 submitting 同时作为视觉反馈和逻辑锁:

if (submitting.value) return
submitting.value = true
try {
// 创建或更新订单,再上传凭证
} finally {
submitting.value = false
}

单靠按钮变灰不够,网络慢时用户可能连续触发事件,逻辑层仍需兜底。

8. 本篇小结#

下单失败如何保留现场#

接口失败后不能调用 reset(),用户选择的地址、订单号和图片都应保留。只有创建与凭证上传全部成功,才清理 Store 并跳转订单页。如果订单创建成功但图片上传失败,页面需要提示“订单已创建,凭证上传失败”,并允许进入编辑模式补传,不能再次创建一张重复订单。

页面返回时的状态边界#

从地址簿或地图选点返回属于下单流程的一部分,表单状态必须保留;从提交成功页重新发起下单,则需要干净的新表单。为此,Store 的清理动作由明确业务事件触发,而不是粗暴地写在每次 onUnload 中。

联调时我按“必填字段—服务范围—订单创建—凭证上传—成功跳转”逐段制造失败,确认每一步的按钮状态和用户输入都合理。这比只测一次顺利下单更能暴露流程问题。

下单流程表面是填表,实际串起了地址、空间校验、订单状态和图片凭证。两个页面既不能复制粘贴到失控,也不能抽象成谁都看不懂的超级表单。最后我找到的平衡是:业务页面各说各话,共用仓库在背后默契配合。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
小程序开发日记①:快递与外卖下单流程
https://firefly.cuteleaf.cn/posts/mmp-dev-diary-1/
作者
Yore
发布于
2026-08-08
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Yore
Hello, I'm Yore.
公告
欢迎来到Yore的博客!本站基于原创作者夏叶的博客网站模版创作。
分类
标签
最新动态
站点统计
文章
51
分类
2
标签
124
总字数
94,115
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
EdgeOne Pages
博客版本
Firefly v6.15.0
文章许可
CC BY-NC-SA 4.0