news 2026/10/21 7:35:22

uniapp微信商城小程序模板:从数据流到支付的全链路解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
uniapp微信商城小程序模板:从数据流到支付的全链路解析

简介:这是一份基于 uni-app 开发的微信商城小程序通用模板,适合前端入门者、小程序开发者以及需要快速搭建商城原型的学生或创业团队。模板覆盖商品展示、购物车、订单、个人中心等典型业务模块,预置常用图表、图标与实时通信支持,功能确认可运行,可直接在此基础上做二次开发。压缩包共490个文件,核心代码以275个js逻辑文件和117个vue页面组件为主,辅以json配置、scss/css样式以及wxml/wxss等小程序原生结构文件,整体仅2.04MB,目录轻量清晰,适合按模块拆解学习。目前已有382人学习浏览。借助该模板,读者既能理解 uni-app 跨端项目的组织方式、页面路由与状态管理思路,也可把界面和后端接口改造成独立商城应用;无论用于课程设计、毕业设计还是初期项目立项,都是实用且易上手的参考代码。

1. 开篇:为什么说商城模板的核心不在页面,而在几条数据流

市面上并不缺 UI 好看的商城小程序模板,但很多开发者下载后卡在第一步:HBuilderX 里跑起来白屏、真机预览请求全部失败、SKU 选不了、支付唤起没反应。这份基于 uniapp 开发的微信商城小程序通用模板,价值不在它长得好看,而在于登录、购物车、SKU、支付这几条链路是完整贯通的。它给出的是从页面到接口的一整套数据流,适合两类人:一是接单或自研小程序、想省掉基础搭建时间的前端开发者,二是团队内还没有统一商城代码规范、需要一个可复用骨架的负责人。无论走哪条路,你先要能把它跑起来,然后才谈得上改造。

2. 技术栈与目录结构:先搞清 uniapp 模板的骨架长什么样

2.1 技术选型:为什么用 uniapp 而不是原生微信小程序

原生小程序开发的问题在于:一套代码只能跑微信端,后续如果要出支付宝小程序或 App,又得重新写一遍。uniapp 的核心优势是编译时把 Vue 语法转成各端代码,模板本身用 Vue 2 语法编写,运行在小程序端时会被编译为 WXML 和 WXSS。对商城这类页面结构偏固定、交互以列表和表单为主的项目,用 uniapp 能大幅减少重复开发。

模板里没有引入重型 UI 库,商品卡片、SKU 弹层、数量选择器这些组件都是手写完成的,只是引用了 uniapp 内置组件如 view、text、image、scroll-view,以及 vuex 做购物车和用户状态的统一管理。网络请求部分基于 uni.request 封装了一层,方便统一注入 token、处理错误码。

2.2 目录结构:每个目录负责什么

拿到模板后,第一件事不是看代码,而是先把这个项目的目录结构摸清。模板核心结构如下:

├── App.vue # 应用入口文件,全局生命周期 ├── main.js # Vue 实例入口,注册 vuex ├── manifest.json # 应用配置:appid、小程序权限、SDK 配置 ├── pages.json # 页面路由与 tabBar 配置 ├── uni.scss # 全局 scss 变量,主题色修改入口 ├── api/ # 接口层,统一管理请求地址 │ ├── request.js # uni.request 封装 │ ├── goods.js # 商品相关接口 │ ├── cart.js # 购物车接口 │ └── order.js # 订单与支付接口 ├── store/ # vuex 状态管理 │ ├── index.js # store 主入口 │ ├── modules/ │ │ ├── cart.js # 购物车模块 │ │ ├── user.js # 用户信息与登录态 │ │ └── goods.js # 商品与 SKU 状态 ├── pages/ │ ├── index/ # 首页 │ ├── goods/ │ │ ├── list.vue # 商品列表页 │ │ └── detail.vue # 商品详情页 │ ├── cart/ # 购物车页 │ ├── order/ │ │ ├── confirm.vue # 确认订单页 │ │ └── pay-success.vue # 支付成功页 │ ├── user/ # 个人中心页 │ └── login/ # 登录页 ├── components/ │ ├── goods-card.vue # 商品卡片组件 │ ├── sku-popup.vue # SKU 选择弹层 │ └── number-box.vue # 数量选择器 └── static/ # 静态资源:图片、图标

