首页 / 资讯中心 / 文章详情

微信小程序商城源码解析:云开发配置、SKU与购物车实战

微信小程序商城源码解析:云开发配置、SKU与购物车实战 ★ FEATURED ARTICLE
简介微信小程序是无需下载安装即可使用的轻量级应用其开发常涉及逻辑层语言、结构标签与样式语言的配合。这份约185.3MB的压缩包内含100个真实可运行的微信小程序源码覆盖工具、生活、电商等多种业务类型适合从零学习小程序开发的初学者也适合寻找项目参考与灵感的中高级开发者。源码基于微信开发者工具编写结构完整、注释清晰能直观看到页面生命周期、数据绑定、本地存储、网络请求、地图与支付等模块的调用方式以及流式布局、弹性盒模型、响应式设计和动画效果的实现细节。已有555人学习或下载。通过对照阅读读者可快速掌握逻辑层与视图层的协作模式学习组件拆分、状态管理和接口封装技巧也能从真实业务场景中提炼性能优化、异常处理等可复用的实践经验为自主开发或项目排错提供直接参考。1. 微信小程序源码包里到底有什么先看清单再动手拿到一份微信小程序源码我一般不会先翻功能页而是先把目录过一遍确认它是不是能跑的那一类。这份源码属于后者原生微信小程序框架加云开发没有自建服务器集成了商品浏览、购物车、下单、社区发帖这一整条业务链路。适合想快速验证小程序闭环的个人开发者也适合照着学完整数据流的新手。它能直接编译通过也能在微信开发者工具里跑起来但前提是你得把配置项对齐这正是这篇文章要拆开讲的部分。2. 架构与目录从 app.json 到云函数这整条链路先看整体选型。源码没有用 Taro、uni-app 这类跨端框架而是老老实实的原生小程序写法WXML 写结构、WXSS 写样式、JS 写逻辑、JSON 写配置。配套用的是微信云开发也就是云函数加云数据库加云存储。这么选的好处很直接不需要自己买服务器、配域名、搞备案个人开发者也能把一套带登录、商品、订单的完整应用在一天内跑通。很多人拿到源码第一步就点编译结果界面出来了但数据全是空的就是因为没理解目录里每一块各自承担什么职责。下面顺着目录结构逐层看。2.1 目录结构五个目录各管一摊这份源码的顶层结构如下mini-program/ ├── app.js # 全局逻辑初始化云能力、承载登录态 ├── app.json # 全局配置页面路由、TabBar、分包 ├── app.wxss # 全局样式设计变量与公共类 ├── project.config.json # 工具配置AppID、编译选项 ├── sitemap.json # 小程序页面索引配置 ├── pages/ # 页面目录按业务分子目录 │ ├── index/ # 首页搜索、轮播、金刚区、推荐商品 │ ├── category/ # 分类左侧一级分类右侧商品列表 │ ├── cart/ # 购物车本地缓存 批量结算 │ ├── user/ # 个人中心订单入口、地址、设置 │ ├── goods/ │ │ ├── detail/ # 商品详情SKU 选择、加购 │ │ └── list/ # 商品列表筛选与排序 │ ├── order/ │ │ └── list/ # 订单列表按状态切换 │ └── community/ # 社区帖子列表、发布页 ├── components/ # 自定义组件跨页面复用 │ ├── goods-card/ # 商品卡片 │ ├── sku-selector/ # SKU 选择弹层 │ ├── search-bar/ # 搜索栏 │ └── empty-status/ # 空状态占位 ├── utils/ │ ├── config.js # 环境 ID、静态资源域名等配置 │ ├── request.js # wx.request 的 Promise 封装 │ ├── auth.js # 登录态与用户信息读取 │ └── cart.js # 购物车本地缓存读写 ├── cloudfunctions/ # 云函数一个目录一个函数 │ ├── login/ # 登录换 openid自动建用户 │ ├── goods/ # 商品列表与详情的聚合查询 │ ├── order/ # 创建订单服务端重算金额 │ └── community/ # 发帖与评论 └── docs/ ├── db-schema.md # 集合字段说明 └── goods.seed.json # 商品种子数据几个值得注意的细节。pages 下按业务分子目录而不是平铺所有页面这个习惯对后期维护很关键——页面多了之后平铺命名很快会乱成一团。components 和 utils 的职责边界也划得清楚组件只管界面和交互事件utils 只管数据读写。云函数每个函数独立目录里面各自有 package.json部署时右键上传就能跑。2.2 app.json 全局配置路由、TabBar 与分包app.json 是小程序的门面页面能不能跳、底栏长什么样、有没有分包全在这里决定。源码里的核心配置如下{ pages: [ pages/index/index, pages/category/category, pages/cart/cart, pages/user/user, pages/goods/detail/index, pages/goods/list/index, pages/order/list/index, pages/community/index, pages/community/publish ], subPackages: [ { root: packageOrder, pages: [pages/order/detail/index] } ], window: { navigationBarTitleText: 演示商城, navigationBarBackgroundColor: #ffffff, navigationBarTextStyle: black }, tabBar: { color: #999999, selectedColor: #e64340, list: [ { pagePath: pages/index/index, text: 首页, iconPath: images/tab-home.png, selectedIconPath: images/tab-home-active.png }, { pagePath: pages/category/category, text: 分类, iconPath: images/tab-cate.png, selectedIconPath: images/tab-cate-active.png }, { pagePath: pages/cart/cart, text: 购物车, iconPath: images/tab-cart.png, selectedIconPath: images/tab-cart-active.png }, { pagePath: pages/user/user, text: 我的, iconPath: images/tab-user.png, selectedIconPath: images/tab-user-active.png } ] }, permission: { scope.userLocation: { desc: 用于计算配送距离 } }, cloud: true, sitemapLocation: sitemap.json }pages 数组的第一项就是启动页这里放在 index。subPackages 把订单详情这种低频页面拆进了分包这样主包体积能控制在 2MB 限制以内是大型一点的小程序必须养成的习惯。tabBar 里 iconPath 和 selectedIconPath 都只能是项目内本地图片路径不支持网络图片也不接受 base64这是后面最容易翻车的地方之一。2.3 数据流设计三层数据各放哪源码里的数据存放分三层每一层生命周期和用途都不同数据位置生命周期适合放什么典型场景globalData进程内存App 销毁即失openid、登录态、临时配置页面间共享登录信息Storage本地持久化上限 10MB购物车、浏览记录、主题偏好用户主动产生的本地数据云数据库云端持久多端一致商品、订单、用户、帖子需要跨设备同步的业务数据读取链路在源码里是有讲究的页面 onLoad 先读 Storage命中就直接渲染未命中再调云函数拉云端数据拿到后顺手写回 Storage 并更新 globalData。这样首屏不用每次都等网络请求用户返回到列表页时也快得多。3. 核心模块拆解登录、缓存与 SKU 的实战代码这一章是这份源码含金量最高的部分。登录链路怎么设计、接口怎么统一封装、SKU 组件怎么解耦、订单金额为什么必须服务端重算四个点看完你对小程序前后端协作方式基本就有数了。3.1 登录链路前端拿 code云函数换 openid很多小程序把 openid 直接返回给前端这是不安全的做法。正确的做法是前端调 wx.login 拿到临时 code再通过云函数用 code 换取 openidopenid 只在云函数内部使用。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 { OPENID, APPID } wxContext const now Date.now() // 先查用户是否已存在不存在则自动建档 const exist await users.where({ openid: OPENID }).count() if (exist.total 0) { await users.add({ data: { openid: OPENID, nickName: , avatarUrl: , createdAt: now, updatedAt: now } }) } return { openid: OPENID, appid: APPID } }cloud.DYNAMIC_CURRENT_ENV 表示自动使用当前调用方所在的环境这样云函数上传到哪个环境取的数据库就是哪个环境省去了硬编码环境 ID 的麻烦。getWXContext 是云函数内置方法能直接拿到调用者的 OPENID、APPID、UNIONID不需要前端传任何身份参数也就杜绝了伪造身份的可能。前端侧的调用同样简短App({ globalData: { openid: , userInfo: null }, onLaunch() { if (!wx.cloud) { wx.showToast({ title: 请使用 2.2.3 以上基础库, icon: none }) return } wx.cloud.init({ env: demo-env, traceUser: true }) wx.cloud.callFunction({ name: login, success: (res) { this.globalData.openid res.result.openid } }) } })这里 env 要和你在云开发控制台里开通的环境 ID 完全一致。traceUser 打开后可以在云开发控制台看到每个用户的操作记录排查问题很有用。3.2 接口封装把 wx.request 改成 Promise源码里保留了一个 utils/request.js用来对接外部 HTTP 服务比如物流查询、天气接口这类云函数不方便处理的场景。小程序原生 wx.request 不支持 Promise回调嵌回调很难维护所以统一封装一层const request (url, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: ${BASE_URL}${url}, method, data, header: { Content-Type: application/json }, success(res) { // 约定业务码 code 为 0 表示成功 if (res.statusCode 200 res.data.code 0) { resolve(res.data.data) } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }) reject(res) } }, fail(err) { wx.showToast({ title: 网络异常请稍后重试, icon: none }) reject(err) } }) }) } module.exports { request }BASE_URL 放在 utils/config.js 里统一维护切换测试环境和生产环境只改一个文件。这个封装的约定很关键后端返回结构统一为{ code, msg, data }调用方只需要关心 data错误处理全在封装层做掉了业务代码里就不需要到处写 try-catch 处理网络错误。实际调用时是这样用的const { request } require(../../utils/request) async function fetchExpress(trackingNo) { const data await request(/api/express, GET, { trackingNo }) return data.traces }页面里拿到的直接是业务数据不用再解一层壳。这套封装在后续二次开发时几乎不用改动新接口接进来只管写业务逻辑就行。3.3 SKU 选择器properties、triggerEvent 与组件通信SKU 选择器是商品详情页最复杂的组件源码里把它独立成 components/sku-selector页面通过属性传数据、通过事件收结果组件内部完全不知道页面长什么样这是组件复用的核心思路。Component({ properties: { skuList: { type: Array, value: [] }, selected: { type: Object, value: null } }, observers: { skuList: function (list) { // 监听属性变化重新生成可选的规格组合 if (list list.length) { this.initSpecs(list) } } }, methods: { initSpecs(list) { // 根据 skuList 生成规格维度如颜色、尺码 const specs [] list.forEach(item { // 这里做规格去重与组合 }) this.setData({ specs }) }, onSelect(e) { const { skuId } e.currentTarget.dataset const sku this.data.skuList.find(item item.skuId skuId) // 通过 triggerEvent 把选中结果抛给页面 this.triggerEvent(change, { sku }) } } })properties 是父组件传给子组件的单向数据入口observers 负责监听数据变化而 triggerEvent 是子组件向父组件通信的唯一通道。页面里这样消费组件事件sku-selector skuList{{skuList}} bind:changeonSkuChange /对应的页面方法拿到事件参数 e.detail.sku就能更新价格、库存和加购信息。整个交互过程中SKU 组件不直接操作页面数据页面也不直接改组件内部状态数据流单向清晰这是组件化写法的基本盘。3.4 购物车与订单本地缓存与服务端重算金额购物车属于用户个人的高频操作源码选择放在本地 Storage 而不是每次请求云端体验更顺滑。缓存的结构很简单const CART_KEY CART_ITEMS function getCart() { return wx.getStorageSync(CART_KEY) || [] } function addToCart(goods, sku, count) { const cart getCart() // 用 goodsId skuId 组合作为唯一键判断是否重复 const idx cart.findIndex( item item.goodsId goods._id item.skuId sku.skuId ) if (idx -1) { cart[idx].count count } else { cart.push({ goodsId: goods._id, skuId: sku.skuId, name: goods.name, price: sku.price, image: sku.image, count }) } wx.setStorageSync(CART_KEY, cart) return cart.length } module.exports { getCart, addToCart }唯一键的选取是这里的关键同一个商品不同 SKU 必须视为不同条目不然红色 M 码和蓝色 L 码会互相覆盖。价格快照直接存在购物车条目里是为了结算页展示方便但真正下单时不能信这个快照。下单云函数里有个非常重要的设计——金额必须服务端重算exports.main async (event) { const { OPENID } cloud.getWXContext() const db cloud.database() const _ db.command const { items, address, remark } event const goodsIds items.map(item item.goodsId) const goodsResult await db.collection(goods) .where({ _id: _.in(goodsIds) }) .get() const goodsMap {} goodsResult.data.forEach(g { goodsMap[g._id] g }) // 金额以数据库实时价格为准前端价格只做展示 let totalPrice 0 items.forEach(item { const goods goodsMap[item.goodsId] if (!goods) throw new Error(商品不存在) totalPrice goods.price * item.count }) const order { openid: OPENID, items, address, remark, totalPrice: Math.round(totalPrice * 100) / 100, status: pending, createdAt: Date.now() } const res await db.collection(orders).add({ data: order }) return { orderId: res._id } }前端传来的 items 里即使带了 price服务端也不采用而是用 goods 表里的实时价格重新计算总价。这样用户改不了购物车里的价格快照来薅羊毛是电商类小程序必须守住的一条底线。4. 从导入到跑通开发者工具的完整配置流程源码拿到手能不能跑起来全看配置流程走不走得对。以下每一步都是实操过的照着顺序来基本一次过。4.1 准备阶段AppID、开发者工具与基础库先去微信公众平台注册一个小程序账号个人主体就够用注册完在设置-开发设置里能找到 AppID。注意这里拿到的 AppID 和项目里的 AppSecret 不是一回事AppSecret 是后端用的绝不能写进小程序前端代码里。然后安装微信开发者工具稳定版即可。项目导入前先确认工具的设置-项目设置里基础库版本选得不要太老建议选最新的稳定基础库否则云开发相关 API 可能不生效。4.2 导入项目替换 AppID 与配置环境 ID打开开发者工具选择导入项目目录选到源码根目录也就是 project.config.json 所在的那一层。导入时工具会让你填 AppID先填你自己账号的 AppID。如果导入后发现工具里显示的 AppID 还是旧的直接改 project.config.json 里的 appid 字段{ appid: wx你的真实AppID, projectname: demo-mall, setting: { es6: true, minified: true, urlCheck: false } }urlCheck 在开发阶段建议设为 false这样 wx.request 请求本地调试接口时不会因为域名未配置而报错。等正式上线再把合法域名配到公众平台并把 urlCheck 改回 true。接着打开 utils/config.js把环境 ID 换成自己的module.exports { envId: demo-env, // 云开发环境 ID baseUrl: https://api.example.com // 外部接口域名 }envId 要和后面开通的云开发环境 ID 一模一样差一个字符云函数就会找不到数据库。4.3 云开发初始化开通环境、创建集合并配置权限在开发者工具顶部工具栏找到云开发按钮首次使用会引导开通云开发创建环境时给它起个名字比如 demo-env。这个过程会分配独立的环境 ID复制出来填进 utils/config.js。云开发控制台里需要创建四个集合goods、users、orders、posts。每个集合都要单独设权限建议按下表配置集合名权限设置原因goods所有用户可读仅创建者可写商品是公共数据任何访客都要能看users仅创建者可读写用户数据只对自己可见orders仅创建者可读写订单是用户隐私不能暴露给他人posts所有用户可读仅创建者可写社区帖子要公开可读但只有作者能改权限配错是后面体验版查不到数据最常见的根源尤其是把 goods 设成了仅创建者可读写游客视角直接空列表。最后把 docs/goods.seed.json 导入到 goods 集合。云开发控制台支持直接导入 JSON 文件导入时确认字段格式与集合一致种子数据里包含商品名称、价格、图片地址、SKU 列表等字段导入后首页和详情页就有内容了。4.4 部署云函数、真机预览与提交审核云函数是源码的业务后端必须逐个上传部署。在编辑器左侧资源管理器中展开 cloudfunctions右键 login 目录选择上传并部署云端安装依赖其余云函数同样操作。注意选云端安装依赖而不是不上传 node_modules因为 wx-server-sdk 必须部署到云端才能跑。全部部署完成后在模拟器里重新编译首页轮播图出来、商品列表有数据、点商品进详情能选 SKU、加购后购物车角标变化、下单成功且订单列表可见这一条链路走通说明核心功能都正常。真机预览前在工具栏普通编译旁边下拉选择真机调试用手机微信扫码。首次预览会要求确认环境扫码后手机端小程序加载的就是你刚部署的云端代码。确认无误后上传代码并提交审核审核注意类目要与实际功能匹配涉及交易的类目需要额外资质个人主体上电商类目可能受限这是很多个人开发者绕不开的现实门槛。5. 避坑指南五个高频翻车现场与排查步骤这份源码我前后跑过几遍也踩过不少坑。下面五条几乎每个接手的人都会遇到每一条都按现象、原因、解决的顺序拆开讲省得你重新翻一遍文档。5.1 现象wx.request 请求一直报 403 或 url not in domain list原因小程序的 wx.request 有域名白名单机制只有配置在公众平台开发设置-服务器域名里的 HTTPS 域名才被允许访问。开发阶段如果用 ip 或未备案域名工具会直接拦截。解决开发阶段在 project.config.json 里把 urlCheck 设为 false或者直接在工具详情-本地设置里勾选不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书。上线前再回到公众平台配置正式域名域名必须 HTTPS 且完成 ICP 备案。5.2 现象云函数调用报错Environment not found或env not exists原因云函数里手动指定了 env但那个环境 ID 在你账号下不存在。常见于复制别人的源码别人环境 ID 直接写在代码里你换了自己的环境没同步改。解决把云函数里所有 cloud.init 的 env 改成 cloud.DYNAMIC_CURRENT_ENV让函数自动使用当前环境。同时检查 utils/config.js 里的 envId 与你云开发控制台的环境 ID 完全一致。改完重新上传部署云函数别只改本地。5.3 现象TabBar 图标不显示底栏只有文字原因tabBar 的 iconPath 和 selectedIconPath 对路径和格式敏感必须是项目内相对路径必须以 / 开头不支持网络图片文件后缀和实际格式大小写也要一致图片建议 81px 81px 的 PNG。解决检查 images 目录下图标文件是否存在文件名、后缀、大小写逐一核对路径改成/images/tab-home.png这种绝对形式。图标文件本身损坏也会导致这个问题换一个标准 81px 的 PNG 重新放进去。5.4 现象模拟器正常真机预览白屏或样式错乱原因真机基础库版本和模拟器不一致或者代码里用了较新的 API 与 CSS 特性。多数情况是 ES6 转 ES5 没开部分 Android 低版本机型的 JS 引擎跑不了高版本语法。解决在 project.config.json 的 setting 里确认 es6 为 true并勾选增强编译。同时把基础库版本选到最新稳定版。还有个小技巧真机调试时打开 vConsole看控制台具体报错白屏通常能在 console 里找到线索。5.5 现象体验版或真机上登录失败、商品列表为空原因数据库权限没配对。goods 集合权限设成了仅创建者可读写时其他用户的 openid 读不到任何数据users 集合如果设成所有用户可读又会把用户 openid 暴露给所有人两件事刚好相反。解决按第 4 章的权限表格设置goods 设为所有用户可读仅创建者可写users 和 orders 设为仅创建者可读写。改完权限后退出小程序重新进入让登录重新走一遍。云函数里操作数据库不受集合权限限制所以通过云函数读数据时也要在业务逻辑里自己判断 openid 归属。6. 二次开发实操把单店铺改造成多商户的三个抓手6.1 数据模型扩展给核心集合加 merchantId单店铺改多商户起点在数据模型。给 goods、orders、posts 三个集合各加一个 merchantId 字段商品查询、下单归属、社区帖子都按这个字段过滤。云函数里通过 event 接收当前商户 ID查询条件从裸的 where 变成where({ merchantId, status: on })改动面集中在云函数和详情页的数据请求。6.2 云函数鉴权按商户维度隔离数据集合权限只能区分创建者和非创建者多商户场景必须自己在云函数里做隔离。每个涉及商户数据的云函数先用 getWXContext 拿 openid再去商户表查这个 openid 归属的 merchantId后续所有查询都带这个条件。这样商户 A 永远看不到商户 B 的订单。云函数代码做好这件事前端改不改都无所谓。6.3 首屏体验自定义导航栏与骨架屏多商户改造同时值得做的是把首页导航栏换成自定义的。app.json 的 window 里设navigationStyle: custom页面顶部用组件模拟导航栏配合状态栏高度计算视觉上更贴近品牌调性。搭配骨架屏组件在商品数据返回前先渲染占位视图首屏白屏的时间肉眼可见地缩短。我从那以后每次接手小程序源码都强制先走一遍流程先看目录结构再确认 AppID 和环境 ID然后配数据库权限最后才编译。这套顺序帮我过滤掉了九成以上的启动问题。这份源码本身就是按这个思路组织的你按这个顺序过一遍大概率也能一次跑通。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站