简介:一套仿小米商城的微信小程序源码,面向微信小程序初学者和想搭建电商类应用的开发者。项目涵盖商品列表、商品详情、购物车、订单等电商核心页面,并配有源码导入视频教程与图文文档教程,亲测可用,能帮助解决导入报错、环境配置等常见问题。代码结构清晰:全局逻辑脚本负责生命周期,配置文件定义页面路径与窗口表现,全局样式表统一视觉规范,页面目录下按组件拆分js、wxml、wxss、json,适合课程设计、毕业设计或商业项目起步。整包共16个文件,包含JS逻辑、WXML结构、WXSS样式、JSON配置、PNG图片素材、MP4视频教程以及DOC/DOCX文档教程,压缩包约33.79MB,体量适中。当前已有113人学习下载,便于快速上手微信小程序或借鉴完整商城实现。
1. 仿小米商城微信小程序源码:拿到手别急着导,坑在后面
资源站里那种几百 MB 的“仿小米商城小程序”压缩包,解压后通常会看到源码、导入视频教程和文档教程三样东西。标题里的“亲测可用”在这个场景下只承诺一件事:代码能在微信开发者工具里编译通过,不保证你换了 AppID 之后还能正常出数据。很多人下载这类项目,第一步卡在导入路径选错,第二步卡在域名校验,第三步卡在不知道怎么把写死的商品数据换成接口数据。
这套源码最典型的受众是三类人:做电商类毕设的学生、想快速验证小程序商城交互的产品经理,以及想拿现成项目练手组件和数据流的初中级开发者。看懂它不是为了跟小米商城逐像素对齐,而是把它当骨架,按自己的商品、图片和运营逻辑把内容换掉。所以这篇不讲怎么“欣赏”源码,只讲拿到这种模板项目后,怎么导、怎么拆、怎么改、怎么验。
2. 源码导入微信开发者工具:第一步操作决定后续成败
“亲测可用”和“我能用”之间,通常只差一个正确的导入姿势。仿小米商城这类模板源码为了兼容不同版本的开发者工具,目录层级往往比较深,选错了根目录会直接导致编译失败,而且报错信息不会告诉你“目录选错了”,只会给你一个看不懂的app.json not found。
2.1 导入前先找 app.json 所在层,别把外层文件夹当项目根目录
拿到压缩包先解压,然后看一眼目录结构。常见的上层目录是这样:
wechat-mall/ ├─ project.config.json ├─ miniprogram/ │ ├─ app.js │ ├─ app.json │ ├─ app.wxss │ ├─ pages/ │ │ ├─ index/ │ │ ├─ category/ │ │ ├─ cart/ │ │ └─ user/ │ ├─ components/ │ └─ images/ └─ README.md注意project.config.json在wechat-mall/这一层,但app.json在miniprogram/这一层。微信开发者工具导入时,要选择wechat-mall这个根目录,工具会读取project.config.json,实际的小程序代码则由该文件中的miniprogramRoot字段指定到miniprogram/。
这段配置的开头通常是这样的:
{ "miniprogramRoot": "miniprogram/", "appid": "touristappid", "compileType": "miniprogram", "setting": { "urlCheck": false, "es6": true, "postcss": true } }miniprogramRoot告诉工具去哪里找app.json;appid如果填的是touristappid,代表游客模式,模拟器能跑但真机预览有功能限制,需要换成你自己注册的小程序 AppID。setting.urlCheck为false表示关闭域名校验,这对应开发者工具里的“不校验合法域名”选项,模板项目默认关掉它是有原因的,因为它的接口域名通常不在白名单里。
2.2 本地设置里三个必勾选项:域名校验、ES6、npm 模块
导入项目后,先别点编译,进入右上角“详情 → 本地设置”,把下面三项确认一遍:
| 设置项 | 选项位置 | 作用 |
|---|---|---|
| 不校验合法域名 | 本地设置 → 合法域名校验 | 允许请求未配置白名单的接口地址,调试期必备 |
| 将 JS 编译成 ES5 | 本地设置 → 编译选项 | 兼容旧基础库,模板里如果用了async/await建议打开 |
| 使用 npm 模块 | 本地设置 → npm | 源码里如果引用了第三方库,必须先开启再执行“工具 → 构建 npm” |
这三项里最容易忽略的是“构建 npm”。很多仿商城源码会引入vant-weapp之类的组件库,但仓库里只放了package.json没有node_modules。直接点编译会报module is not defined,这时要回到项目根目录执行npm install,回到开发者工具点“工具 → 构建 npm”,再重新编译。
urlCheck关闭只建议在开发调试阶段使用。小程序上线时,接口域名必须配置到小程序后台的 request 合法域名里,并且要求 HTTPS,否则真机预览会直接报request:fail url not in domain list。
2.3 首次编译高频报错对照表:遇到哪个查哪个
下面这张表是我处理模板项目时总结的报错对照,覆盖了绝大多数第一次编译就挂的情况:
| 报错特征 | 常见原因 | 处理动作 |
|---|---|---|
app.json: file not found | 导入的目录不是项目根目录 | 重新导入,选择包含project.config.json的目录作为根目录 |
request:fail url not in domain list | 接口域名未在小程序后台白名单 | 开发阶段勾选“不校验合法域名”,上线前把域名配好 |
module 'xxx' is not defined | npm 依赖未安装或未构建 | 执行npm install,再点击“工具 → 构建 npm” |
component is not found | 组件路径或usingComponents写错 | 检查 json 文件里的组件路径是否真实存在 |
Do not have handler in component | 事件名拼写与组件方法不一致 | 对照 WXML 中的绑定名和 JS 中的函数名 |
wx.getUserProfile is not a function | 基础库版本过低 | 在详情里把调试基础库版本调整到 2.10.4 以上 |
2.4 用 console 面板验证源码是否真的“能用”
编译通过只是第一步,模板项目里经常藏着隐藏问题。点完“编译”后,我会依次做四个动作:打开调试器的 Console 面板看有没有红色报错和黄色警告;在 Network 面板里看接口请求是否返回 200;逐个点击底部 tab 切换页面,确认每个页面的生命周期正常执行;最后用真机预览扫码,因为模拟器和真机的渲染差异会导致某些样式错位。
做完这些再下“可用”的结论。源码包自带的视频教程一般只演示到“编译成功”,这一步之后的内容要靠自己排查。
3. 页面骨架与首页实现:看懂仿小米商城的目录和组件
仿小米商城的源码价值不在于它的样式有多像,而在于它的页面目录结构能代表电商小程序的通用范式。搞清楚首页轮播、宫格导航和商品列表这三个模块是怎么写的,后续换成自己的数据源就有了明确入口。
3.1 底部 tabBar 与 pages 目录的对应关系
打开app.json,pages 数组的顺序就是启动顺序和路由顺序。仿小米商城的典型配置长这样:
{ "pages": [ "pages/index/index", "pages/category/category", "pages/cart/cart", "pages/user/user" ], "tabBar": { "color": "#999999", "selectedColor": "#ff6700", "list": [ { "pagePath": "pages/index/index", "text": "首页", "iconPath": "images/tab-home.png", "selectedIconPath": "images/tab-home-active.png" }, { "pagePath": "pages/category/category", "text": "分类", "iconPath": "images/tab-category.png", "selectedIconPath": "images/tab-category-active.png" } ] } }selectedColor是选中态的文字颜色,iconPath是未选中图标,selectedIconPath是选中图标,图标 PNG 文件不能大于 40 KB。理解这一层之后,改 tab 文案、换图标、增减 tab 页面都变得很直接,但要记住 tabBar 的 list 至少两项、最多五项。
3.2 首页轮播:swiper 参数与把静态 banners 换成接口数据
首页轮播是仿商城项目里第一个值得细读的模块。先在 data 里声明banners数组,然后在 WXML 里用swiper渲染:
<swiper indicator-dots="{{true}}" autoplay="{{true}}" interval="3500" circular="{{true}}" bindchange="onBannerChange"> <block wx:for="{{banners}}" wx:key="id"> <swiper-item> <image src="{{item.imageUrl}}" class="banner-image" mode="aspectFill" /> </swiper-item> </block> </swiper>interval表示自动切换间隔毫秒数,circular开启循环切换,indicator-dots控制底部圆点显隐。bindchange的事件对象里有current值,可以用来做“第几张”的角标显示。这里有个常见误解:target和currentTarget,在事件回调里拿数据时要用e.currentTarget.dataset,否则取到的可能是触摸起始位置的组件数据。
静态数据替换成接口数据的步骤很固定:在onLoad中调用wx.request,成功回调里setData({ banners: res.data })。注意给失败回调一个兜底数组,避免接口挂了之后页面渲染时找不到item.imageUrl而白屏。
3.3 宫格导航:循环渲染入口,不写死固定菜单
宫格导航在仿小米商城首页通常有四个或十个入口,模板里常见的写法是把每个入口手写一遍,这是后期维护成本最高的一种方式。推荐做法是维护一份数组:
data: { navItems: [ { title: '手机', icon: '/images/nav-phone.png', url: '/pages/list/list?cat=phone' }, { title: '电视', icon: '/images/nav-tv.png', url: '/pages/list/list?cat=tv' }, { title: '电脑', icon: '/images/nav-pc.png', url: '/pages/list/list?cat=pc' } ] }WXML 中通过wx:for循环生成:
<view class="nav-grid"> <view wx:for="{{navItems}}" wx:key="title" class="nav-item" bindtap="onNavTap" >onReachBottom() { if (this.data.loading || !this.data.hasMore) { return; } this.setData({ pageNo: this.data.pageNo + 1 }, () => { this.fetchGoods(); }); }, fetchGoods() { this.setData({ loading: true }); wx.request({ url: 'https://api.example.com/goods', data: { pageNo: this.data.pageNo, pageSize: this.data.pageSize }, success: (res) => { const list = res.data.list || []; this.setData({ goodsList: this.data.goodsList.concat(list), hasMore: list.length >= this.data.pageSize }); }, complete: () => { this.setData({ loading: false }); } }); }loading是一把简单的锁,防止onReachBottom在短时间内连续触发多次请求;hasMore则根据单次返回条数判断是否还有下一页。分页参数pageNo从 1 开始,pageSize建议 10 到 20 之间,做得太重会拉长首屏渲染时间。把setData的回调里发请求而不是直接在外面调用,是因为setData是异步的,回调里读到的pageNo才是更新后的值。
4. 购物车与登录态:把仿小米商城源码从演示版改成可用版
首页轮播和列表能替换数据后,这个项目就算“长得像商城”了。但离“能用”还差两环:购物车的数据不落地,一刷新就清空;登录态如果不处理,下单这类操作根本做不了闭环。这两个问题在仿小米商城的模板里通常都只做了表面实现,需要自己补。
4.1 购物车数据必须落本地缓存,全局变量靠不住
很多模板项目会在app.globalData里放一个cart数组,页面里直接操作它。这样在同一个页面生命周期里没问题,但小程序冷启动、或从其它入口重新进入时,globalData会被重置。验证方法很简单:加购一件商品,点开发者工具的“编译”按钮,再看购物车还有没有数据。
正确做法是把它写入本地缓存:
const CART_KEY = 'cart_guest'; function getCart() { return wx.getStorageSync(CART_KEY) || []; } function saveCart(cart) { wx.setStorageSync(CART_KEY, cart); }缓存键名建议按用户维度区分,这样后续接登录后不会串数据。这里列一个缓存键的设计约定:
| 缓存键 | 适用场景 | 说明 |
|---|---|---|
cart_guest | 未登录游客 | 游客加购数据,登录后可以并入账号数据 |
cart_${openid} | 已登录用户 | 每个用户独立的购物车,不互相覆盖 |
token | 登录态凭证 | 接口请求时放入 header,过期后引导重新登录 |
cart_guest这类键名能直接用,但要意识到游客和登录用户的数据合并是另一个问题,模板源码通常不做这一步。
4.2 加购逻辑:findIndex 比遍历覆盖更可靠
购物车加购的核心逻辑不是往数组里 push,而是要判断商品是否已存在。已存在就累加数量,不存在才新增一条,这是电商购物车的通用行为:
addToCart(e) { const goods = e.currentTarget.dataset.goods; const cart = getCart(); const index = cart.findIndex((item) => item.goodsId === goods.goodsId); if (index > -1) { cart[index].count += 1; } else { cart.push({ goodsId: goods.goodsId, title: goods.title, price: goods.price, imageUrl: goods.imageUrl, count: 1, selected: true }); } saveCart(cart); const totalCount = cart.reduce((sum, item) => sum + item.count, 0); this.setData({ cartCount: totalCount }); }findIndex返回第一个匹配项的下标,用它来定位已有商品,比遍历整个数组赋值更简洁。注意这里读取goods用的是e.currentTarget.dataset.goods,在 WXML 里把商品对象通过>wx.login({ success: (res) => { wx.request({ url: 'https://api.example.com/login', method: 'POST', data: { code: res.code }, success: (res) => { if (res.data.token) { wx.setStorageSync('token', res.data.token); } else { wx.showToast({ title: '登录失败', icon: 'none' }); } } }); } });
code是临时凭证,有效期只有五分钟,拿到后要马上发给后端。后端用这个 code 调微信的code2Session接口换openid和session_key,然后返回一个自定义的token给前端。前端拿到token后存进wx.setStorageSync,后续所有请求都在 header 里带上它:
function request({ url, method = 'GET', data = {} }) { return new Promise((resolve, reject) => { wx.request({ url: `https://api.example.com${url}`, method, data, header: { 'Authorization': wx.getStorageSync('token') || '' }, success: (res) => { if (res.statusCode >= 200 && res.statusCode < 300) { resolve(res.data); } else { reject(res); } }, fail: reject }); }); }把请求封装成request函数的收益是后续不用在每个页面里重复写header和错误处理。注意fail回调不能省略,它对应的是断网、域名不可达这类非业务异常,业务异常应当通过statusCode判断。
4.4 价格与接口地址是两个最先要重构的点
仿小米商城模板里最容易埋雷的两个位置,一个是价格计算,一个是接口地址。价格浮点运算在小程序里会有经典的0.1 + 0.2 !== 0.3问题,模板里如果直接对price做乘法,结算金额会有偏差。处理方式是所有价格以“分”为单位存储,前端展示时再转成带两位小数的字符串,可以用 WXS 写一个过滤器:
function formatPrice(value) { return (value / 100).toFixed(2); } module.exports = { formatPrice: formatPrice };接口地址则要统一收口到一个config.js文件,不要在十几个页面里各自写死。后端接口域名一定会有变动,模板里的地址多半是作者自己的测试域名,拿到手的第一步就是把它们集中替换,否则后面每换一次域名就要全局搜索一次。
5. 进阶:把仿小米商城源码改成自己店铺的三个落地技巧
模板跑通、购物车能用、登录态完整之后,项目就进入了“看起来像自己的”阶段。最后这一步只处理三件事:换肤换图标、自定义首屏加载、上架前用验收清单过一遍。
5.1 全局配色与 tabBar 图标的替换顺序
先去app.wxss里找primaryColor或#ff6700这类主题色变量,替换成自己的品牌色。但如果模板里用的是硬编码颜色而不是 CSS 变量,建议先全局搜索替换;只改 tabBar 里的selectedColor而不改页面里的按钮颜色,视觉上会非常割裂。
tabBar 图标替换要准备两套图,未选中态和选中态,尺寸按官方规范 81px × 81px,选中态颜色建议与品牌色一致。很多源码包自带的图标是第三方资源,直接用会有版权隐患,换成自己生成的 PNG 最稳妥。
5.2 修改刚进入的加载页面:用自定义组件接管首屏
“修改刚进入的加载页面”这个需求在小程序里指的不是 launch 启动图,而是从点击 icon 到首页数据渲染完成之间的过渡。常见做法是在首页根部放一个全屏遮罩,数据请求完成后再隐藏:
<view wx:if="{{loading}}" class="app-loading"> <view class="loading-logo">商城</view> <view class="loading-text">正在加载商品...</view> </view>对应的 JS 逻辑是让loading的初始值为true,在首页onLoad里发请求,请求完成后置为false:
Page({ data: { loading: true }, onLoad() { Promise.all([ this.fetchBanners(), this.fetchGoods() ]).finally(() => { this.setData({ loading: false }); }); } });Promise.all同时发起两个请求,等全部结束再隐藏加载层,比单独处理每个接口更稳妥。这里不要用wx.showLoading,因为它的 loading 指示器在页面中间,无法展示品牌 Logo,而且频繁调用hideLoading容易在真机上出现闪烁。自定义遮罩里可以放品牌名,同时设置app.json里 window 的backgroundColor与遮罩底色一致,让用户冷启动时不会看到突兀的白屏跳变。
5.3 上架前用真机预览清单:亲测可用和可上线的分界线
“亲测可用”意味着编译通过,可上线意味着要过一遍用户体验和合规检查。下面这张清单是提交审核前至少应跑完的验证项:
| 检查项 | 操作 | 通过标准 |
|---|---|---|
| 首屏加载 | 冷启动观察首页 | 数据在 2 秒内出现,加载遮罩正常关闭 |
| 购物车持久化 | 加购后重新编译 | 商品数量不变,勾选状态保留 |
| 登录态失效 | 手动清掉 token 并触发请求 | 页面跳转登录而不是报错 |
| 所有请求域名 | 在 Network 面板逐个核对 | 全部为 HTTPS,且已配置到后台白名单 |
| 图片资源 | 真机滑动所有列表 | 无裂图,无 http 协议的图片 |
| 页面跳转 | 逐个点击入口 | 没有“页面不存在”提示 |
仿小米商城这种模板项目,跑通是及格,改好缓存和登录是进阶,把首屏体验和验收流程补上才谈得上交付。最后一步建议把上面这张清单打印出来,真机上逐项勾选,比反复看模拟器截图有效得多。
本文还有配套的精品资源,点击获取