Python+uniapp微信小程序商城开发实战:从零搭建羽毛球购物商城 做这个“Python uniapp 微信小程序羽毛球购物商城”之前我其实已经在球馆群被问过无数次“有没有靠谱的装备渠道”。后来干脆自己动手后端用 Python 写接口前端用 uniapp 编译成微信小程序做出来一个能浏览、搜索、加购、下单、支付的羽毛球用品商城。这个项目跑通之后我最大的感受是商城类小程序的门槛不在“会写接口”或“会画页面”而在品类设计、规格处理、订单链路和支付回调这些容易被忽略的细节。这篇文章就从技术选型、后端接口、前端页面到上线打包把整条链路完整拆开讲一遍适合正在准备做全栈小程序、或者想接类似商城私活的朋友参考。1. 项目整体设计与技术选型思路1.1 为什么选 Python uniapp而不是原生小程序先说结论这个组合的核心优势是“开发效率高、试错成本低、一套代码覆盖多个端口”。后端选 Python不是因为它性能最强而是因为商城的接口逻辑大多是 CRUD 加上状态流转Python 在这类业务场景下开发速度极快生态里的 FastAPI、Django 都非常成熟。我做的是个人项目没有团队协作压力用 FastAPI 写接口、SQLAlchemy 操作数据库代码量小且结构清晰比 Java、Go 要省掉大量模板代码。Go 性能确实好但个人项目第一优先级是跑通业务瓶颈不在语言。前端选 uniapp理由更直接一次编写能编译到微信小程序、H5、App 等多端。虽然这个项目主要目标是微信小程序但保不准后面想做个 H5 商城放公众号里或者打包成 Appuniapp 的跨端能力让这些扩展变得非常轻量。用 Vue 语法写页面对长期做前端开发的人很友好坑也相对少。做个简单对比技术方案开发效率多端支持社区资料个人项目适用性Python uniapp高强丰富很推荐Node.js uniapp高强较丰富推荐Java 原生小程序中弱丰富不推荐重Go 原生小程序低弱一般不推荐没必要我实际开发中从后端接口设计到小程序页面跑通主流程大约用了两周下班后的时间这个节奏比用原生小程序要快不少。如果你准备接商城的私活这套组合能让你在短时间内拿出可演示、可上线的成果对前期谈项目很有帮助。1.2 羽毛球商城的核心功能拆解很多新手拿到“商城”两个字就慌了觉得东西很多。实际上任何商城都必须先分清主链路和辅助功能。我这个羽毛球商城只保留了主链路浏览商品、搜索筛选、加入购物车、提交订单、微信支付、查看订单。加上用户登录和地址管理一共八个模块没有做积分、秒杀、优惠券这些营销功能。羽毛球这个品类和普通百货商城有个明显区别规格维度不一样。羽毛球拍要考虑磅数、握柄粗细G4/G5羽毛球要区分耐打型和比赛型球鞋要关注尺码和脚型宽窄球服要分 S/M/L/XL。这些规格组合必须在商品模型里设计好前端加购时选择规格后端下单时校验规格和库存。如果照搬普通商城只做一个“价格数量”的简单模型后面订单会出现大量“拍子磅数不对”“鞋子码数没选”的售后问题。我在功能设计上还做了一个取舍没有单独做后台管理系统而是初期直接用 FastAPI 自动生成的 Swagger 文档来维护商品数据。运营数据量小的时候完全够用每个商品手工录入也就一分钟的事。等你确定要长期运营、需要给运营人员建账号的时候再补一个简单的管理后台也不迟。步子别迈太大先用最小可用版本验证流程。1.3 数据模型与目录规划项目采用前后端分离结构根目录下分两个子目录badminton-shop/ ├── backend/ # Python 后端 │ ├── app/ │ │ ├── main.py # FastAPI 入口 │ │ ├── models.py # ORM 模型 │ │ ├── schemas.py # Pydantic 校验模型 │ │ ├── auth.py # JWT 登录鉴权 │ │ ├── deps.py # 公共依赖获取当前用户等 │ │ ├── api/ │ │ │ ├── goods.py # 商品接口 │ │ │ ├── cart.py # 购物车接口 │ │ │ └── order.py # 订单与支付接口 │ ├── requirements.txt │ └── run.py ├── frontend/ # uniapp 前端 │ ├── pages/ │ │ ├── index/index.vue # 首页 │ │ ├── goods/list.vue # 商品列表 │ │ ├── goods/detail.vue # 商品详情 │ │ ├── cart/cart.vue # 购物车 │ │ ├── order/confirm.vue # 确认订单 │ │ ├── order/list.vue # 订单列表 │ │ └── user/user.vue # 我的 │ ├── static/ │ ├── utils/request.js # 请求封装 │ ├── App.vue │ ├── main.js │ └── pages.json这个结构的核心是“前后端解耦”。后端只提供接口前端只管渲染和交互调试时可以独立进行后端用 Swagger 测接口前端用 mock 数据先把页面画出来最后再联调。我见过很多新手把接口请求和页面渲染硬绑在一起后端没写好人只能干等完全没必要。2. 后端接口层用 Python 把商城核心链路跑通2.1 FastAPI 项目初始化与数据库连接后端我选了 FastAPI主要原因有三个第一自带交互式接口文档手机端不在身边时也能用浏览器模拟整个购买流程第二Pydantic 模型可以一次性完成参数校验和序列化少写很多 if 判断第三异步支持好虽然商城逻辑不至于高并发但写起来顺手。初始化项目时先建虚拟环境装依赖pip install fastapi uvicorn sqlalchemy pymysql pydantic python-jose[cryptography] passlib[bcrypt] python-multipart数据库我用 MySQL配置在database.py中连接字符串长这样# database.py from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, declarative_base SQLALCHEMY_DATABASE_URL mysqlpymysql://root:yourpassword127.0.0.1:3306/badminton_shop?charsetutf8mb4 engine create_engine(SQLALCHEMY_DATABASE_URL, pool_pre_pingTrue, pool_recycle3600) SessionLocal sessionmaker(bindengine, autocommitFalse, autoflushFalse) Base declarative_base()新手常犯一个错建表字段不带utf8mb4字符集等用户昵称里出现特殊符号或者中文表情时直接插入失败。我一开始也踩过这个坑后来统一改成了charsetutf8mb4。FastAPI 入口文件做一个依赖注入保证每个请求独立使用数据库会话避免连接泄露# main.py from fastapi import FastAPI, Depends from sqlalchemy.orm import Session app FastAPI(title羽毛球商城接口) def get_db(): db SessionLocal() try: yield db finally: db.close()启动时执行一句建表逻辑把自己的模型引入进来然后直接uvicorn run:app --reload就能看到效果。这个阶段的核心目标是把服务跑起来Swagger 自动挂在/docs路径下。2.2 用户、商品、购物车、订单四张核心表设计商城的数据模型我拆成了四张核心表用户表、商品表、购物车表、订单表。这里我不列全字段只放最关键的字段和设计理由。用户表class User(Base): __tablename__ users id Column(Integer, primary_keyTrue, indexTrue) openid Column(String(64), uniqueTrue, indexTrue) # 微信 openid nickname Column(String(64), default) avatar_url Column(String(255), default) phone Column(String(20), default) created_at Column(DateTime, defaultdatetime.now)openid 是用户在微信体系内的唯一标识务必加唯一索引。后面用户多起来这个字段就是所有业务关联的锚点。用户表不需要存密码因为登录完全通过微信授权完成这是小程序商城的优势。商品表class Goods(Base): __tablename__ goods id Column(Integer, primary_keyTrue, indexTrue) title Column(String(100), nullableFalse) category Column(String(50), indexTrue) # 拍子/球/鞋/服/包 cover Column(String(500), default) # 封面图 images Column(Text, default[]) # JSON 数组存轮播图 price Column(Integer, nullableFalse) # 价格单位分 original_price Column(Integer, default0) # 划线价 stock Column(Integer, default0) # 总库存 sales Column(Integer, default0) # 销量 detail Column(Text, default) # 富文本详情 specs Column(Text, default[]) # 规格 JSON如 [{name:磅数,values:[24,26]}] status Column(SmallInteger, default1) # 1上架 0下架 created_at Column(DateTime, defaultdatetime.now)商品价格用整数存储也就是“分”。这是我从第一个项目里学到的血泪教训数据库里用 DECIMAL 或浮点存价格订单金额一经过加减乘除就可能出现 0.1 0.2 不等于 0.3 的问题虽然商城金额一般只到两位小数但浮点误差一旦出现很难排查。把单位定死为“分”每次展示时除以 100订单计算全部用整数运算精度问题彻底消失。规格我直接用一个 JSON 字段存没有单独建规格表。原因是我这个商场每个商品的规格比较简单最多一两组选择。等商品数量大了、规格维度复杂了再拆库也不迟。前期过度设计是个人项目最大的坑。购物车表class CartItem(Base): __tablename__ cart_items id Column(Integer, primary_keyTrue, indexTrue) user_id Column(Integer, indexTrue) goods_id Column(Integer, indexTrue) spec_desc Column(String(255), default) # 前端拼好的规格描述如磅数24 / 颜色黑 quantity Column(Integer, default1) checked Column(SmallInteger, default1) # 是否勾选 created_at Column(DateTime, defaultdatetime.now)购物车设计成后端保存而不是前端本地缓存理由是用户换手机、清缓存后购物车还能保留。每个购物车条目由“用户 商品 规格描述”三个维度确定唯一性同一用户加入同一商品同一规格时直接累加数量。订单表class Order(Base): __tablename__ orders id Column(Integer, primary_keyTrue, indexTrue) order_no Column(String(64), uniqueTrue, indexTrue) # 订单号 user_id Column(Integer, indexTrue) total_fee Column(Integer, default0) # 总金额分 status Column(SmallInteger, default0) # 0待支付 1已支付 2已发货 3已完成 4已取消 address Column(Text, default{}) # 收货信息 JSON items Column(Text, default[]) # 商品快照 JSON out_trade_no Column(String(64), default) # 微信支付商户订单号 created_at Column(DateTime, defaultdatetime.now)订单表里一定要存“商品快照”不能联表查订单商品数据。意思是下单那一刻把商品标题、规格描述、单价、数量、图片地址这些信息原样存一份在订单的itemsJSON 字段里。否则以后商品改价、改标题、下架历史订单就会显示成乱七八糟的内容用户找你扯皮时非常被动。2.3 微信登录、商品列表、下单支付三组核心接口这组接口是整个商城的主命脉我把每个接口的逻辑拆开讲。微信登录接口是商城一切业务的前提。小程序端通过wx.login获取临时 code传给后端后端拿 code 去微信接口换 openid 和 session_key# auth.py import httpx from jose import jwt APPID 你的appid SECRET 你的appsecret async def code2openid(code: str): url https://api.weixin.qq.com/sns/jscode2session params { appid: APPID, secret: SECRET, js_code: code, grant_type: authorization_code } async with httpx.AsyncClient() as client: resp await client.get(url, paramsparams) data resp.json() return data.get(openid), data.get(session_key)拿到 openid 后查用户表不存在就自动注册然后签发 JWT token有效期设 7 天。这里不要自己发明加密方案直接用 python-jose 库生成标准 JWTSECRET_KEY your-secret-key ALGORITHM HS256 EXPIRE_DAYS 7 def create_token(user_id: int): expire datetime.utcnow() timedelta(daysEXPIRE_DAYS) payload {sub: str(user_id), exp: expire} return jwt.encode(payload, SECRET_KEY, algorithmALGORITHM)商品列表接口要注意分页和搜索参数。小程序端做“下拉加载更多”时后端必须支持页码和每页条数app.get(/goods) def list_goods(category: str , keyword: str , page: int 1, page_size: int 10, db: Session Depends(get_db)): query db.query(Goods).filter(Goods.status 1) if category: query query.filter(Goods.category category) if keyword: query query.filter(Goods.title.like(f%{keyword}%)) total query.count() items query.order_by(Goods.sales.desc()).offset((page - 1) * page_size).limit(page_size).all() return {total: total, page: page, page_size: page_size, items: [serialize_goods(g) for g in items]}排序我默认用销量倒序这是电商的默认逻辑人气高的商品排前面转化率自然更好。创建订单接口是整个项目里最容易写错的地方。核心逻辑分几步先从前端传的购物车条目 ID 列表找到购物车数据然后循环校验每个商品是否上架、库存是否足够再根据当前商品最新单价计算总金额最后生成订单号和订单快照。这里最关键的校验是“价格必须后端算不能信前端传的总价”。前端传 total_fee 上来直接入库等于给用户留了一个任意改价的漏洞。app.post(/order/create) def create_order(checked_ids: list[int], db: Session Depends(get_db), user: User Depends(get_current_user)): cart_items db.query(CartItem).filter(CartItem.id.in_(checked_ids), CartItem.user_id user.id).all() if not cart_items: raise HTTPException(400, 购物车为空) total_fee 0 item_snapshot [] for cart in cart_items: goods db.query(Goods).filter(Goods.id cart.goods_id, Goods.status 1).first() if not goods: raise HTTPException(400, f商品 {cart.goods_id} 已下架) if goods.stock cart.quantity: raise HTTPException(400, f{goods.title} 库存不足) fee goods.price * cart.quantity total_fee fee item_snapshot.append({ goods_id: goods.id, title: goods.title, cover: goods.cover, spec_desc: cart.spec_desc, price: goods.price, quantity: cart.quantity, fee: fee }) goods.stock - cart.quantity goods.sales cart.quantity order Order( order_nogenerate_order_no(), user_iduser.id, total_feetotal_fee, status0, itemsjson.dumps(item_snapshot, ensure_asciiFalse) ) db.add(order) db.commit() # 清掉已购买的购物车项 db.query(CartItem).filter(CartItem.id.in_(checked_ids), CartItem.user_id user.id).delete() db.commit() return {order_id: order.id, order_no: order.order_no, total_fee: total_fee}订单号生成有个细节建议用“日期 随机数”格式比如20250407123300加 4 位随机数不要用自增 ID 直接当订单号否则同行能通过订单号看出你的日单量而且自增 ID 很容易被遍历。微信支付接口是另一个容易卡住的地方。后端需要调微信支付统一下单接口拿到 prepay_id再按官方规则生成签名参数返回给小程序端。这里我用的是微信支付 V3 的接口先强调一个重要前提微信小程序的“支付功能”必须用企业主体注册的账号才能开通个人主体小程序没有支付权限。如果你只是先练手可以先把订单流程走到“待支付”这一步支付回调用模拟数据测试等项目主体资质下来再接真支付。3. uniapp 前端羽毛球商城页面从零搭建3.1 项目创建、tabBar 与页面结构规划前端我用 HBuilderX 创建 uniapp 项目模板选“默认模板”勾选 Vue 3。创建后最重要的一件事是改pages.json把底部 tabBar 配置好。商场类小程序的 tabBar 一般是四个首页、商城、购物车、我的。{ pages: [ { path: pages/index/index, style: { navigationBarTitleText: 羽毛球商城 } }, { path: pages/goods/list, style: { navigationBarTitleText: 全部商品 } }, { path: pages/cart/cart, style: { navigationBarTitleText: 购物车 } }, { path: pages/user/user, style: { navigationBarTitleText: 我的 } }, { path: pages/goods/detail, style: { navigationBarTitleText: 商品详情 } }, { path: pages/order/confirm, style: { navigationBarTitleText: 确认订单 } }, { path: pages/order/list, style: { navigationBarTitleText: 我的订单 } } ], tabBar: { color: #999999, selectedColor: #e93b3d, list: [ { pagePath: pages/index/index, text: 首页 }, { pagePath: pages/goods/list, text: 商城 }, { pagePath: pages/cart/cart, text: 购物车 }, { pagePath: pages/user/user, text: 我的 } ] } }这里给出一个我从实际项目里总结的习惯页面路径第一段是功能模块名第二段是页面名比如goods/detail、order/confirm。这样页面多了之后目录结构一目了然不像pages/detail1/detail1这种命名方式等页面超过二十个就彻底乱了。请求封装是前端的基建工程我写了一个utils/request.js统一处理 baseURL、登录 token、401 跳转和错误提示// utils/request.js const BASE_URL https://你的后端域名/api export function request({ url, method GET, data {} }) { return new Promise((resolve, reject) { uni.request({ url: BASE_URL url, method, data, header: { Authorization: Bearer (uni.getStorageSync(token) || ), Content-Type: application/json }, success: (res) { if (res.statusCode 401) { uni.removeStorageSync(token) uni.navigateTo({ url: /pages/user/login }) return } if (res.statusCode 200 res.statusCode 300) { resolve(res.data) } else { uni.showToast({ title: res.data.detail || 请求失败, icon: none }) reject(res) } }, fail: (err) { uni.showToast({ title: 网络异常, icon: none }) reject(err) } }) }) }注意BASE_URL不能用本地 localhost小程序真机上 localhost 指向的是手机本身不是你的电脑。调试阶段可以用电脑的局域网 IP但如果后端没做跨域处理H5 端会报跨域错误微信小程序端只要在开发者工具勾选“不校验合法域名”连局域网 IP 是没问题的。3.2 首页、商品详情、购物车三个关键页面实现首页我分成四个模块顶部搜索框、轮播 Banner、分类快捷入口、热销商品瀑布流。首页的主要目的是留存和转化商品列表直接请求/goods?page1page_size10按销量倒序展示。下拉刷新和上拉加载是商城类页面的必修课uniapp 里在页面配置enablePullDownRefresh: true然后在onPullDownRefresh和onReachBottom生命周期里重新请求数据即可。商品详情页是用户决定购买的关键页面布局顺序为轮播图、标题价格区、规格选择区、图文详情区、底部操作栏。规格选择我用了 uniapp 的uni-popup弹层弹出后展示商品的所有规格组。核心逻辑是用户选择规格后前端拼出spec_desc比如“磅数24 / 柄号G5 / 颜色黑”这个描述会传给购物车和订单系统。规格选择交互有个细节每个规格选项点一下高亮但必须在所有规格组都选完后才能加入购物车。我在点击“加入购物车”时先做校验没选全就弹提示并用一个变量保存已选中的规格let selectedSpecs {} function selectSpec(groupName, value) { selectedSpecs[groupName] value specDesc.value Object.entries(selectedSpecs).map(([k, v]) ${k}${v}).join( / ) canSubmit.value Object.keys(selectedSpecs).length specGroups.value.length }购物车页面看起来简单但坑不少。第一每个条目需要勾选框支持全选和反选第二数量增加减少时得实时计算小计第三滑动删除条目。uniapp 没有内置滑动删除组件我直接用swiper组件的垂直模式包了一个单元格左滑露出删除按钮。这里提醒一下用swiper做滑动删除时每个单元格要设独立的current值否则一个滑起来其他也跟着滑。购物车底部固定一个结算栏显示“已选 N 件 合计 ¥XX”点击“去结算”把选中的购物车条目 ID 组成数组跳转到确认订单页。注意跳转前要过滤掉未勾选的条目这个筛选逻辑在computed里完成即可。3.3 微信登录、地址选择与支付对接微信登录在小程序端的实现比传统账号密码简单太多。用户进入“我的”页面点击登录前端调uni.login拿 code再通过 request 传给后端async function wxLogin() { const [err, res] await uni.login({ provider: weixin }) if (err) return const data await request({ url: /auth/login, method: POST, data: { code: res.code } }) uni.setStorageSync(token, data.token) uni.setStorageSync(userInfo, data.user) }登录成功后把 token 存本地请求封装里每次自动带上。这里有个体验优化小程序冷启动时会静默登录一次避免用户点“我的”才弹登录框。具体做法是在App.vue的onLaunch里判断本地有没有 token没有就静默调登录接口。地址选择这里要提醒微信官方有wx.chooseAddress接口但只对已通过微信认证的企业主体小程序开放个人主体没权限。所以我在确认订单页做了自己的一套地址管理用户进订单确认页如果本地没有地址就跳转到“新增地址”页面填写收货人、手机号、详细地址存到后端用户地址表。这样可以绕开权限限制体验也完整。支付对接时uniapp 的封装是uni.requestPayment。后端从微信支付统一下单接口拿到支付参数后前端这样调用async function payOrder(orderNo) { const res await request({ url: /order/pay, method: POST, data: { order_no: orderNo } }) await uni.requestPayment({ timeStamp: res.timeStamp, nonceStr: res.nonceStr, package: res.package, signType: RSA, paySign: res.paySign }) uni.showToast({ title: 支付成功 }) }支付结果有一个重要认知uni.requestPayment的成功回调不代表订单真的支付成功了最终状态要以微信支付的异步回调为准。所以前端支付成功后不要急着改订单状态应该轮询订单详情接口等回调更新订单状态后再展示“已支付”。否则可能出现“用户已付钱但服务端订单还是待支付”的错乱。4. 联调、部署与常见问题实录4.1 本地联调与后端部署本地联调阶段最常用的是“微信开发者工具中不校验合法域名”这个开关。开发工具右上角“详情”-“本地设置”里勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”就能直接请求http://127.0.0.1:8000或局域网 IP。真机调试时手机和电脑连同一个 Wi-Fi后端监听0.0.0.0:8000前端BASE_URL改成电脑的局域网 IP就能在真机上跑通。上线阶段就不能这么随意了。微信小程序要求接口必须是 HTTPS 且域名已备案。部署方案我用的是云服务器 Nginx uvicorn流程如下先在后端服务器上装好 Python 环境用 systemd 托管 uvicorn 进程# /etc/systemd/system/badminton-shop.service [Unit] Descriptionbadminton shop API Afternetwork.target [Service] Userwww WorkingDirectory/var/www/badminton-shop/backend ExecStart/var/www/badminton-shop/backend/venv/bin/uvicorn run:app --host 127.0.0.1 --port 8000 Restartalways [Install] WantedBymulti-user.target然后 Nginx 做反向代理和 HTTPS 终止核心配置片段server { listen 443 ssl; server_name api.example.com; ssl_certificate /etc/nginx/ssl/server.crt; ssl_certificate_key /etc/nginx/ssl/server.key; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }排错时可以先用curl https://api.example.com/docs测试接口文档能不能访问通了再回微信开发者工具里测试真实请求。4.2 常见问题排查速查表做完这个项目我把踩过的坑整理成一张排查表很多问题在别的商城项目里也通用现象可能原因解决方法小程序请求接口报“不在以下 request 合法域名列表”正式环境未配置域名或域名未备案微信公众平台配置合法域名必须 HTTPS真机上图片全部不显示图片域名没加到“downloadFile 合法域名”将图片 CDN/服务器域名加白名单用户点了支付但订单一直是待支付支付回调没写或回调验签失败检查回调地址确认微信服务器能访问下单成功但用户显示“库存不足”下单时扣减了库存但未关闭重复提交在事务中锁定行记录增加并发控制iPhone 底部操作栏被 Home 条遮挡未适配安全区使用env(safe-area-inset-bottom)做 padding商品详情页图片加载慢图片原始文件太大后端压缩或前端用懒加载lazy-load关于真机上scroll-view高度塌陷的问题我也踩过一次把scroll-view直接放在 flex 布局里没有设高度导致只能滚动一点点。解决办法是给scroll-view容器设定固定高度或者用flex: 1配合min-height: 0。另外再说一个微信小程序的特殊渲染机制坑如果自定义导航栏且里面放了动态变化的元素不要直接操作uni.getSystemInfoSync().statusBarHeight在部分安卓机上这个值会拿到 0。正确的做法是用uni.getMenuButtonBoundingClientRect()拿到菜单按钮位置再计算出自定义导航栏高度。4.3 打包发布到微信小程序的注意事项uniapp 打包微信小程序其实很简单HBuilderX 菜单栏选“发行”-“小程序-微信”填上小程序 AppID生成dist/build/mp-weixin目录然后在微信开发者工具里“导入项目”选择这个目录即可。打包前必须检查几件事第一pages.json里配了mp-weixin的appid第二manifest.json里微信小程序配置的appid要正确第三接口请求根域名已经改成线上 HTTPS 地址。我最初打包上线时就是把BASE_URL忘改了导致开发工具里跑得好好的真机上一打开全部请求失败。还有一个容易被忽略的点微信小程序每个包体积有上限主包最大 2MB。如果项目里引用了比较大的图片资源或第三方库很容易超限。解决办法是使用分包加载。uniapp 配置分包只需要在pages.json里加一个subPackages字段{ subPackages: [ { root: pages/order, pages: [ { path: list, style: { navigationBarTitleText: 订单列表 } }, { path: confirm, style: { navigationBarTitleText: 确认订单 } } ] } ] }这样订单模块的页面会打进分包用户首次进入小程序时只下载主包进入订单页再加载分包体验会好很多。商品图片这类体积大的文件不要放进包里直接放 CDN 或对象存储上用https://地址引用。发布时还要注意一个实战细节微信公众平台支持“体验版”和“提交审核”两个动作。先上传版本在“开发版本”里设为“体验版”拿真机扫二维码测全流程确认无误后再提交审核。审核一般 1 到 3 天首次提交建议把小程序类目选为“电商平台-购物”类目选错会被驳回。5. 这个项目做下来我个人的几点真实体会项目上线后我回看了整个过程最值钱的不是差价的商品数据而是这几点第一做一个商城先跑通主链路再谈细节。我第一次做的时候起初想的是把“优惠券、秒杀、积分、分销”全塞进去做了一星期页面后连订单逻辑都没写完整后来狠心砍掉了所有营销功能两周就把核心流程跑通了。商城的核心永远是“浏览-下单-支付-查询订单”这个链路稳定了其他所有功能都是锦上添花。第二后端接口设计时一定要考虑“被恶意调用”的场景。前端传价格、前端传数量、前端传用户 ID 这些都是大忌。价格后端算用户从 token 里取库存用事务扣减这三个原则守住了商城的安全底线就基本有了。第三真机调试比模拟器能发现更多问题。很多坑只会在真机上出现比如安全区适配、图片域名白名单、接口超时这些在开发者工具里根本看不出来。建议从第一天起就用真机预览痛苦一次后面会顺畅很多。我后来把这套结构稍作修改给一个开渔具店的朋友做了个小商城商品分类、规格逻辑、订单流程几乎原样复用只换了页面样式和品牌信息。这也说明前后端分离 合理拆模块的做法真的是能复用的。希望这篇文章能让你少踩几个我踩过的坑把你的羽毛球商城或者其他品类的购物小程序尽快跑起来。