api、store、components、pages 四层分离,是这套模板后续二次开发最依赖的骨架。常见的翻车姿势是:开发者拿到模板后直接在页面里写 uni.request,跳过 api 层,导致后续换接口时所有页面都要改一遍。这个模板的 api 层就是用来规避这种问题的。

2.3 manifest.json 与 pages.json:跑起来之前必改的两处配置

很多人下载模板后直接运行,结果在微信开发者工具里报错或白屏,绝大多数问题出在 manifest.json。这个文件里要注意的重点包括:

  • 微信小程序 appid:模板自带的是测试 appid,真机预览和上传必须替换成自己的
  • 权限配置:如果模板里用到定位、获取手机号等功能,需要在 mp-weixin 节点下的 permission 里声明
  • 基础库版本:最低版本设置太高,低版本微信会打不开;设置太低,部分 API 用不了

pages.json 决定页面路由和底部 tabBar。模板里底部 tab 有四个,对应首页、分类、购物车、个人中心,配置在 tabBar.list 数组里。新增页面时必须在 pages 数组里注册,顺序决定页面加载顺序,首个页面就是启动页。

{ "pages": [ { "path": "pages/index/index", "style": { "navigationBarTitleText": "商城首页" } }, { "path": "pages/goods/list", "style": { "navigationBarTitleText": "商品列表", "enablePullDownRefresh": true } } ], "tabBar": { "list": [ { "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/cart/cart", "text": "购物车" } ] } }

pages 数组里每个对象的 path 是页面路径,style 中的 navigationBarTitleText 控制导航栏标题,enablePullDownRefresh 决定是否支持下拉刷新。tabBar 的 pagePath 必须是 pages 里已注册的路径,否则运行时会提示找不到页面。改完这两处配置后,再用 HBuilderX 运行到微信开发者工具,基本就能看到首页了。

3. 核心模块拆解:商品从列表到下单的完整链路

3.1 首页信息流:request 封装与分页加载

商城首页最基础的能力是商品列表加载。这个模板里所有请求都走 api/request.js 这个封装,核心代码逻辑如下:

// api/request.js const BASE_URL = 'https://api.example.com/v1' export function request(options) { return new Promise((resolve, reject) => { uni.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'Content-Type': 'application/json', 'Authorization': uni.getStorageSync('token') || '' }, success: (res) => { if (res.statusCode === 200 && res.data.code === 0) { resolve(res.data.data) } else if (res.statusCode === 401) { uni.navigateTo({ url: '/pages/login/login' }) reject(res) } else { uni.showToast({ title: res.data.msg || '请求失败', icon: 'none' }) reject(res) } }, fail: (err) => { uni.showToast({ title: '网络异常', icon: 'none' }) reject(err) } }) }) }

封装的核心逻辑有三个:统一拼接 BASE_URL,调用方只传相对路径;从本地存储读取 token 并注入请求头,不用每个页面手写;对返回结构做了约定,code 为 0 时业务成功,401 时自动跳登录页,其他 code 统一 toast 错误信息。

首页商品列表调用商品接口时,用到 uniapp 的分页监听。模板中的做法是在 onReachBottom 生命周期里触发加载下一页:

// pages/index/index.vue export default { data() { return { goodsList: [], page: 1, pageSize: 10, loading: false, finished: false } }, onLoad() { this.loadGoods(true) }, onReachBottom() { if (!this.loading && !this.finished) { this.page += 1 this.loadGoods() } }, methods: { async loadGoods(reset = false) { if (this.loading) return this.loading = true try { const data = await getGoodsList({ page: this.page, pageSize: this.pageSize }) if (reset) { this.goodsList = data.list } else { this.goodsList = this.goodsList.concat(data.list) } this.finished = data.list.length < this.pageSize } finally { this.loading = false } } } }

