微信小程序实战案例 - 餐馆点餐系统 阶段 3 - 下单 & 云数据库

🚀 阶段 3 -- 下单 & 云数据库

目标:

  1. 把购物车内容写入 orders 集合,生成唯一订单号
  2. 用云函数保证写入安全
  3. 实现确认订单页 & 下单成功页(仅使用原生控件)
  4. 下单成功后清空购物车,并打 Git Tag v3.0-order

1. 核心知识点

知识点 关键 API/概念 说明
云函数事务 db.runTransaction 原子写多条记录,防止并发冲突
云函数安全 context.OPENID 用服务端鉴权写入 userId
数据结构 嵌套数组 vs. 子表 小项目直接在 orders.items 存数组即可
唯一订单号 Date.now() + 随机串 避免与支付单号冲突,保持可读性
UX 流程 确认页 → 下单中 Loading → 成功页 提升用户体验

2. 🧱 数据结构设计(orders 集合)

json 复制代码
{
  "_id": "auto",
  "orderNo": "OD20250410-123456",
  "userId": "openid_xxx",
  "items": [
    { "dishId": "abc123", "name": "宫保鸡丁", "price": 18, "count": 2 },
    { "dishId": "def456", "name": "酸辣汤",   "price": 10, "count": 1 }
  ],
  "totalPrice": 46,
  "status": "PENDING",
  "createdAt": 1712710000000
}

3. ✅ 云函数:addOrder

目录结构
复制代码
cloudfunctions/addOrder/
  └── index.js
index.js
js 复制代码
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()

exports.main = async (event, context) => {
  console.log('接收到的参数:', event)
  const { cartItems, totalPrice } = event
  const { OPENID } = cloud.getWXContext()

  if (!cartItems || cartItems.length === 0) {
    return { ok: false, msg: '购物车为空' }
  }

  const orderNo = `OD${new Date().toISOString().slice(0,10).replace(/-/g,'')}-${Date.now().toString().slice(-6)}-${Math.random().toString(36).substr(2,3)}`

  try {
    await db.collection('orders').add({
      data: {
        orderNo,
        userId: OPENID,
        items: cartItems,
        totalPrice,
        status: 'PENDING',
        createdAt: Date.now()
      }
    })
    return { ok: true, orderNo }
  } catch (e) {
    console.error('订单添加失败:', e)
    return { ok: false, msg: '下单失败' }
  }
}

4. ✅ 页面:确认订单页(pages/confirm/index)

index.wxml
xml 复制代码
<view class="page">
  <block wx:for="{{list}}" wx:key="_id">
    <view class="item">
      <text class="name">{{item.name}}</text>
      <text class="count">x{{item.count}}</text>
      <text class="price">¥{{item.price}}</text>
    </view>
  </block>

  <view class="total">
    共 {{totalCount}} 件,总计 ¥{{totalPrice}}
  </view>

  <button type="primary" bindtap="onSubmit">提交订单</button>
</view>
index.wxss
css 复制代码
.page {
  padding: 20rpx;
}
.item {
  display: flex;
  justify-content: space-between;
  margin-bottom: 12rpx;
  background: #fff;
  padding: 16rpx;
  border-radius: 8rpx;
}
.name {
  font-weight: bold;
}
.count, .price {
  color: #555;
}
.total {
  font-size: 30rpx;
  font-weight: bold;
  margin: 20rpx 0;
  text-align: right;
}
button {
  width: 100%;
  height: 88rpx;
  font-size: 32rpx;
}
index.js
js 复制代码
const cart = require('../../store/cart')

Page({
  data: {
    list: [],
    totalCount: 0,
    totalPrice: 0
  },

  onLoad() {
    const items = Object.values(cart.data.items)
    if (!items.length) {
      wx.navigateBack()
      return
    }

    this.setData({
      list: items,
      totalCount: cart.totalCount(),
      totalPrice: cart.totalPrice()
    })
  },

  async onSubmit() {
    wx.showLoading({ title: '下单中...', mask: true })

    const res = await wx.cloud.callFunction({
      name: 'addOrder',
      data: {
        cartItems: this.data.list,
        totalPrice: Number(this.data.totalPrice)
      }
    })

    wx.hideLoading()

    if (res.result.ok) {
      cart.data.items = {}
      cart.save()
      wx.removeTabBarBadge({ index: 1 })
      wx.redirectTo({
        url: `/pages/order-success/index?no=${res.result.orderNo}`
      })
    } else {
      wx.showToast({ title: res.result.msg || '下单失败', icon: 'none' })
    }
  }
})

