理论知识只有在实战中才能真正内化。本文将以一个完整的电商小程序项目为例,从需求分析、技术选型、架构设计、前后端开发到最终部署,完整呈现全栈小程序项目的构建过程。这个项目综合运用了前文讨论的组件化架构、状态管理、云开发和性能优化等技术,为读者提供一个可参考、可复用的工程化实践样本。
一、项目概述
1.1 需求范围
本项目实现一个简版 B2C 电商小程序,覆盖以下核心功能模块:
| 模块 | 功能点 | 技术重点 |
|---|---|---|
| 首页 | 轮播图、分类入口、商品推荐流 | 虚拟列表、骨架屏、数据缓存 |
| 商品详情 | 轮播图、SKU 选择、规格联动、评价列表 | 组件化、状态管理、事件通信 |
| 购物车 | 商品管理、数量调整、价格计算、失效标记 | 全局状态、持久化、批量操作 |
| 订单流程 | 地址选择、优惠券、支付下单 | 云函数、事务、微信支付 |
| 用户中心 | 个人信息、订单列表、地址管理 | 云数据库、权限控制 |
| 搜索与筛选 | 关键词搜索、多维度筛选、历史记录 | 全文索引、数据聚合 |
1.2 技术选型
| 层面 | 选型 | 理由 |
|---|---|---|
| 框架 | 原生微信小程序 | 直接调用平台能力,无抽象损耗 |
| 状态管理 | MobX + mobx-miniprogram | 响应式更新,开发体验佳 |
| 后端 | 微信云开发 | Serverless,免运维,天然鉴权 |
| 数据库 | 云数据库 (MongoDB) | 文档型,与 JSON 自然映射 |
| 存储 | 云存储 CDN | 自动 CDN 加速,支持图片处理 |
| 支付 | 微信支付 | 原生集成,用户体验流畅 |
二、架构设计
2.1 目录结构
ecommerce-miniprogram/
├── cloudfunctions/ # 云函数
│ ├── product/ # 商品相关接口
│ │ ├── index.js # list / detail / search
│ │ └── config.json
│ ├── cart/ # 购物车逻辑
│ ├── order/ # 订单与支付
│ └── user/ # 用户信息管理
├── components/ # 公共组件
│ ├── product-card/
│ ├── count-down/
│ ├── sku-selector/
│ ├── address-picker/
│ ├── loading-skeleton/
│ └── price-display/
├── pages/ # 页面
│ ├── index/ # 首页
│ ├── category/ # 分类页
│ ├── search/ # 搜索页
│ ├── product-detail/ # 商品详情
│ ├── cart/ # 购物车
│ ├── order-confirm/ # 订单确认
│ ├── order-list/ # 订单列表
│ ├── user-center/ # 用户中心
│ └── address-list/ # 地址管理
├── store/ # 状态管理
│ ├── index.js # Store 入口
│ ├── cart-store.js # 购物车状态
│ ├── user-store.js # 用户状态
│ └── order-store.js # 订单状态
├── utils/ # 工具函数
│ ├── request.js # 请求封装
│ ├── format.js # 格式化工具
│ ├── cache.js # 缓存策略
│ └── validators.js # 校验规则
├── behaviors/ # 公共行为
│ ├── trackable.js # 埋点
│ └── validatable.js # 表单校验
├── app.js # 应用入口
├── app.json # 全局配置
├── app.wxss # 全局样式
└── project.config.json
2.2 数据流架构
视图层 (WXML/WXSS)
↑↓ setData / 事件
逻辑层 (Page/Component)
↑↓ dispatch / subscribe
状态管理层 (MobX Store)
↑↓ 持久化 / 事件总线
服务层 (云函数调用)
↑↓ HTTP / 云调用
数据层 (云数据库 / 云存储)
所有数据变更都通过 MobX Store 进行集中管理,Store 中的 Action 负责调用云函数获取或修改数据。视图层只负责展示和触发 Action,不直接操作数据。这种单向数据流保证了状态变化的可预测性和可追踪性。
2.3 分包策略
{
"pages": [
"pages/index/index",
"pages/category/category",
"pages/user-center/user-center"
],
"subpackages": [
{
"root": "package-product",
"pages": [
"pages/product-detail/product-detail",
"pages/search/search"
]
},
{
"root": "package-trade",
"pages": [
"pages/cart/cart",
"pages/order-confirm/order-confirm",
"pages/order-list/order-list",
"pages/address-list/address-list"
],
"preloadRule": {
"pages/index/index": {
"network": "all",
"packages": ["package-product"]
}
}
}
]
}
主包仅保留首页、分类页和用户中心三个 Tab 页面 ensures冷启动最快。商品详情和搜索归入商品分包,交易链路归入交易分包,通过 preloadRule 在用户浏览首页时预下载商品分包。
三、状态管理实现
3.1 购物车 Store
购物车状态是本项目中最复杂的状态之一,涉及跨页面同步、持久化和价格实时计算:
// store/cart-store.js
import { observable, action } from 'mobx-miniprogram';
export const cartStore = observable({
items: [], // 商品项数组
selectedIds: [], // 已勾选商品的 SKU ID
invalidItems: [], // 失效商品(下架/售罄)
// 计算属性:已勾商品列表
get selectedItems() {
return this.items.filter(item => this.selectedIds.includes(item.skuId));
},
// 计算属性:总价(含优惠计算)
get totalPrice() {
return this.selectedItems.reduce((sum, item) => {
const effectivePrice = item.promotionPrice || item.price;
return sum + effectivePrice * item.quantity;
}, 0);
},
// 计算属性:总数量
get totalCount() {
return this.selectedItems.reduce((sum, item) => sum + item.quantity, 0);
},
// 计算属性:是否全选
get isAllSelected() {
return this.items.length > 0 &&
this.selectedIds.length === this.items.length;
},
// 添加商品
addItem: action(function(product, sku, quantity = 1) {
const existing = this.items.find(item => item.skuId === sku.id);
if (existing) {
existing.quantity += quantity;
// 触发更新
this.items = [...this.items];
} else {
this.items.push({
productId: product.id,
skuId: sku.id,
name: product.name,
skuName: sku.name,
image: sku.image || product.mainImage,
price: sku.price,
promotionPrice: sku.promotionPrice,
stock: sku.stock,
quantity,
addedAt: Date.now()
});
}
this._persist();
}),
// 更新数量
updateQuantity: action(function(skuId, quantity) {
const item = this.items.find(i => i.skuId === skuId);
if (!item) return;
if (quantity <= 0) {
this.removeItem(skuId);
} else {
item.quantity = Math.min(quantity, item.stock);
this.items = [...this.items];
this._persist();
}
}),
// 移除商品
removeItem: action(function(skuId) {
this.items = this.items.filter(item => item.skuId !== skuId);
this.selectedIds = this.selectedIds.filter(id => id !== skuId);
this._persist();
}),
// 切换选中状态
toggleSelect: action(function(skuId) {
if (this.selectedIds.includes(skuId)) {
this.selectedIds = this.selectedIds.filter(id => id !== skuId);
} else {
this.selectedIds = [...this.selectedIds, skuId];
}
}),
// 全选/取消全选
toggleSelectAll: action(function() {
if (this.isAllSelected) {
this.selectedIds = [];
} else {
this.selectedIds = this.items.map(item => item.skuId);
}
}),
// 清空失效商品
clearInvalid: action(function() {
const invalidIds = this.invalidItems.map(item => item.skuId);
this.items = this.items.filter(item => !invalidIds.includes(item.skuId));
this.selectedIds = this.selectedIds.filter(id => !invalidIds.includes(id));
this.invalidItems = [];
this._persist();
}),
// 持久化到本地
_persist() {
wx.setStorageSync('cart_data', {
items: this.items,
selectedIds: this.selectedIds
});
},
// 从本地恢复
restore() {
const saved = wx.getStorageSync('cart_data');
if (saved) {
this.items = saved.items || [];
this.selectedIds = saved.selectedIds || [];
}
}
});
购物车 Store 采用 MobX 的响应式机制,任何商品的增删改都会自动触发绑定组件的重新渲染。selectedIds 与 items 分离存储,使得勾选状态和价格计算的变更可以独立追踪。
3.2 云函数层设计
// cloudfunctions/product/index.js
const cloud = require('wx-server-sdk');
cloud.init();
const db = cloud.database();
const _ = db.command;
const $ = db.command.aggregate;
// 商品列表查询
async function listProducts(event) {
const { category, page = 1, size = 20, sort = 'default' } = event;
let query = db.collection('products').where({ status: 'on_sale' });
if (category) {
query = query.where({ categoryId: category });
}
const sortMap = {
default: { sortOrder: -1 },
price_asc: { price: 1 },
price_desc: { price: -1 },
sales: { salesCount: -1 },
new: { createdAt: -1 }
};
const { data } = await query
.orderBy(Object.entries(sortMap[sort] || sortMap.default)[0])
.skip((page - 1) * size)
.limit(size)
.field({
name: true,
mainImage: true,
price: true,
promotionPrice: true,
salesCount: true,
tags: true
})
.get();
return { success: true, list: data, page, size };
}
// 商品详情查询
async function getProductDetail(event) {
const { productId } = event;
const { data: product } = await db.collection('products').doc(productId).get();
// 并行查询 SKU 和评价统计
const [skuResult, reviewResult] = await Promise.all([
db.collection('skus').where({ productId }).get(),
db.collection('reviews').where({ productId }).count()
]);
// 计算评分分布
const ratingStats = await db.collection('reviews').aggregate()
.match({ productId })
.group({
_id: '$rating',
count: $.sum(1)
})
.end();
return {
success: true,
data: {
...product,
skus: skuResult.data,
reviewCount: reviewResult.total,
ratingStats: ratingStats.list
}
};
}
// 搜索商品
async function searchProducts(event) {
const { keyword, filters, page = 1, size = 20 } = event;
let query = db.collection('products').where({
status: 'on_sale',
$or: [
{ name: db.RegExp({ regexp: keyword, options: 'i' }) },
{ tags: _.in([keyword]) }
]
});
// 应用筛选条件
if (filters?.priceRange) {
query = query.where({
price: _.gte(filters.priceRange[0]).and(_.lte(filters.priceRange[1]))
});
}
const { data } = await query
.orderBy('relevance', 'desc')
.skip((page - 1) * size)
.limit(size)
.get();
return { success: true, list: data, keyword };
}
exports.main = async (event, context) => {
const { action } = event;
switch (action) {
case 'list': return listProducts(event);
case 'detail': return getProductDetail(event);
case 'search': return searchProducts(event);
default: return { success: false, message: 'Unknown action' };
}
};
云函数层采用 Action 分发模式,单个云函数通过 action 参数路由到不同的处理函数。这种模式减少了云函数数量,简化了部署和调用管理。对于性能敏感的查询(如商品列表),使用 field() 投影仅返回前端需要的字段,减少网络传输量。
四、核心页面实现
4.1 首页与虚拟列表
// pages/index/index.js
import { createStoreBindings } from 'mobx-miniprogram-bindings';
import { cartStore } from '../../store/cart-store';
Page({
data: {
banners: [],
categories: [],
products: [],
page: 1,
hasMore: true,
loading: false,
refreshing: false
},
onLoad() {
this.storeBindings = createStoreBindings(this, {
store: cartStore,
fields: ['totalCount']
});
this.loadData();
cartStore.restore();
},
onUnload() {
this.storeBindings.destroyStoreBindings();
},
async loadData() {
this.setData({ loading: true });
try {
const [bannerRes, categoryRes, productRes] = await Promise.all([
this.loadBanners(),
this.loadCategories(),
this.loadProducts(1)
]);
this.setData({
banners: bannerRes,
categories: categoryRes,
products: productRes.list,
page: 1,
hasMore: productRes.list.length >= 20,
loading: false
});
} catch (err) {
console.error('Load data failed:', err);
this.setData({ loading: false });
wx.showToast({ title: '加载失败', icon: 'none' });
}
},
async loadBanners() {
const { result } = await wx.cloud.callFunction({
name: 'banner',
data: { action: 'list' }
});
return result.data || [];
},
async loadCategories() {
const { result } = await wx.cloud.callFunction({
name: 'category',
data: { action: 'list' }
});
return result.data || [];
},
async loadProducts(page) {
const { result } = await wx.cloud.callFunction({
name: 'product',
data: { action: 'list', page, size: 20 }
});
return result;
},
async onReachBottom() {
if (!this.data.hasMore || this.data.loading) return;
const nextPage = this.data.page + 1;
this.setData({ loading: true });
try {
const { list } = await this.loadProducts(nextPage);
this.setData({
products: [...this.data.products, ...list],
page: nextPage,
hasMore: list.length >= 20,
loading: false
});
} catch (err) {
this.setData({ loading: false });
}
},
onPullDownRefresh() {
this.setData({ refreshing: true });
this.loadData().finally(() => {
this.setData({ refreshing: false });
wx.stopPullDownRefresh();
});
},
onProductTap(e) {
const { id } = e.currentTarget.dataset;
wx.navigateTo({ url: `/package-product/pages/product-detail/product-detail?id=${id}` });
}
});
4.2 商品详情与 SKU 选择
商品详情页是转化率的关键页面。SKU 选择器的核心难点在于规格联动——当用户选择某个属性时,需要实时计算可选的剩余规格组合,并更新价格、库存和对应图片。
// components/sku-selector/sku-selector.js
Component({
properties: {
skus: Array,
attributes: Array // [{ name: '颜色', values: ['红', '蓝'] }, ...]
},
data: {
selected: {}, // { '颜色': '红', '尺码': 'XL' }
currentSku: null
},
observers: {
'selected.**': function(selected) {
this._updateCurrentSku(selected);
}
},
methods: {
onSelectAttribute(e) {
const { attrName, value } = e.currentTarget.dataset;
const { selected } = this.data;
// 切换选择:已选则取消,未选则选中
const newSelected = { ...selected };
if (newSelected[attrName] === value) {
delete newSelected[attrName];
} else {
newSelected[attrName] = value;
}
this.setData({ selected: newSelected });
},
_updateCurrentSku(selected) {
const { skus } = this.data;
// 查找匹配的 SKU
const matched = skus.find(sku => {
return Object.entries(selected).every(
([key, val]) => sku.attributes[key] === val
);
});
this.setData({ currentSku: matched || null });
// 通知父组件
this.triggerEvent('change', {
selected,
sku: matched,
valid: !!matched && matched.stock > 0
});
},
// 判断某属性值是否可选
isValueAvailable(attrName, value) {
const { selected, skus } = this.data;
// 模拟选择此属性值,检查是否有 SKU 匹配
const testSelected = { ...selected, [attrName]: value };
return skus.some(sku => {
return Object.entries(testSelected).every(
([key, val]) => sku.attributes[key] === val
) && sku.stock > 0;
});
}
}
});
4.3 订单确认与支付
// pages/order-confirm/order-confirm.js
import { createStoreBindings } from 'mobx-miniprogram-bindings';
import { cartStore } from '../../store/cart-store';
Page({
data: {
selectedItems: [],
totalPrice: 0,
address: null,
coupon: null,
remark: '',
submitting: false
},
onLoad() {
this.storeBindings = createStoreBindings(this, {
store: cartStore,
fields: {
selectedItems: 'selectedItems',
totalPrice: 'totalPrice'
}
});
// 检查是否有选中商品
if (cartStore.selectedItems.length === 0) {
wx.showToast({ title: '请先选择商品', icon: 'none' });
setTimeout(() => wx.switchTab({ url: '/pages/cart/cart' }), 1500);
return;
}
this.loadDefaultAddress();
},
async loadDefaultAddress() {
try {
const { result } = await wx.cloud.callFunction({
name: 'user',
data: { action: 'getDefaultAddress' }
});
if (result.success) {
this.setData({ address: result.data });
}
} catch (err) {
console.error('Load address failed:', err);
}
},
async onSubmitOrder() {
const { address, remark, totalPrice } = this.data;
if (!address) {
wx.showToast({ title: '请选择收货地址', icon: 'none' });
return;
}
this.setData({ submitting: true });
try {
// 1. 创建服务端订单
const { result: orderResult } = await wx.cloud.callFunction({
name: 'order',
data: {
action: 'create',
items: cartStore.selectedItems.map(item => ({
productId: item.productId,
skuId: item.skuId,
quantity: item.quantity
})),
address,
remark,
totalAmount: totalPrice
}
});
if (!orderResult.success) {
throw new Error(orderResult.message);
}
const { orderId, prepayId } = orderResult.data;
// 2. 获取支付参数并调起支付
const { result: payParams } = await wx.cloud.callFunction({
name: 'order',
data: { action: 'getPayParams', prepayId }
});
await wx.requestPayment({
...payParams.data,
package: `prepay_id=${prepayId}`
});
// 3. 支付成功,清理购物车
cartStore.selectedItems.forEach(item => {
cartStore.removeItem(item.skuId);
});
// 4. 跳转到订单详情
wx.redirectTo({
url: `/package-trade/pages/order-detail/order-detail?id=${orderId}`
});
} catch (err) {
console.error('Order failed:', err);
wx.showToast({ title: err.message || '下单失败', icon: 'none' });
} finally {
this.setData({ submitting: false });
}
}
});
五、数据库设计
5.1 核心集合结构
// products 集合文档示例
{
_id: "prod_123456",
name: "无线蓝牙耳机 Pro",
description: "主动降噪,长续航...",
mainImage: "cloud://env-id.bucket/images/prod1.jpg",
images: ["...", "..."],
categoryId: "cat_electronics",
categoryPath: ["数码", "音频", "耳机"],
price: 299,
promotionPrice: 249, // 促销价,null 表示无促销
status: "on_sale", // on_sale | off_shelf | sold_out
salesCount: 1850,
tags: ["新品", "热销"],
attributes: [
{ name: "颜色", values: ["白色", "黑色"] },
{ name: "版本", values: ["标准版", "降噪版"] }
],
createdAt: ISODate("2024-06-01"),
updatedAt: ISODate("2024-09-15")
}
// skus 集合文档示例
{
_id: "sku_789012",
productId: "prod_123456",
skuCode: "PROD1-WHITE-STD",
attributes: {
"颜色": "白色",
"版本": "标准版"
},
image: "cloud://env-id.bucket/images/sku1-white.jpg",
price: 299,
promotionPrice: 249,
stock: 150,
salesCount: 320
}
// orders 集合文档示例
{
_id: "order_345678",
_openid: "user_openid_xxx",
orderNo: "E20240915000123",
items: [
{
productId: "prod_123456",
skuId: "sku_789012",
name: "无线蓝牙耳机 Pro",
skuName: "白色 / 标准版",
image: "...",
price: 249,
quantity: 1
}
],
totalAmount: 249,
freight: 0,
discountAmount: 0,
payAmount: 249,
status: "paid", // pending | paid | shipped | completed | cancelled | refunded
address: {
name: "张三",
phone: "138****5678",
province: "广东省",
city: "深圳市",
district: "南山区",
detail: "科技园 xx 栋"
},
remark: "请发顺丰",
timeline: [
{ status: "created", time: ISODate("2024-09-15T10:00:00Z") },
{ status: "paid", time: ISODate("2024-09-15T10:02:30Z") }
],
createdAt: ISODate("2024-09-15T10:00:00Z"),
updatedAt: ISODate("2024-09-15T10:02:30Z")
}
5.2 索引设计
// 商品查询索引
db.collection('products').createIndex({
name: 'idx_products_category_status',
keys: { categoryId: 1, status: 1, sortOrder: -1 }
});
// SKU 查询索引
db.collection('skus').createIndex({
name: 'idx_skus_product',
keys: { productId: 1 }
});
// 订单查询索引
db.collection('orders').createIndex({
name: 'idx_orders_user_status',
keys: { _openid: 1, status: 1, createdAt: -1 }
});
六、性能优化实践
6.1 启动优化
- 主包仅保留 Tab 页面,商品和交易链路放入分包
- 商品列表使用分页加载 + 骨架屏
- 图片全部使用 CDN + 自适应压缩参数
- 首页数据预拉取(
wx.getBackgroundFetchData)
6.2 渲染优化
- 商品列表采用虚拟列表,DOM 节点数固定
- 购物车使用纯数据字段
_persistTimer避免setData循环 - 评价列表图片使用
lazy-load
6.3 数据优化
- 商品列表接口仅返回必要字段,排除详情 HTML 等大字段
- 搜索结果本地缓存 5 分钟
- 购物车状态本地持久化,应用启动时自动恢复
七、部署与发布
7.1 云函数部署
# deploy-cloud.sh
#!/bin/bash
ENV="prod"
FUNCTIONS=("product" "cart" "order" "user" "payment")
for fn in "${FUNCTIONS[@]}"; do
echo "Deploying cloud function: ${fn}..."
wx cloud functions:deploy --name ${fn} --env ${ENV}
done
echo "All cloud functions deployed."
7.2 小程序发布流程
- 开发阶段:本地开发
→开发者工具预览→自测 - 提审阶段:代码上传
→体验版测试→提交审核 - 发布阶段:审核通过
→分阶段发布(10%→50%→100%) - 监控阶段:小程序助手查看性能数据
→异常告警→热修复
八、总结
本项目展示了从零构建全栈小程序工程的完整方法论。通过组件化拆分实现 UI 复用,通过 MobX 状态管理实现跨页面数据同步,通过微信云开发简化后端建设,通过分包加载和虚拟列表保障性能表现。在实际生产环境中,还应补充错误监控(如 Sentry)、运营埋点和 A/B 测试等能力。
电商小程序的复杂度远不止于此——营销系统(秒杀、拼团、优惠券)、物流追踪、售后退款、多商户平台等场景都会带来更高的架构挑战。但掌握本文所述的基础框架后,这些扩展功能都可以在稳固的地基上逐步搭建。小程序开发的本质是在有限资源约束下(包体积、启动速度、渲染性能)交付最优用户体验,每一个技术决策都应在理解平台特性的基础上做出权衡。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。