onReachBottom 是 uniapp 页面滚动到底部的生命周期,相当于小程序原生的 onReachBottom。每次触发时先判断 loading 和 finished,避免重复请求和到底后继续请求。reset 参数用于区分是首次加载还是分页追加,首次加载同时会把 page 重置为 1。

3.2 商品详情与 SKU:把规格选择做成可复用弹层

商品详情页的复杂度集中在 SKU 选择。模板中商品规格的数据结构是典型的多规格组合,比如一个商品有颜色和尺码两个维度:

{ "spec_list": [ { "id": 1, "name": "颜色", "values": ["黑色", "白色"] }, { "id": 2, "name": "尺码", "values": ["S", "M", "L"] } ], "sku_list": [ { "spec_ids": "1:黑色,2:S", "price": 199, "stock": 50 }, { "spec_ids": "1:黑色,2:M", "price": 199, "stock": 30 } ] }

spec_list 描述规格维度,sku_list 描述具体可售组合。弹层组件的核心逻辑是:用户每选中一个规格值,就把该规格值加入已选集合,然后遍历 sku_list 判断当前组合是否可售。

<template> <view v-if="show" class="sku-mask"> <view class="sku-panel"> <view v-for="spec in specList" :key="spec.id" class="spec-group"> <text class="spec-name">{{ spec.name }}</text> <view class="spec-values"> <view v-for="value in spec.values" :key="value" :class="['spec-item', selectedSpec[spec.name] === value ? 'active' : '']" @tap="selectSpec(spec.name, value)" > {{ value }} </view> </view> </view> </view> </view> </template> <script> export default { data() { return { selectedSpec: {} } }, methods: { selectSpec(specName, value) { this.$set(this.selectedSpec, specName, value) const combo = Object.entries(this.selectedSpec) .map(([name, val]) => `${name}:${val}`) .join(',') const matched = this.skuList.find(sku => sku.spec_ids === combo) if (matched) { this.setState({ price: matched.price, stock: matched.stock }) } else { this.setState({ price: 0, stock: 0 }) } } } } </script>

selectSpec 方法做的事是:更新当前选中规格集合,把已选规格拼成和 sku_list 中 spec_ids 一致的字符串,然后匹配可售组合。匹配不到就置空价格和库存,提示用户当前组合不可选。这段逻辑不依赖任何第三方库,是模板里值得反复看的片段,你在电商项目里做秒杀、预售、多规格商品时都能直接复用这套思路。

3.3 购物车与结算:vuex 管理全局状态

购物车数据在多个页面共享,模板用 vuex 的 cart 模块来管理。要点是购物车列表和选中状态存在 store 里,页面只负责渲染和派发 action。

// store/modules/cart.js export default { state: { cartList: [], checkedList: [] }, getters: { totalCount(state) { return state.cartList.reduce((sum, item) => sum + item.count, 0) }, totalPrice(state) { return state.cartList .filter(item => state.checkedList.includes(item.id)) .reduce((sum, item) => sum + item.price * item.count, 0) } }, mutations: { ADD_CART(state, goods) { const exists = state.cartList.find(item => item.id === goods.id) if (exists) { exists.count += goods.count } else { state.cartList.push({ ...goods, count: goods.count }) } }, TOGGLE_CHECK(state, id) { const idx = state.checkedList.indexOf(id) if (idx > -1) { state.checkedList.splice(idx, 1) } else { state.checkedList.push(id) } } } }

totalPrice 这个 getter 依赖 checkedList,只有勾选了的商品才计入结算金额。这个设计比在购物车页面里用 data 临时计算更稳,因为从购物车进入确认订单页后,仍然需要读取同样的选中数据。结算金额的计算、勾选状态的同步,全部以 store 为准,页面销毁也不会丢。

