简介:这套代码是一份微信小程序商店的完整实现,面向想搭建应用分发/商店类小程序的开发者,也适合初学者系统学习小程序开发。压缩包仅131KB,共57个文件,其中包含17张png图片、9个js业务逻辑、9个json配置、8个wxml页面结构、8个wxss样式,以及README与工程配置等;目录覆盖app启动配置、pages页面、utils工具、template模板和signin签到模块,结构清晰易读。目前已有4041人学习浏览,热度较高。整套代码从前端界面、数据配置到后端交互完成串联,可深入理解页面路由、组件复用、小程序API调用、用户授权登录、本地缓存及性能优化等关键技能;同时目录中的yiguo模板和signin登录页也提供了真实业务场景的参考。对于希望快速入手小程序商店/商城的开发者,直接导入开发者工具即可对照源码研读并二次开发,是一份轻量而实用的入门学习资料。
1. 一套完整的微信小程序商店代码能做什么
很多开发者第一次接触微信小程序商店项目时,容易陷入一个误区:以为难点在代码本身。实际上,这类项目的门槛藏在账号体系、云环境配置、审核规则和支付开通这些环节里。当你手里拿到一套完整可跑的代码时,能省下的不只是一周两周的编码时间,还有大量查阅文档、试错踩坑的隐性成本。这套代码适合这样的人:想快速给线下门店、工作室或独立品牌搭一个小程序商城,不想从零开始写登录、商品、购物车和订单这些基础模块,也适合接单的独立开发者拿它当脚手架,在上面替换成客户的业务逻辑。它解决的核心问题,是把一套电商小程序最通用的能力先替你趟平。
2. 技术栈与工程结构:原生小程序 + 云开发,从目录看懂整体设计
2.1 技术选型:为什么是原生框架搭配云开发
这套代码使用的是微信小程序原生框架,也就是 WXML 描述页面结构、WXSS 控制样式、JavaScript 承载逻辑,没有引入 TypeScript 和任何跨端框架。很多人会问,为什么不用 uni-app 或者 Taro 这类跨端方案?这个选择本身是权衡后的结果:代码的目标场景是微信生态内的商店,不需要同时输出 App 和 H5,原生框架在调试链路和工具链支持上是最直接的,编译速度快,上传体验版也省去一层转译的损耗。更重要的是,对于想二次开发的用户来说,原生代码的阅读门槛最低,任何一个没接触过小程序的人,打开目录基本能猜到每个文件是干什么的。
后端采用的是微信云开发,即 CloudBase 这套体系。云函数、云数据库、云存储和云托管都在同一个框架内,不需要自己购买服务器、维护反向代理和数据库。对这个体量的商店项目来说,云开发最大的价值是省掉了后端鉴权这一整套复杂设计——云函数里可以直接拿到用户的 openid,天然解决了身份识别问题。整个项目的后端能力被拆成若干个云函数,每个函数对应一个独立的业务动作。用一句话概括技术选型的思路:前端用平台原生方案,后端用平台自带的服务,把成本压到最低,让代码可以在半小时内跑起来。
2.2 目录结构:每个目录在项目中承担的角色
拿到代码后,第一时间应该先把目录结构读一遍,这决定了后续改代码时能不能快速定位到目标文件。整个项目的组织方式遵循了小程序的规范,同时把可复用的逻辑抽离到了独立的目录中。核心的几个目录如下:
├── cloudfunctions # 云函数目录 │ ├── login # 微信登录换取 openid │ ├── createOrder # 创建订单 │ └── getProducts # 拉取商品列表 ├── components # 自定义组件 │ ├── product-card # 商品卡片组件 │ └── stepper # 数量加减器 ├── pages │ ├── index # 首页 │ ├── products # 商品列表 │ ├── detail # 商品详情 │ ├── cart # 购物车 │ ├── checkout # 确认订单 │ └── orders # 订单列表 ├── styles # 全局公共样式 ├── utils │ ├── format.wxs # 价格格式化 │ └── request.js # 云函数调用封装 ├── app.js # 应用入口 ├── app.json # 全局配置 └── app.wxss # 全局样式在这个结构里,pages 的每个目录就是一个页面,页面内部通常包含 .js、.wxml、.wxss、.json 四个同名的文件,职责边界很清楚:js 里写数据绑定和事件处理,wxml 负责页面结构,wxss 负责当前页面的样式。components 目录存放的是多个页面复用的能力,比如商品卡片在首页、商品列表页都会出现,抽成组件后就不用重复写模板了。
cloudfunctions 目录是后端逻辑所在地。每个云函数都是一个独立的小程序,拥有自己的 package.json 和入口文件 index.js。部署时需要在开发者工具中对每个函数单独执行上传操作。utils 里的 request.js 很关键,它把 wx.cloud.callFunction 这个 API 封装成 Promise 形式,后面的所有页面都通过它来调用后端逻辑,这让代码里的异步处理干净很多。
2.3 数据模型:商品、订单、用户集合的设计与字段说明
云数据库里规划了三个核心集合,分别是 products、orders、users。集合的概念可以类比为关系型数据库里的表,每个集合中的每一条记录是一条 JSON 文档。这套代码没有做购物车集合,购物车数据主要存在本地缓存里,只有在下单那一刻才把购物车内容同步到订单集合,这个设计后面会详细讲。
商品集合 products 的字段设计目的是兼顾展示和交易,核心字段包括:_id 是系统自动生成的主键;name 是商品名称;description 是详情介绍;price 是价格,统一用整数类型存储,单位是分;images 是图片地址数组,支持一张商品图配多张轮播图;category 是商品分类;status 用来控制上下架,1 代表上架,0 代表下架;stock 是库存数量;createdAt 是创建时间。价格用分做单位是个重要约定,电商项目最忌讳用浮点数算钱,否则在结算时容易出现精度问题。
订单集合 orders 记录的是每一笔交易结果。orderNo 是订单号,生成规则是前缀加时间戳加随机数;userId 关联用户集合中的 _id;items 是一个数组,每个元素包含商品信息、规格、单价和数量;totalAmount 是订单总金额;address 是收货地址;status 表示订单状态,0 是待支付,1 是已支付,2 是已发货,3 是已完成。users 集合相对简单,_id、openid、nickName、avatarUrl、createdAt,openid 是用户在微信体系内的唯一标识,后端通过这个字段识别身份。
3. 核心模块拆解:一次购物交互从登录到下单的完整链路
3.1 登录授权:云函数换 openid 的链路
很多小程序项目把登录做成一个黑匣子,其实核心链路就三步:前端调用 wx.login 拿到临时凭证 code,把 code 传给云函数,云函数在云端用这个凭证换取用户的 openid。云开发环境下,这个操作被简化到了极致,登录云函数里通过 cloud.getWXContext() 可以直接获取 openid,连换取凭证的步骤都被框架封装好了。
// cloudfunctions/login/index.js const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) exports.main = async (event, context) => { const wxContext = cloud.getWXContext() const db = cloud.database() const users = db.collection('users') const existing = await users.where({ openid: wxContext.OPENID }).get() if (existing.data.length > 0) { return { code: 0, data: { isNew: false, user: existing.data[0] } } } const user = { openid: wxContext.OPENID, nickName: event.nickName || '', avatarUrl: event.avatarUrl || '', createdAt: db.serverDate() } const res = await users.add({ data: user }) return { code: 0, data: { isNew: true, userId: res._id } } }这段登录逻辑里,判断用户是否已存在用的是 where 查询加数据体检,而不是每次都往集合里插一条新记录。云函数返回的数据结构统一用 code 字段表示状态,0 是成功,非 0 是失败,这个约定贯穿整个项目的所有云函数。前端拿到登录结果后,会把用户 id 存入全局变量和本地缓存,后续所有需要身份的操作都依赖这个 id。值得注意的是,前端不能直接拿 openid 做业务字段,因为 openid 属于敏感信息,一旦被恶意使用可能带来安全问题。
3.2 商品列表与详情:数据请求与 SKU 选择
商品列表页是用户进入小程序后最先接触的功能之一。页面加载时调用云函数 getProducts,传入分页参数,每次拉取十条记录,上拉触底时继续加载下一页。这是移动端常见的分页方式,接口层的实现并不复杂,关键在于查询条件的组合。
// pages/products/products.js const db = wx.cloud.database() Page({ data: { products: [], loading: true, page: 0, hasMore: true }, async fetchProducts() { const { page, hasMore } = this.data if (!hasMore) return wx.showLoading({ title: '加载中' }) try { const res = await db.collection('products') .where({ status: 1 }) .skip(page * 10) .limit(10) .get() this.setData({ products: this.data.products.concat(res.data), page: page + 1, hasMore: res.data.length === 10 }) } finally { wx.hideLoading() } }, onReachBottom() { this.fetchProducts() } })这里有几个值得展开的参数细节。where 条件中的 status 字段只查询上架商品,避免把下架商品展示给用户。skip 和 limit 的组合是云数据库分页的标准写法,skip 接收的是偏移量,limit 限制返回条数。hasMore 的判断逻辑是:如果本次返回的数据条数刚好等于 limit,就认为还有下一页,否则认为已经到末尾。这个判断方式在数据量不大时完全够用,但当集合中有大量数据时,推荐改用游标方式,避免 skip 带来的深度分页性能问题。
商品详情页比列表页复杂的地方在于多规格选择。当商品包含多个 SKU 时,用户选择不同规格,价格、库存、图片都会联动刷新。detail 页面的实现方式是在 data 中维护一个 skuIndex 变量,当用户点击某个规格选项时更新索引,相关的价格和库存信息都从预加载的商品数据中计算得出,整个过程不需要重新请求后端。
3.3 购物车与结算:从本地缓存到云订单
购物车模块的设计思路是优先用本地缓存。用户在商品详情页点击加入购物车时,会把商品信息、SKU、数量直接写入 wx.setStorageSync 中。为什么不把购物车直接存进云数据库?原因有两个:一是购物车是高频操作,频繁读写云数据库会产生不必要的费用和延迟;二是购物车不属于交易凭证,不需要云端持久化。真正需要云端记录的是下单之后的订单数据。
addToCart(product, sku, quantity) { let cart = wx.getStorageSync('cart') || {} const key = product._id + (sku ? '_' + sku.id : '') if (cart[key]) { cart[key].quantity += quantity } else { cart[key] = { product, sku, quantity, checked: true } } wx.setStorageSync('cart', cart) wx.showToast({ title: '已加入购物车', icon: 'success' }) }这段代码的关键在 key 的拼接规则。同样的商品如果选择了不同规格,在购物车里要显示成两条记录,所以 key 由商品 id 和 SKU id 组合而成。quantity 累加,checked 字段记录该商品是否被勾选。整个购物车数据以对象而非数组的形式存储,读取和更新都更高效,但对象中的 value 仍然是数组结构,方便在 wxml 中直接遍历渲染。
结算流程发生在确认订单页,用户点击提交订单时,前端把购物车中 checked 为 true 的商品项组装成 items 数组,连同收货地址和备注信息一起传给 createOrder 云函数。云函数在服务端重新计算总金额,校验商品库存,生成订单记录写入 orders 集合。注意这里有个设计细节:前端展示的总价只是一种预览,云函数中会基于商品价格重新计算一遍,防止用户通过伪造请求篡改订单金额。
// cloudfunctions/createOrder/index.js const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) exports.main = async (event) => { const db = cloud.database() const { items, address, remark } = event const wxContext = cloud.getWXContext() const userRes = await db.collection('users').where({ openid: wxContext.OPENID }).get() const userId = userRes.data[0]._id const totalAmount = items.reduce((sum, item) => { return sum + item.price * item.quantity }, 0) const order = { orderNo: 'SO' + Date.now() + Math.floor(Math.random() * 1000), userId, items, address, remark, totalAmount, status: 0, createdAt: db.serverDate() } const res = await db.collection('orders').add({ data: order }) return { code: 0, data: { orderId: res._id, totalAmount } } }订单号在云函数里生成,前缀 SO 代表 store order,时间戳加随机数保证基本不重复。status 初始为 0,表示待支付。在实际生产中,下单后应该继续调用微信支付统一下单接口,但这套代码为了降低起步门槛,把支付环节留成了模拟态,后续接入真实支付时只需要在创建订单后追加一段支付调用逻辑。
4. 把项目跑起来:导入、配置到真机预览的完整步骤
4.1 准备工作:三件套缺一不可
运行这个项目只需要准备三样东西:一个小程序账号、微信开发者工具、一个云开发环境。账号在微信公众平台注册,注册类型选企业或个人。这里有个容易忽略的点:个人主体的小程序在类目选择上有限制,部分电商类目需要企业资质,注册前先想清楚自己的业务范围。云开发环境在微信开发者工具内开通,免费额度对开发和轻量运营来说足够撑过前期。
工具的版本建议用最新的稳定版,旧的开发者工具对云开发的支持有各种小毛病。安装完成之后,用管理员账号扫码登录,工具会自动关联你名下注册的小程序。
4.2 导入与部署:菜单操作背后的含义
打开开发者工具,选择导入项目,目录指向源码根目录,AppID 填入你自己小程序的 AppID。如果没有 AppID,开发者工具也支持测试号,但云开发功能在测试号下不可用,所以这一步不能偷懒。项目导入成功后,第一步是初始化云环境,在工具栏中找到云开发按钮,开通环境,记下环境 ID,后面配置要用。
接下来是上传云函数。在编辑器左侧的文件树中找到 cloudfunctions 目录,对每个函数子目录执行右键,选择“上传并部署:云端安装依赖”。这个操作的含义是把本地代码打包上传到云端,同时根据 package.json 在云端安装依赖。常见错误是只上传不安装依赖,会导致线上运行时找不到模块。云函数全部上传完毕后,到云开发控制台的数据库选项卡中创建 products、users、orders 三个集合,可以顺手给集合配上合适的权限。
products 集合权限:所有用户可读,仅创建者可写 orders 集合权限:仅创建者可读写 users 集合权限:仅创建者可读写权限设置决定了前端代码能否直接调用数据库接口。商品数据需要被所有用户读取,所以设为所有用户可读。订单和用户数据涉及隐私,必须限制为仅创建者可读写。这个权限模型和电商场景的需求是对应的。
4.3 数据初始化与首次联调:验证每一个环节
集合是空的,页面自然显示不出商品。手动在 products 集合中添加一条测试数据,字段严格按照前面的数据模型填写,price 用分做单位,image 字段先用开发者工具生成的临时图片地址占位。在 app.js 中检查 wx.cloud.init 是否已经填入了正确的环境 ID,这个配置项是整个项目连接到云端的入口。
// app.js wx.cloud.init({ env: 'your-env-id', traceUser: true })数据准备完成后,点击编译按钮。如果一切正常,首页和商品列表页应该能看到刚插入的商品。接着走一遍完整链路:进入详情页,加入购物车,打开购物车勾选商品,提交订单,然后在云开发控制台的 orders 集合中确认订单记录已生成。这样一个完整的闭合链路验证通过,就说明项目跑起来了。如果某个环节没有按预期工作,优先回查云函数是否成功部署、集合权限是否正确、环境 ID 是否匹配这三个点。
5. 避坑与排查:微信生态里的四个典型翻车点
5.1 开发版和体验版数据错乱
现象:本地开发者工具里一切正常,商品能加载、订单能创建,但把代码上传为体验版之后,发现页面数据变成了一片空白,或者看到的还是旧的测试数据。
原因:开发版和体验版使用了不同的云环境。开发者工具中调试用的是默认环境,而体验版或正式版在 app.js 中如果写死了环境 ID,或者没写环境 ID 让系统自动选择,就会导致走错环境。这套代码在交付时,app.js 中的 env 字段往往保留的是开发者自己的环境 ID,拿到代码的人如果直接上传,就会连到原开发者的数据库上,造成数据错乱。
解决:在 app.js 里对环境 ID 做统一管理,推荐的写法是把环境 ID 抽到独立的 config 文件中,发布前检查一遍。如果你希望开发环境和线上环境隔离,可以按照版本自动切换环境。
5.2 支付功能在开发者工具里调不通
现象:点击提交订单后,模拟支付流程没有任何反应,控制台报错信息指向支付接口。
原因:微信支付功能需要单独申请开通,且要求小程序主体为企业资质。个人开发者账号在小程序后台根本没有支付菜单入口。即使主体是企业,支付接口联调也需要配置商户号和支付证书,开发者工具内的模拟器不支持完整的支付流程。
解决:这套代码在支付环节做了简化处理,订单提交后直接置为待支付状态,这是有意为之的留白。二次开发时如果确实需要真实支付,先确认主体资质是否符合要求,然后按官方文档指引接入支付后,再回来替换代码中的模拟逻辑。在接好之前,不要浪费时间在开发者工具里反复测试支付弹窗。
5.3 图片加载失败引发审核被拒
现象:上传体验版后,iOS 端商品图片全部无法显示,但 Android 端正常。提交审核时被拒,理由通常是“涉及图片域名不合规”或“内容加载失败”。
原因:小程序对网络图片有域名白名单限制。开发调试时会开启“不校验合法域名”,所以本地看图片一直是好的。但体验版和正式版强制走域名校验,图片必须存储在微信云存储或者已经配置在后台的合法域名下。使用 wx.cloud 上传的图片路径是云存储的默认域名,也需要确认是否已经加入白名单。
解决:最简单的方式是把商品图片全部上传到云存储,用云存储返回的 fileID 存入数据库,在 wxml 中渲染时通过 img.src 直接绑定 fileID,云开发会自动处理域名鉴权。如果非要使用自有服务器上的图片,提前在微信公众平台的后台将图片域名配置到 request 合法域名列表中。
5.4 云函数超时导致下单失败
现象:首次提交订单时经常卡在 loading 状态,几分钟后提示云函数超时,重试一次反而成功了。
原因:云函数存在冷启动问题,长时间没有被调用的情况下,首次请求需要拉起一个新的运行实例,这个过程可能要几秒钟。如果云函数中又做了比较复杂的数据库查询,整体耗时就会超过默认的超时时间。createOrder 云函数中包含了用户查询、金额计算、订单写入三个动作,在冷启动场景下确实容易超时。
解决:在云开发控制台中将高频使用的云函数超时时间从默认的 3 秒调大一些,比如调整到 10 秒。另外,在云函数入口处做一些缓存优化,把用户 id 的查询路径简化。冷启动虽然无法完全消除,但可以通过这些手段让用户体验不至于断崖式变差。前端页面请求时的 loading 提示也要保留,让用户知道系统正在处理而不是卡死了。
6. 二次开发思路:三个改造点让通用代码变成你的专属商店
6.1 改造点一:把云数据库换成自有后端 API
如果你们的业务已经有了一套成熟的商城后端,这套小程序代码的改造重点就是数据层。云数据库调用集中在 pages 目录里的数据请求代码中,改造思路是把 wx.cloud.database() 的直接操作全部替换为 wx.request 调用。统一的请求封装可以放在 utils/request.js 中:
// utils/request.js const BASE_URL = 'https://api.your-backend.com' // 替换为你的后端地址 function request(path, method = 'GET', data = {}) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + path, method, data, header: { 'Content-Type': 'application/json' }, success: (res) => { if (res.statusCode === 200) resolve(res.data) else reject(res) }, fail: reject }) }) } module.exports = { request }替换之后要记住,后端返回的数据结构需要和原页面中的字段名保持一致,否则要同步修改页面中的渲染逻辑。另外,小程序后台的 request 合法域名必须配置成你的后端 API 域名,否则真机预览时会被拦截。
6.2 改造点二:商品模型扩展多规格和库存扣减
原始代码的商品集合只支持单规格,改造多规格的关键不是写更多页面,而是重新设计商品数据模型。在 products 集合的文档里增加 skus 数组,每个元素包含规格描述、独立价格和独立库存。购物车和订单的 items 中也要追加 skuId 字段,结算时后端根据 skuId 去匹配对应的价格,并做库存扣减。
// 扩展后的商品文档结构示例 { name: "手作陶杯", category: "生活器皿", basePrice: 6800, skus: [ { id: "sku_001", spec: "230ml / 天青釉", price: 6800, stock: 12 }, { id: "sku_002", spec: "330ml / 天青釉", price: 7800, stock: 8 } ] }skuId 要保证在全局范围内唯一。习惯的生成规则是商品 id 加下划线加序号,这样在代码里看到 skuId 就能反查是哪个商品的哪个规格,排查问题时少走很多弯路。
6.3 改造点三:品牌视觉适配和商品分类首页
把项目的默认样式替换成你自己的品牌视觉,改动点集中在 app.json、app.wxss 和 tabBar 图标资源上。app.json 中配置 window 的背景色、导航栏标题和导航栏文字颜色,tabBar 的图标换成你们自己设计的 PNG 文件。首页 index 目前是默认的欢迎页,可以改造成品牌介绍页加上商品分类入口,分类导航使用可滚动视图组件绑定数据,实际跳转仍然复用商品列表页,只是根据点击的分类传参过滤列表数据。
有三点验证建议。第一,每一次改造后走一遍完整链路,从登录到下单再到订单列表,确认没有打断原有闭环。第二,云函数修改后重新上传并看控制台日志,自建后端改造则重点检查接口返回字段是否映射到位。第三,体验版和正式版各跑一遍,因为本地调试通过的路径不代表线上环境也能通过。
从那以后,我每次拿到一套新代码,都强制自己先做一次最小可运行验证,跑通一条核心链路再动手改任何业务逻辑。这个习惯帮我避开了很多次改了十几处代码、最后才发现是环境配置出问题的尴尬局面。希望这篇拆解笔记能帮到你,也欢迎你在跑通之后按自己的业务逻辑往里填模块。
本文还有配套的精品资源,点击获取