简介:这套微信小程序生鲜商城项目附带完整截图与可运行源码,适合小程序入门开发者、前端学习者及电商项目实训人员,解决从页面设计到功能逻辑实现缺少完整参考的问题。资源压缩包共39个文件,大小仅576KB,其中15张png截图用于展示界面效果,7个js文件负责交互逻辑,7个wxss文件控制视觉样式,6个wxml文件搭建页面结构,2个json文件完成页面与组件配置,另有md说明文档和dwg设计参考文件,各文件类型分工明确,便于按需查看。项目内部包含pages、component、template等目录,并配有app.js、app.wxss、app.json等全局配置文件以及README.md,能较完整地呈现生鲜商城类小程序的项目组织方式。读者可以对照截图与代码快速理解商品展示、分类导航、购物车等典型模块的写法,也可以基于现有结构进行二次开发,搭建自定义的生鲜商城小程序。目前已有1860人学习下载,适合用来积累小程序项目实战经验。
1. 一套带截图的生鲜商城源码包,先别急着跑
当你把《微信小程序 生鲜商城(截图+源码)》解压后,会看到一堆 png 图标、一份 README、几个页面文件夹,甚至还有一个.dwg文件。别被这些文件吓到,这其实是一条完整的微信小程序学习链路:app.json负责入口注册,app.wxss和「生鲜商城 style layout.wxss」负责全局视觉,pages下的 index、list、logs、component 四个页面演示了商城最常见的列表、日志和组件交互,template/item-template.wxml把商品卡片抽成了可以复用的模板。这份资源最大的价值不是拿来直接上线,而是让你对照截图,一步步看懂一个真实小程序工程是怎么组织样式、数据和模板的。下面我按从全局到局部的顺序拆,中间会给出可以直接抄的代码和参数说明。
2. 从 app.json 到 style layout.wxss:先看清全局骨架
2.1 pages 是入口清单,第一个页面就是首页
微信小程序运行时第一件事就是读根目录下的app.json。这个文件里最重要的是pages数组,它声明了工程里所有页面路径,而数组的第一项会作为编译后默认打开的页面。以这份资源为例,pages目录下能看到 index、list、logs、component 几个页面文件夹,所以app.json里的pages至少应该包含下面这些路径。
{ "pages": [ "pages/index/index", "pages/list/list", "pages/logs/logs", "pages/component/component" ], "window": { "navigationBarBackgroundColor": "#3CB371", "navigationBarTitleText": "生鲜商城", "navigationBarTextStyle": "white", "backgroundColor": "#f6f6f6" }, "sitemapLocation": "sitemap.json" }pages数组的作用是告诉开发者工具哪些.js、.wxml、.wxss、.json文件属于一个页面。如果你想把商品列表页作为首页,把pages/list/list移到数组第一位就可以。window里的navigationBarBackgroundColor控制顶部导航栏颜色,生鲜类目通常选绿色系,因为绿色能直接传递新鲜、天然的感觉;navigationBarTextStyle只支持white或black,配深绿背景时选白色。
提示:如果你改动
app.json后模拟器没有刷新,点击开发者工具顶部的「编译」按钮即可,不需要重启工具。
2.2 全局样式拆成 style 与 layout 两层
这份压缩包里有一个名字很长的样式文件,叫「生鲜商城 style layout.wxss」。从命名看,它没有把全部样式塞进单一文件,而是单独拆出了 style 与 layout 两层。这种拆分方式在小程序工程里很实用:style 层负责颜色、字体、圆角这类主题变量,layout 层负责 flex 布局、间距、栅格这类结构样式。如果你以后维护一个超过 5 个页面的商城,强烈建议也这样拆。
app.wxss中放全局变量和公共类,例如品牌色和主要按钮:
page { --primary-color: #3CB371; --price-color: #ff3b30; --bg-color: #f5f5f5; font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif; } .btn-primary { background-color: var(--primary-color); color: #ffffff; border-radius: 8rpx; }CSS 变量在微信小程序基础库 2.6.0 之后才支持。如果你在开发者工具里看到变量没有生效,把var(--primary-color)直接替换成#3CB371即可。layout 层可以抽象更通用的布局类,配合图片资源里的箭头图标使用:
.flex-row { display: flex; flex-direction: row; align-items: center; } .flex-between { display: flex; flex-direction: row; align-items: center; justify-content: space-between; } .goods-card { display: flex; padding: 24rpx; margin: 20rpx; background: #ffffff; border-radius: 12rpx; }.flex-row解决 80% 的水平排列需求,.flex-between负责左右分布,.goods-card是商品卡片的通用容器。看到image目录下的arrowright.png和green_tri.png时,通常就是把小图标放进 flex 容器中做箭头、角标、价格装饰。这种原子化类的缺点是在 wxml 里类名会变长,但好处是样式复用率高,页面之间视觉一致。
2.3 从 image 目录的图标名还原界面功能
看一个源码包有没有货,最快的方法是把image目录里的文件名列出来,逐个推断界面长什么样。下面是我对这份资源的初步判断。
| 文件名 | 推测用途 | 对应界面位置 |
|---|---|---|
| icon64_appwx_logo.png | 小程序默认图标 | 项目配置、分享卡片 |
| wechatHL.png | 微信标识 | 授权登录页 |
| green_tri.png | 三角装饰或价格角标 | 商品卡片 |
| plus.png | 加购按钮 | 商品行右侧 |
| trash.png | 删除按钮 | 购物车列表 |
| play.png / pause.png / stop.png | 音频播放/暂停/停止 | 商品详情或公告区域 |
| arrowright.png | 右侧箭头 | 设置项、列表行 |
| icon_API.png / icon_component.png | 组件/API 演示入口 | logs 或 component 页 |
这个表能帮你快速定位功能:plus和trash说明购物车有加购和删除,play/pause/stop三个成组的图标说明工程里还做了音频播放。小程序里每个图片都会占用包体积,如果你只想保留纯商城购物流程,可以把这三个音频图标连同对应代码一起移除;移除时注意搜索play、pause、stop在.js和.wxml中的所有引用,避免留下无效逻辑。
3. 模板与工具:item-template.wxml 和 util.js 的复用姿势
3.1 把商品卡片抽成 template,避免三处重复代码
如果首页、列表页、搜索结果页都要显示商品卡片,最蠢的做法是每页复制一份 wxml。微信小程序提供了template模板机制,把卡片结构写在template/item-template.wxml里,页面通过import引入后就能反复使用。
<template name="item-template"> <view class="goods-item"> <image class="goods-thumb" src="{{goods.thumb}}" mode="aspectFill"></image> <view class="goods-info"> <text class="goods-name">{{goods.name}}</text> <view class="price-row"> <text class="price-symbol">¥</text> <text class="price-int">{{goods.priceInt}}</text> <text class="price-dec">.{{goods.priceDec}}</text> <view class="cart-btn" bindtap="onAddCart"><import src="../../template/item-template.wxml" /> <view class="goods-list"> <block wx:for="{{goodsList}}" wx:key="id"> <template is="item-template" data="{{goods: item}}" /> </block> </view>import的src是相对路径:页面在pages/index/下,想要回到根目录再进template/,就要写../../template/item-template.wxml,这里少一个../就会编译报错。data="{{goods: item}}"表示把循环变量item重命名为模板内部的goods对象,模板里所有字段都从goods下读取。需要注意import有作用域限制,模板里的onAddCart事件绑定的其实是页面 js 中同名方法,所以页面必须定义onAddCart,否则点击加购按钮会直接报not a function。
3.3 util.js:价格拆分和时间格式化,提前做好数据处理
模板取值越简单越好,所以复杂的数据加工应该在utils/util.js里完成。价格格式化是最常见的需求:后台返回的价格可能是12.9,也可能直接是整数12,统一转成两位小数后拆开,前端就只需要关心展示。
function formatPrice(price) { const val = Number(price); if (isNaN(val)) { return { int: '0', dec: '00' }; } const fixed = val.toFixed(2); const parts = fixed.split('.'); return { int: parts[0], dec: parts[1] }; } function formatTime(date) { const d = date instanceof Date ? date : new Date(date); const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); return `${y}-${m}-${day}`; } module.exports = { formatPrice, formatTime };formatPrice先做数字校验,再用toFixed(2)保底,确保不会出现12这种没法拆小数的情况。formatTime负责把时间戳或 Date 对象转成2025-06-01这种标准格式,logs页面记录操作日志时正好用得上。在页面里使用时先const util = require('../../utils/util.js'),在onLoad中遍历商品列表,把每个 item 转成包含priceInt和priceDec的结构,再setData交给模板渲染。不要在 wxml 里直接调util函数,因为import只能引模板,不能引 JS 函数。
4. 页面交互:index、list 与 component 页面串联逻辑
4.1 首页分类切换:dataset 下标与高亮状态
首页index在商城里的角色是流量入口,常见结构是顶部轮播图、分类 tab、商品列表。分类 tab 的交互核心是「点击分类后高亮当前项,并过滤商品列表」。实现时可以先把全量商品存在allGoods里,用一个activeIndex记录当前分类下标。
Page({ data: { categories: ['全部', '蔬菜', '水果', '肉禽蛋'], activeIndex: 0, goodsList: [] }, onLoad() { const all = [ { id: 1, category: '蔬菜', name: '上海青', price: 3.5 }, { id: 2, category: '水果', name: '烟台苹果', price: 6.8 } ]; this.setData({ allGoods: all, goodsList: all }); }, onSwitchCategory(e) { const index = Number(e.currentTarget.dataset.index); this.setData({ activeIndex: index }); const all = this.data.allGoods || []; const filtered = index === 0 ? all : all.filter(item => item.category === this.data.categories[index]); this.setData({ goodsList: filtered }); } });wxml 里给每个 tab 项绑定>addToCart(e) { const id = e.currentTarget.dataset.id; const goods = this.data.goodsList.find(item => item.id === Number(id)); if (!goods) return; const cart = this.data.cart || {}; if (cart[id]) { cart[id].count += 1; } else { cart[id] = { ...goods, count: 1 }; } this.setData({ cart }); wx.setStorageSync('cart', cart); }, removeFromCart(e) { const id = e.currentTarget.dataset.id; const cart = { ...this.data.cart }; delete cart[id]; this.setData({ cart }); wx.setStorageSync('cart', cart); }
用对象cart存储购物车,cart[id]直接访问,删除时delete即可。wx.setStorageSync做本地持久化,下次进入小程序还能读到购物车数据。注意wx.setStorageSync是同步接口,数据量大时会有轻微的阻塞;商城购物车数据量通常只有几十条,用同步写法没有性能问题。removeFromCart里的...this.data.cart是浅拷贝,避免直接修改 data 中的对象引用,这是 setData 常见的踩坑点。
4.3 play/pause/stop 成组出现,多半是音频播放
image 目录里的play.png、pause.png、stop.png是一个信号:原工程还实现了音频播放能力。微信小程序早期支持<audio>组件,但组件在基础库 1.6.0 后已经不再维护,新项目推荐用wx.createInnerAudioContext()这个 API。
const audioCtx = wx.createInnerAudioContext(); function initAudio(src) { audioCtx.src = src; audioCtx.onPlay(() => { // 播放开始,wxml 中把 play 图标切换成 pause }); audioCtx.onEnded(() => { audioCtx.stop(); }); } function toggleAudio() { if (audioCtx.paused) { audioCtx.play(); } else { audioCtx.pause(); } }生鲜商品详情页可能加一段语音介绍,讲产地、甜度、配送方式。用wx.createInnerAudioContext()时要注意:离开页面要在onUnload里调用audioCtx.destroy(),否则音频会继续播放并造成内存泄漏。图标切换的常见做法是在 data 里加一个isPlaying标记,wxml 中根据标记用wx:if判断显示 play 还是 pause;如果同时引入 stop 图标,可以在stop时调用audioCtx.stop(),并把isPlaying重置为false。
5. 截图反推布局:WXSS 里值得直接抄的几段
5.1 商品卡片左图右文的最稳写法
如果你手里只有截图,没有设计稿,我一般先从商品卡片开始拆。把卡片结构定位成「左侧固定宽高的图片 + 右侧自适应文字区域」,再叠加价格和按钮,这样一个布局就能覆盖首页、搜索页、收藏页。
.goods-item { position: relative; display: flex; padding: 20rpx; background: #ffffff; border-radius: 16rpx; margin-bottom: 20rpx; } .goods-thumb { width: 180rpx; height: 180rpx; border-radius: 12rpx; background: #fafafa; flex-shrink: 0; } .goods-info { flex: 1; margin-left: 20rpx; display: flex; flex-direction: column; justify-content: space-between; }.goods-item用display: flex建立水平排列,.goods-thumb设置flex-shrink: 0防止图片被右侧文字挤压。.goods-info作为右侧容器,flex: 1撑满剩余空间,justify-content: space-between让名称在顶部、价格在底部。生鲜商品名经常有「新鲜采摘 有机蔬菜 顺丰直达」这类长文案,所以.goods-name建议加两行省略:
.goods-name { display: -webkit-box; overflow: hidden; text-overflow: ellipsis; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }两行省略在 iOS 和 Android 的 WebView 里都兼容,前提是容器必须是 block 或 flex item,并且固定宽度。把-webkit-line-clamp改成 1 就是单行省略,适合商品分类名称。
5.2 价格、角标与绿色三角的三种组合
生鲜商城的价格要突出「便宜」两个字,所以整数部分通常放大加粗,小数部分缩小并靠底部对齐。green_tri.png 在源码里的出现,大概率是放在价格旁边的小三角标识,或者热门角标的底图。我如果不用图片,会直接用 CSS 画一个右上角角标。
.price-row { display: flex; align-items: baseline; } .price-int { font-size: 44rpx; font-weight: 600; color: #ff3b30; } .price-dec { font-size: 24rpx; color: #ff3b30; } .price-tag { position: absolute; top: 0; right: 0; padding: 4rpx 12rpx; background: linear-gradient(135deg, #ff9a56, #ff6b1a); color: #ffffff; font-size: 20rpx; border-radius: 0 16rpx 0 12rpx; }align-items: baseline让两个不同字体大小的 text 成为基线对齐,视觉上小数会自然下沉。角标使用绝对定位,所以.goods-item必须加position: relative。如果你想把图片资源里的green_tri.png用起来,可以把绝色角标替换成<image src="/image/green_tri.png" class="tag-tri" />,两种方式效果差不多,但图片更省 CSS 代码,CSS 画三角则适合做动态颜色。
5.3 rpx 适配与底部安全区参数
rpx 是微信小程序的核心适配单位,它的设计原则是屏幕宽度固定为 750rpx,因此不同机型的 rpx 与 px 换算比例不同。
| 机型 | 屏幕宽度 | 1rpx 约等于 |
|---|---|---|
| iPhone SE | 320px | 0.4267px |
| iPhone 6/7/8 | 375px | 0.5px |
| iPhone Pro Max | 414px | 0.552px |
| 主流 Android | 360px~412px | 0.48px~0.55px |
只要设计稿按 750px 输出,你直接把设计稿里的 px 数字写进 rpx 就可以,不用手动换算。但要注意字体大小如果也用 rpx,在超大屏上会被放大得很夸张,所以正文可以混用 px 或者用28rpx这种折中值。
底部购物车结算栏容易挡住 iPhone 的 Home 指示条,必须加安全区适配:
.footer-bar { padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom); background-color: #ffffff; box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.05); }constant()是老版本 iOS 的写法,env()是较新的标准,两条都要写,顺序不能反。开发者工具模拟器里可以在设备列表选择 iPhone X 以上机型验证底部是否出现黑色横条遮挡,如果遮挡说明env()没有生效。
6. 导入开发者工具,从源码到上线的最小验收流程
6.1 导入工程的三步操作
解压后不要直接双击某个文件,要用微信开发者工具导入整个工程目录。首先打开开发者工具,进入「项目」页面选择「导入」,然后把目录定位到你解压出的包含app.json的那个文件夹;如果工具提示没有找到 app.json,说明目录层级不对,应该往上一层。接着 AppID 可以选「测试号」,这个选项不需要注册小程序账号,适合本地学习;如果你有自己的小程序账号,也可以选自己的 AppID,这样能真机预览和上传体验版。最后点「确定」后等待编译完成,模拟器里应该能看到首页。
6.2 三个最常见的编译报错
| 报错信息 | 原因 | 处理方式 |
|---|---|---|
app.json未找到 | 导入时选中了内层子目录 | 改选包含 app.json 的根目录 |
Cannot find module '../../utils/util.js' | require 路径层级不对 | 检查从当前 js 文件到 utils 的相对路径 |
template is not found | import 路径写错或模板名拼错 | 对照<template name="item-template">确认 is 属性 |
报错信息里最容易被忽略的是路径。微信小程序里的相对路径都是基于当前文件所在目录计算的,和浏览器里的 base 路径逻辑一样。你只要记住:页面在pages/index/下,访问根目录的template/要写../../;同样访问utils/也是../../utils/util.js。
6.3 改造顺序与运行后的验证技巧
把这份源码改造成能上线的生鲜商城,不建议一上来就重写页面,而是按三条线推进。第一条是视觉线,把app.json和「生鲜商城 style layout.wxss」里的主色、按钮圆角、价格颜色替换成自己的品牌规范。第二条是数据线,把onLoad里写死的allGoods数组换成wx.request或云开发数据库,接口返回结构和本地数组保持相同的字段名,这样template/item-template.wxml完全不用改。第三条是存储线,购物车从wx.setStorageSync升级为云函数或服务端接口,提交订单时把购物车数据 POST 到后端。联调阶段在开发者工具详情面板勾选「不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书」只用于本地代理,上线前必须在后台配置真实的 request 合法域名。运行起来后打开调试器切到 WXML 面板,检查商品卡片有没有出现多余的undefined节点,再对着截图逐项核对价格对齐、图片裁剪和底部安全区,这几项确认无误后,这套源码才算真正吃透了。
本文还有配套的精品资源,点击获取