简介:这是一套基于uni-app开发的微信商城小程序通用模板,面向前端初学者、小程序开发者及课程设计/毕设学生,提供开箱即用的电商类项目实践基础。资源包含490个文件,主体为275个JavaScript逻辑文件与117个Vue组件文件,辅以WXML结构、WXSS样式、JSON配置及SCSS预编译样式,整体包体仅2.04MB,轻量易读且结构清晰,便于理解小程序生命周期、页面路由、API调用与UI组件化组织方式。内容预览显示已集成echarts图表、u-charts可视化库、GoEasy实时通信及iconfont字体图标等常用能力,覆盖首页、商品列表、购物车、订单管理等核心模块。目前已有383人学习下载,模板经严格测试可直接运行,既可作为学习uni-app跨端开发的完整案例,也支持快速二次开发——如替换后端接口、调整UI主题或扩展营销功能,具备扎实的教学参考价值与工程复用潜力。
1. 一个能当天跑通、三天改出自己商城的 uni-app 微信小程序模板:不是 demo,是带真实支付闭环和后台联调能力的「可交付骨架」
上周帮某高校实验室快速上线一个校园二手书流转小程序,从拉代码、配环境、连测试支付,到在真机上完成一笔 0.01 元的模拟下单——总共花了不到 4 小时。关键不是我多快,而是这个「基于 uni-app 开发的微信商城小程序通用模板」真的把那些最耗时间的脏活累活全包圆了:登录态自动续期、商品列表无限滚动+防抖加载、购物车本地持久化+跨端同步、微信原生支付封装(含支付成功回调校验)、订单状态机驱动的 UI 切换、甚至连小程序码生成和分享卡片预渲染都写好了。它不叫「商城框架」,因为没塞满业务逻辑;它也不叫「UI 组件库」,因为每个页面都走通了真实数据流。适合三类人:刚转小程序的 Vue 工程师想甩开文档直接撸业务;外包团队接单前快速搭出可演示原型;还有就是像我这样,每次新项目都要重写一遍「用户授权 → 获取 openid → 拉商品 → 加购 → 支付 → 查订单」链路的疲惫开发者。这不是玩具,是能进测试环境、经得住真机扫码、连得上你自己的后端 API 的最小可运行商城内核。
2. 技术栈与工程结构:Vue 3 + Composition API + Pinia + uView Plus,目录即文档
这个模板不是靠“炫技”堆砌新东西,而是用当前微信小程序生态里最稳、最省心、社区支持最足的一套组合。uni-app 本身屏蔽了小程序平台差异,而它选的配套技术栈,全是经过大量线上项目验证的“少踩坑”选项。下面拆解它的物理结构——你下载解压后看到的,就是它的真实骨架。
2.1 核心依赖与版本锚点:为什么是这些,而不是别的
项目package.json中锁定的关键依赖如下(已剔除 devDependencies):
{ "dependencies": { "@dcloudio/uni-app": "^3.9.12", "@dcloudio/uni-h5": "^3.9.12", "@dcloudio/uni-mp-weixin": "^3.9.12", "pinia": "^2.1.7", "uview-plus": "^1.8.45", "vue": "^3.4.21" } }提示:uni-app 官方推荐使用
@dcloudio/uni-app作为主包,而非旧版uni-app。这里所有uni-xxx子包版本严格对齐主包,避免 H5 和小程序端行为不一致——这是很多新手翻车的第一步:H5 跑得好好的,一编译成小程序就白屏或接口 404。
uview-plus是uView的增强分支,专为 uni-app 3.x + Vue 3 重构,比原版多出 37 个高复用组件(如<u-swipe-action>左滑删除、<u-sticky>吸顶导航、<u-count-down>倒计时),且所有组件默认支持v-model双向绑定和onBeforeUnmount生命周期,不用再手动写watch监听值变化。Pinia 替代 Vuex,是因为它天然支持 TypeScript、模块热更新(HMR)无丢失、store 实例可直接console.log查看状态树——调试购物车数量变更时,你再也不用翻 5 层嵌套去this.$store.state.cart.items[0].count,而是cartStore.items[0].count一眼可见。
2.2 目录结构:每一层都在回答「这个文件该放什么」
解压后根目录结构清晰到近乎刻板,但正是这种“不自由”,换来的是团队协作时的零理解成本:
├── components/ # 全局可复用原子组件(非页面级) │ ├── goods-card.vue # 商品卡片(含收藏、加购按钮) │ ├── order-status.vue # 订单状态标签(待付款/已发货/已完成) │ └── pay-button.vue # 支付按钮(含 loading、禁用、支付中状态) ├── pages/ # 页面路由入口(uni-app 规范) │ ├── index/ # 首页(含轮播、分类、商品瀑布流) │ ├── cart/ # 购物车页(含全选、编辑、结算) │ ├── order/ # 订单页(含创建、列表、详情) │ └── user/ # 用户中心(含地址管理、优惠券、设置) ├── store/ # Pinia 状态管理 │ ├── index.ts # 主 store(用户信息、全局配置) │ ├── cart.ts # 购物车 store(核心:add, remove, syncWithServer) │ └── order.ts # 订单 store(管理订单列表、创建参数、支付状态) ├── utils/ # 工具函数(非业务逻辑) │ ├── request.ts # 封装 uni.request,自动携带 token、错误统一拦截 │ ├── auth.ts # 登录态管理(wx.login → code → 后端换取 openid/session_key) │ └── validate.ts # 表单校验(手机号、密码强度、收货地址必填) ├── static/ # 静态资源(图片、字体、iconfont) ├── unpackage/ # 编译输出目录(勿提交) ├── pages.json # 页面路由配置(tabBar、窗口样式) ├── manifest.json # 小程序基础配置(appid、名称、图标) └── main.ts # Vue 应用入口(挂载 Pinia、uView、全局指令)注意pages/下每个子目录名即对应pages.json中的路径,比如pages/index/index.vue对应"path": "pages/index/index"。这种强约定让新人第一天就能猜出「我要改首页轮播图,该去哪找」——直接进pages/index/,而不是在几十个.vue文件里 grepswiper。
2.3 关键配置文件:pages.json 与 manifest.json 的实战填坑点
pages.json不只是声明页面,它决定了小程序的「第一眼体验」。本模板中几个关键配置值得抄作业:
{ "tabBar": { "color": "#7A7E83", "selectedColor": "#007AFF", "borderStyle": "black", "backgroundColor": "#ffffff", "list": [ { "pagePath": "pages/index/index", "text": "首页", "iconPath": "static/tabbar/home.png", "selectedIconPath": "static/tabbar/home-active.png" }, { "pagePath": "pages/cart/cart", "text": "购物车", "iconPath": "static/tabbar/cart.png", "selectedIconPath": "static/tabbar/cart-active.png" } ] }, "subNVue": [{ "id": "loading-mask", "path": "subNVue/loading-mask.nvue", "type": "popup", "style": { "width": "100%", "height": "100%", "zIndex": 9999 } }] }注意:
subNVue是 uni-app 的原生子窗体机制,这里定义了一个全局 loading 遮罩(loading-mask.nvue),所有需要 loading 的地方只需uni.showSubNVue({id: 'loading-mask'})即可唤起,比uni.showToast({icon: 'loading'})更可控、不打断用户操作流。很多模板漏掉这个,导致支付中点击返回键就卡死。
manifest.json中最关键的不是name或description,而是appid和mp-weixin配置:
{ "name": "uni-mall-template", "appid": "", // 此处必须留空!编译时由 HBuilderX 或 CLI 自动注入 "description": "", "versionName": "1.0.0", "versionCode": "100", "transformPx": false, "app-plus": { /* H5/App 配置 */ }, "mp-weixin": { "usingComponents": true, // 必须为 true,否则 uView 组件无法渲染 "appid": "", // 同样留空,由工具注入 "setting": { "urlCheck": false // 开发阶段关闭域名校验,避免本地调试 404 } } }血泪经验:如果你手动填了appid,编译时会报错appid not match;如果usingComponents设为false,uView 的<u-button>会变成空白 div——因为小程序原生不识别自定义组件标签。这两个坑,我见过至少 7 个外包团队在客户演示前 2 小时才紧急修复。
3. 核心模块拆解:从首页商品加载到微信支付回调的完整链路
光有目录没用,真正决定项目能否落地的,是关键模块的代码是否经得起推敲。下面挑三个最常被问爆、也最容易写崩的模块,逐行带你读透源码逻辑。所有代码均来自pages/index/index.vue、store/cart.ts和pages/order/create.vue,不虚构、不简化。
3.1 首页商品列表:防抖 + 节流 + 分页加载的三重保险
首页瀑布流看似简单,但真实场景下极易因用户快速滚动触发多次请求,导致接口限流或重复渲染。本模板用useRequest组合式函数封装了完整的加载控制:
// pages/index/index.vue <script setup lang="ts"> import { ref, onMounted, onReachBottom } from 'vue' import { useRequest } from '@/utils/request' import type { GoodsItem } from '@/types/goods' const goodsList = ref<GoodsItem[]>([]) const currentPage = ref(1) const totalPage = ref(1) const isLoading = ref(false) const isNoMore = ref(false) // 防抖 + 节流:防止用户连续滚动触发多次 const loadGoods = useRequest<{ list: GoodsItem[]; total: number }>( (page) => uni.request({ url: '/api/goods/list', method: 'GET', data: { page, size: 10 } }), { manual: true, onSuccess: (res) => { if (res.data.list.length === 0) { isNoMore.value = true return } // 追加新数据,避免覆盖已有项(解决上拉刷新时清空列表的 bug) goodsList.value = [...goodsList.value, ...res.data.list] totalPage.value = Math.ceil(res.data.total / 10) currentPage.value++ isLoading.value = false }, onError: () => { isLoading.value = false uni.showToast({ title: '加载失败', icon: 'none' }) } } ) // 页面挂载时加载第一页 onMounted(() => { loadGoods.run(1) }) // 上拉触底:仅当有下一页且未在加载时才触发 onReachBottom(() => { if (currentPage.value < totalPage.value && !isLoading.value && !isNoMore.value) { isLoading.value = true loadGoods.run(currentPage.value) } }) </script>关键点说明:
useRequest是一个自定义 Hook,封装了uni.request的 loading 状态、错误拦截、响应解析,避免每个页面重复写uni.showLoading();onReachBottom不直接调用loadGoods.run(),而是加了三重守卫:currentPage < totalPage(还有页可拉)、!isLoading(上一次请求未完成则跳过)、!isNoMore(已无数据则不触发);goodsList.value = [...goodsList.value, ...res.data.list]用展开运算符追加,而非push(...),确保 Vue 3 的响应式系统能正确触发列表更新(push在某些场景下不会触发视图更新)。
3.2 购物车状态管理:Pinia store 的本地持久化与服务端同步
购物车是小程序最典型的「本地 + 远程」双状态场景。本模板的cart.tsstore 同时维护内存状态和 localStorage,并在关键节点(如用户登录、进入购物车页、支付成功后)主动同步到服务端:
// store/cart.ts import { defineStore } from 'pinia' import { uniSetStorageSync, uniGetStorageSync } from '@/utils/storage' export interface CartItem extends GoodsItem { count: number selected: boolean } export const useCartStore = defineStore('cart', { state: () => ({ items: [] as CartItem[], // 从 localStorage 初始化,避免页面刷新丢失 _initFromStorage() { const saved = uniGetStorageSync('cart_items') if (saved) this.items = JSON.parse(saved) } }), actions: { // 添加商品:先本地更新,再异步同步服务端 add(goods: GoodsItem, count = 1) { const exist = this.items.find(item => item.id === goods.id) if (exist) { exist.count += count exist.selected = true } else { this.items.push({ ...goods, count, selected: true }) } this.persist() // 异步同步,失败不影响本地操作 this.syncToServer().catch(() => {}) }, // 持久化到 localStorage(防白屏丢失) persist() { uniSetStorageSync('cart_items', JSON.stringify(this.items)) }, // 同步到服务端(需用户已登录) async syncToServer() { const token = uni.getStorageSync('token') if (!token) return await uni.request({ url: '/api/cart/sync', method: 'POST', data: { items: this.items } }) }, // 清空购物车(仅清本地,服务端需另调接口) clear() { this.items = [] uni.removeStorageSync('cart_items') } }, getters: { totalCount: (state) => state.items.reduce((sum, item) => sum + item.count, 0), totalPrice: (state) => state.items .filter(item => item.selected) .reduce((sum, item) => sum + item.price * item.count, 0) } })参数与逻辑说明:
uniSetStorageSync是对uni.setStorageSync的封装,增加了 try-catch 防止存储失败导致 JS 报错中断;syncToServer使用async/await,但调用处用.catch(() => {})吞掉错误——购物车本地操作必须绝对可靠,网络失败不能阻塞用户;totalCount和totalPrice是计算属性(getters),它们自动响应items变化,无需手动watch;clear()方法只清本地,因为「清空购物车」通常是用户主动操作,服务端同步应由独立接口(如/api/cart/clear)完成,避免误删他人数据。
3.3 微信支付闭环:从调起支付到服务端验签的端到端实现
支付是商城的生命线,也是最易出错的环节。本模板的支付流程严格遵循微信官方文档,且包含服务端验签逻辑(防止前端篡改金额):
// pages/order/create.vue <script setup lang="ts"> import { ref, onMounted } from 'vue' import { useOrderStore } from '@/store/order' import { useAuthStore } from '@/store/index' const orderStore = useOrderStore() const authStore = useAuthStore() const paymentLoading = ref(false) // 创建订单并调起支付(前端只负责调起,金额、商品等均由服务端生成) const handlePay = async () => { if (paymentLoading.value) return paymentLoading.value = true try { // 1. 前端校验:地址、商品库存(调用本地缓存或轻量接口) if (!orderStore.addressId) { uni.showToast({ title: '请先选择收货地址', icon: 'none' }) return } // 2. 调用服务端创建预支付订单(关键:金额、商品 ID、用户 openid 全部由后端生成) const res = await uni.request({ url: '/api/order/prepay', method: 'POST', data: { address_id: orderStore.addressId, items: orderStore.cartItems.map(i => ({ id: i.id, count: i.count })) } }) if (res.statusCode !== 200) throw new Error('创建预支付单失败') const { appId, timeStamp, nonceStr, package: pkg, signType, paySign } = res.data // 3. 调起微信支付(uni-app 封装,兼容各平台) await uni.requestPayment({ provider: 'wxpay', orderInfo: { appId, timeStamp: timeStamp.toString(), nonceStr, package: pkg, signType, paySign } }) // 4. 支付成功:清除购物车、跳转成功页 orderStore.clearCart() uni.navigateTo({ url: '/pages/order/success?order_id=' + res.data.order_id }) } catch (err) { console.error('支付失败', err) uni.showToast({ title: '支付失败,请重试', icon: 'none' }) } finally { paymentLoading.value = false } } </script>服务端关键逻辑(供你对接时参考):
/api/order/prepay接口必须做三件事:① 查询用户 openid(从 session 中取,非前端传入);② 校验商品库存(查 DB);③ 调用微信统一下单 API 获取prepay_id,再用prepay_id生成签名(paySign);timeStamp必须是字符串类型(微信要求),所以代码中强制.toString();package字段值为prepay_id=xxx,不是 JSON 字符串,别写成{ prepay_id: 'xxx' };- 支付成功回调(
/notify/pay)必须验签:用收到的sign、nonce_str、timestamp等参数,按微信规则拼接字符串,再用商户 API 密钥 MD5 加密,比对一致才更新订单状态。
4. 运行与调试:从零开始跑通真机扫码的全流程
下载解压后,别急着改代码。先确保环境能跑起来——这是后续所有开发的前提。以下步骤已在 Windows/macOS/Linux 三端实测,全程无坑。
4.1 环境准备:HBuilderX 与 Node.js 版本硬性要求
- Node.js:必须 ≥ 16.14.0(低于此版本,
@dcloudio/uni-app3.9.x 会报ERR_OSSL_PEM_ROUTINE错误) - HBuilderX:推荐 4.22+(官网下载最新稳定版),不要用 VS Code + 插件——uni-app 的小程序编译、调试、真机预览深度依赖 HBuilderX 的原生能力,VS Code 插件仅支持基础语法高亮。
- 微信开发者工具:最新正式版(非 Nightly),需在设置 → 安全 → 勾选「服务端口」,否则 HBuilderX 无法自动唤醒调试器。
提示:HBuilderX 启动后,顶部菜单栏「运行」→「运行到小程序模拟器」会自动打开微信开发者工具。若提示「未检测到微信开发者工具」,请手动设置路径:HBuilderX → 设置 → 运行配置 → 微信开发者工具路径。
4.2 三步启动:初始化、配置、真机扫码
第一步:导入项目并安装依赖
- HBuilderX → 文件 → 导入现有项目 → 选择解压后的文件夹
- 右键项目根目录 → 「使用 npm 安装依赖」(自动执行
npm install) - 等待右下角「依赖安装完成」提示,此时
node_modules已就绪
第二步:配置微信 AppID(仅开发阶段)
- 打开
manifest.json - 找到
"mp-weixin"节点,将"appid"字段留空(如前所述,勿填) - 在 HBuilderX 顶部菜单栏:运行 → 运行到小程序模拟器 → 微信开发者工具
- 微信开发者工具弹出窗口中,AppID 输入框留空,直接点确定——HBuilderX 会自动注入测试 AppID(
wx1234567890abcdef),用于本地调试
第三步:真机扫码预览(关键验证)
- HBuilderX → 运行 → 运行到手机或模拟器 → 选择「微信小程序」
- 确保手机微信已登录同一账号,且开启「允许通过局域网访问」(微信 → 我 → 设置 → 普通设置 → 允许通过局域网访问)
- HBuilderX 底部控制台会生成一个二维码,用手机微信「扫一扫」即可在真机上打开(非开发者工具模拟器)
- 首页轮播图、商品列表、底部 tabbar 全部正常渲染,即表示基础运行成功
4.3 常见问题排查:这 4 个错误占了 80% 的咨询量
现象:HBuilderX 编译时报错Error: Cannot find module 'vue'
原因:node_modules未正确安装,或package-lock.json与node_modules不一致(常见于从其他电脑复制项目)
解决:删除node_modules和package-lock.json,在项目根目录终端执行npm install --legacy-peer-deps(--legacy-peer-deps解决 Vue 3 与某些依赖的 peer 冲突)
现象:真机扫码后白屏,控制台报Cannot read property 'request' of undefined
原因:main.ts中uni对象未正确挂载,或vue版本与@dcloudio/uni-app不兼容
解决:检查package.json中vue和@dcloudio/uni-app版本是否匹配(如前文所示),然后执行npm update vue @dcloudio/uni-app
现象:首页商品列表加载失败,微信开发者工具 Network 面板显示404
原因:utils/request.ts中基础 URL 配置错误,或manifest.json中mp-weixin.setting.urlCheck: false未生效
解决:打开utils/request.ts,确认BASE_URL指向你的后端测试域名(如https://test-api.example.com);再检查manifest.json中mp-weixin.setting.urlCheck是否为false(开发阶段必须关)
现象:点击支付按钮无反应,控制台报uni.requestPayment is not a function
原因:manifest.json中mp-weixin.usingComponents为false,或 HBuilderX 未正确识别为小程序项目
解决:确认manifest.json中mp-weixin.usingComponents为true;右键项目根目录 → 「将项目转换为小程序项目」(若菜单存在)
5. 二次开发实战:如何在 2 小时内接入你自己的后端 API 并上线
模板的价值,不在于它多完美,而在于它让你能以「小时」为单位完成定制。下面以「接入你公司现有 Java Spring Boot 后端」为例,手把手带你改完所有必要文件。假设你的后端 API 文档如下:
| 接口 | 方法 | URL | 参数 | 返回 |
|---|---|---|---|---|
| 商品列表 | GET | /api/v1/goods | page=1&size=10 | {list:[{id:1,name:"iPhone",price:5999}],total:120} |
| 创建订单 | POST | /api/v1/orders | {address_id:123,items:[{goods_id:1,count:2}]} | {order_id:"ORD20240501001",pay_url:"https://pay.example.com/xxx"} |
5.1 后端适配:修改 request.ts 与 API 映射
首先,统一修改请求基地址和拦截器:
// utils/request.ts const BASE_URL = 'https://your-backend-domain.com/api/v1' // ← 改为你的真实域名 // 请求拦截:自动添加 token const requestInterceptor = (options: UniApp.RequestOptions) => { const token = uni.getStorageSync('token') if (token && options.url.indexOf('/login') === -1) { options.header = { ...options.header, 'Authorization': `Bearer ${token}` } } } // 响应拦截:统一处理业务错误码 const responseInterceptor = (res: any) => { if (res.statusCode === 200) { // 后端约定:data 字段为实际数据,code=0 表示成功 if (res.data.code === 0) return res.data.data else throw new Error(res.data.msg || '请求失败') } else { throw new Error(`HTTP ${res.statusCode}`) } }然后,修改首页商品请求(pages/index/index.vue):
// 将原来的 /api/goods/list 替换为 /goods const loadGoods = useRequest<{ list: GoodsItem[]; total: number }>( (page) => uni.request({ url: '/goods', // ← 改为后端实际路径 method: 'GET', data: { page, size: 10 } }), // ... 其余配置不变 )5.2 前端字段映射:处理后端返回字段与模板不一致
你的后端返回goods_id,而模板期望id;返回goods_name,模板用name。这时不要改后端,用transformResponse在请求层做映射:
// utils/request.ts 中扩展 transformResponse 选项 const transformResponse = (res: any) => { if (res.data?.list) { res.data.list = res.data.list.map((item: any) => ({ id: item.goods_id, name: item.goods_name, price: item.sell_price, image: item.cover_image, // ... 其他字段映射 })) } return res }5.3 支付流程改造:放弃微信 JSAPI,改用后端跳转支付
如果你的后端已封装好支付跳转(返回pay_url),则完全绕过微信 JSAPI,改用uni.navigateTo:
// pages/order/create.vue 中 handlePay 方法替换为: const handlePay = async () => { try { const res = await uni.request({ url: '/orders', // ← 后端创建订单接口 method: 'POST', data: { address_id: orderStore.addressId, items: orderStore.cartItems } }) // 后端返回 pay_url,直接跳转 if (res.data.pay_url) { uni.navigateTo({ url: '/pages/webview?url=' + encodeURIComponent(res.data.pay_url) }) return } // 否则 fallback 到原 JSAPI 支付 await uni.requestPayment({ /* 原逻辑 */ }) } catch (err) { uni.showToast({ title: '支付失败', icon: 'none' }) } }注意:
/pages/webview是模板自带的 WebView 页面,用于加载外部 H5 支付页,已处理url参数解析和返回监听。
5.4 发布前必做:三处安全加固与性能优化
上线前,务必检查这三项,否则可能被微信审核驳回或引发线上事故:
| 位置 | 操作 | 原因 |
|---|---|---|
manifest.json | 将mp-weixin.setting.urlCheck改为true | 上线必须开启域名白名单校验,否则request请求全部被拦截 |
utils/auth.ts | 删除console.log('login code:', code)等调试日志 | 微信审核会扫描代码,发现敏感日志(如 code、token)直接拒审 |
pages.json | 为每个页面添加"style"配置,如"navigationBarTitleText": "首页" | 无标题栏的页面会被微信判定为「体验差」,影响审核通过率 |
从那以后我每次接手新项目,都强制走一遍「改 BASE_URL → 跑通首页 → 改支付 → 真机扫码」四步验证,哪怕客户说「先做个 demo 看看」,我也坚持把这四步走完——因为 90% 的线上问题,其实在这四步里就已经暴露了。模板不是银弹,但它把「从 0 到 1」的路径压到了最短,剩下的,就是你对业务的理解和对细节的较真。希望帮到你。
本文还有配套的精品资源,点击获取