微信小程序仿小米商城源码拆解:从启动到购物车实战

发布时间:2026/9/16 10:20:15
微信小程序仿小米商城源码拆解:从启动到购物车实战 简介这是一份精选的仿小米商城微信小程序源码面向微信小程序初学者、前端开发者以及想快速搭建电商类小程序的人群可帮助理解小程序工程结构、生命周期与电商核心流程。压缩包共16个文件容量33.79MB包含4个js逻辑文件、3个wxss样式文件、2个wxml页面结构文件、2个json配置文件以及mp4导入视频教程和doc/docx图文教程配合png图片素材从页面结构到样式、逻辑和配置一应俱全。目前已有113人浏览学习亲测可运行。项目涉及商品列表、商品详情、购物车、订单等电商典型场景包含全局脚本、全局配置与全局样式表并封装工具类模块目录结构清晰可直接运行观察效果配套的源码导入视频和图文教程能引导快速完成导入与调试降低上手门槛适合作为课程设计或实战练手项目。1. 微信小程序仿小米商城源码先跑通再拆代码很多同学拿到一份源码包第一反应是直接拖进微信开发者工具结果不是报app.json not found就是首页白屏最后把问题归咎于资源本身。这份仿小米商城的小程序源码包把导入视频教程和图文文档教程都备好了说明它的价值不在解压步骤而在怎么把模板变成自己改得动的项目。包内结构很典型app.js、app.json、app.wxss三个全局文件pages/index首页和pages/logs日志页两个页面单元utils/util.js工具函数以及配套的导入教程。可以说从启动到页面渲染的完整链路都齐了。适合学过小程序基础但没做过完整电商项目的人也适合要交课程设计、想快速搭商城原型的开发者。下面按启动顺序、首页渲染、购物车缓存、导入排查四条线拆开讲。2. app.json 与 app.js仿小米商城小程序的启动顺序小程序不是从 index.wxml 直接开始渲染的而是先读 app.json 完成页面注册和窗口配置再执行 app.js 里的 App() 生命周期最后才加载首页的四个同名文件。这个顺序决定了你改页面标题时应该动 app.json 还是 index.json也决定了登录态初始化该写在 onLaunch 还是 onLoad。2.1 页面注册顺序决定启动页app.json 的 pages 数组打开模板里的 app.json核心是 pages 数组。数组第一项的路径就是小程序冷启动时打开的页面本模板里第一项是pages/index/index所以启动后直接进首页pages/logs/logs排在第二位不承担入口职责更像是一个调试页。如果你要把商城补全新增 detail 和 cart 页面后必须同步注册到 pages 数组否则wx.navigateTo跳转时会提示页面路径不存在。下面这段是补全后的 app.json可以当作骨架参考{ pages: [ pages/index/index, pages/logs/logs, pages/detail/detail, pages/cart/cart ], window: { navigationBarBackgroundColor: #ff6700, navigationBarTitleText: 小米商城, navigationBarTextStyle: white, backgroundColor: #f5f5f5 }, tabBar: { color: #999999, selectedColor: #ff6700, list: [ { pagePath: pages/index/index, text: 首页 }, { pagePath: pages/cart/cart, text: 购物车 } ] }, style: v2, sitemapLocation: sitemap.json }pages 字段的值是字符串数组每项对应一个页面目录不需要写 .js 后缀。window 里navigationBarTitleText控制顶部导航文字navigationBarTextStyle只能取 white 或 black这里配了白色文字配橙色底和商城类 App 的视觉习惯一致。tabBar 的 list 最少 2 项、最多 5 项pagePath 必须先出现在 pages 数组中selectedColor 是选中态颜色。本模板原本没有 tabBar需要底部导航时加上这一段不需要就删掉整个 tabBar 字段。顶栏高度不需要你手工算微信会自动避让胶囊按钮只有把 navigationStyle 设成 custom 时才用wx.getMenuButtonBoundingClientRect()取胶囊位置来定位自定义导航内容。提示真机预览时首页白屏先检查 pages 第一项路径和文件名是否大小写一致index.js/index.json/index.wxml/index.wxss 四个文件缺一不可。2.2 app.js 生命周期与 globalData初始化状态别放错位置app.js 里最常用的是 onLaunch 和 onShow。onLaunch 只在冷启动时执行一次适合读取本地缓存、发起静默登录onShow 在每次小程序从后台切回前台时都会触发适合刷新购物车角标这类需要实时性的数据。这个模板的 app.js 本身记录了启动时间稍作扩展就能变成登录态初始化App({ onLaunch() { // 冷启动时读取本地登录态token 要持久化不能只放 globalData const token wx.getStorageSync(token) if (token) { this.globalData.token token } // 记录每次冷启动时间logs 页面会读取这个数组 const launchLogs wx.getStorageSync(launchLogs) || [] launchLogs.unshift(Date.now()) wx.setStorageSync(launchLogs, launchLogs) }, globalData: { token: , userInfo: null, cartCount: 0 } })wx.getStorageSync(token)是同步读取适合在启动阶段直接拿值token 不存在时返回空字符串所以用 if 判断后放入 globalData。unshift把最新时间放到数组头部logs 页面用 wx:for 渲染就是倒序。globalData 不是持久化存储App 实例被回收后数据会重置因此登录态必须同时写 Storage 和 globalData不能只依赖其中一个。全局文件的职责区别可以用一张表看清楚文件影响范围执行时机典型用途app.json全局启动前读取页面注册、窗口配置、tabBarapp.js全局冷启动执行一次登录态初始化、全局数据app.wxss全局样式随页面加载生效公共按钮、背景色、字体模板里的 app.wxss 适合放两类内容用 page 选择器设置全局背景色和默认字号以及抽公共类名比如橙色的主按钮。rpx 是自适应单位750rpx 等于屏幕宽度写卡片宽度时比 px 省心这份模板的首页样式就是按这个思路组织的。2.3 utils/util.js工具函数不要散落在页面里utils 目录里的 util.js 在模板中主要承担时间格式化logs 页面需要把时间戳转成可读字符串。这类函数不要复制到每个页面里放在 utils 下用 CommonJS 导出所有页面都能 require。function formatTime(date) { // 兼容 Date 对象和数字时间戳 const d date instanceof Date ? date : new Date(date) const pad n (n 10 ? 0 n : n) return ( d.getFullYear() - pad(d.getMonth() 1) - pad(d.getDate()) pad(d.getHours()) : pad(d.getMinutes()) : pad(d.getSeconds()) ) } module.exports { formatTime }入参接收 Date 对象或毫秒时间戳内部统一转成 Date避免调用方自己判断类型。pad 函数做补零否则 2025-07-08 会变成 2025-7-8。在订单场景里可以给订单时间字段直接调formatTime(Date.now())。页面 require 的路径是以当前文件为基准的相对路径pages/index/index.js 到 utils 要写../../utils/util.js层级写错会报 module not found这是很常见的导入后第一坑。3. 首页商品列表WXML 数据绑定与 wx:for 渲染首页是用户看到的第一个页面也是电商小程序里信息密度最高的页面。把 app.json 配好之后接下来就是理解 pages/index 这套四文件结构如何把数据显示到屏幕上。3.1 页面配置与样式index.json 覆盖全局配置pages/index 目录下有 index.js、index.json、index.wxml、index.wxss 四个文件共同组成一个页面单元。其中 index.json 里的 navigationBarTitleText 会覆盖 app.json window 中的同名配置所以首页标题可以单独改成“小米商城首页”详情页再单独改标题互不干扰。{ usingComponents: {}, navigationBarTitleText: 小米商城首页 }usingComponents 用来注册自定义组件这个模板里留空是因为所有页面元素都用基础组件完成。页面样式 index.wxss 只作用于当前页面你在里面写的 class 名不会污染其他页面如果 app.wxss 和页面 wxss 都定义了同一个类页面级样式优先生效。3.2 data 与 setData页面数据的唯一更新入口商品列表数据在 data 里声明模板通过插值表达式读取。页面 data 里要用的字段尽量预先定义不要等接口返回后再临时往 data 里塞对象否则渲染阶段容易出现 undefined。Page({ data: { goodsList: [], // 商品数组先占位再赋值 loading: false // 是否处于加载中 }, onLoad() { this.setData({ loading: true }) // 这里用 setTimeout 模拟接口返回真实项目换成 wx.request setTimeout(() { this.setData({ goodsList: [ { id: 1, name: 小米手环 8, price: 249, image: /assets/band.png }, { id: 2, name: 小米移动电源 3, price: 129, image: /assets/power.png } ], loading: false }) }, 500) } })setData 是小程序里唯一能触发视图更新的入口直接this.data.goodsList.push(...)不会让页面变化。setData 的参数是一个对象key 支持路径写法例如this.setData({ goodsList[0].price: 199 })可以只更新第一项的价格路径写错不会报错但视图不会变调试时优先打印this.data确认数据是否真的被改掉。loading 字段在真实项目中常配合 wx.showLoading 使用也可以结合 wx:if 渲染骨架屏。3.3 wxml 渲染wx:for、wx:key 和点击事件传参首页的商品卡片适合用 wx:for 循环渲染每张卡片绑定同一个点击事件通过>view classgoods-list !-- 列表渲染wx:key 用商品 id让 diff 更精确 -- view classgoods-card wx:for{{goodsList}} wx:keyid bindtapgoDetail >goDetail(e) { // dataset 由>Page({ data: { goods: null, count: 1 }, onLoad(options) { // options.id 来自跳转链接上的 ?idxxx const id options.id // mock 数据真实项目替换成 wx.request(/api/goods/ id) const detailMap { 1: { id: 1, name: 小米手环 8, price: 249, stock: 10 }, 2: { id: 2, name: 小米移动电源 3, price: 129, stock: 20 } } this.setData({ goods: detailMap[id] || null }) } })如果 URL 参数缺失detailMap[id] 是 undefined这里统一转成 nullwxml 里用wx:if{{goods}}包裹详情区域就不会出现读取 null.name 报错。真实商城中详情页通常还会再请求一次商品接口因为列表页价格可能不是最新价库存和促销信息要以详情接口为准这时候 onLoad 里的 id 只是请求参数不应该承担数据源职责。4.2 购物车数组的持久化Storage 读写与去重逻辑购物车数据需要在小程序关闭后仍然保留最直接的做法是把整个数组存进 Storage。下面这段是加购逻辑的标准写法addToCart() { const goods this.data.goods if (!goods) return // 读取本地购物车兼容 key 不存在时返回空字符串的情况 const cart wx.getStorageSync(cart) || [] const index cart.findIndex(item item.id goods.id) if (index -1) { // 已存在同一商品只增加数量 cart[index].count this.data.count } else { // 新商品则 push 一条记录selected 用于后续勾选结算 cart.push({ id: goods.id, name: goods.name, price: goods.price, count: this.data.count, selected: true }) } // 写回 Storage下次冷启动购物车仍然在 wx.setStorageSync(cart, cart) wx.showToast({ title: 已加入购物车, icon: success }) }wx.getStorageSync(cart)在 key 不存在时返回空字符串所以|| []是必要兜底否则后续 findIndex 会直接报错。加购判断用 findIndex 而不是 includes是因为数组里存的是对象includes 无法按 id 精确去重。setStorageSync 是同步写购物车数据量几 KB 到几十 KB 不会有性能问题不需要刻意改成异步。Storage 整体上限是 10MB单个 key 建议控制在 1MB 以内商城类小程序的购物车离这个阈值还很远。同步和异步 API 的选型可以这样记方式特点适合场景setStorageSync / getStorageSync同步、简单直接购物车、登录态、开关状态setStorage / getStorage异步、有回调大日志、批量写入同一个 key 建议固定一种读写方式混用同步和异步可能读到旧值排错时很难复现。4.3 页面间通信eventChannel 与 globalData 的边界商品加购后首页角标或购物车 tab 的数字要更新。最简单的方案是把总数存在 app.js 的 globalData 里详情页加购后调用getApp().globalData.cartCount首页在 onShow 里重新读取。这个方案适合只更新一个数字的场景。如果加购的同时还要把用户选择的规格、数量回传给上一个页面用 eventChannel 更清晰// 列表页跳详情时注册事件 wx.navigateTo({ url: /pages/detail/detail?id1, events: { cartChanged(total) { console.log(购物车总数, total) } } })// 详情页点击加购后回传 const eventChannel this.getOpenerEventChannel() if (eventChannel) { eventChannel.emit(cartChanged, cart.length) }eventChannel 是一次导航内的消息管道目标页面卸载后事件就失效了它适合详情页回传 sku、回传选中地址这类一次性的通信。this.getOpenerEventChannel()只能在被 navigateTo 打开的页面里拿到并且要在导航建立之后调用一般在按钮点击时获取比较稳妥。注意如果首页走的是 tabBar从详情页跳回首页不要用 wx.navigateTo应该用 wx.switchTab否则控制台会提示 can not navigateTo a tabbar page。5. 导入真机调试与排查把这个模板改成自己的项目源码包里附带的导入视频教程和图文文档教程解决的是“怎么打开项目”的问题。真正决定能不能二次开发的是导入后的三个动作目录选对、AppID 选好、本地设置打开调试开关。导入项目时选择解压后的根目录判断标准是目录下直接能看到 app.js 和 app.json。选到外层文件夹会报app.json: 文件未找到这是最常见的导入失败原因和源码本身无关。AppID 可以先用测试号但测试号在支付、获取用户手机号这类企业能力上有限制要完整真机测试就替换成自己的小程序 AppID。开发阶段如果接口是http://或 IP 地址需要在详情-本地设置里勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”否则 wx.request 会直接在 fail 回调里结束。几个高频报错可以先收藏报错现象原因处理方式app.json: 文件未找到导入目录选错选择包含 app.json 的目录重新导入module not found: util.jsrequire 相对路径错误检查../../utils/util.js层级url not in domain list域名未配置或未开调试勾选不校验合法域名或配置 request 合法域名Cannot read property of undefined参数缺失或字段名大小写不一致打印 options核对>const request (url, data {}, method GET) { return new Promise((resolve, reject) { wx.request({ url, data, method, success: res { // 按后端结构取数据常见业务码在 res.data.code 中 if (res.statusCode 200 res.statusCode 300) { resolve(res.data) } else { reject(res) } }, fail: reject }) }) } module.exports { request }request 函数接收三个参数接口地址、请求参数对象、HTTP 方法默认 GET。首页 onLoad 里那段 setTimeout 模拟数据可以直接替换成request(/api/goods).then(res this.setData({ goodsList: res.data }))。要记录请求日志就在 success 分支里把 url 和Date.now()存进一个 Storage 数组logs 页面用现有的 wx:for 就能渲染成可读的请求记录。这样模板自带的日志页就从“冷启动时间表”变成了接口调试面板排查商品列表加载慢、购物车提交失败时你会感谢自己多写了这一行 Storage。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询