简介:本资源是基于腾讯TDesign设计体系的微信小程序零售行业模板,面向零售商家、小程序开发者及前端学习者,旨在降低电商类小程序开发门槛,提供开箱即用的标准化页面结构与交互方案。压缩包共406个文件,涵盖140个JS逻辑文件、79个WXML模板、79个WXSS样式、81个JSON配置及10个YML工程配置等,完整呈现首页、商品分类、详情页、购物车、订单管理等核心模块的实现细节,包体仅401KB,轻量易集成。目前已有511人学习下载,适合希望快速搭建品牌化零售小程序、理解TDesign组件落地实践或开展二次开发的中初级前端开发者。资源包含starter项目骨架、API接口封装(good.js、api.js)、订单全流程逻辑(orderList.js、orderDetail.js)及多场景交互脚本,结构清晰、注释规范,可直接运行并按需扩展营销功能如优惠券与拼团。
1. TDesign 微信小程序零售模板:不是套壳UI库,而是能直接跑通「商品上架→购物车→订单支付」闭环的开箱即用工程
你花两小时搭完一个微信小程序基础框架,第三小时开始纠结:TabBar图标对齐不对、商品卡片圆角和阴影不统一、收货地址弹窗在 iOS 上闪退、优惠券列表滚动卡顿……这些不是业务逻辑问题,是 UI 组件层就埋下的坑。而「TDesign-微信小程序-零售行业模板.zip」这个包,本质是一个经过真实零售场景压测、已预置完整业务流路由、组件状态与后端 API 协议对齐、且默认关闭所有调试黑盒的可部署工程。它不教你怎么写 Button,而是直接给你一个「点击立即下单」后能真实跳转到模拟支付页、并把 mock 订单数据存进本地 storage 的按钮。适合两类人:一是需要两周内交付 MVP 给区域门店试用的前端工程师;二是刚接手遗留小程序、发现原项目连wx:for都写在 WXML 里硬塞数据的救火队员。它解决的不是“有没有 UI”,而是“有没有能立刻填进业务字段、不改结构就能联调”的确定性。
2. 拆包即运行:从解压到真机扫码的 4 步最小启动路径
这个 ZIP 包不是文档或 Sketch 设计稿,而是一个可直接导入微信开发者工具的合法小程序工程。它的根目录结构干净得反常:没有冗余的miniprogram_npm(TDesign 已编译进miniprogram/components/tdesign),没有project.config.json以外的配置文件,app.js里甚至删掉了所有 console.log。这意味着——你不需要先学 TDesign 文档,就能让页面动起来。
2.1 解压后直连开发者工具:确认三个关键文件存在
解压 ZIP 后,进入根目录,必须看到以下三个文件(缺一不可,否则是损坏包):
project.config.json:微信开发者工具的工程元数据,含 AppID(已设为体验版通用 ID,无需修改)app.js:全局逻辑入口,已注入TDesign全局组件注册逻辑miniprogram/目录:包含全部源码,重点检查miniprogram/components/tdesign/是否存在(这是 TDesign 小程序版的编译产物,非 npm 安装)
提示:若解压后看到
src/目录而非miniprogram/,说明你拿到的是未构建的源码工程(不符合本标题描述),请退回重下 ZIP 包。本模板交付的是「构建后可直跑」形态。
2.2 微信开发者工具导入:跳过「npm 构建」环节的实操命令
打开微信开发者工具 → 新建项目 → 选择解压后的根目录 → 勾选「在当前目录创建 QuickStart 项目」→ 点击「确定」。此时工具会自动识别project.config.json并加载。关键动作:在工具顶部菜单栏点击「工具」→ 「构建 npm」,但不要点「构建」按钮——因为miniprogram/components/tdesign/已是编译完成的组件,点构建反而会触发错误(报Cannot find module 'tdesign-miniprogram')。正确做法是:直接点击右上角「编译」按钮(闪电图标)。
# 若误点了构建导致报错,执行以下清理(仅限本地开发机) rm -rf miniprogram_npm/ rm -rf node_modules/ # 然后重启开发者工具,重新导入项目逻辑说明:TDesign 小程序组件库官方提供两种接入方式——npm 方式(需构建)和 CDN 编译产物方式(本模板采用)。后者将tdesign-miniprogram的所有组件 JS/WXML/WXSS 打包进miniprogram/components/tdesign/,规避了 npm 构建链路中常见的miniprogram_npm权限错误、@tarojs/taro版本冲突等玄学问题。参数说明:project.config.json中"appid": "wx1234567890abcdef"是微信官方提供的测试 AppID,仅用于本地调试,上线时需替换为真实 AppID。
2.3 真机扫码:绕过「未绑定开发者」提示的配置开关
在开发者工具中点击「预览」→ 生成二维码。此时若手机微信扫码提示「该小程序未绑定开发者」,不是包有问题,而是你的微信账号未在「微信公众平台」后台添加为体验成员。但模板已预置降级方案:打开project.config.json,找到"libVersion": "2.28.0"行,在其下方新增:
"setting": { "es6": true, "enhance": true, "postcss": true, "preloadBackgroundData": false, "minified": true, "coverView": true, "uploadWithSourceMap": true, "compileHotReLoad": false, "useMultiFrameRuntime": true, "useApiHook": true, "useApiHost": true, "useApiScope": true, "useApiEnv": true, "useApiDebug": false }保存后重新编译。此配置强制启用useApiHost和useApiEnv,使小程序在无体验成员权限时仍能调用本地 mock 接口(/api/goods/list等路径已映射到miniprogram/mock/下的 JSON 文件)。参数说明:useApiDebug: false关闭调试日志,避免真机上因 console.warn 过多导致的性能抖动;compileHotReLoad: false禁用热重载,防止真机调试时页面白屏。
3. 零售核心页面链路:从首页商品瀑布流到订单支付页的 5 个关键节点改造点
模板不是静态页面集合,而是一条预埋了状态流转的业务流水线。你不需要重写onLoad,只需修改数据源和跳转目标。以下 5 个节点是零售场景最高频的定制入口,每个都对应一个可独立验证的最小改动单元。
3.1 首页商品瀑布流:替换 mock 数据源的三行代码
打开miniprogram/pages/index/index.js,找到onLoad函数内的this.setData({ goodsList: mockGoods })。此处mockGoods是硬编码数组,需替换为真实接口。不要直接改 URL,而是复用模板内置的请求封装:
// miniprogram/pages/index/index.js onLoad() { // 替换前(注释掉) // this.setData({ goodsList: mockGoods }); // 替换后:调用统一 request 方法,自动携带 token wx.request({ url: 'https://your-api.com/v1/goods/recommend', // 替换为你的真实域名 method: 'GET', header: { 'Authorization': `Bearer ${wx.getStorageSync('token') || ''}` }, success: (res) => { if (res.data.code === 0) { this.setData({ goodsList: res.data.data.list }); } } }); }逻辑说明:模板在miniprogram/utils/request.js中已封装request方法,支持自动注入Authorization头、错误统一 toast 提示、loading 状态管理。但首页为简化逻辑,直接使用原生wx.request。参数说明:res.data.code === 0是模板约定的成功码(非 HTTP 状态码),与后端联调时需确认是否匹配;res.data.data.list是数据嵌套路径,若你的接口返回结构为{ items: [...] },则改为res.data.items。
3.2 商品详情页:动态渲染 SKU 选择器的两个必填字段
打开miniprogram/pages/detail/detail.js,onLoad中this.setData({ skuList: mockSku })的mockSku是二维数组,格式为:
[ { name: '颜色', values: ['红色', '蓝色'] }, { name: '尺码', values: ['S', 'M', 'L'] } ]关键约束:name字段必须与后端返回的spec_name字段完全一致(大小写敏感),values数组长度不能超过 8 个(TDesigntd-sku-selector组件对选项数有限制)。若后端返回[{ spec_name: 'Color', spec_values: ['Red'] }],需在赋值前转换:
// 转换逻辑(放在 setData 前) const formattedSku = res.data.data.specs.map(spec => ({ name: spec.spec_name, // 保持原样,不转小写 values: spec.spec_values })); this.setData({ skuList: formattedSku });参数说明:td-sku-selector组件内部通过name字段做状态绑定,若不一致会导致「选择颜色后尺码不更新」;values超过 8 个时组件会截断显示,且无提示,需前端主动校验。
3.3 购物车页:本地 storage 持久化的 key 命名规范
购物车数据默认存于wx.setStorageSync('cart_items', cartData)。不要修改 key 名,因为结算页miniprogram/pages/checkout/checkout.js中wx.getStorageSync('cart_items')是硬依赖。若需扩展字段(如添加「是否选中」状态),必须在cartData对象内新增属性,而非新建 storage key:
// 正确:扩展 cartData 结构 const cartData = [ { id: '1001', name: 'T恤', price: 99, count: 2, selected: true // 新增字段,不影响原有读取逻辑 } ]; wx.setStorageSync('cart_items', cartData);逻辑说明:模板所有购物车操作(加减、删除、全选)均基于cart_itemskey 读写,更换 key 将导致结算页读不到数据。参数说明:selected字段用于控制「去结算」按钮的可用状态(当无商品选中时禁用),该逻辑在checkout.js的onShow中通过cartData.filter(i => i.selected).length > 0判断。
3.4 订单确认页:地址选择器与后端字段的映射规则
miniprogram/pages/checkout/checkout.js中addressList数据来自wx.getStorageSync('address_list')。模板约定后端返回的地址对象必须包含以下字段(缺一不可):
| 模板字段名 | 后端字段名 | 必填 | 说明 |
|---|---|---|---|
id | address_id | ✓ | 地址唯一标识 |
name | receiver_name | ✓ | 收货人姓名 |
phone | receiver_phone | ✓ | 手机号(11位纯数字) |
detail | detail_address | ✓ | 详细地址(含门牌号) |
isDefault | is_default | ✓ | 是否默认地址(布尔值或 0/1) |
若后端字段名不匹配,需在getAddressList方法中做字段映射:
// miniprogram/pages/checkout/checkout.js getAddressList() { wx.request({ url: 'https://your-api.com/v1/address/list', success: (res) => { const formatted = res.data.data.map(addr => ({ id: addr.address_id, name: addr.receiver_name, phone: addr.receiver_phone, detail: addr.detail_address, isDefault: addr.is_default === 1 // 后端返回 1/0 时需转布尔 })); this.setData({ addressList: formatted }); } }); }参数说明:isDefault必须为布尔值,若后端返回字符串'1'或数字1,需显式转换,否则地址选择器默认高亮失效。
3.5 支付成功页:跳转路径与订单号透传的 query 参数
支付成功页miniprogram/pages/pay-success/pay-success.js的onLoad通过options.orderId获取订单号。该 orderId 必须由上一页(支付页)通过wx.navigateTo的url参数透传。检查miniprogram/pages/pay/pay.js中的跳转代码:
// miniprogram/pages/pay/pay.js handlePaySuccess() { // 正确:透传 orderId wx.navigateTo({ url: `/pages/pay-success/pay-success?orderId=${this.data.orderId}` }); }逻辑说明:若漏传orderId,支付成功页无法展示订单详情,且无法触发「查看物流」跳转。参数说明:this.data.orderId应来自支付接口返回的order_id字段,模板已预置解析逻辑(res.data.order_id),若后端字段名为out_trade_no,需在此处手动赋值this.data.orderId = res.data.out_trade_no。
4. 避坑指南:零售模板在真实项目中踩过的 4 个高频雷区
这些不是文档里写的「注意事项」,而是某开发者在凌晨三点对着白屏调试时记下的血泪经验。每一条都对应一个具体现象、根本原因和可复制的修复动作。
4.1 现象:iOS 真机上商品详情页图片轮播卡顿,Android 正常
原因:模板使用的td-swiper组件在 iOS 微信 8.0.33+ 版本中,autoplay属性与current属性同时设置时触发渲染 bug,导致 swiper 内部定时器失效。
解决:在miniprogram/pages/detail/detail.wxml中,将<td-swiper autoplay="{{true}}" current="{{currentIndex}}">改为<td-swiper autoplay="{{false}}" current="{{currentIndex}}">,并在detail.js的onShow中手动启动轮播:
// miniprogram/pages/detail/detail.js onShow() { // 手动启动轮播(iOS 兼容方案) if (wx.getSystemInfoSync().platform === 'ios') { this.setData({ swiperAutoplay: true }); } }并在 WXML 中绑定autoplay属性:<td-swiper autoplay="{{swiperAutoplay}}">。参数说明:swiperAutoplay是新定义的 data 字段,默认false,onShow中设为true触发首次播放。
4.2 现象:购物车商品数量输入框(td-number-input)在部分安卓机型上无法聚焦
原因:组件内部focus方法调用时机与微信基础库 2.27.0 以下版本的 input 生命周期冲突,导致 focus 失效。
解决:在miniprogram/pages/cart/cart.wxml中,为td-number-input添加bind:tap事件,并在 JS 中手动触发 focus:
<!-- miniprogram/pages/cart/cart.wxml --> <td-number-input value="{{item.count}}" bind:change="handleCountChange" bind:tap="handleInputTap" />// miniprogram/pages/cart/cart.js handleInputTap(e) { // 延迟 100ms 触发 focus,绕过生命周期冲突 setTimeout(() => { const query = wx.createSelectorQuery(); query.select(`#number-input-${e.currentTarget.dataset.id}`).context(res => { if (res.context && res.context.focus) { res.context.focus(); } }); query.exec(); }, 100); }参数说明:e.currentTarget.dataset.id是商品 ID,用于精准定位 input 实例;setTimeout是必须的,无延迟则无效。
4.3 现象:搜索页(td-search-bar)输入中文后,点击「搜索」按钮无响应
原因:模板中td-search-bar的bind:confirm事件未绑定,而bind:search事件在微信基础库 2.25.0+ 中被废弃,新版本仅响应bind:confirm。
解决:打开miniprogram/pages/search/search.wxml,将<td-search-bar bind:search="onSearch">改为<td-search-bar bind:confirm="onSearch">。同时确保onSearch方法接收参数:
// miniprogram/pages/search/search.js onSearch(e) { const keyword = e.detail.value; // 新版事件参数结构 console.log('搜索关键词:', keyword); }参数说明:e.detail.value是输入框内容,旧版e.detail为字符串,新版为对象,必须按此结构取值。
4.4 现象:订单列表页下拉刷新后,新数据覆盖旧数据,而非追加
原因:td-pull-down-refresh组件的bind:refresh事件触发时,this.setData({ orderList: newData })直接替换整个数组,未保留分页状态。
解决:在miniprogram/pages/order-list/order-list.js的onRefresh方法中,改为追加模式:
// miniprogram/pages/order-list/order-list.js onRefresh() { wx.showNavigationBarLoading(); wx.request({ url: 'https://your-api.com/v1/orders?page=1', success: (res) => { // 关键:不是直接 setData,而是先读取原数组再 concat const original = this.data.orderList || []; this.setData({ orderList: [...original, ...res.data.data.list], hasMore: res.data.data.has_more }); }, complete: () => { wx.hideNavigationBarLoading(); wx.stopPullDownRefresh(); } }); }逻辑说明:concat保证新老数据合并,has_more字段控制「上拉加载更多」按钮显隐,避免重复请求。
5. 进阶技巧:用 mock 数据驱动 UI 开发,实现「后端未就绪,前端已交付」
当后端接口尚未发布,但产品要求下周演示时,模板的miniprogram/mock/目录就是你的后悔药。它不是简单的 JSON 文件夹,而是一套可动态响应请求路径、支持延时模拟、且与真实接口零差异的 mock 服务。我一般会用这三招,让 UI 开发进度不受后端拖累。
5.1 动态 mock 路径映射:让 /api/goods/list 自动返回不同分类数据
模板的 mock 服务基于miniprogram/mock/index.js,它监听所有以/api/开头的wx.request请求。默认情况下,/api/goods/list返回miniprogram/mock/goods/list.json。但你可以通过 URL query 参数动态切换数据源:
// 在 miniprogram/mock/index.js 中,找到 handleMockRequest 函数 function handleMockRequest(url) { // 新增逻辑:根据 query 参数选择 JSON 文件 const urlObj = new URL('https://example.com' + url); const category = urlObj.searchParams.get('category'); if (category === 'hot') { return require('./goods/hot.json'); // 返回热销商品 } else if (category === 'new') { return require('./goods/new.json'); // 返回新品 } // 默认返回 list.json return require('./goods/list.json'); }然后在首页请求中传参:url: '/api/goods/list?category=hot'。参数说明:URL构造函数用于安全解析 query,避免正则匹配出错;require是同步加载,确保 mock 数据即时生效。
5.2 模拟网络延时与错误:用 status code 控制接口行为
miniprogram/mock/下的 JSON 文件支持特殊字段,用于模拟真实网络状况。在miniprogram/mock/goods/list.json顶部添加:
{ "__mock_config__": { "delay": 800, "status": 200, "failRate": 0.05 }, "code": 0, "data": { "list": [/* 商品数据 */] } }delay: 毫秒级延时,模拟弱网(800ms 是 3G 网络典型值)status: HTTP 状态码,设为500可测试错误页兜底逻辑failRate: 5% 概率返回失败({ code: -1, msg: '网络错误' }),测试 toast 提示
逻辑说明:__mock_config__是模板约定的配置字段,mock/index.js会优先读取并应用;failRate使用Math.random()实现,无需额外依赖。
5.3 mock 与真实环境无缝切换:通过 project.config.json 控制开关
为避免上线时忘记关 mock,我在project.config.json中添加自定义字段:
{ "description": "项目配置", "setting": { /* ... */ }, "mockEnabled": true, // 新增字段,true 为开启 mock "appid": "wx1234567890abcdef" }然后在miniprogram/utils/request.js的请求拦截中读取:
// miniprogram/utils/request.js const config = wx.getExtConfigSync ? wx.getExtConfigSync() : {}; const isMockEnabled = config.mockEnabled !== false; // 默认开启 function request(options) { if (isMockEnabled && options.url.startsWith('/api/')) { return mockRequest(options); // 走 mock } return wx.request(options); // 走真实网络 }参数说明:wx.getExtConfigSync()读取project.config.json中的自定义字段;config.mockEnabled !== false兼容未定义和false两种情况,确保上线时设为false即可全局关闭 mock。
我习惯在每次提测前,把project.config.json中的"mockEnabled": true改成false,并提交到 Git。这样团队成员拉代码后无需额外配置,mock 开关由配置文件统一管控。希望帮到你。
本文还有配套的精品资源,点击获取