这篇想聊聊我最近用UniApp做的一个微信小程序:电脑配件推荐小程序。选题背景很简单,现在配电脑的人在CPU、主板、显卡、内存这些品类面前基本是懵的,参数表密密麻麻,价格又天天变,一个普通用户很难判断“5000元预算打游戏”到底应该怎么选。这个小程序就是用来解决这个问题的:用户输入预算、用途和品牌偏好,系统自动从配件库里筛选出合适的CPU、主板、内存、显卡、硬盘、电源和机箱,最后拼成一套可以直接照买的配置单。项目本身也覆盖了UniApp跨端开发、微信小程序上线配置、数据接口设计、推荐逻辑初版实现这些关键环节,适合准备做毕设、想入门小程序开发,或者想了解推荐系统落地过程的人参考。
1. 项目整体设计与思路拆解
1.1 为什么做“电脑配件推荐”小程序
配电脑和买手机不太一样。手机型号少,参数维度相对固定;电脑配件是自由组合,CPU用哪家、主板芯片组对不对得上、显卡功耗和电源余量够不够、内存频率主板支不支持,这些信息对新手来说是叠加态,很难一次选对。再加上每个平台价格波动、促销节点不一样,听朋友推荐一个型号,转头看价格又超预算了,整个决策链条非常长。我用了一段时间笔记梳理常见的配件选择逻辑,发现多数需求可以归纳成“预算+用途+偏好”三个变量,只要给出一套规则化映射,就能快速生成一个合理的方案。
把这个需求做成微信小程序,是因为这种“轻决策”场景非常适合小程序形态。用户不需要下载App,微信里搜一下或者朋友转发一个卡片就能打开,用完即走。同时小程序内置分享能力,配置单可以很方便地发给别人讨论,这在PC DIY人群里是很自然的传播路径。对开发者来说,这也是一个复杂度适中、又能把所有核心流程走完的项目:前端页面、数据表设计、推荐接口、用户收藏、分享,全都涉及到了。
1.2 为什么选择UniApp而不是原生小程序
我在定技术方案之前,先想了一下到底用原生微信小程序还是UniApp。原生开发的好处是工具链官方支持最直接,调试也方便,但问题在于代码绑定在微信生态,后续如果想把同样的功能搬到其他平台,几乎要重新写一遍。UniApp基于Vue语法,写一套代码可以编译到微信小程序、H5、App等多个端,日常开发用到的组件化、状态管理、生命周期这些概念都能无缝迁移过来,对有Vue基础的人来说上手成本很低。
项目里还会用到大量列表页、表单页和详情页,UniApp已经内置了一批基础组件和API封装,比如uni.request、uni.navigateTo、uni.setStorageSync,不用再自己封装一层微信原生api。搭配它配套的云开发能力,连后端服务都可以用云函数来实现,免去自己买服务器、配Nginx、处理HTTPS证书这一堆运维工作,特别适合个人项目和毕设场景。当然UniApp也有自己的限制,比如某些特殊布局需要写条件编译处理不同端,但就这个项目的复杂度来说,这些限制完全可控。
1.3 功能规划与边界控制
很多同学拿到这种题目,第一反应是把功能往大了做:用户系统、社区、帖子、评测文章、价格监控、商家入驻……全堆上来,最后开发周期拖到失控。我的建议是先把MVP边界卡死,只做这四个核心块:首页推荐(预算+用途生成配置单)、分类配件库(按品类浏览筛选)、配件详情(参数+推荐理由+收藏)、配置单分享(生成一张带参数列表的分享图或分享卡片)。
其他功能可以等主流程稳定后再迭代。比如对比功能,虽然很实用,但第一版可以先放一放,等配件详情页和分类列表做好之后,再在最外层加一个“加入对比”入口,改动成本很低。这样做的目的是保证项目在最短时间内能跑通,而不是死在半途。任何推荐类产品,如果连最基础的“输入条件得到结果”链路都没跑顺,后面加再多功能都是空中楼阁。
2. 系统架构与数据设计
2.1 小程序端、服务端与数据源划分
这个项目的整体架构可以拆成三层。表现层就是UniApp编写的小程序端,负责收集用户条件、渲染推荐结果和配件详情;业务层是推荐接口和数据接口,接收前端请求后执行筛选、评分、排序逻辑,返回结构化数据;数据层是配件库、配置单、用户行为这几张核心表。我在实际实现里用的是UniApp生态配套的云开发模式,把接口写成云函数,数据库用云端存储,好处是不用单独搭建后端服务。
如果你更习惯传统模式,也可以用UniApp写前端,后端单独用Node.js或Java起一个服务,数据库用MySQL,前端通过uni.request调用HTTP接口。两条路线对业务逻辑的实现方式是一样的,区别主要在部署和运维成本。答辩时两种方案都能讲清楚选型理由,关键是能说清为什么做这个选择。我更推荐个人项目先走云开发,因为不需要处理域名、证书、备案这些前置条件,可以把精力完全放在产品逻辑上。
2.2 配件数据结构与数据库设计
配件数据是这类项目的核心资产,表结构设计不能太随意。我最初用的是“一张表存所有配件”,字段直接写死核心参数,比如CPU就放核心数、频率,显卡就放显存、功耗,后来发现不同品类的参数完全无法对齐,导致大量列是空值。
后来改成“公共字段 + JSON规格”的模式。公共字段包括分类、品牌、名称、价格、评分、状态、图片,特殊参数统一放在JSON字段里,前端解析后动态渲染。这样既保证了列表页的查询效率,又保留了不同品类参数的灵活性。下面是一份简化后的配件表设计:
CREATE TABLE parts ( id INT AUTO_INCREMENT PRIMARY KEY, category VARCHAR(20) NOT NULL COMMENT 'cpu/gpu/memory/storage/motherboard/psu/case', brand VARCHAR(50) COMMENT '品牌', name VARCHAR(100) NOT NULL COMMENT '商品名称', model VARCHAR(100) COMMENT '型号', specs JSON COMMENT '规格参数,如核心数、频率、显存等', features JSON COMMENT '功能标签,如游戏、渲染、办公', price DECIMAL(10,2) COMMENT '参考价', score DECIMAL(5,2) DEFAULT 0 COMMENT '综合性能分', image_url VARCHAR(255) COMMENT '图片地址', status TINYINT DEFAULT 1 COMMENT '1上架 0下架', created_at DATETIME, updated_at DATETIME ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;相比把所有参数做成独立列,JSON字段的维护成本明显更低。比如今天要新增一种类型的参数校验,前端只需要根据category去解析对应字段即可。唯一需要注意的是,不要把需要频繁参与筛选和排序的字段也放到JSON里。比如价格、评分、分类这些必须保留成普通列,否则每次查询都要先解析JSON,数据库在数据量大时性能会很差。
另外还要准备配置单表、收藏表和用户行为表。配置单表用来存推荐出来的整机方案,通常由主表加明细表组成:主表记录预算档位、用途、生成时间,明细表记录方案里每个类别选了哪个配件。收藏表记录用户收藏的配件,后续做“我的收藏”页时直接用。用户行为表是给后续推荐算法升级用的,比如记录用户看了哪些详情、收藏了哪些品类、停留了多久。这些表不需要一开始全建好,但至少要预留结构,不然后面加功能时又要回改数据库。
2.3 推荐逻辑设计:规则优先,算法其次
推荐逻辑是这个项目的灵魂,也是最容易搞复杂的地方。很多人一提到“推荐”就想到协同过滤、矩阵分解、深度学习,但对一个起步阶段的小程序来说,既没有大量用户行为数据,也没有强大的算力支撑,强行上模型的结果往往是“推荐结果不可解释”且“效果不如简单规则”。
我第一版采用的是规则+评分的混合方式。先把用户输入翻译成一组结构化条件,例如“预算6000元,用途游戏优先,也兼顾办公,品牌方面CPU偏好指定品牌”。然后按品类分别筛选候选配件,用一套评分函数对每个候选打分,最后按分数排序取前几个作为结果。评分维度包括:用途标签匹配度、预算区间匹配度、品牌偏好加成、性价比加成。每个维度的权重可以先用经验值,比如标签匹配占总分30%,预算匹配占40%,品牌偏好占20%,性价比占10%。这套权重不需要一次调好,做完之后找几个真实预算区间测试,不合适再微调即可。
这么设计还有一个好处:推荐理由能讲清楚。在详情页里我会把“为什么推荐这个配件”拆成几行小字,比如“该CPU为6核12线程,性能评分匹配你的游戏需求”“当前价格处于6000元整机预算的合理区间”。用户看到的是可理解的原因,信任感会被明显拉高。这在产品体验上的价值,比一个黑盒算法的准确性要重要得多。
3. 核心功能实现与实操细节
3.1 项目搭建:从创建到跑通小程序
第一步先在HBuilderX这类开发工具里新建一个uni-app项目,模板选择默认模板,运行时选择微信开发者工具。项目创建之后,需要在manifest.json里配置微信小程序的AppID,这个ID需要到微信公众平台用小程序的账号信息生成。如果是个人开发者,选择个人主体即可,注册完成后在开发者工具里导入项目,就能看到页面跑起来。
配置完成后,pages.json是整个项目路由和页面样式的核心。我一般会在这里提前把tabBar配好,首页和“我的”两个页面作为tab页,配件库和详情页用普通页面注册。需要注意的是,页面路径不要随便嵌套太深,微信小程序页面栈最多十层,如果用户连续点了很多详情页,会出现跳转无响应的问题,后期可以用uni.redirectTo代替uni.navigateTo去规避。
项目目录上我习惯分成pages、components、api、utils、static几个目录。pages放页面,components放复用组件比如配件卡片、筛选弹层,api目录统一管理所有后端请求函数,utils放格式化工具和常量。配件分类的枚举、预算档位映射这些常量,我会集中放在utils里,避免在多个页面里重复写死。
3.2 首页推荐:预算、用途与结果展示
首页是用户进入小程序看到的第一屏,也是最核心的转化入口。布局上我会把“帮我选配置”的按钮放在视觉中心,下面放预算选择和用途多选,点击按钮后调推荐接口,再把返回的配置单列表渲染出来。使用场景决定了这个页面不能复杂,尽量让用户在30秒内完成操作。
筛选器部分我用picker和checkbox-group实现,预算档位从4000元到10000元分几档,用途选项包括办公、游戏、设计渲染、编程、影音娱乐。用户选择后,前端把参数组装成一个对象传给接口。
<template> <view class="home"> <view class="hero"> <text class="hero-title">电脑配件推荐</text> <text class="hero-sub">输入预算和用途,生成可照买的配置单</text> </view> <view class="condition-panel"> <picker mode="selector" :range="budgetRanges" @change="onBudgetChange"> <view class="field">{{ budgetRanges[budgetIndex] }}</view> </picker> <checkbox-group @change="onUsageChange"> <label v-for="item in usageOptions" :key="item" class="usage-item"> <checkbox :value="item" :checked="usages.includes(item)" /> {{ item }} </label> </checkbox-group> <button type="primary" :disabled="!selectedBudget" @click="generatePlan"> 生成推荐配置单 </button> </view> <view class="plan-list"> <plan-card v-for="item in plan" :key="item.category" :item="item" @click="goDetail(item)" /> </view> </view> </template>这里有个体验细节:button的disabled状态要随着预算是否选中联动。一开始没选预算时,“生成推荐配置单”按钮是置灰的,用户被引导先做选择,能减少无效请求。结果列表用plan-card子组件渲染,每个卡片展示品类、配件名称、核心参数和价格,点击跳转详情。
推荐接口返回的数据结构建议统一成数组,每一项包含category、partId、partName、price、specs、reason。reason字段是推荐理由,前端直接展示,这样接口和UI之间的耦合度很低,后续调整推荐逻辑完全不需要改前端。
3.3 配件库分类浏览与详情页
配件库页面按品类做tab切换,品类包括CPU、主板、内存、固态硬盘、显卡、电源、机箱等。每个品类下支持价格区间和品牌两个筛选条件,筛选逻辑优先在服务端做,前端只负责传参。接口设计成category、brand、minPrice、maxPrice、page这几个参数,返回分页数据。这里要注意分页不能用scroll-view自己硬算滚动高度,直接让接口返回总页数,前端判断触底后累加页码请求下一页,逻辑更稳。
详情页需要展示的参数比较杂,CPU要看核心线程数和频率,显卡要看核心架构和显存,主板要看芯片组和接口。直接用一组固定字段肯定覆盖不全,所以我在模板里用动态列表渲染specs对象:
<view class="spec-table"> <view v-for="(value, key) in part.specs" :key="key" class="spec-row"> <text class="spec-key">{{ specLabelMap[key] || key }}</text> <text class="spec-value">{{ value }}</text> </view> </view>specLabelMap存放字段名映射,比如coreCount对应“核心数”、threadCount对应“线程数”、memSize对应“显存容量”。如果某个新配件带了没见过的字段,至少还能显示原始key,不会直接把页面撑爆。
详情页下面再放“推荐理由”区域和“收藏”按钮。推荐理由由接口返回,一般是算法打分过程的可读化结果;收藏按钮调收藏接口,成功后用uni.showToast提示。收藏状态要在onShow生命周期里重新拉取,避免用户取消收藏后返回页面还显示旧状态。
3.4 推荐接口的实现:云函数还是自建API
推荐接口是整个系统的核心。如果用云函数,我一般会在云函数目录下建一个recommend目录,里面写主入口和评分函数。伪代码逻辑如下:
// 假设入参结构 const params = { budget: 6000, usage: ['游戏'], brandPref: '指定品牌' } function buildPlan(params) { const categories = ['cpu', 'motherboard', 'memory', 'gpu', 'storage', 'psu', 'case'] const plan = [] for (const category of categories) { const candidates = await db.collection('parts') .where({ category, status: 1 }) .get() const ranked = candidates .map(part => ({ part, score: scorePart(part, params) })) .sort((a, b) => b.score - a.score) plan.push({ category, ...ranked[0] }) } return plan } function scorePart(part, params) { let score = 0 const tagMatched = (part.features || []).filter(tag => params.usage.includes(tag)).length score += tagMatched * 30 const expectRange = getBudgetRange(params.budget, part.category) const mid = (expectRange[0] + expectRange[1]) / 2 if (part.price >= expectRange[0] && part.price <= expectRange[1]) { score += 40 } else { score += Math.max(0, 20 - Math.abs(part.price - mid) / 50) } if (params.brandPref && part.brand === params.brandPref) score += 20 score += (part.score / part.price) * 1000 return score }这段代码看着简单,但已经能把规则和评分串起来了。getBudgetRange的作用是把整机预算按品类的经验占比切开,比如CPU占20%,显卡占30%,内存和固态各占10%,机箱电源加起来15%,剩下的给主板。这个比例是DIY圈常见的经验值,不追求精确,但能让每个品类都在合理的价格区间内选出候选。
如果走自建API,前端用uni.request请求后端地址,后端把同样的逻辑用Node或Java实现一遍。自建API的好处是调试方便、可以用成熟的日志系统,代价是要自己处理域名、HTTPS证书、服务器稳定性这些杂事。对个人项目,我建议第一版直接上云函数,把精力集中在业务逻辑上。
3.5 参数对比与配置单分享
对比功能放在第二版,但结构和逻辑可以提前设计好。对比页面的数据来源是两个配件的specs对象,前端要做的是把两个对象的key取并集,然后逐行展示。比如CPU对比,并集可能是核心数、线程数、基础频率、加速频率、TDP、支持内存类型,两边都没有的字段显示“-”。
function compareParts(a, b) { const keys = new Set([ ...Object.keys(a.specs || {}), ...Object.keys(b.specs || {}) ]) return [...keys].map(key => ({ key, a: a.specs?.[key] || '-', b: b.specs?.[key] || '-' })) }配置单分享是传播的关键。生成分享图最省事的方案是把配置单数据拼成一张长图,用canvas绘制;如果不想自己写绘图逻辑,也可以直接用微信小程序的onShareAppMessage转发一个配置单页面,让别人打开时带上配置单ID,由页面重新拉取数据。前者适合生成漂亮的社区传播图,后者开发成本低,适合MVP先跑通。
分享卡片上的文案最好不要只写配置单三个字,可以动态带上预算档位,比如“6000元游戏配置单”,这样分享到群里时,用户对里面的内容有预判,打开率会更高。
4. 常见问题与踩坑记录
4.1 样式适配与真机渲染差异
UniApp的rpx单位在大多数场景下能自动适配屏幕宽度,但真机和开发者工具的表现偶尔有差异。最典型的是底部iOS横条区域,被tabBar或按钮遮挡,需要在安全区加padding。我处理的时候用了一个比较稳的适配方案:给底部按钮容器加上padding-bottom: env(safe-area-inset-bottom),并且把最小padding设为20rpx,保证非全面屏也有间距。
另一个容易踩的坑是图片尺寸。小程序的image标签默认是300px宽、240px高,如果配件图没有设置mode和固定宽高,列表页会非常乱。我统一在卡片里给图片容器固定宽高,mode设为aspectFit,并加上背景色,这样不同比例的厂商图都能整齐展示。字体方面不建议用太多自定义字体,小程序里加载字体文件会影响首屏,线上项目里我基本都是用系统字体。
4.2 分包加载与首屏性能优化
微信小程序主包大小限制在2MB以内,本地图片一旦放多,很容易超限。我在开发中期就遇到主包膨胀的问题,后来把所有详情页、对比页挪到分包里,静态图片全部改成远程地址,主包体积才降下来。UniApp里配分包很简单,在pages.json里维护subPackages字段:
{ "pages": [ "pages/index/index", "pages/my/my" ], "subPackages": [ { "root": "subpackages/detail", "pages": [ "pages/part-detail", "pages/compare" ] } ] }分包之后,tabBar页面必须留在主包,详情页和对比页进入分包,可以减少用户首次打开时的下载量。配合骨架屏方案,首屏渲染速度和感知流畅度都会有明显提升。骨架屏不用做得很复杂,列表容器先用灰色块占位,数据返回后替换成真实内容即可。
4.3 接口联调、登录态与开发工具的那些坑
开发阶段最常见的报错是“url not in domain list”,这是因为微信小程序要求所有请求域名必须是HTTPS且已经在公众平台配置过。调试时可以在微信开发者工具里勾选“不校验合法域名”,但真机预览时这个选项就不生效了,需要提前把后端域名配置成HTTPS并加入合法域名列表。如果用了云函数,请求的域名是平台自带的,这个坑就基本不存在了。
用户登录也是容易踩坑的点。现在微信对用户头像和昵称的获取规则改过几次,直接调uni.getUserInfo已经不能稳定拿到真实头像昵称了。推荐的做法是:用户主动点击“头像昵称填写”组件,在回调里拿到数据后传给后端保存;openid则通过uni.login拿到code,再由云函数或后端换取。这样既合规,又能保证用户数据是主动授权后拿到的。
另外,WebSocket、分享卡片这些能力在小程序里有各自的启用条件,不要在manifest里盲目开启。以我的经验,能不开的权限尽量不开,等真正需要某个能力时再申请开通,这样不容易在审核环节遇到麻烦。
5. 项目后续扩展与开发建议
5.1 让推荐结果更“整机化”:兼容性校验与功耗估算
第一版推荐按品类独立挑选,最大的隐患是选出来的配件之间可能存在兼容性问题。后续升级时,至少要把主板和CPU的接口匹配、内存类型匹配、电源功率是否满足整机峰值功耗这三项加进去。电源功率可以用“CPU功耗+显卡功耗+其他部件预留100W”的粗算方式,在拿到具体功耗数据后,规则式的校验并不难实现。
兼容性校验的价值不只是降低翻车概率,它还能让推荐结果显得更专业。用户看到“当前主板与所选的CPU接口匹配”“这套配置满载功耗约420W,配650W电源有充足余量”这种说明时,对方案的信任感会明显不同。这也是推荐类产品从“参数匹配”走向“方案顾问”的关键一步。
5.2 数据维护是长期工程
配件库需要持续更新,价格也会波动,不能指望上线后就不管。我的建议是做一个简单的管理端,哪怕是控制台命令或CSV导入都行,让运营人员可以批量更新价格、上下架配件、调整推荐排序。如果没有管理端,至少要保证数据库里有updated_at字段,并定期通过脚本核对价格和库内状态。
另外要给所有推荐结果增加一个“报错”入口,用户发现价格不对或参数有误时可以一键反馈,反馈数据直接写入后台表。这套机制能帮你以很低的成本维护数据质量,比定期手动核对靠谱得多。用户反馈本身就是产品迭代的重要信号,看到某个型号频繁被报错,就能直观判断它是不是该下架了。
5.3 我个人做完这个项目后的几点体会
做完这套东西,我最大的感受是:推荐算法不一定要“聪明”,但一定要“讲得出理由”。在冷启动阶段,规则评分带来的用户理解度远高于黑盒模型,因为用户能在结果里看到逻辑链条。与其急着引入协同过滤,不如先把用户行为埋点做好,把“预算+用途+筛选结果”的数据存下来,等积累了足够的真实行为,再考虑用更复杂的模型去做个性化排序。
最后再分享一个小技巧:所有推荐接口返回的字段里,一定要带上一个version或score_version字段。推荐逻辑调整时,通过version就能判断旧页面上缓存的数据属于哪个规则版本,排查线上问题时这个字段能省大量时间。这也是很多生产环境里常用的做法,做项目时养成这种习惯,后续无论扩展什么功能都会顺手很多。