确认订单页的流程相对固定:从 cart 模块读取 checkedList,调下单接口,拿到订单 id 后跳转支付。这里有个开发中容易忽略的点:从购物车到确认订单的跳转,需要把选中的商品数据通过页面参数或 store 传递,模板里是用 store 读取的,避免 URL 传参过长被截断。

4. 登录与支付:前后端联调里最绕的两个环节

4.1 微信登录态:uni.login 与自定义登录的衔接

模板的登录流程不是直接用微信的 openid 作为用户标识,而是走「微信授权码换 token」的标准流程。为什么需要这一层?因为商户后台需要把微信身份和自己系统的用户体系绑定,后续的订单、积分、会员等级都挂在自有用户 id 上。

// api/user.js export function wxLogin() { return new Promise((resolve, reject) => { uni.login({ provider: 'weixin', success: async (loginRes) => { try { const res = await request({ url: '/user/login', method: 'POST', data: { code: loginRes.code } }) uni.setStorageSync('token', res.token) uni.setStorageSync('userInfo', res.userInfo) resolve(res) } catch (err) { reject(err) } }, fail: (err) => { reject(err) } }) }) }

uni.login 返回的 code 是一次性的,有效期大概几分钟,后端拿这个 code 调微信接口换 openid 和 session_key,再生成自己系统的 token 返回给前端。这个 token 就是后续所有请求头里 Authorization 的值,也是 request.js 封装里从 storage 读取的那个 token。

实际开发中要注意 code 的时效性。有的开发者把 code 存在本地复用,结果第二次请求时后端直接报 code 无效。正确做法是每次登录都重新 uni.login 获取新的 code。如果模板里登录后请求接口还是报 401,优先检查后端是否真的收到了有效 code。

4.2 支付流程:从下单到 uni.requestPayment

支付流程是商城项目里最容易出问题的环节。模板的做法是:确认订单页调下单接口,后端返回支付所需的参数,前端再调 uni.requestPayment 拉起微信支付面板。

// api/order.js export function createOrder(orderData) { return request({ url: '/order/create', method: 'POST', data: orderData }) } export function payOrder(orderId) { return request({ url: '/order/pay', method: 'POST', data: { orderId } }) }
// pages/order/confirm.vue async function submitOrder() { const orderData = { goodsList: this.checkedGoods, addressId: this.addressId, remark: this.remark } const order = await createOrder(orderData) const payParams = await payOrder(order.orderId) uni.requestPayment({ provider: 'wxpay', timeStamp: payParams.timeStamp, nonceStr: payParams.nonceStr, package: payParams.package, signType: payParams.signType, paySign: payParams.paySign, success: () => { uni.redirectTo({ url: '/pages/order/pay-success' }) }, fail: (err) => { uni.showToast({ title: '支付已取消', icon: 'none' }) } }) }

一定要注意:uni.requestPayment 里的 package 参数是后端返回的,不是自己拼的。很多新手在这里写死或写错,导致支付面板一直起不来。时间戳 timeStamp 是秒级字符串,后端返回什么就传什么,前端不需要转换格式,paySign 的签名规则完全由后端控制。

4.3 与后端联调时的接口约定

一个商城模板能否快速接上真实业务,就看 api 层和返回结构是否规范。模板里已经把返回结构统一为:

{ "code": 0, "msg": "success", "data": { "list": [], "total": 100 } }

code 为 0 表示成功,非 0 表示业务错误,401 特殊处理为登录失效。如果你们的后端返回结构不是这样,改 request.js 里的判断逻辑就行。我一般会在模板基础上增加一个拦截 uni.showToast 的开关,调试阶段把错误信息显示全,上线后只显示统一文案,避免把后端异常直接暴露给用户。

5. 避坑指南:跑通这套模板时的 5 个高频问题

5.1 HBuilderX 运行后白屏或页面找不到

现象:模板在 HBuilderX 中编译成功,但微信开发者工具打开后白屏,或控制台提示 page 路径不存在。

原因:pages.json 的 pages 数组里注册了不存在的页面路径,或改过目录结构后路径没有同步更新。