5.✅ 页面:下单成功页(pages/order-success/index)

index.wxml
xml 复制代码
<view class="page success">
  <image src="/icons/success.png" class="icon" />
  <text class="title">下单成功!</text>
  <text class="sub">订单号:{{orderNo}}</text>
  <button bindtap="back">返回首页</button>
</view>
index.wxss
css 复制代码
.success {
  padding: 80rpx;
  text-align: center;
}
.icon {
  width: 120rpx;
  height: 120rpx;
  margin-bottom: 20rpx;
}
.title {
  font-size: 36rpx;
  font-weight: bold;
}
.sub {
  margin: 20rpx 0 60rpx;
  color: #666;
}
button {
  width: 60%;
  font-size: 32rpx;
}
index.js
js 复制代码
Page({
  data: {
    orderNo: ''
  },

  onLoad(options) {
    this.setData({ orderNo: options.no })
  },

  back() {
    wx.switchTab({ url: '/pages/index/index' })
  }
})

6.✅ 更新购物车页结算按钮

cart/index.js 中新增方法:
js 复制代码
  onCheckout() {
    if (!this.data.totalCount) {
      wx.showToast({ title: '购物车为空', icon: 'none' })
      return
    }
    wx.navigateTo({ url: '/pages/confirm/index' }) // 下一阶段页
  }

7.✅ Git Tag & 测试 Checklist

  1. 添加 1~2 道商品进购物车
  2. 进入购物车页面 → 点击「去结算」
  3. 确认页显示商品、价格、总价
  4. 点击「提交订单」 → 跳转成功页
  5. 云开发控制台 → 数据库 → orders 集合中出现新记录
  6. 本地购物车已清空,角标消失
bash 复制代码
git add .
git commit -m "feat: confirm order"
git tag v3.0-order
git push --tags

8. 权限 & 错误处理要点

场景 做法
空购物车 进入确认页时检测,无数据就 navigateBack
网络超时 云函数 catch 后返回 { ok:false },前端 Toast
多端并发 事务内后续可加库存校验(大店才需要)
店员修改状态 先留接口,下一阶段实现

9. 练习(加深理解)

难度 练习内容
orders 文档增加 remarks 字段,确认页让用户填写"口味/忌口"。
⭐⭐ 扫桌码:在桌码二维码中带 tableNo 参数,进入小程序后自动写入订单。
⭐⭐ 生成 取餐号 :如 A08-001(桌号‑序号),写入 orders.pickNo,成功页展示。
⭐⭐⭐ addOrder 云函数加 幂等校验 :同一 orderNo 不重复写入。

阶段小结

  • 你已完成 购物车 → 确认 → 下单 → 云数据库持久化 的完整闭环
  • 代码量 ≈ 350 行,正式迈入"后端交互"阶段
  • 下一步 阶段 4 -- 订单列表 & 状态:让用户和店员都能查看并更新订单进度

继续冲刺,Happy Coding!

相关推荐
XY.散人18 分钟前
初识Redis · list和hash类型
数据库·redis·哈希算法
Arbori_2621518 分钟前
Oracle WITH 子句(也称为 公共表表达式,Common Table Expression,CTE)
数据库·oracle
Tapdata33 分钟前
拒绝停服, 随时回退:Sybase 到 Postgresql 的无缝数据库双向迁移方案
数据库
moxiaoran57531 小时前
mysql自动赋值
数据库·mysql
结衣结衣.1 小时前
【MySQL】数据库基础
数据库·mysql
博界IT精灵1 小时前
SQL语言
数据库·sql
小样vvv1 小时前
【微服务管理】注册中心:分布式系统的基石
java·数据库·微服务
struggle20251 小时前
Trinity三位一体开源程序是可解释的 AI 分析工具和 3D 可视化
数据库·人工智能·学习·3d·开源·自动化
低代码布道师2 小时前
加油站小程序实战教程10开通会员
前端·javascript·低代码·小程序
傻小胖2 小时前
UniApp 实现兼容 H5 和小程序的拖拽排序组件
小程序·uni-app