【微信小程序教程】第20节:项目实战 - 电商小程序实践
🎈 第20节课:项目实战 - 电商小程序
📕引言总结
经过前19节课的系统学习,我们已经掌握了微信小程序开发的全部核心技能,包括基础语法、组件使用、数据绑定、网络请求、云开发等关键技术点。本节课将综合运用所学知识,完成一个完整的电商小程序项目实战。
💻电商小程序是目前最常见的小程序应用场景之一,涵盖了用户注册登录、商品展示、购物车管理、订单处理、支付集成等核心功能模块。
通过本项目实战,你将学会如何设计项目结构、组织代码、处理复杂业务逻辑,以及如何将各个技术点有机结合,最终构建出一个功能完整、用户体验良好的电商应用。
本项目将采用模块化开发思想,按照功能划分页面和组件,使用云开发作为后端服务,实现数据存储、用户管理、订单处理等功能。项目完成后,你将具备独立开发完整小程序项目的能力,能够胜任企业级小程序开发工作。
⚡项目功能模块设计
// 项目结构概览/*├── app.js // 应用入口文件├── app.json // 全局配置├── app.wxss // 全局样式├── cloudfunctions/ // 云函数目录│ ├── login // 登录云函数│ ├── pay // 支付云函数│ └── order // 订单处理云函数├── components/ // 自定义组件│ ├── goods-item // 商品项组件│ ├── cart-item // 购物车项组件│ └── order-item // 订单项组件├── pages/ // 页面目录│ ├── index/ // 首页│ ├── category/ // 分类页│ ├── cart/ // 购物车页│ ├── user/ // 用户中心│ ├── goods/ // 商品详情页│ ├── order/ // 订单确认页│ └── pay/ // 支付页└── utils/ // 工具类 └── request.js // 网络请求封装*/
🎯 项目架构与流程图详解
🧱 项目整体架构图
#mermaid-svg-5gXNm1MDHcGsWZa2 {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-5gXNm1MDHcGsWZa2 .error-icon{fill:#552222;}#mermaid-svg-5gXNm1MDHcGsWZa2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5gXNm1MDHcGsWZa2 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-5gXNm1MDHcGsWZa2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5gXNm1MDHcGsWZa2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5gXNm1MDHcGsWZa2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5gXNm1MDHcGsWZa2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5gXNm1MDHcGsWZa2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5gXNm1MDHcGsWZa2 .marker.cross{stroke:#333333;}#mermaid-svg-5gXNm1MDHcGsWZa2 svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5gXNm1MDHcGsWZa2 .label{font-family:\"trebuchet ms\",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5gXNm1MDHcGsWZa2 .cluster-label text{fill:#333;}#mermaid-svg-5gXNm1MDHcGsWZa2 .cluster-label span{color:#333;}#mermaid-svg-5gXNm1MDHcGsWZa2 .label text,#mermaid-svg-5gXNm1MDHcGsWZa2 span{fill:#333;color:#333;}#mermaid-svg-5gXNm1MDHcGsWZa2 .node rect,#mermaid-svg-5gXNm1MDHcGsWZa2 .node circle,#mermaid-svg-5gXNm1MDHcGsWZa2 .node ellipse,#mermaid-svg-5gXNm1MDHcGsWZa2 .node polygon,#mermaid-svg-5gXNm1MDHcGsWZa2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5gXNm1MDHcGsWZa2 .node .label{text-align:center;}#mermaid-svg-5gXNm1MDHcGsWZa2 .node.clickable{cursor:pointer;}#mermaid-svg-5gXNm1MDHcGsWZa2 .arrowheadPath{fill:#333333;}#mermaid-svg-5gXNm1MDHcGsWZa2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5gXNm1MDHcGsWZa2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5gXNm1MDHcGsWZa2 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-5gXNm1MDHcGsWZa2 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-5gXNm1MDHcGsWZa2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5gXNm1MDHcGsWZa2 .cluster text{fill:#333;}#mermaid-svg-5gXNm1MDHcGsWZa2 .cluster span{color:#333;}#mermaid-svg-5gXNm1MDHcGsWZa2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5gXNm1MDHcGsWZa2 :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}小程序客户端全局配置 app.json全局样式 app.wxss入口文件 app.js云开发环境云函数模块login - 用户登录pay - 支付处理order - 订单管理云数据库用户信息表商品信息表购物车表订单表页面模块index - 首页category - 分类页cart - 购物车user - 用户中心goods - 商品详情order - 订单确认pay - 支付页面自定义组件goods-item - 商品项cart-item - 购物车项order-item - 订单项工具类request.js - 网络请求
🔄 核心业务流程图
✅1. 用户登录流程
#mermaid-svg-RbLbwjXvugCEFstV {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-RbLbwjXvugCEFstV .error-icon{fill:#552222;}#mermaid-svg-RbLbwjXvugCEFstV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RbLbwjXvugCEFstV .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-RbLbwjXvugCEFstV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RbLbwjXvugCEFstV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RbLbwjXvugCEFstV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RbLbwjXvugCEFstV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RbLbwjXvugCEFstV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RbLbwjXvugCEFstV .marker.cross{stroke:#333333;}#mermaid-svg-RbLbwjXvugCEFstV svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RbLbwjXvugCEFstV .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-RbLbwjXvugCEFstV text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-RbLbwjXvugCEFstV .actor-line{stroke:grey;}#mermaid-svg-RbLbwjXvugCEFstV .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-RbLbwjXvugCEFstV .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-RbLbwjXvugCEFstV #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-RbLbwjXvugCEFstV .sequenceNumber{fill:white;}#mermaid-svg-RbLbwjXvugCEFstV #sequencenumber{fill:#333;}#mermaid-svg-RbLbwjXvugCEFstV #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-RbLbwjXvugCEFstV .messageText{fill:#333;stroke:#333;}#mermaid-svg-RbLbwjXvugCEFstV .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-RbLbwjXvugCEFstV .labelText,#mermaid-svg-RbLbwjXvugCEFstV .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-RbLbwjXvugCEFstV .loopText,#mermaid-svg-RbLbwjXvugCEFstV .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-RbLbwjXvugCEFstV .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-RbLbwjXvugCEFstV .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-RbLbwjXvugCEFstV .noteText,#mermaid-svg-RbLbwjXvugCEFstV .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-RbLbwjXvugCEFstV .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-RbLbwjXvugCEFstV .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-RbLbwjXvugCEFstV .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-RbLbwjXvugCEFstV .actorPopupMenu{position:absolute;}#mermaid-svg-RbLbwjXvugCEFstV .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-RbLbwjXvugCEFstV .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-RbLbwjXvugCEFstV .actor-man circle,#mermaid-svg-RbLbwjXvugCEFstV line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-RbLbwjXvugCEFstV :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}用户客户端云函数(login)云数据库启动小程序调用wx.cloud.callFunction获取openid查询用户信息无用户记录创建新用户返回用户信息alt[用户首次登录][用户已存在]返回登录状态显示用户中心用户客户端云函数(login)云数据库
✅2. 商品浏览流程
#mermaid-svg-TE1mMrWq9dXA7KnG {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-TE1mMrWq9dXA7KnG .error-icon{fill:#552222;}#mermaid-svg-TE1mMrWq9dXA7KnG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TE1mMrWq9dXA7KnG .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-TE1mMrWq9dXA7KnG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TE1mMrWq9dXA7KnG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TE1mMrWq9dXA7KnG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TE1mMrWq9dXA7KnG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TE1mMrWq9dXA7KnG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TE1mMrWq9dXA7KnG .marker.cross{stroke:#333333;}#mermaid-svg-TE1mMrWq9dXA7KnG svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TE1mMrWq9dXA7KnG .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TE1mMrWq9dXA7KnG text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-TE1mMrWq9dXA7KnG .actor-line{stroke:grey;}#mermaid-svg-TE1mMrWq9dXA7KnG .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-TE1mMrWq9dXA7KnG .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-TE1mMrWq9dXA7KnG #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-TE1mMrWq9dXA7KnG .sequenceNumber{fill:white;}#mermaid-svg-TE1mMrWq9dXA7KnG #sequencenumber{fill:#333;}#mermaid-svg-TE1mMrWq9dXA7KnG #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-TE1mMrWq9dXA7KnG .messageText{fill:#333;stroke:#333;}#mermaid-svg-TE1mMrWq9dXA7KnG .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TE1mMrWq9dXA7KnG .labelText,#mermaid-svg-TE1mMrWq9dXA7KnG .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-TE1mMrWq9dXA7KnG .loopText,#mermaid-svg-TE1mMrWq9dXA7KnG .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-TE1mMrWq9dXA7KnG .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-TE1mMrWq9dXA7KnG .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-TE1mMrWq9dXA7KnG .noteText,#mermaid-svg-TE1mMrWq9dXA7KnG .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-TE1mMrWq9dXA7KnG .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TE1mMrWq9dXA7KnG .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TE1mMrWq9dXA7KnG .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TE1mMrWq9dXA7KnG .actorPopupMenu{position:absolute;}#mermaid-svg-TE1mMrWq9dXA7KnG .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-TE1mMrWq9dXA7KnG .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TE1mMrWq9dXA7KnG .actor-man circle,#mermaid-svg-TE1mMrWq9dXA7KnG line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-TE1mMrWq9dXA7KnG :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}用户客户端页面(index/category)组件(goods-item)云函数(goods)进入首页/分类页请求商品列表查询数据库返回商品数据渲染商品项展示商品信息点击商品跳转到详情页用户客户端页面(index/category)组件(goods-item)云函数(goods)
✅3. 购物车操作流程
#mermaid-svg-n46CaCXA79itFzeW {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-n46CaCXA79itFzeW .error-icon{fill:#552222;}#mermaid-svg-n46CaCXA79itFzeW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-n46CaCXA79itFzeW .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-n46CaCXA79itFzeW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-n46CaCXA79itFzeW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-n46CaCXA79itFzeW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-n46CaCXA79itFzeW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-n46CaCXA79itFzeW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-n46CaCXA79itFzeW .marker.cross{stroke:#333333;}#mermaid-svg-n46CaCXA79itFzeW svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-n46CaCXA79itFzeW .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-n46CaCXA79itFzeW text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-n46CaCXA79itFzeW .actor-line{stroke:grey;}#mermaid-svg-n46CaCXA79itFzeW .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-n46CaCXA79itFzeW .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-n46CaCXA79itFzeW #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-n46CaCXA79itFzeW .sequenceNumber{fill:white;}#mermaid-svg-n46CaCXA79itFzeW #sequencenumber{fill:#333;}#mermaid-svg-n46CaCXA79itFzeW #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-n46CaCXA79itFzeW .messageText{fill:#333;stroke:#333;}#mermaid-svg-n46CaCXA79itFzeW .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-n46CaCXA79itFzeW .labelText,#mermaid-svg-n46CaCXA79itFzeW .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-n46CaCXA79itFzeW .loopText,#mermaid-svg-n46CaCXA79itFzeW .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-n46CaCXA79itFzeW .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-n46CaCXA79itFzeW .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-n46CaCXA79itFzeW .noteText,#mermaid-svg-n46CaCXA79itFzeW .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-n46CaCXA79itFzeW .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-n46CaCXA79itFzeW .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-n46CaCXA79itFzeW .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-n46CaCXA79itFzeW .actorPopupMenu{position:absolute;}#mermaid-svg-n46CaCXA79itFzeW .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-n46CaCXA79itFzeW .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-n46CaCXA79itFzeW .actor-man circle,#mermaid-svg-n46CaCXA79itFzeW line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-n46CaCXA79itFzeW :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}用户客户端(cart页面)云函数(cart)云数据库添加商品到购物车调用添加接口查询购物车记录更新数量插入新记录alt[商品已存在][新商品]返回操作结果显示成功提示查看购物车请求购物车列表查询用户购物车返回购物车数据返回给客户端展示购物车列表用户客户端(cart页面)云函数(cart)云数据库
✅4. 订单处理流程
#mermaid-svg-2scNvuCHMcCQQpwl {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-2scNvuCHMcCQQpwl .error-icon{fill:#552222;}#mermaid-svg-2scNvuCHMcCQQpwl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2scNvuCHMcCQQpwl .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-2scNvuCHMcCQQpwl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2scNvuCHMcCQQpwl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2scNvuCHMcCQQpwl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2scNvuCHMcCQQpwl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2scNvuCHMcCQQpwl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2scNvuCHMcCQQpwl .marker.cross{stroke:#333333;}#mermaid-svg-2scNvuCHMcCQQpwl svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2scNvuCHMcCQQpwl .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-2scNvuCHMcCQQpwl text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-2scNvuCHMcCQQpwl .actor-line{stroke:grey;}#mermaid-svg-2scNvuCHMcCQQpwl .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-2scNvuCHMcCQQpwl .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-2scNvuCHMcCQQpwl #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-2scNvuCHMcCQQpwl .sequenceNumber{fill:white;}#mermaid-svg-2scNvuCHMcCQQpwl #sequencenumber{fill:#333;}#mermaid-svg-2scNvuCHMcCQQpwl #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-2scNvuCHMcCQQpwl .messageText{fill:#333;stroke:#333;}#mermaid-svg-2scNvuCHMcCQQpwl .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-2scNvuCHMcCQQpwl .labelText,#mermaid-svg-2scNvuCHMcCQQpwl .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-2scNvuCHMcCQQpwl .loopText,#mermaid-svg-2scNvuCHMcCQQpwl .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-2scNvuCHMcCQQpwl .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-2scNvuCHMcCQQpwl .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-2scNvuCHMcCQQpwl .noteText,#mermaid-svg-2scNvuCHMcCQQpwl .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-2scNvuCHMcCQQpwl .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-2scNvuCHMcCQQpwl .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-2scNvuCHMcCQQpwl .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-2scNvuCHMcCQQpwl .actorPopupMenu{position:absolute;}#mermaid-svg-2scNvuCHMcCQQpwl .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-2scNvuCHMcCQQpwl .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-2scNvuCHMcCQQpwl .actor-man circle,#mermaid-svg-2scNvuCHMcCQQpwl line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-2scNvuCHMcCQQpwl :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}用户客户端(order页面)云函数(order)云数据库支付系统提交订单调用创建订单接口插入订单记录返回订单ID返回订单信息跳转支付页面点击支付调用微信支付显示支付界面完成支付支付结果回调更新订单状态更新订单为已支付确认更新返回支付成功显示支付成功页面用户客户端(order页面)云函数(order)云数据库支付系统
📐 架构原理详解
✅1. 分层架构设计
客户端层(View Layer)
- 页面模块:承载具体业务场景,如首页、商品详情页、购物车等
- 组件模块:封装可复用的UI组件,如商品项、购物车项等
- 全局配置:统一管理页面路由、窗口样式、底部导航等
业务逻辑层(Logic Layer)
- 云函数:处理后端业务逻辑,包括用户认证、订单处理、支付等
- 工具类:提供通用功能封装,如网络请求、数据处理等
数据存储层(Data Layer)
- 云数据库:持久化存储用户信息、商品数据、订单记录等
- 本地缓存:临时存储用户偏好、购物车等非关键数据
✅2. 数据流向原理
#mermaid-svg-Uhbh8FE39viLHpkK {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Uhbh8FE39viLHpkK .error-icon{fill:#552222;}#mermaid-svg-Uhbh8FE39viLHpkK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Uhbh8FE39viLHpkK .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-Uhbh8FE39viLHpkK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Uhbh8FE39viLHpkK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Uhbh8FE39viLHpkK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Uhbh8FE39viLHpkK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Uhbh8FE39viLHpkK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Uhbh8FE39viLHpkK .marker.cross{stroke:#333333;}#mermaid-svg-Uhbh8FE39viLHpkK svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Uhbh8FE39viLHpkK .label{font-family:\"trebuchet ms\",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Uhbh8FE39viLHpkK .cluster-label text{fill:#333;}#mermaid-svg-Uhbh8FE39viLHpkK .cluster-label span{color:#333;}#mermaid-svg-Uhbh8FE39viLHpkK .label text,#mermaid-svg-Uhbh8FE39viLHpkK span{fill:#333;color:#333;}#mermaid-svg-Uhbh8FE39viLHpkK .node rect,#mermaid-svg-Uhbh8FE39viLHpkK .node circle,#mermaid-svg-Uhbh8FE39viLHpkK .node ellipse,#mermaid-svg-Uhbh8FE39viLHpkK .node polygon,#mermaid-svg-Uhbh8FE39viLHpkK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Uhbh8FE39viLHpkK .node .label{text-align:center;}#mermaid-svg-Uhbh8FE39viLHpkK .node.clickable{cursor:pointer;}#mermaid-svg-Uhbh8FE39viLHpkK .arrowheadPath{fill:#333333;}#mermaid-svg-Uhbh8FE39viLHpkK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Uhbh8FE39viLHpkK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Uhbh8FE39viLHpkK .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-Uhbh8FE39viLHpkK .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-Uhbh8FE39viLHpkK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Uhbh8FE39viLHpkK .cluster text{fill:#333;}#mermaid-svg-Uhbh8FE39viLHpkK .cluster span{color:#333;}#mermaid-svg-Uhbh8FE39viLHpkK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Uhbh8FE39viLHpkK :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}用户操作页面逻辑云函数调用数据库操作数据返回页面更新
✅3. 状态管理机制
// 全局状态管理示例// app.jsApp({ globalData: { userInfo: null, cartCount: 0, loginStatus: false }, // 更新全局状态方法 updateUserInfo(userInfo) { this.globalData.userInfo = userInfo; }, updateCartCount(count) { this.globalData.cartCount = count; }})
✅4. 组件化设计原则
#mermaid-svg-oUegeOovLwc65Uoa {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-oUegeOovLwc65Uoa .error-icon{fill:#552222;}#mermaid-svg-oUegeOovLwc65Uoa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oUegeOovLwc65Uoa .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-oUegeOovLwc65Uoa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oUegeOovLwc65Uoa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oUegeOovLwc65Uoa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oUegeOovLwc65Uoa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oUegeOovLwc65Uoa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oUegeOovLwc65Uoa .marker.cross{stroke:#333333;}#mermaid-svg-oUegeOovLwc65Uoa svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oUegeOovLwc65Uoa .label{font-family:\"trebuchet ms\",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oUegeOovLwc65Uoa .cluster-label text{fill:#333;}#mermaid-svg-oUegeOovLwc65Uoa .cluster-label span{color:#333;}#mermaid-svg-oUegeOovLwc65Uoa .label text,#mermaid-svg-oUegeOovLwc65Uoa span{fill:#333;color:#333;}#mermaid-svg-oUegeOovLwc65Uoa .node rect,#mermaid-svg-oUegeOovLwc65Uoa .node circle,#mermaid-svg-oUegeOovLwc65Uoa .node ellipse,#mermaid-svg-oUegeOovLwc65Uoa .node polygon,#mermaid-svg-oUegeOovLwc65Uoa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oUegeOovLwc65Uoa .node .label{text-align:center;}#mermaid-svg-oUegeOovLwc65Uoa .node.clickable{cursor:pointer;}#mermaid-svg-oUegeOovLwc65Uoa .arrowheadPath{fill:#333333;}#mermaid-svg-oUegeOovLwc65Uoa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oUegeOovLwc65Uoa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oUegeOovLwc65Uoa .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-oUegeOovLwc65Uoa .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-oUegeOovLwc65Uoa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oUegeOovLwc65Uoa .cluster text{fill:#333;}#mermaid-svg-oUegeOovLwc65Uoa .cluster span{color:#333;}#mermaid-svg-oUegeOovLwc65Uoa div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oUegeOovLwc65Uoa :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}页面自定义组件属性传入事件回调独立样式生命周期
✅5. 网络请求封装
// utils/request.js 封装原理const request = (options) => { return new Promise((resolve, reject) => { wx.request({ ...options, success: (res) => { // 统一处理响应数据 if (res.statusCode === 200) { resolve(res.data); } else { reject(res); } }, fail: reject }); });};module.exports = { get: (url, data) => request({ url, method: \'GET\', data }), post: (url, data) => request({ url, method: \'POST\', data })};
⚡ 核心技术要点
1. 云开发集成
- 使用
wx.cloud
API 进行云端资源调用 - 通过云函数实现服务端逻辑处理
- 利用云数据库进行数据持久化存储
2. 组件化开发
- 自定义组件实现UI复用
- 通过 properties 实现父子组件通信
- 使用 triggerEvent 实现子父组件通信
3. 状态管理
- 利用
app.js
的globalData
管理全局状态 - 页面间通过
getApp()
获取全局实例 - 本地存储
wx.setStorageSync
缓存临时数据
4. 路由管理
- 使用
wx.navigateTo
进行页面跳转 - 通过
wx.redirectTo
替换当前页面 - 利用
wx.switchTab
切换 tabBar 页面
🤖 这套架构设计充分体现了现代小程序开发的最佳实践,既保证了代码的模块化和可维护性,又充分利用了微信云开发的能力,降低了后端开发的复杂度。
✍ 具体项目实现
✅1. 项目入口与全局配置
文件:app.js
, app.json
, app.wxss
-
app.js
:- 功能:作为小程序的入口文件,负责初始化应用。可以在此处进行全局数据管理、生命周期监听(如
onLaunch
,onShow
)等操作。 - 示例用途:
- 初始化用户登录状态。
- 检查网络状态。
- 加载全局配置或缓存数据。
- 功能:作为小程序的入口文件,负责初始化应用。可以在此处进行全局数据管理、生命周期监听(如
-
app.json
:- 功能:定义小程序的全局配置,包括页面路径、窗口样式、底部导航栏等。
- 关键字段:
pages
:定义所有页面的路径。window
:设置全局窗口样式,如导航栏标题、背景色等。tabBar
:定义底部导航栏的样式和页面切换逻辑。
-
app.wxss
:- 功能:存放全局样式,定义通用的 CSS 样式规则,适用于所有页面。
- 示例用途:
- 定义统一的颜色主题、字体大小、间距等。
- 提供基础样式复用,减少重复代码。
✅2. 云函数模块
目录:cloudfunctions/
云函数是小程序后端逻辑的核心部分,提供了无服务器架构的能力,处理业务逻辑、数据库交互等。
-
login
:- 功能:处理用户登录逻辑,例如生成用户唯一标识(
openid
),绑定用户信息等。 - 典型场景:
- 用户首次登录时,调用云函数获取
openid
并存储到数据库。 - 验证用户身份,返回登录状态。
- 用户首次登录时,调用云函数获取
- 功能:处理用户登录逻辑,例如生成用户唯一标识(
-
pay
:- 功能:封装支付相关逻辑,例如生成支付订单、调用微信支付接口等。
- 典型场景:
- 创建支付订单,返回支付参数给前端。
- 处理支付结果回调,更新订单状态。
-
order
:- 功能:处理订单相关的业务逻辑,包括创建订单、查询订单详情、更新订单状态等。
- 典型场景:
- 用户提交订单时,调用云函数保存订单信息。
- 查询历史订单列表,提供分页、筛选等功能。
✅3. 自定义组件
目录:components/
自定义组件用于封装可复用的 UI 和逻辑模块,提升开发效率和代码可维护性。
-
goods-item
:- 功能:封装商品项的展示逻辑,包括图片、名称、价格等信息。
- 使用场景:
- 在首页推荐商品列表中使用。
- 在分类页、搜索结果页等场景中复用。
-
cart-item
:- 功能:封装购物车项的展示逻辑,包括商品信息、数量选择、删除按钮等。
- 使用场景:
- 在购物车页面中使用,支持商品的增删改操作。
-
order-item
:- 功能:封装订单项的展示逻辑,包括订单编号、商品信息、状态等。
- 使用场景:
- 在订单列表页、订单详情页中使用。
✅4. 页面模块
目录:pages/
页面模块是小程序的主要功能载体,每个页面对应一个具体的业务场景。
-
index/
:- 功能:首页展示核心内容,包括轮播图、分类导航、推荐商品等。
- 典型交互:
- 轮播图自动播放,点击跳转到指定页面。
- 分类导航点击跳转到分类页。
- 推荐商品点击跳转到商品详情页。
-
category/
:- 功能:分类页展示商品分类及对应的商品列表。
- 典型交互:
- 左侧分类列表滚动切换。
- 右侧商品列表展示,点击跳转到商品详情页。
-
cart/
:- 功能:购物车页展示用户已添加的商品,支持选中商品、修改数量、结算等操作。
- 典型交互:
- 勾选商品,计算总价。
- 点击“去结算”跳转到订单确认页。
-
user/
:- 功能:用户中心页展示用户信息、订单列表、收货地址等。
- 典型交互:
- 查看历史订单,点击跳转到订单详情页。
- 编辑收货地址,跳转到地址管理页。
-
goods/
:- 功能:商品详情页展示商品的详细信息,包括图片、规格、价格等。
- 典型交互:
- 选择规格,加入购物车或立即购买。
- 点击“查看更多评价”跳转到评价页。
-
order/
:- 功能:订单确认页展示用户选中的商品、收货地址、总价等信息。
- 典型交互:
- 确认订单信息,提交订单。
- 修改收货地址,跳转到地址选择页。
-
pay/
:- 功能:支付页展示订单支付信息,调用微信支付接口完成支付。
- 典型交互:
- 点击“立即支付”调起微信支付。
- 支付成功后跳转到支付结果页。
✅5. 工具类
目录:utils/
工具类模块提供通用的功能封装,便于复用和维护。
request.js
:- 功能:封装网络请求逻辑,简化 API 调用。
- 典型实现:
const request = (url, method = \'GET\', data = {}) => { return new Promise((resolve, reject) => { wx.request({ url, method, data, success: resolve, fail: reject }); });};module.exports = { get: (url, data) => request(url, \'GET\', data), post: (url, data) => request(url, \'POST\', data)};
- 使用场景:
- 在页面或云函数中调用封装的
get
或post
方法,减少重复代码。
- 在页面或云函数中调用封装的
✅总结
通过上述模块化设计,项目实现了清晰的职责划分和高内聚低耦合的架构特点。各模块的功能如下:
- 入口与全局配置:提供全局初始化和样式支持。
- 云函数:处理后端业务逻辑,确保前后端分离。
- 自定义组件:封装可复用的 UI 和逻辑,提升开发效率。
- 页面模块:承载具体业务场景,提供用户交互。
- 工具类:提供通用功能封装,增强代码复用性。
💡 这种设计不仅提高了代码的可维护性和扩展性,还为后续功能迭代奠定了良好的基础。
🌊核心功能实现示例
🥥1. 首页实现
<view class=\"container\"> <swiper indicator-dots autoplay interval=\"3000\" duration=\"1000\" class=\"banner\"> <swiper-item wx:for=\"{{banners}}\" wx:key=\"index\"> <image src=\"{{item.image}}\" class=\"banner-image\" mode=\"aspectFill\"></image> </swiper-item> </swiper> <view class=\"category-nav\"> <view class=\"nav-item\" wx:for=\"{{categories}}\" wx:key=\"id\" bindtap=\"goCategory\" data-id=\"{{item.id}}\"> <image src=\"{{item.icon}}\" class=\"nav-icon\"></image> <text class=\"nav-text\">{{item.name}}</text> </view> </view> <view class=\"recommend\"> <view class=\"section-title\">推荐商品</view> <view class=\"goods-list\"> <goods-item wx:for=\"{{recommendGoods}}\" wx:key=\"id\" goods=\"{{item}}\" bindtap=\"goGoodsDetail\" data-id=\"{{item.id}}\" /> </view> </view></view>
老曹讲解:
- ✅轮播图:使用了
swiper
和swiper-item
组件来实现轮播效果。indicator-dots
属性用于显示指示点,autoplay
属性设置自动播放,interval
设置自动切换的时间间隔,duration
设置动画时长。wx:for
指令用于遍历banners
数据数组,动态生成轮播图片。 - ✅分类导航:通过
view
和image
组件实现分类导航,wx:for
指令遍历categories
数组,动态生成每个分类的图标和名称。点击事件bindtap
绑定到goCategory
方法,传递data-id
参数用于区分不同分类。 - ✅推荐商品:推荐商品部分通过自定义组件
goods-item
来展示商品信息。wx:for
指令遍历recommendGoods
数组,动态生成商品列表。点击事件bindtap
绑定到goGoodsDetail
方法,传递商品id
。
📚2. 商品详情页实现
// pages/goods/detail.jsPage({ data: { goods: {}, selectedSku: {}, showSkuPopup: false, count: 1 }, onLoad(options) { const goodsId = options.id; this.getGoodsDetail(goodsId); }, // 获取商品详情 async getGoodsDetail(id) { wx.showLoading({ title: \'加载中...\' }); try { const res = await wx.cloud.callFunction({ name: \'goods\', data: { action: \'getDetail\', id: id } }); this.setData({ goods: res.result.data }); } catch (error) { wx.showToast({ title: \'获取商品详情失败\', icon: \'none\' }); } finally { wx.hideLoading(); } }, // 选择规格 selectSku() { this.setData({ showSkuPopup: true }); }, // 加入购物车 async addToCart() { if (!this.data.selectedSku.id) { wx.showToast({ title: \'请选择规格\', icon: \'none\' }); return; } try { await wx.cloud.callFunction({ name: \'cart\', data: { action: \'add\', goodsId: this.data.goods.id, skuId: this.data.selectedSku.id, count: this.data.count } }); wx.showToast({ title: \'已加入购物车\' }); this.setData({ showSkuPopup: false }); } catch (error) { wx.showToast({ title: \'加入购物车失败\', icon: \'none\' }); } }, // 立即购买 buyNow() { if (!this.data.selectedSku.id) { wx.showToast({ title: \'请选择规格\', icon: \'none\' }); return; } wx.navigateTo({ url: `/pages/order/confirm?goodsId=${this.data.goods.id}&skuId=${this.data.selectedSku.id}&count=${this.data.count}` }); }});
老曹讲解:
- ✅页面数据初始化:
data
中定义了商品详情goods
、选中的规格selectedSku
、是否显示规格弹窗showSkuPopup
以及购买数量count
。 - ✅商品详情加载:
onLoad
方法接收页面参数id
,调用getGoodsDetail
方法通过云函数获取商品详情并更新到页面数据中。 - ✅规格选择:
selectSku
方法用于显示规格选择弹窗,用户需选择规格后才能进行后续操作。 - ✅加入购物车:
addToCart
方法检查是否选择了规格,若未选择则提示用户;否则调用云函数将商品加入购物车,并关闭规格弹窗。 - ✅立即购买:
buyNow
方法同样需要检查规格选择,然后跳转到订单确认页,传递商品 ID、规格 ID 和购买数量作为参数。
🛒3. 购物车实现
// pages/cart/index.jsPage({ data: { cartItems: [], selectedIds: [], totalPrice: 0, isAllSelected: false }, onShow() { this.getCartList(); }, // 获取购物车列表 async getCartList() { wx.showLoading({ title: \'加载中...\' }); try { const res = await wx.cloud.callFunction({ name: \'cart\', data: { action: \'list\' } }); this.setData({ cartItems: res.result.data }); this.calculateTotal(); } catch (error) { wx.showToast({ title: \'获取购物车失败\', icon: \'none\' }); } finally { wx.hideLoading(); } }, // 选择商品 selectItem(e) { const id = e.currentTarget.dataset.id; const selectedIds = this.data.selectedIds; const index = selectedIds.indexOf(id); if (index > -1) { selectedIds.splice(index, 1); } else { selectedIds.push(id); } this.setData({ selectedIds }); this.calculateTotal(); this.checkAllSelected(); }, // 全选/取消全选 selectAll() { const isAllSelected = !this.data.isAllSelected; let selectedIds = []; if (isAllSelected) { selectedIds = this.data.cartItems.map(item => item.id); } this.setData({ isAllSelected, selectedIds }); this.calculateTotal(); }, // 计算总价 calculateTotal() { const selectedItems = this.data.cartItems.filter(item => this.data.selectedIds.includes(item.id) ); const totalPrice = selectedItems.reduce((sum, item) => { return sum + item.goods.price * item.count; }, 0); this.setData({ totalPrice: totalPrice.toFixed(2) }); }, // 检查是否全选 checkAllSelected() { const isAllSelected = this.data.cartItems.length > 0 && this.data.selectedIds.length === this.data.cartItems.length; this.setData({ isAllSelected }); }, // 去结算 goToCheckout() { if (this.data.selectedIds.length === 0) { wx.showToast({ title: \'请选择商品\', icon: \'none\' }); return; } const selectedItems = this.data.cartItems.filter(item => this.data.selectedIds.includes(item.id) ); wx.navigateTo({ url: `/pages/order/confirm?cartIds=${JSON.stringify(this.data.selectedIds)}` }); }});
老曹讲解:
- ✅页面数据初始化:
data
中定义了购物车商品列表cartItems
、选中的商品 ID 列表selectedIds
、总价totalPrice
和是否全选状态isAllSelected
。 - ✅购物车加载:
onShow
方法在页面显示时调用getCartList
方法,通过云函数获取购物车列表并计算总价。 - ✅商品选择:
selectItem
方法根据用户点击的商品 ID 更新选中状态,并重新计算总价和检查全选状态。 - ✅全选功能:
selectAll
方法切换全选状态,并更新选中商品 ID 列表和总价。 - ✅总价计算:
calculateTotal
方法根据选中的商品计算总价,并保留两位小数。 - ✅结算跳转:
goToCheckout
方法检查是否有选中的商品,若有则跳转到订单确认页,传递选中的商品 ID 列表。
✔4. 订单确认页实现
// pages/order/confirm.jsPage({ data: { address: {}, goodsList: [], totalPrice: 0, note: \'\' }, onLoad(options) { this.getAddress(); if (options.cartIds) { // 从购物车结算 this.getCartGoods(JSON.parse(options.cartIds)); } else { // 直接购买 this.getDirectGoods(options.goodsId, options.skuId, options.count); } }, // 获取收货地址 async getAddress() { try { const res = await wx.cloud.callFunction({ name: \'address\', data: { action: \'getDefault\' } }); this.setData({ address: res.result.data }); } catch (error) { console.log(\'获取地址失败\'); } }, // 获取购物车商品 async getCartGoods(cartIds) { try { const res = await wx.cloud.callFunction({ name: \'cart\', data: { action: \'getByIds\', ids: cartIds } }); this.calculateGoods(res.result.data); } catch (error) { wx.showToast({ title: \'获取商品失败\', icon: \'none\' }); } }, // 获取直接购买商品 async getDirectGoods(goodsId, skuId, count) { try { const res = await wx.cloud.callFunction({ name: \'goods\', data: { action: \'getById\', id: goodsId } }); const goodsList = [{ goods: res.result.data, skuId, count: parseInt(count) }]; this.calculateGoods(goodsList); } catch (error) { wx.showToast({ title: \'获取商品失败\', icon: \'none\' }); } }, // 计算商品信息 calculateGoods(goodsList) { const totalPrice = goodsList.reduce((sum, item) => { return sum + item.goods.price * item.count; }, 0); this.setData({ goodsList, totalPrice: totalPrice.toFixed(2) }); }, // 提交订单 async submitOrder() { if (!this.data.address.id) { wx.showToast({ title: \'请选择收货地址\', icon: \'none\' }); return; } wx.showLoading({ title: \'提交中...\' }); try { const res = await wx.cloud.callFunction({ name: \'order\', data: { action: \'create\', address: this.data.address, goodsList: this.data.goodsList, totalPrice: this.data.totalPrice, note: this.data.note } }); wx.navigateTo({ url: `/pages/pay/index?orderId=${res.result.orderId}` }); } catch (error) { wx.showToast({ title: \'提交订单失败\', icon: \'none\' }); } finally { wx.hideLoading(); } }});
老曹讲解:
- ✅页面数据初始化:
data
中定义了收货地址address
、商品列表goodsList
、总价totalPrice
和备注信息note
。 - ✅页面加载逻辑:
onLoad
方法根据传入参数判断是从购物车结算还是直接购买,分别调用getCartGoods
或getDirectGoods
方法获取商品信息。 - ✅收货地址获取:
getAddress
方法通过云函数获取默认收货地址并更新到页面数据中。 - ✅商品信息计算:
calculateGoods
方法根据商品列表计算总价,并更新到页面数据中。 - ✅订单提交:
submitOrder
方法检查是否选择了收货地址,若未选择则提示用户;否则调用云函数创建订单,并跳转到支付页面,传递订单 ID 作为参数。
❓10大高频面试题详解
✅1. 请描述微信小程序的生命周期函数及其执行顺序
答案:
微信小程序包含应用生命周期和页面生命周期:
应用生命周期(app.js):
onLaunch
: 小程序初始化完成时触发,全局只触发一次onShow
: 小程序启动,或从后台进入前台显示时触发onHide
: 小程序从前台进入后台时触发onError
: 小程序发生脚本错误,或者 API 调用失败时触发
页面生命周期:
onLoad
: 页面加载时触发,一个页面只会调用一次,可以在 onLoad 中获取打开当前页面路径中的参数onShow
: 页面显示/切入前台时触发onReady
: 页面初次渲染完成时触发,一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互onHide
: 页面隐藏/切入后台时触发,如 navigateTo 或底部 tab 切换到其他页面onUnload
: 页面卸载时触发,如 redirectTo 或 navigateBack 到其他页面
执行顺序:
- App.onLaunch
- App.onShow
- Page.onLoad
- Page.onShow
- Page.onReady
✅2. 如何优化小程序的性能?
答案:
小程序性能优化可以从以下几个方面入手:
-
包体积优化:
- 合理使用分包加载,将不常用的页面放到子包中
- 删除无用代码和资源文件
- 图片压缩和格式优化
- 使用 webpack 等工具进行代码压缩
-
渲染性能优化:
- 避免在
wx:for
中嵌套过多层级 - 使用
wx:key
提高列表渲染性能 - 避免频繁 setData,合并多次 setData 操作
- 使用自定义组件减少重复渲染
- 避免在
-
网络请求优化:
- 合理设置缓存策略,避免重复请求
- 使用 HTTP/2 提高请求效率
- 对于图片等静态资源使用 CDN 加速
-
图片优化:
- 使用适当的图片格式(WebP)
- 实现图片懒加载
- 根据屏幕尺寸加载合适分辨率的图片
// 合并setData示例this.setData({ \'userInfo.name\': \'张三\', \'userInfo.age\': 25, \'list[0].name\': \'新名称\'});
✅3. 小程序如何实现数据缓存?有哪些注意事项?
答案:
小程序提供了多种数据缓存方式:
本地存储(Storage):
// 同步存储wx.setStorageSync(\'key\', \'value\');const value = wx.getStorageSync(\'key\');// 异步存储wx.setStorage({ key: \'key\', data: \'value\', success: function() { console.log(\'存储成功\'); }});wx.getStorage({ key: \'key\', success: function(res) { console.log(res.data); }});
注意事项:
- 每个小程序的存储上限为10MB
- 同步方法会阻塞线程,异步方法不会
- 存储的数据会持久化保存,除非用户主动删除或超过存储上限
- 不要存储敏感信息,如密码等
- 大量数据建议使用云数据库存储
文件缓存:
// 下载文件并缓存wx.downloadFile({ url: \'https://example.com/image.jpg\', success: function(res) { // 文件保存到本地 wx.saveFile({ tempFilePath: res.tempFilePath, success: function(saveRes) { const savedFilePath = saveRes.savedFilePath; // 保存文件路径到Storage wx.setStorageSync(\'cachedImage\', savedFilePath); } }); }});
✅4. 小程序如何处理用户授权和登录?
答案:
小程序用户授权和登录流程:
// 用户登录流程async function userLogin() { // 1. 调用wx.login获取code const loginRes = await wx.login(); // 2. 发送code到后端换取session_key和openid const res = await wx.request({ url: \'https://yourserver.com/api/login\', method: \'POST\', data: { code: loginRes.code } }); // 3. 保存登录状态 wx.setStorageSync(\'token\', res.data.token); // 4. 获取用户信息(需要用户授权) try { const userInfoRes = await wx.getUserProfile({ desc: \'用于完善用户资料\' }); // 5. 将用户信息发送到后端 await wx.request({ url: \'https://yourserver.com/api/userinfo\', method: \'POST\', data: { userInfo: userInfoRes.userInfo, encryptedData: userInfoRes.encryptedData, iv: userInfoRes.iv } }); } catch (error) { console.log(\'用户拒绝授权\'); }}
注意事项:
wx.getUserProfile
需要用户主动触发(如点击按钮)- 不要频繁调用登录接口
- 妥善保管 token 等敏感信息
- 处理好用户拒绝授权的情况
✅5. 如何实现小程序的支付功能?
答案:
小程序支付流程:
// 发起支付async function requestPayment(orderId) { try { // 1. 调用后端统一下单接口 const res = await wx.request({ url: \'https://yourserver.com/api/pay/unifiedorder\', method: \'POST\', data: { orderId: orderId, openid: wx.getStorageSync(\'openid\') } }); // 2. 调用微信支付 const payRes = await wx.requestPayment({ timeStamp: res.data.timeStamp, nonceStr: res.data.nonceStr, package: res.data.package, signType: res.data.signType, paySign: res.data.paySign }); // 3. 支付成功处理 if (payRes.errMsg === \'requestPayment:ok\') { wx.showToast({ title: \'支付成功\' }); // 更新订单状态等操作 } } catch (error) { if (error.errMsg.includes(\'fail cancel\')) { wx.showToast({ title: \'取消支付\' }); } else { wx.showToast({ title: \'支付失败\', icon: \'none\' }); } }}
支付流程要点:
- 前端调用统一下单接口获取支付参数
- 使用
wx.requestPayment
发起支付 - 处理支付成功、取消、失败等不同情况
- 支付成功后更新订单状态
✅6. 小程序云开发有哪些优势?如何使用?
答案:
云开发优势:
- 无需搭建服务器:提供云函数、云数据库、云存储等后端服务
- 天然鉴权:基于微信身份的鉴权机制
- 快速开发:减少后端开发工作量
- 弹性扩容:根据业务量自动扩容
- 免费额度:提供一定的免费使用额度
使用示例:
// 初始化云开发wx.cloud.init({ env: \'your-env-id\'});// 云数据库操作const db = wx.cloud.database();const collection = db.collection(\'users\');// 查询数据async function getUsers() { try { const res = await collection.get(); return res.data; } catch (error) { console.error(\'查询失败\', error); }}// 添加数据async function addUser(user) { try { const res = await collection.add({ data: user }); return res._id; } catch (error) { console.error(\'添加失败\', error); }}// 云函数调用async function callCloudFunction() { try { const res = await wx.cloud.callFunction({ name: \'getUserInfo\', data: { userId: \'123\' } }); return res.result; } catch (error) { console.error(\'调用失败\', error); }}
✅7. 小程序如何实现组件间通信?
答案:
小程序组件间通信有以下几种方式:
- 父组件向子组件传递数据(properties):
// 父组件<!-- parent.wxml --><custom-component name=\"{{userName}}\" age=\"{{userAge}}\" />// 子组件// custom-component.jsComponent({ properties: { name: String, age: { type: Number, value: 0 } }});
- 子组件向父组件传递数据(triggerEvent):
// 子组件// custom-component.jsComponent({ methods: { onTap() { this.triggerEvent(\'myevent\', { value: \'hello\' }); } }});<!-- custom-component.wxml --><view bindtap=\"onTap\">点击我</view>// 父组件<!-- parent.wxml --><custom-component bind:myevent=\"onMyEvent\" />// parent.jsPage({ onMyEvent(e) { console.log(e.detail.value); // hello }});
- 全局状态管理(使用全局变量或状态管理库):
// app.jsApp({ globalData: { userInfo: null }, setUserInfo(userInfo) { this.globalData.userInfo = userInfo; }});// 在页面中使用const app = getApp();app.setUserInfo({ name: \'张三\' });
✅8. 小程序如何处理网络请求异常?
答案:
网络请求异常处理策略:
// 封装网络请求function request(options) { return new Promise((resolve, reject) => { wx.showLoading({ title: \'加载中\' }); wx.request({ ...options, success(res) { if (res.statusCode === 200) { resolve(res.data); } else { reject(new Error(`请求失败: ${res.statusCode}`)); } }, fail(err) { reject(err); }, complete() { wx.hideLoading(); } }); });}// 使用示例async function fetchUserData() { try { const data = await request({ url: \'https://api.example.com/user\', method: \'GET\' }); return data; } catch (error) { // 根据不同错误类型处理 if (error.errMsg.includes(\'fail url not in domain list\')) { wx.showToast({ title: \'域名未配置\', icon: \'none\' }); } else if (error.errMsg.includes(\'fail timeout\')) { wx.showToast({ title: \'请求超时\', icon: \'none\' }); } else { wx.showToast({ title: \'网络错误\', icon: \'none\' }); } throw error; }}// 全局错误处理wx.onError((error) => { console.error(\'小程序错误:\', error); // 上报错误日志});
✅9. 小程序如何实现路由管理?
答案:
小程序路由管理方式:
// 页面跳转// 保留当前页面,跳转到新页面wx.navigateTo({ url: \'/pages/detail/detail?id=123\'});// 关闭当前页面,跳转到新页面wx.redirectTo({ url: \'/pages/detail/detail?id=123\'});// 关闭所有页面,打开到 tabBar 页面wx.switchTab({ url: \'/pages/index/index\'});// 关闭所有页面,打开到应用内的某个页面wx.reLaunch({ url: \'/pages/index/index\'});// 返回上一页wx.navigateBack({ delta: 1});// 获取页面栈const pages = getCurrentPages();const currentPage = pages[pages.length - 1];// 页面传参接收Page({ onLoad(options) { console.log(options.id); // 123 }});// 自定义路由管理器class Router { static navigateTo(url, params = {}) { const queryString = Object.keys(params) .map(key => `${key}=${params[key]}`) .join(\'&\'); wx.navigateTo({ url: `${url}?${queryString}` }); } static switchTab(url) { wx.switchTab({ url }); }}// 使用Router.navigateTo(\'/pages/detail/detail\', { id: 123, name: \'test\' });
✅10. 小程序如何进行版本更新和灰度发布?
答案:
小程序版本更新和灰度发布策略:
// 检查更新function checkUpdate() { if (wx.canIUse(\'getUpdateManager\')) { const updateManager = wx.getUpdateManager(); updateManager.onCheckForUpdate(function (res) { // 请求完新版本信息的回调 console.log(res.hasUpdate); }); updateManager.onUpdateReady(function () { wx.showModal({ title: \'更新提示\', content: \'新版本已经准备好,是否重启应用?\', success: function (res) { if (res.confirm) { // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启 updateManager.applyUpdate(); } } }); }); updateManager.onUpdateFailed(function () { // 新的版本下载失败 wx.showToast({ title: \'更新失败\', icon: \'none\' }); }); }}// 灰度发布策略// 1. 基于用户ID进行灰度function isInGrayRelease(userId) { // 使用用户ID的哈希值决定是否进入灰度 const hash = hashCode(userId.toString()); return hash % 100 < 10; // 10%的用户进入灰度}function hashCode(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // 转换为32位整数 } return Math.abs(hash);}// 2. 功能开关控制const featureFlags = { newFeature: false, // 新功能开关 abTest: true // A/B测试开关};// 在代码中使用功能开关if (featureFlags.newFeature) { // 新功能代码} else { // 旧功能代码}// 3. 服务端配置控制async function getFeatureConfig() { try { const res = await wx.request({ url: \'https://yourserver.com/api/feature-config\', data: { version: \'1.0.0\', // 当前小程序版本 userId: wx.getStorageSync(\'userId\') } }); return res.data; } catch (error) { return {}; // 默认配置 }}
🙂 通过以上20节课的系统学习和项目实战,老曹相信你应该已经掌握了微信小程序开发的核心技能。在实际开发中,大家还需要不断实践和积累经验,关注官方更新,持续学习新技术。💪