解决:打开 pages.json,逐个核对 pages 数组里的 path 是否与 pages 目录下的真实文件对应;再确认 manifest.json 里的微信 appid 是否填写正确。还有一个常见原因:首次运行没重新编译,HBuilderX 里点运行后,如果改了 pages.json 需要重新编译,增量编译有时不会刷新路由。

5.2 真机预览时请求全部失败

现象:开发者工具里接口正常,真机预览后所有请求都是 fail 或报域名不在合法列表。

原因:微信小程序要求所有请求域名必须配置在 mp 后台的 request 合法域名里,且必须是 HTTPS。开发时工具可以勾选「不校验合法域名」,真机上这个开关不生效。

解决:开发阶段在微信开发者工具的「详情-本地设置」里勾选不校验合法域名;真机调试或上线前,把接口域名加到 mp 后台的 request 合法域名列表。域名必须是备案过的 HTTPS,且不能带端口。如果只是临时联调,可以用真机调试模式绕过,但最终上线前必须配好域名。

5.3 支付唤起失败,报 package 参数错误

现象:调 uni.requestPayment 时提示 package 参数错误,或支付面板弹出来立即消失。

原因:package 参数是从后端获取的,前端把它写错或漏传;也可能是后端生成签名时用的 appid 与小程序 appid 不一致。

解决:确认 uni.requestPayment 的入参中 timeStamp、nonceStr、package、signType、paySign 全部由后端下单接口返回,前端严格透传。检查后端支付证书里的商户号 mch_id 和 appid 对应关系,支付签名用 v2 还是 v3 的规则也要和后端确认清楚。

5.4 商品图片裂开或不显示

现象:列表页图片加载不出来,或本地调试正常、部署后图片失效。

原因:图片如果走的是 http 协议,在小程序里会被拦截;部分图片服务器做了防盗链,拒绝了小程序的请求头。

解决:图片地址必须保证可公网访问的 HTTPS 链接,且响应头中不能包含禁止外部引用的 X-Frame-Options 或防盗链校验。开发时如果图片来自本地 static 目录,确认路径是相对路径而不是绝对路径。真机预览后图片挂掉的,多半是图片域名没有在 mp 后台的 downloadFile 合法域名里配置。

5.5 SKU 选中后价格库存不刷新

现象:在 SKU 弹层里点击不同规格,价格和库存没有变化,甚至显示为 0。

原因:SKU 弹层里 spec_ids 的拼接格式与 sku_list 里的 spec_ids 字段不一致,比如规范里用的是冒号分隔,后端返回的是下划线分隔,导致匹配失败。

解决:先打印出前端拼接的字符串和后端返回的 spec_ids,一行一行对比。模板里约定的是规格名:规格值,规格名:规格值的格式,如果后端格式不同,统一在后端转换,不要在前端做兼容处理,否则每种商品都可能有不同的拼接规则,维护成本极高。

6. 二次开发实战:把通用模板改成你自己的线上项目

6.1 换主题:改 uni.scss 一处,全局变色

模板里的主色、辅助色、圆角、间距全部定义在 uni.scss 中。改动最集中的是 $primary-color 这个变量,它是底部 tab 选中态、价格文字、按钮背景、SKU 选中态共用的颜色。这个模板把颜色变量抽出来做了全局覆盖,改一处全局生效,省去每个页面逐个找颜色的麻烦。如果你要改成多主题,则可以再抽出 theme.scss,配合条件编译做不同端的差异化。

6.2 新增页面:走一遍完整的四步注册

加一个新页面不是只建 .vue 文件,还要完成四个动作:在 pages 目录下创建 vue 文件;在 pages.json 的 pages 数组里注册路由;如果要在底部 tab 显示,把路径加进 tabBar.list;如果页面是商品详情的二级入口,在组件里写好跳转。这套流程不复杂,但容易漏。我自己的习惯是每次新增页面都强制走一遍「建文件、注册路由、起本地预览、真机看一次」的完整流程,确认页面可以正常编译后再写业务逻辑,避免攒了一堆页面最后一起编译报错,排查起来非常痛苦。

