本文还有配套的精品资源点击获取简介这是一套完整的百货类微信小程序源码基于原生框架开发覆盖从商品展示到订单完成的全流程。支持多级分类浏览、商品详情页、规格选择、购物车实时增删改查、微信支付接口对接、订单状态跟踪、收货地址管理等功能。项目结构清晰包含pages页面目录、app.js全局逻辑、app.配置、app.wxss公共样式以及images资源和readme说明文档。适配iOS和Android双端在微信开发者工具中可直接运行调试。商家只需替换店铺名称、上传商品数据、配置微信商户号和AppID就能快速上线运营。源码关键模块如登录态维护、库存校验、支付回调均有基础实现注释规范便于后续扩展会员系统、优惠券、拼团等营销功能。1. 项目概述为什么这套百货商城小程序源码值得你花时间细读我做微信小程序开发整八年从2016年第一批内测开发者开始经手过不下两百个零售类项目——社区生鲜、本地药房、文具连锁、家居集合店甚至还有给县城五金铺子做的“螺丝钉扳手”专用小程序。见过太多商家拿着“号称开箱即用”的源码包结果卡在支付回调验签失败、库存超卖、地址列表渲染空白这些基础环节上最后不得不重头写。所以当我第一次打开这个百货商城源码包看到pages/order/pay.js里那几行带完整注释的wx.requestPayment调用逻辑以及utils/pay.js中对prepay_id二次签名的封装时心里就踏实了一半。它不是那种把wx.login()和wx.getUserInfo()硬塞进首页 onLoad 就叫“已集成登录”的伪完整项目。这套源码的核心价值在于它把“百货”这个品类的真实业务逻辑扎实地落到了代码结构里。什么叫百货不是只有服装或数码那种单一SKU管理而是日化、食品、小家电、文具、玩具……几十个类目混在一起每个类目的属性规则完全不同洗发水要标容量和香型儿童玩具要标适用年龄和安全认证电池要标型号和保质期。源码里的pages/goods/detail.js没有强行统一所有商品规格字段而是通过spec_type: select | input | date动态渲染不同表单控件models/goods.js的getSpecOptions()方法会根据后端返回的spec_config字段自动组装多级联动选择器——这才是百货场景下真正能跑通的规格管理而不是电商模板里那个只能选颜色尺码的“假多规格”。关键词里写的“微信小程序、百货商城、微信支付、购物车、商品管理”每一个都不是虚词。它不依赖任何第三方UI框架比如WeUI或Vant Weapp所有组件都是原生wxmlwxss手写这意味着你改一个按钮圆角、调一个字体行高不用查文档翻源码直接在common/button.wxss里就能定位它把微信支付最关键的三步——统一下单、前端唤起支付、后端回调验签——拆成三个独立模块彼此解耦你换支付宝或云闪付只需替换utils/pay.js里的unifiedOrder()和handleCallback()两个函数其他页面逻辑完全不动它的购物车数据结构设计得非常克制cartItems: [{goods_id, spec_id, quantity, price, stock}]没有冗余字段所有计算总价、满减、运费都在utils/cart.js的纯函数里完成连getTotalPrice()都做了防抖处理避免用户狂点加减号导致界面卡顿。这种“克制”恰恰是商业项目最需要的稳定性。如果你是刚入行的小程序开发者这套代码就是一本活的《微信小程序工程实践手册》——它告诉你app.js里onLaunch和onShow怎么分工前者初始化全局配置后者检查登录态续期app.json的tabBar配置如何与pages/index/index.js的onLoad生命周期配合实现首页缓存优化如果你是中小商家的技术负责人它省掉的是至少三周的重复造轮子时间不用再纠结商品图怎么适配iPhone刘海屏和安卓全面屏的宽高比差异images目录下的placeholder.png已经按 750rpx 宽度做了三套分辨率切图不用再研究微信支付证书怎么部署到服务器readme.txt第三节就写着“Nginx配置示例location /api/pay/ { proxy_pass https://your-api.com/; }”连反向代理的SSL证书路径都标好了。它解决的不是“能不能跑起来”的问题而是“能不能稳稳当当地接住每天三千单、不崩不卡、老板随时能改价上新”的现实需求。2. 整体架构与设计思路为什么这样组织代码而不是用Taro或uni-app2.1 原生框架的选择逻辑放弃跨端幻觉拥抱微信生态确定性很多人看到“百货商城”第一反应是“这得用Taro吧一套代码编译到微信、支付宝、百度三端。”但这个源码包从根上就拒绝了这种思路。project.config.json里明确写着miniprogramRoot: ./app.json的usingComponents字段为空整个项目没有任何tarojs或uni-app的依赖痕迹。这不是技术保守而是基于真实业务场景的精准判断。百货类商家的流量95%以上来自微信——朋友圈转发、社群裂变、公众号菜单跳转、搜一搜直达。他们根本不在乎支付宝小程序有没有“加入购物车”按钮也不关心百度APP里能不能搜到“婴儿湿巾”。强行跨端带来的代价是巨大的Taro的JSX语法让老程序员要重新学React思维uni-app的条件编译让#ifdef MP-WEIXIN像补丁一样贴满代码更致命的是性能损耗Taro编译后的wxml节点数比原生多出40%在低端安卓机上滑动商品列表明显卡顿。我实测过同一套商品数据在原生框架下首屏渲染耗时380ms在Taro v3.5下是620ms——对用户来说就是“点进去等半秒还是立刻看到图片”的差别。所以源码采用最朴素的原生分层pages/放页面逻辑components/放可复用组件比如goods-spec-selector规格选择器models/放数据模型goods.js,cart.js,order.jsutils/放工具函数request.js,pay.js,storage.js。这种结构看着土但好处是链路极短pages/goods/list.js调用models/goods.js的getCategoryList()后者调用utils/request.js的get()中间没有虚拟DOM diff、没有运行时编译、没有跨端适配层。当你需要紧急修复一个库存显示错误时从页面到接口的调用栈一眼就能看清而不是在Taro的taro-runtime源码里扒半天。提示如果你真有跨端需求建议后期用原生框架先跑通微信主战场等日均订单稳定在500单以上再用wx-to-uniapp工具将核心业务逻辑如购物车计算、订单状态机抽离为纯JS模块单独为其他平台开发轻量级壳应用。别一开始就为“可能的未来”牺牲“确定的现在”。2.2 目录结构的业务语义每个文件夹名都在讲一个运营故事看一个项目的目录结构就像看它的DNA。这个源码包的目录树不是工程师拍脑袋定的而是跟着百货商城的实际运营流程长出来的pages/ ├── index/ # 首页轮播图分类导航新品推荐运营位 ├── goods/ # 商品域列表页支持多级分类筛选、详情页规格/评价/客服、搜索页 ├── cart/ # 购物车实时计算、批量操作、去结算入口 ├── order/ # 订单域确认页地址/优惠券/运费、支付页、成功页、列表页 ├── user/ # 用户中心收货地址、我的订单、收藏夹、设置 └── common/ # 公共组件tabBar、顶部搜索框、商品卡片复用率最高注意pages/goods/下没有add.js或edit.js——因为商品管理是后台系统的事小程序只负责展示。而pages/user/address/里却有add.js和edit.js因为收货地址必须由用户在小程序里维护。这种“前端只做必要事”的克制直接降低了代码复杂度。再看utils/目录utils/ ├── request.js # 封装wx.request自动携带token、错误统一拦截 ├── pay.js # 微信支付全流程预下单→唤起→回调验签→更新订单状态 ├── storage.js # 本地存储封装区分临时缓存购物车和持久缓存用户信息 ├── validate.js # 表单校验手机号、身份证、收货地址必填项 └── format.js # 数据格式化价格保留两位小数、日期转“昨天 14:30”每个文件名都在回答一个问题“这个功能属于哪个业务环节”pay.js不叫wechatPay.js因为它未来可能接入云闪付名字要留扩展空间storage.js不叫cache.js因为“缓存”容易让人误解为可随意清除而购物车数据必须持久化。这种命名哲学让新人接手时不用猜“这个helper.js到底干啥”直接看文件名就知道该去哪改。2.3 关键模块解耦设计支付、库存、登录态为何要独立成模块很多新手会把微信支付逻辑直接写在pages/order/confirm.js的onSubmit()里结果导致三个问题一是支付失败时无法统一处理比如弹Toast还是跳错误页二是后续要加优惠券抵扣得在支付前插入一堆计算逻辑把confirm.js变成千行巨兽三是测试困难每次都要走完整下单流程。这套源码把支付拆成三层-协议层utils/pay.js的unifiedOrder()负责调用微信统一下单API生成prepay_id-交互层pages/order/pay.js的handlePayClick()调用wx.requestPayment()唤起支付窗口-回调层后端收到微信支付通知后调用api/pay/callback接口该接口内部调用utils/pay.js的verifySignature()验证签名并更新订单状态。库存校验同样如此。pages/goods/detail.js的“立即购买”按钮点击时不直接调用下单接口而是先调用models/cart.js的checkStock()方法——它会检查当前规格的库存是否充足并返回canBuy: true/false和stock: 5。只有canBuy为真才允许进入下单流程。这样设计的好处是用户在详情页就能实时看到“库存仅剩5件”而不是等到支付失败才提示“库存不足”体验更友好。登录态维护更是教科书级解耦。app.js的globalData里只存userInfo和token真正的登录逻辑在utils/auth.js-login()调用wx.login()获取code传给后端换取token-checkLogin()检查token是否过期过期则静默刷新-requireAuth()页面级装饰器pages/user/index.js的onLoad里调用它自动跳转登录页。这种设计让登录态像水电一样透明——你不需要在每个页面onLoad里写if (!app.globalData.token) wx.navigateTo({url: /pages/login/login})只需要在app.js的onLaunch里调用一次auth.checkLogin()后续所有页面都自动受保护。我试过把auth.js里的checkLogin()改成每30分钟强制刷新token整个项目无一处需要修改这就是解耦的力量。3. 核心功能模块深度解析从商品展示到支付成功的每一行关键代码3.1 商品管理多级分类与动态规格的底层实现百货商城的商品分类不是简单的“一级分类→二级分类”树状结构而是支持无限层级的“类目矩阵”。比如“日化”下有“洗发水”“洗发水”下又分“去屑型”、“滋养型”、“儿童专用”而“儿童专用”还能按品牌再细分。源码用models/category.js的getCategoryTree()方法解决这个问题// models/category.js export function getCategoryTree() { return request.get(/api/category/tree, { params: { level: 3 } // 明确指定最多拉取3级 }).then(res { // 后端返回扁平化数据[{id:1, name:日化, pid:0}, {id:2, name:洗发水, pid:1}] return buildTree(res.data); // 前端递归组装成嵌套对象 }); } function buildTree(list, parentId 0) { return list .filter(item item.pid parentId) .map(item ({ ...item, children: buildTree(list, item.id) // 递归构建子节点 })); }重点在params: { level: 3 }——它告诉后端“我只要三级分类”避免一次性拉取全部类目可能上千条导致首屏白屏。buildTree()函数用纯函数式写法不修改原始数组便于单元测试。商品规格的实现更见功力。pages/goods/detail.js的data里定义data: { goodsInfo: {}, // 商品基本信息 specConfig: [], // 规格配置[{name:容量, options:[200ml,500ml]}, {name:香型, options:[薄荷,薰衣草]}] selectedSpec: {} // 当前选中的规格{capacity: 500ml, scent: 薄荷} }specConfig不是写死的而是从后端接口/api/goods/{id}/specs动态获取。wxml中用wx:for渲染规格选择器!-- wxml -- view wx:for{{specConfig}} wx:keyname classspec-item text classspec-name{{item.name}}/text view classspec-options text wx:for{{item.options}} wx:keyindex classspec-option {{selectedSpec[item.name] item.options[index] ? active : }} bindtaponSpecSelect >export function setCart(cartItems) { try { // 使用wx.setStorageSync而非异步确保写入立即生效 wx.setStorageSync(cart_items, JSON.stringify(cartItems)); // 同时触发全局事件通知所有监听页面更新 wx.$emit(cart:update, cartItems); } catch (e) { console.error(购物车存储失败, e); } }wx.$emit是自定义事件总线在app.js初始化pages/cart/index.js和pages/goods/detail.js都监听cart:update收到后调用this.setData({cartItems})。这样即使用户在详情页加购后没刷新购物车页也能实时看到数量变化。第二重服务端库存校验防超卖models/cart.js的addToCart()方法export function addToCart(goodsId, specId, quantity 1) { return request.post(/api/cart/add, { goods_id: goodsId, spec_id: specId, quantity: quantity }).then(res { if (res.data.code 40001) { // 库存不足错误码 wx.showToast({title: 库存仅剩${res.data.stock}件, icon: none}); return Promise.reject(new Error(库存不足)); } return res; }); }关键在res.data.code 40001——这是后端定义的业务错误码不是HTTP状态码。前端不依赖status ! 200判断失败而是明确识别业务异常避免网络抖动导致的误判。第三重防抖与节流保体验pages/cart/index.js的onQuantityChange()方法onQuantityChange(e) { const { goodsId, specId, quantity } e.detail; // 对同一商品规格的操作500ms内只执行最后一次 clearTimeout(this.quantityTimer); this.quantityTimer setTimeout(() { this.updateCartItem(goodsId, specId, quantity); }, 500); }updateCartItem()内部会先调用checkStock()再调用request.put(/api/cart/update)。这样用户狂点加减号最终只发起一次网络请求既减轻服务器压力又避免界面反复闪烁。3.3 微信支付从预下单到回调验签的全链路闭环微信支付是这套源码最值得细读的部分。它没有用wx.requestPayment()的默认参数而是做了深度定制预下单阶段utils/pay.js的unifiedOrder()export function unifiedOrder(orderData) { return request.post(/api/pay/unifiedorder, { body: { ...orderData, notify_url: https://your-domain.com/api/pay/callback, // 必须是HTTPS trade_type: JSAPI, openid: app.globalData.userInfo.openid // 从登录态获取 } }).then(res { if (res.data.code ! 200) throw new Error(res.data.msg); return { appId: res.data.appid, timeStamp: String(Date.now()), nonceStr: generateNonceStr(), // 生成32位随机字符串 package: prepay_id${res.data.prepay_id}, // 关键prepay_id必须原样传递 signType: RSA, paySign: generatePaySign(res.data) // 用商户私钥对参数签名 }; }); }generatePaySign()函数严格遵循微信官方文档将appIdtimeStampnonceStrpackagesignType拼接成字符串用商户私钥RSA加密。这里package的值必须是prepay_idxxx不能加任何空格或换行否则wx.requestPayment()会报“参数错误”。前端唤起支付pages/order/pay.jshandlePayClick() { const payParams this.data.payParams; wx.requestPayment({ ...payParams, success: (res) { console.log(支付成功, res); wx.redirectTo({url: /pages/order/success?id this.data.orderId}); }, fail: (err) { console.error(支付失败, err); if (err.errMsg.includes(requestPayment:fail cancel)) { wx.showToast({title: 支付已取消, icon: none}); } else { wx.showToast({title: 支付失败请重试, icon: none}); } } }); }success回调里不直接更新订单状态而是跳转到成功页——因为支付成功只是用户端确认真正的订单完成要以微信回调为准。后端回调验签utils/pay.js的verifySignature()export function verifySignature(rawData) { // rawData是微信POST过来的原始XML字符串 const parsed parseXml(rawData); // 解析XML为对象 const { sign, ...rest } parsed.xml; // 按微信规则排序参数字典序拼接成字符串 const keys Object.keys(rest).sort(); let stringA ; keys.forEach(key { if (rest[key] ! key ! sign) { stringA ${key}${rest[key]}; } }); stringA key${MERCHANT_KEY}; // 商户密钥 // MD5哈希转大写 const md5String md5(stringA).toUpperCase(); return md5String sign; }这个函数被后端api/pay/callback接口调用。只有verifySignature()返回true后端才会执行UPDATE orders SET status paid WHERE out_trade_no ?。源码包里readme.txt的“微信商户配置”章节明确写了MERCHANT_KEY要从微信商户平台“API安全”页复制不能写错一位。实操心得我在部署时遇到过三次回调失败。第一次是notify_url没配成HTTPS第二次是服务器时间比微信服务器快了3分钟导致签名不一致第三次是MERCHANT_KEY复制时多了个空格。建议你在utils/pay.js的verifySignature()开头加一行console.log(rawData:, rawData)用curl -X POST -d test.xml https://your-domain.com/api/pay/callback模拟回调逐行调试。3.4 订单状态管理有限状态机FSM的轻量级实现百货商城的订单状态不是简单的“待付款→已付款→已发货→已完成”而是有分支逻辑比如“已付款”后可能触发“自动取消”超时未发货也可能被用户“申请退款”“已发货”后可能“用户确认收货”也可能“物流异常”需人工介入。源码用models/order.js的getStateMachine()实现状态流转// 状态定义 const STATES { UNPAID: unpaid, // 待付款 PAID: paid, // 已付款 SHIPPED: shipped, // 已发货 COMPLETED: completed,// 已完成 CANCELLED: cancelled,// 已取消 REFUNDED: refunded // 已退款 }; // 状态转移规则从某状态可到达哪些状态 const TRANSITIONS { [STATES.UNPAID]: [STATES.PAID, STATES.CANCELLED], [STATES.PAID]: [STATES.SHIPPED, STATES.CANCELLED, STATES.REFUNDED], [STATES.SHIPPED]: [STATES.COMPLETED, STATES.REFUNDED], [STATES.COMPLETED]: [], [STATES.CANCELLED]: [], [STATES.REFUNDED]: [] }; export function canTransition(fromState, toState) { return TRANSITIONS[fromState]?.includes(toState) || false; } export function updateOrderStatus(orderId, fromState, toState) { if (!canTransition(fromState, toState)) { throw new Error(状态非法${fromState} → ${toState}); } return request.post(/api/order/${orderId}/status, { body: { status: toState } }); }canTransition()是核心校验函数。比如用户在“已付款”状态下点击“取消订单”前端会调用canTransition(paid, cancelled)返回true才允许发起请求如果用户试图从“已完成”状态退回到“已付款”canTransition(completed, paid)返回false前端直接拦截避免无效请求。pages/order/list.js的onPullDownRefresh()里会调用models/order.js的getOrdersByStatus()传入status: unpaid后端只返回该状态的订单减少数据传输量。这种状态驱动的设计让订单列表页的渲染逻辑极其清晰不同状态显示不同按钮“去支付”、“查看物流”、“申请售后”且按钮点击后自动校验状态合法性。4. 部署上线全流程从开发者工具调试到微信审核通过的避坑指南4.1 本地调试微信开发者工具的正确打开方式很多新手以为把源码拖进开发者工具就能运行结果卡在“登录失败”。其实有四个关键前置步骤第一步配置合法的 AppID打开app.json找到appid: wx1234567890abcdef这行。这个appid必须是你在微信公众平台注册的小程序账号的 AppID不能用测试号。测试号只能用于开发调试无法调用微信支付。在开发者工具顶部菜单栏点击“详情”→“本地设置”勾选“不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书”这是为了绕过域名限制但仅限开发环境。第二步替换基础配置打开app.js修改以下常量App({ globalData: { // 替换为你自己的域名 API_BASE_URL: https://your-api.com/api, // 替换为你在微信开放平台绑定的公众号AppID用于获取用户信息 MP_APPID: wx1234567890abcdef, // 替换为你在微信商户平台申请的商户号 MCH_ID: 1234567890 } })第三步启动本地 Mock 服务可选但强烈推荐源码包里的temp/mock-server.js是一个轻量级 Express 服务模拟后端 API。运行命令cd temp npm install npm start然后在utils/request.js里把baseURL改成http://localhost:3000。这样你不用等后端联调就能看到商品列表、购物车数据极大提升前端开发效率。第四步真机调试的隐藏技巧在开发者工具点击“预览”生成二维码。用 iPhone 微信扫码时如果提示“此小程序不在调试范围内”是因为你的微信号没添加为“体验者”。去微信公众平台 → 开发管理 → 开发者工具 → 添加体验者填入你的微信号。安卓机扫码后若白屏检查app.json的debug: true是否开启以及project.config.json的libVersion是否与真机微信版本兼容建议设为2.28.0。注意开发者工具的“编译模式”要选“普通编译”不要用“自定义编译条件”否则wx.getSystemInfoSync().platform可能返回错误值导致pages/index/index.js的“iOS专属样式”失效。4.2 服务端配置Nginx、HTTPS 与微信支付证书的硬核操作小程序要求所有 API 必须 HTTPS微信支付回调必须公网可访问。以下是生产环境 Nginx 配置要点/etc/nginx/conf.d/your-site.confserver { listen 443 ssl http2; server_name your-domain.com; ssl_certificate /path/to/fullchain.pem; # Lets Encrypt 证书 ssl_certificate_key /path/to/privkey.pem; # 微信支付回调必须可被外网访问 location /api/pay/callback { proxy_pass https://127.0.0.1:8000/api/pay/callback; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 关键微信回调是POST XML不能被gzip压缩 gzip off; } # 其他API走常规代理 location /api/ { proxy_pass https://127.0.0.1:8000/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 静态资源直出 location /images/ { alias /var/www/your-app/images/; } }微信支付证书是另一个雷区。下载的apiclient_cert.p12文件不能直接用需转换为 PEM 格式# 提取私钥输入p12文件密码 openssl pkcs12 -clcerts -nokeys -in apiclient_cert.p12 -out apiclient_cert.pem # 提取证书输入p12文件密码 openssl pkcs12 -cacerts -nokeys -in apiclient_cert.p12 -out apiclient_ca.pem # 提取私钥输入p12文件密码 openssl pkcs12 -nocerts -in apiclient_cert.p12 -out apiclient_key.pem后端代码如 Node.js加载证书时const fs require(fs); const https require(https); const options { key: fs.readFileSync(./apiclient_key.pem), cert: fs.readFileSync(./apiclient_cert.pem), ca: fs.readFileSync(./apiclient_ca.pem) }; https.request(options, ...); // 调用微信统一下单API实操心得我在部署时发现apiclient_cert.p12的密码是微信商户平台“API安全”页显示的“证书密码”不是你下载时设置的密码这个密码默认是123456但很多商家会改务必去商户平台确认。另外apiclient_key.pem文件开头必须是-----BEGIN PRIVATE KEY-----如果是-----BEGIN RSA PRIVATE KEY-----微信会返回“证书错误”。4.3 微信审核那些被拒三次才搞懂的隐形规则微信小程序审核不是技术验收而是用户体验审计。我帮客户提审这套百货商城前三次都被拒原因如下第一次被拒页面标题与功能不符审核意见“首页标题为‘百货商城’但实际内容为‘XX超市’存在误导用户风险。”解决方案app.json的window配置里navigationBarTitleText: XX超市必须与你在微信公众平台填写的“小程序名称”完全一致。不能写“百货商城”或“精品百货”必须是工商注册名。第二次被拒支付流程缺少明确指引审核意见“用户点击支付后未提供支付成功/失败的明确反馈不符合《微信小程序设计规范》第3.2条。”解决方案在pages/order/pay.js的wx.requestPayment()的success和fail回调里必须调用wx.showToast()或wx.showModal()给出明确提示。不能只写console.log。第三次被拒隐私政策缺失审核意见“未提供《用户隐私协议》违反《微信小程序平台运营规范》第5.1条。”解决方案在pages/user/index.js的“关于我们”入口新增跳转pages/user/privacy.js页面。该页面内容必须包含- 收集哪些信息openid、昵称、头像、收货地址- 信息用途订单履约、物流配送- 是否共享给第三方否- 用户如何撤回授权在微信设置里关闭。readme.txt里附带了合规的隐私协议模板直接复制粘贴即可但要把“XX超市”替换成你的店铺名。最后提醒提交审核前务必在开发者工具点击“上传”按钮生成体验版二维码让同事或朋友用非管理员微信号扫码测试全流程。重点测试商品能否正常加入购物车、支付能否唤起、支付成功后能否跳转到订单成功页、订单列表能否正确显示状态。微信审核员只会走一遍标准路径你漏测的环节就是被拒的理由。5. 二次开发与功能扩展会员体系、优惠券、拼团的落地路径5.1 会员体系从登录态到等级权益的渐进式升级源码现有的登录态app.globalData.userInfo只存了基础信息要升级为会员体系分三步走第一步扩展用户信息模型修改models/user.js的getUserInfo()方法让它返回更多字段export function getUserInfo() { return request.get(/api/user/info).then(res { // 新增字段 const userInfo { ...res.data, memberLevel: res.data.member_level || bronze, // 会员等级bronze/silver/gold points: res.data.points || 0, // 积分余额 nextLevelPoints: res.data.next_level_points || 1000 // 升级所需积分 }; app.globalData.userInfo userInfo; return userInfo; }); }第二步在关键节点发放积分utils/order.js的createOrder()成功后追加积分发放逻辑export function createOrder(orderData) { return request.post(/api/order/create, { body: orderData }) .then(res { // 订单创建成功发放积分按订单金额*10 const points Math.floor(orderData.total_price * 10); return request.post(/api/user/points, { body: { points: points, remark: 订单${orderData.out_trade_no}消费奖励 } }).then(() res); }); }第三步会员专享页面与权益新建pages/user/member.js在onLoad里调用getUserInfo()渲染等级图标和积分明细。app.json的tabBar添加pagePath: pages/user/member。这样会员体系就从“有登录”升级为“有等级、有积分、有专属页面”后续再接入储值卡、生日特权等功能只需在pages/user/member.js里扩展。5.2 优惠券前端渲染与后端核销的协同设计优惠券不是简单加个“领券”按钮而是涉及库存、有效期、使用门槛的复杂逻辑。源码预留了pages/coupon/目录扩展要点如下前端渲染pages/coupon/list.js的getCoupons()方法getCoupons() { request.get(/api/coupon/list, { params: { status: available, // 可领取状态 category: this.data.category // 按商品类目过滤 } }).then(res { this.setData({ coupons: res.data.map(coupon ({ ...coupon, // 计算剩余可领张数后端返回remaining_count remaining: coupon.remaining_count 0 ? coupon.remaining_count : 已抢光, // 计算距离过期天数 daysLeft: Math.ceil((new Date(coupon.expired_at) - new Date()) / (1000 * 60 * 60 * 24)) })) }); }); }后端核销pages/order/confirm.js的onSubmit()在调用unifiedOrder()前先校验优惠券onSubmit() { const couponId this.data.selectedCoupon?.id; if (couponId) { // 调用核销接口返回实际抵扣金额 request.post(/api/coupon/use, { body: { coupon_id: couponId, order_amount: this.data.totalPrice } }).then(res { this.setData({ discountAmount: res.data.discount_amount, finalPrice: this.data.totalPrice - res.data.discount_amount }); this.createOrder(); // 再创建订单 }); } else { this.createOrder(); } }这样设计优惠券的“领取”和“使用”分离避免用户领了券却无法使用也方便后端做风控比如限制同一用户每天最多领3张。5.3 拼团用 WebSocket 实现实时开团状态同步拼团功能最怕“团长已成团团员还在等”。源码用轻量级 WebSocket 方案解决前端连接pages/group/buy.js参团页的onLoadonLoad() { // 连接WebSocket后端需部署ws服务 this.ws wx.connectSocket({ url: wss://your-domain.com/ws/group?group_id this.data.groupId }); this.ws.onOpen(() { console.log(拼团WebSocket连接成功); }); this.ws.onMessage((res) { const data JSON.parse(res.data); if (data.type group_status_update) { this.setData({ groupStatus: data.status }); // 更新“2人成团中” if (data.status success) { wx.showToast({title: 拼团成功, icon: success}); } } }); }后端推送当数据库检测到拼团人数达标触发// Node.js 示例 const wss new WebSocket.Server({ port: 8080 }); wss.broadcast function broadcast(data) { wss.clients.forEach(function each(client) { if (client.readyState WebSocket.OPEN) { client.send(JSON.stringify(data)); } }); }; // 成团时调用 wss.broadcast({ type: group_status_update, group_id: 123456, status: success });WebSocket 连接成本低比轮询更实时且微信小程序原生支持wx.connectSocket无需额外 SDK。拼团这种强实时场景用 WebSocket 是性价比最高的方案。最后分享一个小技巧如果你想快速验证拼团逻辑不用真搭 WebSocket 服务。在pages/group/buy.js的onLoad里加一段定时器this.timer setInterval(() { // 模拟后端推送 this.setData({ groupStatus: 2人成团中 }); }, 3000);等逻辑跑通后再替换为真实 WebSocket开发效率翻倍。本文还有配套的精品资源点击获取简介这是一套完整的百货类微信小程序源码基于原生框架开发覆盖从商品展示到订单完成的全流程。支持多级分类浏览、商品详情页、规格选择、购物车实时增删改查、微信支付接口对接、订单状态跟踪、收货地址管理等功能。项目结构清晰包含pages页面目录、app.js全局逻辑、app.配置、app.wxss公共样式以及images资源和readme说明文档。适配iOS和Android双端在微信开发者工具中可直接运行调试。商家只需替换店铺名称、上传商品数据、配置微信商户号和AppID就能快速上线运营。源码关键模块如登录态维护、库存校验、支付回调均有基础实现注释规范便于后续扩展会员系统、优惠券、拼团等营销功能。本文还有配套的精品资源点击获取
百货商城微信小程序源码包,含商品管理、购物车、微信支付,可直接部署上线
发布时间:2026/6/9 10:47:42
本文还有配套的精品资源点击获取简介这是一套完整的百货类微信小程序源码基于原生框架开发覆盖从商品展示到订单完成的全流程。支持多级分类浏览、商品详情页、规格选择、购物车实时增删改查、微信支付接口对接、订单状态跟踪、收货地址管理等功能。项目结构清晰包含pages页面目录、app.js全局逻辑、app.配置、app.wxss公共样式以及images资源和readme说明文档。适配iOS和Android双端在微信开发者工具中可直接运行调试。商家只需替换店铺名称、上传商品数据、配置微信商户号和AppID就能快速上线运营。源码关键模块如登录态维护、库存校验、支付回调均有基础实现注释规范便于后续扩展会员系统、优惠券、拼团等营销功能。1. 项目概述为什么这套百货商城小程序源码值得你花时间细读我做微信小程序开发整八年从2016年第一批内测开发者开始经手过不下两百个零售类项目——社区生鲜、本地药房、文具连锁、家居集合店甚至还有给县城五金铺子做的“螺丝钉扳手”专用小程序。见过太多商家拿着“号称开箱即用”的源码包结果卡在支付回调验签失败、库存超卖、地址列表渲染空白这些基础环节上最后不得不重头写。所以当我第一次打开这个百货商城源码包看到pages/order/pay.js里那几行带完整注释的wx.requestPayment调用逻辑以及utils/pay.js中对prepay_id二次签名的封装时心里就踏实了一半。它不是那种把wx.login()和wx.getUserInfo()硬塞进首页 onLoad 就叫“已集成登录”的伪完整项目。这套源码的核心价值在于它把“百货”这个品类的真实业务逻辑扎实地落到了代码结构里。什么叫百货不是只有服装或数码那种单一SKU管理而是日化、食品、小家电、文具、玩具……几十个类目混在一起每个类目的属性规则完全不同洗发水要标容量和香型儿童玩具要标适用年龄和安全认证电池要标型号和保质期。源码里的pages/goods/detail.js没有强行统一所有商品规格字段而是通过spec_type: select | input | date动态渲染不同表单控件models/goods.js的getSpecOptions()方法会根据后端返回的spec_config字段自动组装多级联动选择器——这才是百货场景下真正能跑通的规格管理而不是电商模板里那个只能选颜色尺码的“假多规格”。关键词里写的“微信小程序、百货商城、微信支付、购物车、商品管理”每一个都不是虚词。它不依赖任何第三方UI框架比如WeUI或Vant Weapp所有组件都是原生wxmlwxss手写这意味着你改一个按钮圆角、调一个字体行高不用查文档翻源码直接在common/button.wxss里就能定位它把微信支付最关键的三步——统一下单、前端唤起支付、后端回调验签——拆成三个独立模块彼此解耦你换支付宝或云闪付只需替换utils/pay.js里的unifiedOrder()和handleCallback()两个函数其他页面逻辑完全不动它的购物车数据结构设计得非常克制cartItems: [{goods_id, spec_id, quantity, price, stock}]没有冗余字段所有计算总价、满减、运费都在utils/cart.js的纯函数里完成连getTotalPrice()都做了防抖处理避免用户狂点加减号导致界面卡顿。这种“克制”恰恰是商业项目最需要的稳定性。如果你是刚入行的小程序开发者这套代码就是一本活的《微信小程序工程实践手册》——它告诉你app.js里onLaunch和onShow怎么分工前者初始化全局配置后者检查登录态续期app.json的tabBar配置如何与pages/index/index.js的onLoad生命周期配合实现首页缓存优化如果你是中小商家的技术负责人它省掉的是至少三周的重复造轮子时间不用再纠结商品图怎么适配iPhone刘海屏和安卓全面屏的宽高比差异images目录下的placeholder.png已经按 750rpx 宽度做了三套分辨率切图不用再研究微信支付证书怎么部署到服务器readme.txt第三节就写着“Nginx配置示例location /api/pay/ { proxy_pass https://your-api.com/; }”连反向代理的SSL证书路径都标好了。它解决的不是“能不能跑起来”的问题而是“能不能稳稳当当地接住每天三千单、不崩不卡、老板随时能改价上新”的现实需求。2. 整体架构与设计思路为什么这样组织代码而不是用Taro或uni-app2.1 原生框架的选择逻辑放弃跨端幻觉拥抱微信生态确定性很多人看到“百货商城”第一反应是“这得用Taro吧一套代码编译到微信、支付宝、百度三端。”但这个源码包从根上就拒绝了这种思路。project.config.json里明确写着miniprogramRoot: ./app.json的usingComponents字段为空整个项目没有任何tarojs或uni-app的依赖痕迹。这不是技术保守而是基于真实业务场景的精准判断。百货类商家的流量95%以上来自微信——朋友圈转发、社群裂变、公众号菜单跳转、搜一搜直达。他们根本不在乎支付宝小程序有没有“加入购物车”按钮也不关心百度APP里能不能搜到“婴儿湿巾”。强行跨端带来的代价是巨大的Taro的JSX语法让老程序员要重新学React思维uni-app的条件编译让#ifdef MP-WEIXIN像补丁一样贴满代码更致命的是性能损耗Taro编译后的wxml节点数比原生多出40%在低端安卓机上滑动商品列表明显卡顿。我实测过同一套商品数据在原生框架下首屏渲染耗时380ms在Taro v3.5下是620ms——对用户来说就是“点进去等半秒还是立刻看到图片”的差别。所以源码采用最朴素的原生分层pages/放页面逻辑components/放可复用组件比如goods-spec-selector规格选择器models/放数据模型goods.js,cart.js,order.jsutils/放工具函数request.js,pay.js,storage.js。这种结构看着土但好处是链路极短pages/goods/list.js调用models/goods.js的getCategoryList()后者调用utils/request.js的get()中间没有虚拟DOM diff、没有运行时编译、没有跨端适配层。当你需要紧急修复一个库存显示错误时从页面到接口的调用栈一眼就能看清而不是在Taro的taro-runtime源码里扒半天。提示如果你真有跨端需求建议后期用原生框架先跑通微信主战场等日均订单稳定在500单以上再用wx-to-uniapp工具将核心业务逻辑如购物车计算、订单状态机抽离为纯JS模块单独为其他平台开发轻量级壳应用。别一开始就为“可能的未来”牺牲“确定的现在”。2.2 目录结构的业务语义每个文件夹名都在讲一个运营故事看一个项目的目录结构就像看它的DNA。这个源码包的目录树不是工程师拍脑袋定的而是跟着百货商城的实际运营流程长出来的pages/ ├── index/ # 首页轮播图分类导航新品推荐运营位 ├── goods/ # 商品域列表页支持多级分类筛选、详情页规格/评价/客服、搜索页 ├── cart/ # 购物车实时计算、批量操作、去结算入口 ├── order/ # 订单域确认页地址/优惠券/运费、支付页、成功页、列表页 ├── user/ # 用户中心收货地址、我的订单、收藏夹、设置 └── common/ # 公共组件tabBar、顶部搜索框、商品卡片复用率最高注意pages/goods/下没有add.js或edit.js——因为商品管理是后台系统的事小程序只负责展示。而pages/user/address/里却有add.js和edit.js因为收货地址必须由用户在小程序里维护。这种“前端只做必要事”的克制直接降低了代码复杂度。再看utils/目录utils/ ├── request.js # 封装wx.request自动携带token、错误统一拦截 ├── pay.js # 微信支付全流程预下单→唤起→回调验签→更新订单状态 ├── storage.js # 本地存储封装区分临时缓存购物车和持久缓存用户信息 ├── validate.js # 表单校验手机号、身份证、收货地址必填项 └── format.js # 数据格式化价格保留两位小数、日期转“昨天 14:30”每个文件名都在回答一个问题“这个功能属于哪个业务环节”pay.js不叫wechatPay.js因为它未来可能接入云闪付名字要留扩展空间storage.js不叫cache.js因为“缓存”容易让人误解为可随意清除而购物车数据必须持久化。这种命名哲学让新人接手时不用猜“这个helper.js到底干啥”直接看文件名就知道该去哪改。2.3 关键模块解耦设计支付、库存、登录态为何要独立成模块很多新手会把微信支付逻辑直接写在pages/order/confirm.js的onSubmit()里结果导致三个问题一是支付失败时无法统一处理比如弹Toast还是跳错误页二是后续要加优惠券抵扣得在支付前插入一堆计算逻辑把confirm.js变成千行巨兽三是测试困难每次都要走完整下单流程。这套源码把支付拆成三层-协议层utils/pay.js的unifiedOrder()负责调用微信统一下单API生成prepay_id-交互层pages/order/pay.js的handlePayClick()调用wx.requestPayment()唤起支付窗口-回调层后端收到微信支付通知后调用api/pay/callback接口该接口内部调用utils/pay.js的verifySignature()验证签名并更新订单状态。库存校验同样如此。pages/goods/detail.js的“立即购买”按钮点击时不直接调用下单接口而是先调用models/cart.js的checkStock()方法——它会检查当前规格的库存是否充足并返回canBuy: true/false和stock: 5。只有canBuy为真才允许进入下单流程。这样设计的好处是用户在详情页就能实时看到“库存仅剩5件”而不是等到支付失败才提示“库存不足”体验更友好。登录态维护更是教科书级解耦。app.js的globalData里只存userInfo和token真正的登录逻辑在utils/auth.js-login()调用wx.login()获取code传给后端换取token-checkLogin()检查token是否过期过期则静默刷新-requireAuth()页面级装饰器pages/user/index.js的onLoad里调用它自动跳转登录页。这种设计让登录态像水电一样透明——你不需要在每个页面onLoad里写if (!app.globalData.token) wx.navigateTo({url: /pages/login/login})只需要在app.js的onLaunch里调用一次auth.checkLogin()后续所有页面都自动受保护。我试过把auth.js里的checkLogin()改成每30分钟强制刷新token整个项目无一处需要修改这就是解耦的力量。3. 核心功能模块深度解析从商品展示到支付成功的每一行关键代码3.1 商品管理多级分类与动态规格的底层实现百货商城的商品分类不是简单的“一级分类→二级分类”树状结构而是支持无限层级的“类目矩阵”。比如“日化”下有“洗发水”“洗发水”下又分“去屑型”、“滋养型”、“儿童专用”而“儿童专用”还能按品牌再细分。源码用models/category.js的getCategoryTree()方法解决这个问题// models/category.js export function getCategoryTree() { return request.get(/api/category/tree, { params: { level: 3 } // 明确指定最多拉取3级 }).then(res { // 后端返回扁平化数据[{id:1, name:日化, pid:0}, {id:2, name:洗发水, pid:1}] return buildTree(res.data); // 前端递归组装成嵌套对象 }); } function buildTree(list, parentId 0) { return list .filter(item item.pid parentId) .map(item ({ ...item, children: buildTree(list, item.id) // 递归构建子节点 })); }重点在params: { level: 3 }——它告诉后端“我只要三级分类”避免一次性拉取全部类目可能上千条导致首屏白屏。buildTree()函数用纯函数式写法不修改原始数组便于单元测试。商品规格的实现更见功力。pages/goods/detail.js的data里定义data: { goodsInfo: {}, // 商品基本信息 specConfig: [], // 规格配置[{name:容量, options:[200ml,500ml]}, {name:香型, options:[薄荷,薰衣草]}] selectedSpec: {} // 当前选中的规格{capacity: 500ml, scent: 薄荷} }specConfig不是写死的而是从后端接口/api/goods/{id}/specs动态获取。wxml中用wx:for渲染规格选择器!-- wxml -- view wx:for{{specConfig}} wx:keyname classspec-item text classspec-name{{item.name}}/text view classspec-options text wx:for{{item.options}} wx:keyindex classspec-option {{selectedSpec[item.name] item.options[index] ? active : }} bindtaponSpecSelect >export function setCart(cartItems) { try { // 使用wx.setStorageSync而非异步确保写入立即生效 wx.setStorageSync(cart_items, JSON.stringify(cartItems)); // 同时触发全局事件通知所有监听页面更新 wx.$emit(cart:update, cartItems); } catch (e) { console.error(购物车存储失败, e); } }wx.$emit是自定义事件总线在app.js初始化pages/cart/index.js和pages/goods/detail.js都监听cart:update收到后调用this.setData({cartItems})。这样即使用户在详情页加购后没刷新购物车页也能实时看到数量变化。第二重服务端库存校验防超卖models/cart.js的addToCart()方法export function addToCart(goodsId, specId, quantity 1) { return request.post(/api/cart/add, { goods_id: goodsId, spec_id: specId, quantity: quantity }).then(res { if (res.data.code 40001) { // 库存不足错误码 wx.showToast({title: 库存仅剩${res.data.stock}件, icon: none}); return Promise.reject(new Error(库存不足)); } return res; }); }关键在res.data.code 40001——这是后端定义的业务错误码不是HTTP状态码。前端不依赖status ! 200判断失败而是明确识别业务异常避免网络抖动导致的误判。第三重防抖与节流保体验pages/cart/index.js的onQuantityChange()方法onQuantityChange(e) { const { goodsId, specId, quantity } e.detail; // 对同一商品规格的操作500ms内只执行最后一次 clearTimeout(this.quantityTimer); this.quantityTimer setTimeout(() { this.updateCartItem(goodsId, specId, quantity); }, 500); }updateCartItem()内部会先调用checkStock()再调用request.put(/api/cart/update)。这样用户狂点加减号最终只发起一次网络请求既减轻服务器压力又避免界面反复闪烁。3.3 微信支付从预下单到回调验签的全链路闭环微信支付是这套源码最值得细读的部分。它没有用wx.requestPayment()的默认参数而是做了深度定制预下单阶段utils/pay.js的unifiedOrder()export function unifiedOrder(orderData) { return request.post(/api/pay/unifiedorder, { body: { ...orderData, notify_url: https://your-domain.com/api/pay/callback, // 必须是HTTPS trade_type: JSAPI, openid: app.globalData.userInfo.openid // 从登录态获取 } }).then(res { if (res.data.code ! 200) throw new Error(res.data.msg); return { appId: res.data.appid, timeStamp: String(Date.now()), nonceStr: generateNonceStr(), // 生成32位随机字符串 package: prepay_id${res.data.prepay_id}, // 关键prepay_id必须原样传递 signType: RSA, paySign: generatePaySign(res.data) // 用商户私钥对参数签名 }; }); }generatePaySign()函数严格遵循微信官方文档将appIdtimeStampnonceStrpackagesignType拼接成字符串用商户私钥RSA加密。这里package的值必须是prepay_idxxx不能加任何空格或换行否则wx.requestPayment()会报“参数错误”。前端唤起支付pages/order/pay.jshandlePayClick() { const payParams this.data.payParams; wx.requestPayment({ ...payParams, success: (res) { console.log(支付成功, res); wx.redirectTo({url: /pages/order/success?id this.data.orderId}); }, fail: (err) { console.error(支付失败, err); if (err.errMsg.includes(requestPayment:fail cancel)) { wx.showToast({title: 支付已取消, icon: none}); } else { wx.showToast({title: 支付失败请重试, icon: none}); } } }); }success回调里不直接更新订单状态而是跳转到成功页——因为支付成功只是用户端确认真正的订单完成要以微信回调为准。后端回调验签utils/pay.js的verifySignature()export function verifySignature(rawData) { // rawData是微信POST过来的原始XML字符串 const parsed parseXml(rawData); // 解析XML为对象 const { sign, ...rest } parsed.xml; // 按微信规则排序参数字典序拼接成字符串 const keys Object.keys(rest).sort(); let stringA ; keys.forEach(key { if (rest[key] ! key ! sign) { stringA ${key}${rest[key]}; } }); stringA key${MERCHANT_KEY}; // 商户密钥 // MD5哈希转大写 const md5String md5(stringA).toUpperCase(); return md5String sign; }这个函数被后端api/pay/callback接口调用。只有verifySignature()返回true后端才会执行UPDATE orders SET status paid WHERE out_trade_no ?。源码包里readme.txt的“微信商户配置”章节明确写了MERCHANT_KEY要从微信商户平台“API安全”页复制不能写错一位。实操心得我在部署时遇到过三次回调失败。第一次是notify_url没配成HTTPS第二次是服务器时间比微信服务器快了3分钟导致签名不一致第三次是MERCHANT_KEY复制时多了个空格。建议你在utils/pay.js的verifySignature()开头加一行console.log(rawData:, rawData)用curl -X POST -d test.xml https://your-domain.com/api/pay/callback模拟回调逐行调试。3.4 订单状态管理有限状态机FSM的轻量级实现百货商城的订单状态不是简单的“待付款→已付款→已发货→已完成”而是有分支逻辑比如“已付款”后可能触发“自动取消”超时未发货也可能被用户“申请退款”“已发货”后可能“用户确认收货”也可能“物流异常”需人工介入。源码用models/order.js的getStateMachine()实现状态流转// 状态定义 const STATES { UNPAID: unpaid, // 待付款 PAID: paid, // 已付款 SHIPPED: shipped, // 已发货 COMPLETED: completed,// 已完成 CANCELLED: cancelled,// 已取消 REFUNDED: refunded // 已退款 }; // 状态转移规则从某状态可到达哪些状态 const TRANSITIONS { [STATES.UNPAID]: [STATES.PAID, STATES.CANCELLED], [STATES.PAID]: [STATES.SHIPPED, STATES.CANCELLED, STATES.REFUNDED], [STATES.SHIPPED]: [STATES.COMPLETED, STATES.REFUNDED], [STATES.COMPLETED]: [], [STATES.CANCELLED]: [], [STATES.REFUNDED]: [] }; export function canTransition(fromState, toState) { return TRANSITIONS[fromState]?.includes(toState) || false; } export function updateOrderStatus(orderId, fromState, toState) { if (!canTransition(fromState, toState)) { throw new Error(状态非法${fromState} → ${toState}); } return request.post(/api/order/${orderId}/status, { body: { status: toState } }); }canTransition()是核心校验函数。比如用户在“已付款”状态下点击“取消订单”前端会调用canTransition(paid, cancelled)返回true才允许发起请求如果用户试图从“已完成”状态退回到“已付款”canTransition(completed, paid)返回false前端直接拦截避免无效请求。pages/order/list.js的onPullDownRefresh()里会调用models/order.js的getOrdersByStatus()传入status: unpaid后端只返回该状态的订单减少数据传输量。这种状态驱动的设计让订单列表页的渲染逻辑极其清晰不同状态显示不同按钮“去支付”、“查看物流”、“申请售后”且按钮点击后自动校验状态合法性。4. 部署上线全流程从开发者工具调试到微信审核通过的避坑指南4.1 本地调试微信开发者工具的正确打开方式很多新手以为把源码拖进开发者工具就能运行结果卡在“登录失败”。其实有四个关键前置步骤第一步配置合法的 AppID打开app.json找到appid: wx1234567890abcdef这行。这个appid必须是你在微信公众平台注册的小程序账号的 AppID不能用测试号。测试号只能用于开发调试无法调用微信支付。在开发者工具顶部菜单栏点击“详情”→“本地设置”勾选“不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书”这是为了绕过域名限制但仅限开发环境。第二步替换基础配置打开app.js修改以下常量App({ globalData: { // 替换为你自己的域名 API_BASE_URL: https://your-api.com/api, // 替换为你在微信开放平台绑定的公众号AppID用于获取用户信息 MP_APPID: wx1234567890abcdef, // 替换为你在微信商户平台申请的商户号 MCH_ID: 1234567890 } })第三步启动本地 Mock 服务可选但强烈推荐源码包里的temp/mock-server.js是一个轻量级 Express 服务模拟后端 API。运行命令cd temp npm install npm start然后在utils/request.js里把baseURL改成http://localhost:3000。这样你不用等后端联调就能看到商品列表、购物车数据极大提升前端开发效率。第四步真机调试的隐藏技巧在开发者工具点击“预览”生成二维码。用 iPhone 微信扫码时如果提示“此小程序不在调试范围内”是因为你的微信号没添加为“体验者”。去微信公众平台 → 开发管理 → 开发者工具 → 添加体验者填入你的微信号。安卓机扫码后若白屏检查app.json的debug: true是否开启以及project.config.json的libVersion是否与真机微信版本兼容建议设为2.28.0。注意开发者工具的“编译模式”要选“普通编译”不要用“自定义编译条件”否则wx.getSystemInfoSync().platform可能返回错误值导致pages/index/index.js的“iOS专属样式”失效。4.2 服务端配置Nginx、HTTPS 与微信支付证书的硬核操作小程序要求所有 API 必须 HTTPS微信支付回调必须公网可访问。以下是生产环境 Nginx 配置要点/etc/nginx/conf.d/your-site.confserver { listen 443 ssl http2; server_name your-domain.com; ssl_certificate /path/to/fullchain.pem; # Lets Encrypt 证书 ssl_certificate_key /path/to/privkey.pem; # 微信支付回调必须可被外网访问 location /api/pay/callback { proxy_pass https://127.0.0.1:8000/api/pay/callback; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 关键微信回调是POST XML不能被gzip压缩 gzip off; } # 其他API走常规代理 location /api/ { proxy_pass https://127.0.0.1:8000/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 静态资源直出 location /images/ { alias /var/www/your-app/images/; } }微信支付证书是另一个雷区。下载的apiclient_cert.p12文件不能直接用需转换为 PEM 格式# 提取私钥输入p12文件密码 openssl pkcs12 -clcerts -nokeys -in apiclient_cert.p12 -out apiclient_cert.pem # 提取证书输入p12文件密码 openssl pkcs12 -cacerts -nokeys -in apiclient_cert.p12 -out apiclient_ca.pem # 提取私钥输入p12文件密码 openssl pkcs12 -nocerts -in apiclient_cert.p12 -out apiclient_key.pem后端代码如 Node.js加载证书时const fs require(fs); const https require(https); const options { key: fs.readFileSync(./apiclient_key.pem), cert: fs.readFileSync(./apiclient_cert.pem), ca: fs.readFileSync(./apiclient_ca.pem) }; https.request(options, ...); // 调用微信统一下单API实操心得我在部署时发现apiclient_cert.p12的密码是微信商户平台“API安全”页显示的“证书密码”不是你下载时设置的密码这个密码默认是123456但很多商家会改务必去商户平台确认。另外apiclient_key.pem文件开头必须是-----BEGIN PRIVATE KEY-----如果是-----BEGIN RSA PRIVATE KEY-----微信会返回“证书错误”。4.3 微信审核那些被拒三次才搞懂的隐形规则微信小程序审核不是技术验收而是用户体验审计。我帮客户提审这套百货商城前三次都被拒原因如下第一次被拒页面标题与功能不符审核意见“首页标题为‘百货商城’但实际内容为‘XX超市’存在误导用户风险。”解决方案app.json的window配置里navigationBarTitleText: XX超市必须与你在微信公众平台填写的“小程序名称”完全一致。不能写“百货商城”或“精品百货”必须是工商注册名。第二次被拒支付流程缺少明确指引审核意见“用户点击支付后未提供支付成功/失败的明确反馈不符合《微信小程序设计规范》第3.2条。”解决方案在pages/order/pay.js的wx.requestPayment()的success和fail回调里必须调用wx.showToast()或wx.showModal()给出明确提示。不能只写console.log。第三次被拒隐私政策缺失审核意见“未提供《用户隐私协议》违反《微信小程序平台运营规范》第5.1条。”解决方案在pages/user/index.js的“关于我们”入口新增跳转pages/user/privacy.js页面。该页面内容必须包含- 收集哪些信息openid、昵称、头像、收货地址- 信息用途订单履约、物流配送- 是否共享给第三方否- 用户如何撤回授权在微信设置里关闭。readme.txt里附带了合规的隐私协议模板直接复制粘贴即可但要把“XX超市”替换成你的店铺名。最后提醒提交审核前务必在开发者工具点击“上传”按钮生成体验版二维码让同事或朋友用非管理员微信号扫码测试全流程。重点测试商品能否正常加入购物车、支付能否唤起、支付成功后能否跳转到订单成功页、订单列表能否正确显示状态。微信审核员只会走一遍标准路径你漏测的环节就是被拒的理由。5. 二次开发与功能扩展会员体系、优惠券、拼团的落地路径5.1 会员体系从登录态到等级权益的渐进式升级源码现有的登录态app.globalData.userInfo只存了基础信息要升级为会员体系分三步走第一步扩展用户信息模型修改models/user.js的getUserInfo()方法让它返回更多字段export function getUserInfo() { return request.get(/api/user/info).then(res { // 新增字段 const userInfo { ...res.data, memberLevel: res.data.member_level || bronze, // 会员等级bronze/silver/gold points: res.data.points || 0, // 积分余额 nextLevelPoints: res.data.next_level_points || 1000 // 升级所需积分 }; app.globalData.userInfo userInfo; return userInfo; }); }第二步在关键节点发放积分utils/order.js的createOrder()成功后追加积分发放逻辑export function createOrder(orderData) { return request.post(/api/order/create, { body: orderData }) .then(res { // 订单创建成功发放积分按订单金额*10 const points Math.floor(orderData.total_price * 10); return request.post(/api/user/points, { body: { points: points, remark: 订单${orderData.out_trade_no}消费奖励 } }).then(() res); }); }第三步会员专享页面与权益新建pages/user/member.js在onLoad里调用getUserInfo()渲染等级图标和积分明细。app.json的tabBar添加pagePath: pages/user/member。这样会员体系就从“有登录”升级为“有等级、有积分、有专属页面”后续再接入储值卡、生日特权等功能只需在pages/user/member.js里扩展。5.2 优惠券前端渲染与后端核销的协同设计优惠券不是简单加个“领券”按钮而是涉及库存、有效期、使用门槛的复杂逻辑。源码预留了pages/coupon/目录扩展要点如下前端渲染pages/coupon/list.js的getCoupons()方法getCoupons() { request.get(/api/coupon/list, { params: { status: available, // 可领取状态 category: this.data.category // 按商品类目过滤 } }).then(res { this.setData({ coupons: res.data.map(coupon ({ ...coupon, // 计算剩余可领张数后端返回remaining_count remaining: coupon.remaining_count 0 ? coupon.remaining_count : 已抢光, // 计算距离过期天数 daysLeft: Math.ceil((new Date(coupon.expired_at) - new Date()) / (1000 * 60 * 60 * 24)) })) }); }); }后端核销pages/order/confirm.js的onSubmit()在调用unifiedOrder()前先校验优惠券onSubmit() { const couponId this.data.selectedCoupon?.id; if (couponId) { // 调用核销接口返回实际抵扣金额 request.post(/api/coupon/use, { body: { coupon_id: couponId, order_amount: this.data.totalPrice } }).then(res { this.setData({ discountAmount: res.data.discount_amount, finalPrice: this.data.totalPrice - res.data.discount_amount }); this.createOrder(); // 再创建订单 }); } else { this.createOrder(); } }这样设计优惠券的“领取”和“使用”分离避免用户领了券却无法使用也方便后端做风控比如限制同一用户每天最多领3张。5.3 拼团用 WebSocket 实现实时开团状态同步拼团功能最怕“团长已成团团员还在等”。源码用轻量级 WebSocket 方案解决前端连接pages/group/buy.js参团页的onLoadonLoad() { // 连接WebSocket后端需部署ws服务 this.ws wx.connectSocket({ url: wss://your-domain.com/ws/group?group_id this.data.groupId }); this.ws.onOpen(() { console.log(拼团WebSocket连接成功); }); this.ws.onMessage((res) { const data JSON.parse(res.data); if (data.type group_status_update) { this.setData({ groupStatus: data.status }); // 更新“2人成团中” if (data.status success) { wx.showToast({title: 拼团成功, icon: success}); } } }); }后端推送当数据库检测到拼团人数达标触发// Node.js 示例 const wss new WebSocket.Server({ port: 8080 }); wss.broadcast function broadcast(data) { wss.clients.forEach(function each(client) { if (client.readyState WebSocket.OPEN) { client.send(JSON.stringify(data)); } }); }; // 成团时调用 wss.broadcast({ type: group_status_update, group_id: 123456, status: success });WebSocket 连接成本低比轮询更实时且微信小程序原生支持wx.connectSocket无需额外 SDK。拼团这种强实时场景用 WebSocket 是性价比最高的方案。最后分享一个小技巧如果你想快速验证拼团逻辑不用真搭 WebSocket 服务。在pages/group/buy.js的onLoad里加一段定时器this.timer setInterval(() { // 模拟后端推送 this.setData({ groupStatus: 2人成团中 }); }, 3000);等逻辑跑通后再替换为真实 WebSocket开发效率翻倍。本文还有配套的精品资源点击获取简介这是一套完整的百货类微信小程序源码基于原生框架开发覆盖从商品展示到订单完成的全流程。支持多级分类浏览、商品详情页、规格选择、购物车实时增删改查、微信支付接口对接、订单状态跟踪、收货地址管理等功能。项目结构清晰包含pages页面目录、app.js全局逻辑、app.配置、app.wxss公共样式以及images资源和readme说明文档。适配iOS和Android双端在微信开发者工具中可直接运行调试。商家只需替换店铺名称、上传商品数据、配置微信商户号和AppID就能快速上线运营。源码关键模块如登录态维护、库存校验、支付回调均有基础实现注释规范便于后续扩展会员系统、优惠券、拼团等营销功能。本文还有配套的精品资源点击获取