ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

UniApp家具商城开发复盘:微信小程序从登录到分包避坑指南

UniApp家具商城开发复盘:微信小程序从登录到分包避坑指南 其实早在立项之前我就明确知道要做一套基于微信小程序的家具商城系统技术栈直接锁定 UniApp。这不是拍脑袋的决定而是对比过原生小程序、Taro、Flutter 小程序容器后做的取舍。UniApp 的跨端能力、Vue 开发体验、生态里成熟的组件库让我在不牺牲性能的前提下能把大部分时间花在业务逻辑上而不是跟不同平台的 API 差异死磕。这篇内容不是一份泛泛的教程而是我实际开发“家具商城”小程序过程中的完整复盘从首页信息架构怎么搭到商品列表的加载更多怎么做再到微信登录、支付、坐标定位这些硬骨头怎么啃最后把那些你百度半天都找不到答案的坑——比如包体积超 2MB、日志不打印、导航栏适配、跨域调试——一次性讲透。如果你正准备用 UniApp 做商城类小程序或者已经在开发中反复踩坑这篇文章应该能帮你省下至少两个星期的调试时间。1. 项目概述与整体设计1.1 技术选型背后的真实考量先说结论微信小程序 UniApp Vue 3 Pinia uView Plus这是我能找到的组合里兼顾开发效率和最终体验的最优解。为什么不用原生小程序家具商城这种项目页面形态复杂商品卡片、筛选面板、sku 弹层、订单状态流每一套 UI 在原生小程序里都要重写一遍。更麻烦的是如果后续想上支付宝小程序或 H5 端原生代码几乎全部推倒重来。UniApp 编译到微信小程序端运行时的渲染层还是原生组件只是逻辑层多了一层 Vue 的响应式封装这一点对用户来说是透明的实际体验跟原生没有本质区别。为什么不用 TaroTaro 的 React 生态我很熟但家具商城这种强表单、多状态、需要大量弹层交互的项目Vue 的响应式心智模型写起来更顺手尤其是购物车、订单回显这类父子组件通信的场景Composition API 的复用性明显更好。加上 uView Plus 这套基于 uni-app 的组件库表单、弹层、表格、日历这些高频组件都是现成的能省下大量造轮子的时间。目录结构上我用了最稳妥的分层方式把页面、组件、接口、状态、工具完全拆开src/ ├── pages/ # 页面层每个模块一个文件夹 │ ├── index/ # 首页 │ ├── category/ # 分类页 │ ├── cart/ # 购物车 │ ├── order/ # 订单列表、订单详情 │ ├── goods/ # 商品详情、商品列表 │ └── user/ # 个人中心 ├── components/ # 通用组件 ├── api/ # 接口请求层按模块拆分 ├── stores/ # Pinia 状态管理 ├── utils/ # 工具函数 └── static/ # 静态资源每个页面文件夹里再放一个config.js用来集中管理页面级别的配置比如下拉刷新开关、分享配置、导航栏标题。这样做的目的很直接后续要加新页面复制文件夹改配置就能跑起来不用动之前的代码。1.2 页面架构与信息流设计家具商城的信息架构我参考的是主流电商的成熟模型但没有完全照搬因为家具类目的决策成本高用户不会像买零食那样冲动下单。首页必须解决三个问题让用户快速找到想要的品类、看到有信任感的场景图、以及快速获取活动信息。所以首页我分了四个模块搜索栏 分类快捷入口搜索是电商的刚需分类入口要露出核心品类沙发、床、餐桌、柜类每个入口配一张实景图比纯文字点按率高很多。Banner 轮播新品首发、以旧换新、满减活动每张 banner 上都标了对应的落地页路由。场景化推荐位这是家具商城的特色比如“小户型神器”“奶油风搭配”“实木专场”每个推荐位背后都对应一个后台配置的商品集合。用场景来引导用户比单纯罗列商品更贴合家具的消费心理。瀑布流商品列表展示最近浏览、猜你喜欢、热销榜。这一部分复用了商品列表页的GoodsCard组件只是数据源不同。页面之间的跳转关系我用一张路由脑图理清楚了从首页可以进分类、搜索、商品详情从商品详情可以进购物车、结算、客服所有核心转化路径不超过两步。这一点对电商来说很重要路径短意味着跳出率低。1.3 状态管理与本地缓存规划商城类小程序的状态管理核心就两件事用户信息和购物车。我用 Pinia 拆了两个 storeuserStore管登录态、用户资料、收货地址cartStore管购物车列表、选中状态、结算价格汇总。这里有一个非常关键的决策购物车数据要双写。前端状态存 Pinia同时把精简版同步到uni.setStorageSync。原因是购物车是用户高频操作如果用户加到购物车后没有立刻结算突然杀进程或者网络抖动纯服务端存储会导致丢数据而本地存储只是备胎服务端数据是权威登录后要拉取服务端购物车和服务端合并。缓存方面我把小程序 storage 当做一个简易的二级缓存用商品详情这种高频读取的数据缓存 5 分钟过期重新拉取分类树这种很少变的数据缓存 24 小时首页推荐位的数据每次冷启动拉新的。2. 核心功能模块拆解2.1 登录流程wx.login 与服务端 code2Session微信小程序的登录逻辑和传统网站完全不同没有账号密码也没有输入框它是通过wx.login拿到临时 code然后服务端拿 code 去微信的接口换 openid 和 session_key。我的实现思路是四步走// 前端登录模块 utils/login.js export function uniLogin() { return new Promise((resolve, reject) { uni.login({ provider: weixin, success: async (loginRes) { // 1. 拿到 wx.login 返回的 code const code loginRes.code // 2. 把 code 发给自己的服务端 const res await fetch(/api/user/login, { method: POST, data: { code } }) // 3. 服务端返回自定义 token 用户信息 if (res.code 200) { const { token, userInfo } res.data uni.setStorageSync(token, token) uni.setStorageSync(userInfo, userInfo) resolve(userInfo) } }, fail: reject }) }) }服务端的逻辑更关键拿 code 调https://api.weixin.qq.com/sns/jscode2session参数是appid secret js_code grant_typeauthorization_code返回openid和session_key。这里有一个安全上的坑session_key 绝对不能下发到前端更不能写进 token 里。前端只需要拿到服务端签发的自定义 token后续所有请求通过这个 token 来鉴权openid 只存在服务端。登录时机也很讲究。我没有在冷启动就让用户强制登录而是采用了“静默登录 按需授权”的策略。进入小程序先尝试用uni.login换 token如果用户之前授权过直接跳到首页只有到下单、查看订单这类需要手机号的节点才调uni.getUserProfile或手机号快捷登录。这套策略能让首屏加载时间减少 30% 左右体验提升非常明显。2.2 商品列表与“加载更多”的正确姿势热搜词里“微信小程序页面列表加载更多”是高频问题我在这里吃过不少亏值得单独说。先说列表加载的两种模式翻页模式和触底加载模式。翻页模式是传统 web 的做法每页固定数量上一页/下一页按钮切换触底加载则是移动端的标配滚动到底部时自动加载下一页。我采用的是后者因为场景更自然。实现的核心是监听onReachBottom页面生命周期// pages/goods/list.vue script setup import { ref } from vue import { getGoodsList } from /api/goods const goodsList ref([]) const page ref(1) const pageSize 10 const hasMore ref(true) const loading ref(false) async function loadMore() { if (loading.value || !hasMore.value) return loading.value true const res await getGoodsList({ page: page.value, pageSize, categoryId: currentCategoryId }) const list res.data.list goodsList.value [...goodsList.value, ...list] // 判断是否还有下一页 hasMore.value list.length pageSize page.value loading.value false } onReachBottom(() { loadMore() }) /script这里有几个特别容易出问题的细节loading 状态必须加不加loading判断的话用户快速滚动会同时触发多次 onReachBottom导致重复请求和重复数据。判断还有没有下一页的标准如果返回的数据长度等于 pageSize就认为还有下一页小于 pageSize 说明到末尾了。这个逻辑比根据 total 总数判断更实用因为总数会变而且服务端往往不返回总数。数组追加 vs 赋值新数据一定要[...oldList, ...newList]追加而不是覆盖否则用户滚到第二页之后往回滚第一页数据就丢了。除了触底加载还有一个体验细节容易被忽视加载失误的兜底。如果网络请求失败不能只console.log一下就完事要在列表底部渲染一个“加载失败点击重试”的区块。这个区块绑定的是重新拉取当前页数据的方法。2.3 商品详情与 SKU 选择的实现商品详情页是一个“重灾区”信息密度大交互层级深这里我拆成了四块头部的图片轮播 视频、中间的规格参数区、底部的图文详情、以及悬浮的操作栏客服、购物车、立即购买。sku 选择弹层是整个详情页最复杂的部分。一个家具商品往往有多个规格维度颜色胡桃木色、原木色、黑胡桃色、尺寸1.8 米/1.5 米、材质头层牛皮、科技布三个维度组合起来就是几十个 sku。我的做法是前端一次性拿到所有 sku 的规格数组和价格、库存信息在弹层里通过组合匹配来展示当前可选的规格组合。核心交互是用户点击某个规格值时其他维度的可选项要根据库存和组合关系做置灰处理。这里需要维护一个 sku 的维度索引结构前端每次点击都重算哪些选项可用。这个逻辑听起来复杂但理解透了本质就是遍历所有 sku找出包含当前选中组合的 sku再把它们的其他维度的取值合并成“可用集合”不在集合里的就置灰。详情页还有一个坑图片裁切和预加载。家具商城的详情图通常很长一张图就几百 KB如果一次性全部渲染页面会卡到没办法看。我做了两件事图片用loadinglazy懒加载滚动到可视区域才发起请求详情区域拆成上下两段先渲染首屏的商品实拍图和核心规格用户往下滑到对应区块时再续传详情长图。2.4 购物车与订单流转购物车模块我单独拿出来说是因为它涉及一个最容易忽略的多端同步问题。购物车的本地数据结构长这样const cartItem { skuId: 12345, goodsId: 888, goodsName: 北欧风实木沙发, specText: 1.8米/胡桃木色, price: 3999, quantity: 1, selected: true, coverImage: https://cdn.example.com/xxx.jpg }价格展示有一个原则页面展示价格只做展示结算价格以服务端返回为准。前端如果信任本地购物车价格用户改价格或者参与满减活动结算页跟购物车对不上最终会引发大量客诉。所以我的购物车接口请求的是服务端最新的价格列表前端只负责把数量和选中状态发过去。订单流转也很典型确认订单 → 提交订单 → 支付 → 支付成功回调 → 订单详情。这里最有争议的是“重新计算价格”客户端提交订单前最好让服务端把购物车里的商品价格重新算一遍返回。我遇到过的情况是用户在购物车里加了商品后台改价后没有同步到前端用户提交订单后支付金额跟页面显示不一致这个问题严重起来就是客诉。3. 关键实现细节与踩坑实录3.1 顶部导航栏高度计算与自定义导航“微信小程序顶部导航栏高度”能上热搜说明这是很多人的共性问题。微信小程序的导航栏分成两部分状态栏手机顶部显示时间、电量的那条和导航栏小程序自己的标题栏。要适配不同机型的刘海屏、挖孔屏必须动态计算高度。我在项目里封装了一个useNavBar组合函数// utils/navbar.js export function getNavBarHeight() { const systemInfo uni.getSystemInfoSync() // 状态栏高度单位 px const statusBarHeight systemInfo.statusBarHeight || 20 // 导航栏内容高度不同平台不太一样微信小程序通常是 44px const navBarContentHeight systemInfo.platform ios ? 44 : 48 // 胶囊按钮的位置信息可以用来计算导航栏的真实高度 const menuButton uni.getMenuButtonBoundingClientRect() // 适配逻辑小程序的胶囊按钮垂直居中那么导航栏总高度 // 是胶囊按钮的高度加上下留白的 2 倍 状态栏高度 const navBarHeight menuButton ? (menuButton.top - statusBarHeight) * 2 menuButton.height : navBarContentHeight return { statusBarHeight, navBarHeight, totalHeight: statusBarHeight navBarHeight } }这段代码背后的逻辑是微信提供了uni.getMenuButtonBoundingClientRect()来获取胶囊按钮的几何信息胶囊按钮的 top 值就是状态栏下沿到它的距离所以导航栏的完整高度 (menuButton.top - statusBarHeight) * 2 menuButton.height。这是最精确、最能适配不同机型的方案。如果你的导航栏用的是自定义组件记得给页面的占位容器留够高度padding-top: totalHeight px否则内容会被导航栏盖住。这个适配问题只有真机才能看出来模拟器上是正常的别问我怎么知道的。3.2 分包加载与 2MB 包体上限“source size 2612kb exceed max limit 2mb”这应该是即将上线的同学都会遇到的一个坎。微信小程序主包上限是 2MB超了就直接编译失败。你有三个选择压缩图片、删除无用组件、分包。我的策略是主包只保留 Tabbar 页面和公共组件 / 工具函数所有二级页面全部拆进分包。具体来说pages/ ├── index/ # 首页主包 ├── category/ # 分类页主包 ├── cart/ # 购物车主包 ├── user/ # 个人中心主包 └── subGoods/ # 商品详情、商品列表分包 └── subOrder/ # 订单流程分包在pages.json里这样配置{ pages: [ pages/index/index, pages/category/category, pages/cart/cart, pages/user/user ], subPackages: [ { root: subGoods, pages: [ goods/detail, goods/list, search/index ] }, { root: subOrder, pages: [ order/confirm, order/list, order/detail ] } ] }但要注意分包有一个隐藏很深的地雷分包之间的公共代码不能共享主包里的不可用部分。比如subGoods里用到了一个组件而这个组件又依赖了pages/user里的函数编译时会报错。所以分包后一定要做一次全面的编译检查尤其是那些代码中写死的相对路径引用。还有一个很常见的包体积超限原因是uView Plus 组件库全量引入了。如果用的是 easycom 自动按需引入方式这个问题不太存在但如果你在main.js里做app.use(uviewPlus)全量注册体积会膨胀得离谱。建议改成easycom自动引入配合uni_modules的按需机制包体积能降 30% 以上。3.3 跨域调试与本地环境配置“uniapp如何配置跨域”也是一个高频问题。说实话小程序不像浏览器有跨域限制但它在开发工具有一个合法域名的校验机制。默认情况下uni.request请求的域名必须在小程序后台配置过合法域名否则请求直接被拦截。开发阶段的解法有两层最快捷的做法在微信开发者工具右上角“详情 → 本地设置”里勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。这样本地就能请求任何 HTTPS 接口。但这个方法只在开发工具里有效真机调试还是会拦。标准做法在小程序后台的“开发管理 → 开发设置 → 服务器域名”里把 request 合法域名配置成你的后端域名。注意必须是 HTTPS且不能带路径不能有 IP除非是 localhost。如果后端同时要支持 H5 端那就要在开发时配置 devServer 代理。我用的是 HBuilderXmanifest.json里配一个h5选项的devServer.proxy{ h5: { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } } }这里再提醒一句开发工具勾选了“不校验合法域名”后真机预览还是经常请求失败。原因是真机跑的是正式逻辑不走开发工具的豁免。所以最稳妥的路径是尽早把后端域名申请成 HTTPS并且在微信后台配好合法域名然后真机调试直接用正式域名。本地开发环境如果需要请求内网测试域名可以用 vConsole 看报错信息来判断是跨域拦截还是服务端 5xx。3.4 日志不打印与调试技巧“uniapp 不打印日志信息”这个问题我排查过整整一个下午。最终原因分三类console.log 被生产环境过滤掉如果代码里用了if (process.env.NODE_ENV production)包裹日志或者项目里配置了 eslint 的 no-console 规则并且编译插件是生产模式日志就不会输出。解决办法是封装一个logger.js只在开发环境打印生产环境用上报替代。小程序开发者工具的 vConsole 没开真机调试时手机上需要打开右下角的 vConsole 气泡才能看到 console。有时候你手机上没有 vConsole 按钮是因为小程序基础库版本低或者开启了“不显示 vConsole”开关。日志被用户等级过滤微信开发者工具的 console 面板默认会有等级过滤如果你点的是 Error而你的 console.log 是 Log自然看不到。我最终的方案是开发环境统一用封装的 logger带上模块前缀比如[cart] xxx这样在开发者工具的控制台里能通过关键词筛出自己的日志。生产环境则把关键路径登录、下单、支付回调的日志上报到服务端用于排查真实用户的问题。4. 常见问题与排查技巧实录4.1 uView Plus 插件市场导入失败uView Plus 是 UniApp 生态里最火的组件库但插件市场导入失败的问题非常普遍。我遇到过两种情况版本兼容问题uView Plus 有两个主版本分别适配 Vue2 和 Vue3。如果你项目是 Vue3 Vite必须装uview-plus并且要手动在main.js引入样式文件import uview-plus/index.scss。这个样式文件漏引入了组件会渲染出一堆无样式的裸 DOM看起来就像组件库没生效。easycom 规则冲突项目如果手动配置了easycom要保证uview-plus的组件命名空间没被覆盖。检查pages.json里是否有多余的 easycom 配置或uni.scss里是否有同名 class。我的建议是不要从插件市场直接下载依赖包到项目里而是通过 HBuilderX 的“插件市场 → 导入插件”方式安装并且严格核对版本描述。如果是 CLI 创建的 uni-app 项目则用 npm 安装uview-plus然后在pages.json配置 easycom。4.2 打包上架与隐私权限“uniapp上架安卓应用市场”这个热搜词说明很多人卡在了上架环节。实际上微信小程序和安卓应用市场是两回事小程序上架在微信后台不需要签名如果是 App 端打包上架各大安卓市场则需要处理权限申请、隐私声明和签名文件。先说小程序端提交审核时微信会检查你有未使用的隐私接口。如果你代码里声明了scope.userLocation定位权限但实际没用定位功能会被拒绝。解决方案是检查pages.json里的permission配置以及manifest.json里 App 模块配置只保留真正用到的权限。另外一个高发的点是测试号发布审核时无法使用真机支付。微信支付的审核要求“具备支付功能的小程序必须完成微信认证”如果主体没认证支付功能在审核环境会报invalid pay params。很多人第一次提审都被这个坑过解决方案是提前完成微信认证并在小程序后台配置号支付商户号。4.3 Tabbar 被输入法顶起“uniapp tabbar输入法顶起”是个比较冷门的问题。出现场景是用户在进行搜索时键盘弹起将页面底部的内容往上顶包括 tabbar。在微信小程序里输入框聚焦时键盘默认会挤压页面tabbar 有时也会跟着被顶起来。解决方案是在pages.json对应的搜索页里设置自定义 tabbar或者给页面开启disableScroll: true然后改用uni.pageScrollTo来做滚动。其实我最终是这么解决的把搜索这个动作放到一个单独的页面这个页面不需要关心 tabbar直接用一个绝对定位的底部搜索按钮而不是挂在全局 tabbar 上。这样既符合交互习惯又彻底避开了输入法顶起 tabbar 的问题。4.4 页面生命周期与 onShow 的坑页面列表的“加载更多”如果没有处理好还有另一个隐藏坑从商品详情返回列表页时列表的状态被重置了。因为页面在onUnload时会销毁onHide时不会销毁但你如果用script setup里的普通变量存列表数据返回时组件会被重新初始化。这时候正确的做法是列表数据用页面级全局变量跨生命周期存储或者使用 Pinia / 页面栈缓存。我采用了 Pinia 存商品列表的原始数据再在onShow里判断当前是“首次进入”还是“从详情返回”如果是后者直接展示缓存数据而不重新请求。这样做的最终效果是用户从列表点了某个商品看了详情返回列表时列表停留位置和滚动条高度不会丢失而且不会触发重新加载。在电商场景里这个体验细节对留存数据的影响很直接。5. 经验总结与后续扩展整个项目做下来我最大的感受是小程序商城的技术难度往往不在“写代码”本身而在“边界处理”。边界指的是不同机型的适配、网络异常的状态恢复、支付结果的确认、登录态的刷新。这些场景的代码量不大但如果没有提前设计好后期会消耗大量的修复时间。有几个具体建议送给准备动手的同学登录态一定要设计成可刷新的token 过期不能只清缓存要静默重新走一遍 code2session 流程。购物车和订单的状态变化不能只靠前端要以服务端返回为准同时做一个本地补偿机制。包体积是最容易被忽视的拦路虎建项目第一天就要把图片压缩、未使用组件的排查、分包规划定成规范而不是最后几天才做。德克蓝牙定位、视频播放、息屏播报这些非核心需求不要自己做优先找成熟的 uni_modules 插件哪怕付费也是划算的因为它帮你省的是最不该浪费的业务时间。如果这个项目还要继续做下去我的优先级排序是先接入埋点统计把用户从首页到下单的全链路数据打通然后做消息订阅在订单状态变更时给用户推送通知最后才是上新促销活动因为有了数据底座活动带来的效果才能被量化。最后再分享一个我从这个项目里提炼出来的小技巧每个页面都要在onUnload里取消未完成的网络请求。用uni.request包一层请求管理器页面销毁时统一abort。这个习惯能显著减少页面切换时的卡顿尤其是微信小程序这种单页容器页面残留请求多了内存涨得飞快最终用户感受到的就是卡顿和杀进程。小细节做到位了整个项目的质感就上来了。
返回列表