尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python+uni-app打造微信小程序电脑配件商城与组装机配置器

Python+uni-app打造微信小程序电脑配件商城与组装机配置器 做这个 Python uni-app 组合的微信小程序「电脑配件商城组装机配置」起因特别朴素身边想自己动手装机的人不少但真正能看懂插槽、功耗、板型的没几个。有人在电商平台把 CPU 和主板都加进购物车了最后发现插槽对不上只能退货有人机箱买小了显卡塞不进去。我当时的思路很直接——把商城和组装机配置器放进同一个微信小程序让用户像拼积木一样选配件系统实时告诉你哪里不兼容、功耗够不够、总价多少选完直接下单后端用 Python 提供接口前端用 uni-app 一次性覆盖微信小程序和后续的 H5、App。这篇文章把整个实现过程拆开讲包括后端接口设计、配件数据建模、兼容性校验算法、uni-app 页面交互以及微信登录、支付、上线审核中的各种坑适合正在做电商或工具类小程序的开发者参考。1. 这个项目到底要解决什么问题1.1 不只是商品列表组装机配置器才是核心如果只做一个普通的电脑配件商城完全没有必要单独写一篇总结——无非商品列表、详情、购物车那一套。真正让这个项目有门槛的是组装机配置器用户需要在 CPU、主板、内存、显卡、硬盘、电源、机箱、散热器八个品类里分别挑选一件系统要能判断这一整套方案是否装得上、带得动并实时给出总价和功耗建议。装机踩坑的场景其实就那几类我归纳下来有CPU 和主板的插槽不匹配比如 LGA1700 的 CPU 配了 AM4 的主板内存代数不匹配DDR4 主板插了 DDR5 内存机箱太小显卡太长塞不进去或者电源位放不下电源瓦数不够整机满载以后直接重启保护风冷散热器太高盖上侧板之后顶到机箱。这些问题对一个懂行的 DIY 玩家来说不算事但对刚入门的人就是致命伤。配置器要做的就是把这些规则变成代码在用户选件的过程中实时反馈让不懂装机的人也能大胆下单。所以从功能优先级上我把配置器排在最前面普通商城排在后面。1.2 目标用户与使用场景拆解这个项目的用户画像有三种对应不同的功能设计第一类是装机小白占比最大。他们的诉求是我预算 6000 块帮我配一台能玩主流网游的主机。我给配置器设计了两种入口一是完全手动从头选八件二是在推荐配置单基础上微调比如先选一个 5000 元整机方案再在里面换一块更好的显卡系统会重新校验兼容性并刷新总价。第二类是懂行的进阶玩家他们知道自己要什么更需要的是查漏补缺。这类用户看重配件详情页有没有写清楚插槽、供电、散热限高这些参数所以细节页的信息层级一定要清楚。第三类是运营管理员他们的需求也比较明确调整配件价格、上下架、维护推荐配置单。这部分我做了个简单的 API 给后台用没有做独立的后台页面数据维护直接通过接口完成。明确了这三类场景以后技术选型和数据建模才有方向不然很容易做成一个看起来功能齐全、实际用不起来的玩具。2. 技术栈选择为什么是 Python FastAPI为什么偏偏 uni-app2.1 后端选型的真实原因后端我选了 Python 和 FastAPI。有人会问前后端分离的商城项目为什么不用 Java 或者 Go答案很实际团队里 Python 最熟FastAPI 写 CRUD 和校验逻辑特别快而且自带基于 OpenAPI 的接口文档小程序端对接接口的时候直接看文档就能把参数对清楚。FastAPI 的几个特性在这个项目里很实用基于 Pydantic 的请求参数校验前端传过来一个非法的 part_ids 列表会直接被拦不用自己写一堆 if异步接口支持配合 asyncpg 查数据库高并发下单场景不至于太难看自动生成 Swagger 文档联调的时候前端同学直接访问 /docs 就能试接口。有人可能觉得 FastAPI 在国内生态不如 Django 好但纯 API 项目真的不需要 Django 那么重的全家桶。我在项目里只用到 FastAPI SQLAlchemy Pydantic路由自动前缀统一挂在 /api 下错误码统一返回{code: 400, message: xxx}结构后续接 H5 端、管理后台接口也完全兼容。2.2 uni-app 解决多端复用问题前端选 uni-app 的理由更直接微信小程序、H5、安卓 App 三端同一套代码。uni-app 底层虽然还是编译到各端但 Vue 3 的语法和生命周期在小程序端是完整保留的开发体验比直接用原生小程序写舒服很多。尤其是这个项目未来可能有商家端 App 的需求用 uni-app 走一遍就能省掉一个独立团队。创建项目的时候我特意选了 Vue 3 TypeScript 模板。TypeScript 在配件规格对象这种结构不固定的场景下非常有用能给 specs 定义一个联合类型写代码的时候字段提示不会丢。当然选择 uni-app 也有代价官方组件体系跟微信原生组件不完全一致一些高级能力比如虚拟列表要等编译结果出来在开发者工具里实测不能只看编译产物。这个放到后面踩坑的部分细说。2.3 工程目录前后端分离的仓库长什么样操作层面我是两个仓库并行维护的前端 uni-app 工程由 HBuilderX 或 CLI 创建后端是独立的 Python 工程。uni-app 部分我的目录是这样组织的uniapp-mall/ ├── src/ │ ├── pages/ # 页面 │ │ ├── home/index.vue # 首页 │ │ ├── parts/list.vue # 配件列表 │ │ ├── parts/detail.vue # 配件详情 │ │ ├── configurator/index.vue # 组装机配置器 │ │ ├── cart/index.vue # 购物车 │ │ ├── order/confirm.vue # 确认订单 │ │ ├── order/list.vue # 订单列表 │ │ └── user/index.vue # 我的 │ ├── api/ # 接口封装 │ ├── components/ # 公共组件 │ ├── stores/ # Pinia 状态管理 │ ├── utils/request.ts # 请求封装 │ ├── App.vue │ ├── main.ts │ ├── manifest.json # 微信 AppID 等配置 │ ├── pages.json # 页面路由与导航栏配置 │ └── uni.scss └── package.json后端 Python 部分python-server/ ├── app/ │ ├── main.py # FastAPI 入口 │ ├── routers/ │ │ ├── auth.py # 微信登录 │ │ ├── parts.py # 配件列表/详情 │ │ ├── config.py # 组装机配置校验 │ │ ├── cart.py │ │ └── order.py │ ├── models/ # SQLAlchemy 模型 │ ├── schemas/ # Pydantic 请求/响应模型 │ ├── services/ │ │ ├── compatibility.py # 兼容性校验引擎 │ │ └── wechat.py # 微信接口封装 │ └── core/ # 配置、数据库连接 └── requirements.txt后端跑起来以后用 uvicorn 启动本地开发uvicorn app.main:app --reload --port 8000小程序端在开发者工具里把不校验合法域名勾上就能直接连本地调试。这一步对 uni-app 项目是通用的不需要额外配置代理。3. 配件数据建模零配件怎么存才不会乱3.1 一张统一的配件表 规格 JSON配件数据建模是这类项目最容易翻车的地方。八类配件的规格差异极大CPU 有插槽、核心数、TDP显卡有长度、功耗、显存机箱有板型支持、显卡限长、散热限高。如果给每个品类建一张表字段会膨胀到不可维护如果全塞进一张万能表又会因为字段互相冲突失去意义。我最终的方案是一张统一的 parts 表存储所有通用字段再用一个 JSON 字段 specs 存储品类差异化规格。CREATE TABLE part ( id INTEGER PRIMARY KEY AUTOINCREMENT, category TEXT NOT NULL, -- cpu / motherboard / gpu / memory / storage / psu / case / cooler name TEXT NOT NULL, brand TEXT, model TEXT, price NUMERIC NOT NULL DEFAULT 0, stock INTEGER NOT NULL DEFAULT 0, cover TEXT, -- 封面图 URL specs TEXT NOT NULL DEFAULT {}, -- 差异化的规格 JSON is_on_sale INTEGER NOT NULL DEFAULT 1, created_at TEXT DEFAULT (datetime(now)) );这是一个先牺牲一点查询性能、换开发效率的方案。specs 字段里存什么、校验的时候怎么取值在 Python 的 Pydantic 模型里定义清楚每个品类一组字段。比如 CPU 的 specs{ socket: LGA1700, tdp: 125, cores: 16, threads: 24, base_clock: 3.4GHz, integrated_gpu: true }显卡的 specs{ length_mm: 337, power_w: 320, recommended_psu_w: 750, vram: 12, interface: PCIe 4.0 x16 }用 JSON 而不是拆成多张表的核心原因是写代码时心智负担小新增一个品类只需要扩展 schemas不需要动数据库表结构。上线以后要加水冷散热器加的只是 specs 的枚举值这是我认为最适合中小型商城项目的数据组织方式。3.2 兼容性字段把八类配件统一到可计算的维度数据建模不只是把字段存下来更重要的是让这些字段能被兼容性引擎计算。我从八类配件里提炼出一组兼容维度作为 specs 中必填的公共约定品类关键兼容字段作用CPUsocket, tdp匹配主板插槽、参与功耗估算主板socket, form_factor, ram_type匹配 CPU、内存机箱板型支持内存ram_type, capacity, speed匹配主板代数提示是否超频显卡length_mm, power_w, recommended_psu_w匹配机箱显卡限长、电源功率电源wattage, psu_form_factor匹配机箱电源位、整机功耗机箱support_form_factors, max_gpu_length, cooler_height板型、显卡限长、散热限高散热器socket_list, height_mm, radiator_size匹配 CPU 插槽、机箱限高硬盘interface, form_factor(2.5/3.5/M.2)匹配主板接口预留校验规则这些字段不是拍脑袋定的而是对照真实装机经验整理出来的。以机箱为例support_form_factors 用数组存储比如[ATX, mATX, ITX]这样中塔机箱可以兼容多种主板校验规则里只要有交集就算通过。3.3 推荐配置单给配置器提供起点配置器如果每次都是空白的八个占位符小白用户会很茫然。所以我在数据层预置了三档推荐配置单3000 元办公机、6000 元网游机、10000 元生产力机。每张配置单本质上就是一个预设的 part_ids 数组前端进入配置器时如果没选过任何配件默认加载推荐配置单并触发一次完整校验。这个设计对后续运营也很友好电商活动期间想推某个型号的 CPU只需要改推荐配置单不需要改前端代码。同时推荐配置单的存在也降低了用户的决策成本配置器的主页不是一堆待选择而是一套完整的、马上能下单的方案用户改任意一件系统重新校验并报价。4. 兼容性校验引擎组装机配置的灵魂4.1 五条核心校验规则的拆解兼容性校验是整个项目最不能糊弄的部分。规则设计得不好用户要么被错误拦截要么被骗着下单然后又退货。我把校验拆成了 error 和 warning 两级error 表示绝对不能装warning 表示能装但不建议比如电源余量不足就是 warning 而不是拦截。规则一CPU 插槽与主板插槽必须一致。Intel 的 LGA1700 不能插在 AM4 主板上这是最常见的死局必须 error。规则二内存代数必须和主板支持一致。DDR5 内存插不进 DDR4 插槽这个也是 error。规则三主板板型必须能被机箱支持。支撑的逻辑是判断主板 form_factor 是否在机箱 support_form_factors 列表里只要有一个交集就放行。比如 mATX 主板放进支持 ATX 和 mATX 的中塔机箱完全没问题。规则四显卡长度不能超过机箱限长。显卡长度在 specs 里有 length_mm机箱限长是 max_gpu_length超了就 error。这个坑非常隐蔽很多玩家买显卡的时候只盯着性能完全没想过机箱塞不塞得下。规则五散热器必须支持当前 CPU 插槽高度不能超过机箱限高。这里风冷和水冷要分开判断风冷看 socket_list 和 height_mm水冷看散热器尺寸比如 240 冷排是否被机箱支持。简化处理时我只校验了 socket 和 height水冷机箱兼容性放在 warning 级别避免规则太严格把一些特殊玩法挡在门外。4.2 校验接口的实现与返回结构校验逻辑放在后端的好处是规则更新不用发小程序版本。前端每改一个配件把当前八个部位的 part_ids 打包 POST 到/api/config/check后端返回完整校验报告。核心引擎代码简化后长这样class CompatibilityEngine: def __init__(self, parts: dict): self.parts parts # key: category, value: part dict self.issues [] def check(self): self.check_cpu_motherboard() self.check_ram_motherboard() self.check_motherboard_case() self.check_gpu_case() self.check_cooler() self.check_power() return { compatible: not any(i[level] error for i in self.issues), issues: self.issues, total_price: self.total_price(), total_power: self.total_power(), }单个规则的示例def check_cpu_motherboard(self): cpu self.parts.get(cpu) mb self.parts.get(motherboard) if not cpu or not mb: return cpu_socket cpu[specs].get(socket) mb_socket mb[specs].get(socket) if cpu_socket ! mb_socket: self.issues.append({ level: error, message: fCPU 插槽 {cpu_socket} 与主板插槽 {mb_socket} 不匹配, })FastAPI 接口层只做参数校验和数据装配真正的业务逻辑收敛在 engine 里app.post(/api/config/check) async def check_config(req: ConfigCheckRequest): parts await get_parts_by_ids(req.part_ids) part_map {p.category: p for p in parts} engine CompatibilityEngine(part_map) return engine.check()这样做的好处是接口特别薄以后如果要支持 H5 端或者 App 端复用同一个 service 即可。前端拿到报告以后把 issues 列表渲染在配置器底部error 红色标出warning 黄色提示用户改到兼容之后实时刷新。4.3 电源功率估算别让用户配出开机重启机电源校验其实分两层一是电源是否放得进机箱二是瓦数够不够。瓦数估算我采用累加 TDP 余量系数的方式这也是很多 DIY 玩家手算的简化版整机估算功耗 CPU TDP 显卡实际功耗 固定 50W主板、风扇、内存、硬盘建议电源瓦数 估算功耗 × 1.2 到 1.5 之间取整比如 CPU 125W、显卡 320W估算功耗就是 125 320 50 495W乘以 1.2 得到 594W那么建议至少上 650W 电源。如果用户选了 550W 电源就给出 warning不是不能用但长期满载不稳定而且未来想升级显卡就没有余量了。这里值得强调的是recommended_psu_w这个字段是厂商写进显卡规格的推荐值它通常比单纯按 TDP 算出来的结果更保守所以我的规则是两者取较大值来判断def check_power(self): est self.total_power() psu self.parts.get(psu) if not psu: return wattage psu[specs].get(wattage, 0) suggested max(est * 1.2, self.parts[gpu][specs].get(recommended_psu_w, 0)) if wattage suggested: self.issues.append({ level: warning, message: f电源额定 {wattage}W 偏低建议至少 {int(suggested)}W })总功耗计算也要在返回结构里带上配置器界面底部直接展示整机满载功耗约 xxx W让用户对这套配置的定位有个直观感受。5. uni-app 端配置器与商城页面的落地细节5.1 页面路由与导航栏配置uni-app 的小程序端页面注册在 pages.json 里导航栏标题、下拉刷新这些都能直接配置不用写原生页面代码。我这个项目的页面清单如下{ pages: [ { path: pages/home/index, style: { navigationBarTitleText: 首页 } }, { path: pages/parts/list, style: { navigationBarTitleText: 配件列表 } }, { path: pages/parts/detail, style: { navigationBarTitleText: 配件详情 } }, { path: pages/configurator/index, style: { navigationBarTitleText: 组装机配置 } }, { path: pages/cart/index, style: { navigationBarTitleText: 购物车 } }, { path: pages/order/confirm, style: { navigationBarTitleText: 确认订单 } }, { path: pages/order/list, style: { navigationBarTitleText: 我的订单 } }, { path: pages/user/index, style: { navigationBarTitleText: 我的 } } ], globalStyle: { navigationBarTextStyle: black, navigationBarBackgroundColor: #FFFFFF, backgroundColor: #F5F6F8 } }这里提醒一个细节如果配置器页面想用自定义导航栏来显示实时总价需要在页面 style 里设置navigationStyle: custom然后自己用uni.getSystemInfoSync()获取状态栏高度把页面内的自定义头部往下顶。这个坑在 uni-app 微信小程序端尤其明显——顶部导航栏高度在不同机型上不一致直接用官方默认导航栏反而最省事。5.2 配置器的主页面状态管理配置器页面是核心交互区我把八个部位的选中状态放进 reactive 对象里每个部位对应一个配件对象或 nullconst selectedParts reactiveRecordPartCategory, PartItem | null({ cpu: null, motherboard: null, memory: null, gpu: null, storage: null, psu: null, case: null, cooler: null }); const report reactiveCheckReport({ compatible: true, issues: [], totalPrice: 0, totalPower: 0 });页面上渲染一个部位清单每个部位一行左侧是品类名右侧是已选配件名或者去选择按钮view classpart-row v-for(cat, i) in partCategories :keycat text classpart-cat{{ catName[cat] }}/text view classpart-value tapopenPicker(cat) text v-ifselectedParts[cat]{{ selectedParts[cat].name }}/text text v-else classplaceholder去选择/text /view /view点击去选择后不是跳页面而是打开一个底部弹层弹层里请求/api/parts?categoryxxx获取该品类配件列表支持搜索和按价格排序。选择后把结果赋给 selectedParts马上调用checkConfig()。核心的校验调用逻辑async function checkConfig() { const partIds partCategories .map((cat) selectedParts[cat]?.id) .filter(Boolean); const res await checkBuild({ part_ids: partIds }); report.compatible res.compatible; report.issues res.issues; report.totalPrice res.total_price; report.totalPower res.total_power; }页面底部固定一个总价 整机功耗 加入购物车的操作栏。总价实时刷新、兼容性 error 存在时禁用下单按钮。这一步看起来简单但对体验影响极大用户每换一个配件反馈必须在一秒内出现否则就会觉得自己在做选择题而不是在买电脑。5.3 请求封装与登录态的公共处理小程序端的接口请求我用一个 Promise 包装过的 request 函数统一管理utils/request.tsconst BASE_URL https://api.example.com; export function requestT(options: { url: string; method?: GET | POST; data?: any; }): PromiseT { return new Promise((resolve, reject) { uni.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: Bearer ${uni.getStorageSync(token) || } }, success: (res) { const body res.data as ApiResponseT; if (body.code 0) { resolve(body.data); } else { uni.showToast({ title: body.message, icon: none }); reject(body); } }, fail: (err) reject(err) }); }); }登录态的处理逻辑是首次进入小程序uni.login()拿到 code后端用 code 换 openid 后再签发自己系统的 token前端把 token 存到uni.setStorageSync。每次请求在 header 里带 token。这样小程序退出重进之后token 还留在本地不需要每次都重新登录。配件列表页我用了分页加载每次请求 20 条滚动到底部自动加载下一页。这里的参数是 page 和 page_size响应结构统一为{ list, total, page }这样首页推荐位、配置器弹层、列表页可以共用同一组接口。6. 微信小程序登录、支付与上线从能跑到能上线6.1 code 换 openid 的登录流程微信小程序不能像传统网站那样用账号密码登录唯一可信的标识是微信的 openid。流程是前端先用uni.login()拿到临时 code传给后端后端用 code 去微信接口换 openid再拿 openid 去自己数据库里找对应用户没有就注册一个最后签发自己的 token。后端关键代码import requests WX_APPID 你的AppID WX_SECRET 你的AppSecret def wx_code2session(code: str) - dict: resp requests.get( https://api.weixin.qq.com/sns/jscode2session, params{ appid: WX_APPID, secret: WX_SECRET, js_code: code, grant_type: authorization_code, }, timeout5, ) data resp.json() if openid not in data: raise ValueError(f微信登录失败: {data}) return data注意code2session 的 session_key 是微信用来解密手机号等敏感信息的密钥不是用来维持登录态的。正确的做法是后端把 openid 映射到自己的用户表生成 JWT token 返回给前端。token 过期时间我设的是 7 天配合小程序长期不活跃重新登录的逻辑就够用了。获取手机号是另一个独立能力需要通过button open-typegetPhoneNumber触发获取的 code 传给后端后再调微信接口解密。这里有个很重要的限制条件小程序必须是已认证的非个人主体个人主体小程序拿不到这个权限。项目如果只是自用演示可以先不做手机号绑定用 openid 做用户标识足够。6.2 微信支付接入与回调支付是这类商城小程序的标配。微信支付的流程是后端统一下单生成支付参数前端调uni.requestPayment拉起收银台用户付款后微信服务器往你的回调地址推送支付结果。统一下单核心参数JSAPI 支付appid小程序 AppIDmchid商户号description商品描述out_trade_no商户订单号notify_url支付结果回调地址amount金额单位是分。后端收到回调以后必须做验签和解密然后更新订单状态。这个环节最容易出的问题是回调接口没返回微信要求的应答格式导致微信反复重试。回调处理完以后要返回 200 和固定格式的成功报文哪怕业务逻辑处理失败了也要先把报文收到避免重复通知堆积。uni-app 端的调用const payment await request({ url: /api/pay/create, method: POST, data: { orderId: orderId } }); uni.requestPayment({ provider: wxpay, timeStamp: payment.timeStamp, nonceStr: payment.nonceStr, package: payment.packageValue, signType: RSA, paySign: payment.paySign, success: async () { await confirmOrder(orderId); uni.redirectTo({ url: /pages/order/list }); } });这里务必注意支付参数必须由后端生成前端永远不要自己拼签名。我见过有人图省事把商户密钥配到小程序端上线没几天账户就被刷了——这条底线绝不能碰。6.3 域名、认证与审核上线前最后也是最磨人的一关小程序上线前的三大坎认证、合法域名、类目资质。认证方面微信小程序目前个人主体认证免 300 元但功能有诸多限制企业主体认证 300 元/年才能开通微信支付、手机号获取、附近的小程序等能力。这个成本在立项预算里就要算进去不要等到功能做完了才发现个人主体不能接支付。合法域名方面所有uni.request的接口域名必须是 HTTPS并且在小程序后台配置为request 合法域名。域名要求已经备案的国内域名加 SSL 证书。本地开发时可以在微信开发者工具勾选不校验合法域名但真机预览和线上版本必须走正规域名。上线前建议把 API 的 baseURL 从配置文件里抽出来测试环境和生产环境通过环境变量切换。审核方面电商类小程序需要选择正确的服务类目比如电商平台或商家自营并且可能要求提供营业执照、ICP 备案等资质。电脑配件属于 3C 数码类目类目资质审核比较严格。另外涉及在线支付还需要声明虚拟支付或实物商品业务场景。这块我吃过亏——第一次提交审核因为类目选错被驳回改完类目又重新走了两天的审核排队。6.4 一路踩下来的坑挑三个最值得说的第一个坑是 uni-app 在小程序端有时 console.log 不打印。排查代码时一度以为逻辑没执行后来才发现是开发者工具的控制台过滤级别问题或者因为日志量太大被工具丢弃。遇到这种情况我一般优先用console.info打关键断点或者直接在界面上临时渲染一个调试文本比在控制台翻半天高效得多。第二个坑是微信小程序包体大小限制。小程序主包不能超过 2MB否则无法上传。项目里配件图片如果全部放本地包体直接爆炸所以设计之初就要决定所有商品图放 CDN本地只放默认占位图和少量 icon。图片路径在数据库里存完整 URL开发环境可以用自己的测试图床生产环境必须换正式 CDN 并配置好防盗链。第三个坑是配置器里购物车的数据结构。一个配置单包含八个配件如果按普通商品购物车那样一行一个商品存下单时很难聚合。我把购物车条目设计成配置单和单品两种类型配置单是一个 JSON 数组存储整套 part_ids下单时按配置单整体校验和扣库存。这样既支持整套方案直接下单也保留了单独买个散热器的灵活性。这个项目从立项到跑通全流程前后花了一个多月大部分时间不是花在写代码上而是花在规则定义和边界情况上。比如不同品牌电源的瓦数标注方式不一样有些是峰值有些是额定机箱参数页写的显卡限长有的含线材余量、有的不含。程序员不能替用户做决定我们能做的就是把规则和提示写得尽量清楚把 error 与 warning 分开让用户理解为什么不行以及怎么改就行。最后分享一个亲测有效的经验兼容性校验规则先用推荐配置单做单元测试数据源。我手写了二十多套真实配置从 2000 元亮机卡配置到 15000 元 4090 顶配每套都用手工判断一遍兼容性再拿引擎跑一遍两边结果对不上的地方基本就是规则该修的 bug。把这个步骤纳入发布流程之后配置器上线以后几乎没有用户反馈过能装却不能下单的错判。如果你也在做这类带规则引擎的项目这个笨办法值得一试。
返回列表