6.3 接真实后端:只改 api 层,不碰页面

模板的价值在于 api 层与页面层是解耦的。替换成真实后端时,只需要改 request.js 里的 BASE_URL 和每个 api 文件里的 url、data 字段名。如果后端返回结构和模板不一致,优先在 request.js 或 api 层做一个适配层,把后端的返回字段映射成模板页面里能识别的结构。不要去页面里逐行改字段名,那样改完这个项目,下一个项目还是会踩同样的坑。

这套模板我从头到尾拆过一遍,最深的感触是:下载一个商城模板,先看 pages.json,再看 store 里的模块划分,最后才看页面长什么样。从那以后,我每次接手任何 uniapp 项目,都强制自己先走一遍「看路由、看状态、看请求封装」的三步流程,确认这三点没问题之前,不写任何业务代码。希望这个思路对你也有用。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 7:35:22

MOS管原理与选型:从数据手册参数到工程实践避坑指南

1. 硬件知识总结梳理&#xff1a;MOS管到底是个什么东西做硬件这一行&#xff0c;天天跟MOS管打交道。电源、驱动、保护电路、负载开关&#xff0c;哪儿都有它&#xff0c;但真要说清楚它内部怎么工作、参数怎么挑、电路怎么搭&#xff0c;很多人其实是半吊子。我见过不少工程师…

作者头像 李华
网站建设 2026/10/21 7:35:22

高光谱与近红外光谱预处理全攻略:SNV、SG平滑、导数与归一化

拿到一批高光谱或近红外原始光谱之后&#xff0c;大多数人的第一反应是赶紧塞进建模流程里跑一版&#xff0c;看到效果不理想再回头调预处理。我一开始也这么干过&#xff0c;后来发现这个顺序往往反了——高光谱和近红外数据的原始信号里&#xff0c;噪声、基线漂移、样本厚度…

作者头像 李华
网站建设 2026/10/21 7:35:11

自建表与核心数据表三部分拆分:动态建模与数据治理的落地实践

做技术这么多年&#xff0c;最怕遇到一种需求&#xff0c;业务方一边说着“我们的数据就一张表&#xff0c;很简单”&#xff0c;一边又在项目上线前突然抛出一句“自建表要搞起来哈&#xff0c;客户随时要加字段&#xff0c;还有那个数据表&#xff0c;帮我们拆成三部分”。乍…

作者头像 李华
网站建设 2026/10/21 7:34:41

初中英语介词三维建模教学法:空间感、时间轴与语境排雷

简介&#xff1a;本资源是一份面向初中英语学习者的介词用法系统性总结资料&#xff0c;聚焦中考核心语法难点&#xff0c;帮助学生厘清易混介词的语义差异与使用场景。文档全面梳理表示地点&#xff08;at/in/on/to/above/over/under/below/in front of/behind等&#xff09;和…

作者头像 李华
网站建设 2026/10/21 7:34:39

2026外企技术岗薪资折叠:总包构成与谈判实战指南

1. “薪资折叠”到底在折叠什么关注外企技术岗的朋友&#xff0c;最近应该都刷到过“薪资折叠”这个说法&#xff0c;2026年的行情确实不再是前几年那种“闭眼拿包”的状态了。我见过的真实案例里&#xff0c;同一个人、同一个级别&#xff0c;在2021年拿到的总包和2026年拿到的…

作者头像 李华
网站建设 2026/10/21 7:34:15

Node.js新手实战:从零搭建HTTP服务到Express应用

很多刚接触 Node.js 的朋友&#xff0c;第一个程序几乎都是从打印 Hello World 开始的&#xff0c;node xxx.js一敲&#xff0c;终端里蹦出一行字&#xff0c;成就感是有&#xff0c;但心里也发虚&#xff1a;这就完了&#xff1f;我告诉你&#xff0c;真正的第一个应用&#x…

作者头像 李华