目录
项目概述
开辟环境搭建
微信开辟者工具下载与安装
项目创建流程
项目目录结构及各文件作用
商品展示页面开辟
页面布局(WXML 与 WXSS)
数据获取与绑定(JavaScript)
参加购物车功能实现
购物车功能开辟
购物车页面布局
购物车数据处理
全选、反选与结算功能
订单天生与支付功能开辟
订单天生逻辑
支付功能接入
项目优化与美满
性能优化
安全步伐
项目概述
在移动互联网蓬勃发展的当下,电商行业出现出爆发式增长态势。消耗者的购物习惯渐渐从传统线下购物向线上购物转移,这使得各类电商平台如雨后春笋般涌现。而小程序,作为一种无需下载安装、即点即用的轻量级应用,凭借其便捷性、低门槛以及与交际平台的紧密结合等上风,敏捷在电商领域占据了一席之地。
搭建一个小程序商品购物体系,不仅能为商家拓展贩卖渠道,低落运营成本,还能为消耗者提供更加便捷、高效的购物体验。通过小程序,商家可以随时随地展示商品信息,开展促销运动,与消耗者举行互动;消耗者则可以在微信等交际平台中轻松访问购物小程序,实现快速浏览商品、下单购买、支付结算等一系列操作,无需再在多个应用之间切换,大大节流了购物时间和精力。同时,小程序的交际分享功能还能让消耗者将心仪的商品分享给亲朋挚友,实现裂变式传播,为商家带来更多潜在客户。
接下来,让我们具体探讨如何开辟一个功能完备的小程序商品购物体系,从项目标搭建、页面的设计、功能的实现到终极的摆设上线,一步步揭开小程序开辟的神秘面纱。
开辟环境搭建
要开启小程序商品购物体系的开辟之旅,首先得搭建好开辟环境,而微信开辟者工具就是我们的得力助手。下面来具体介绍其下载安装以及项目创建的流程。
微信开辟者工具下载与安装
- 下载:打开浏览器,访问微信官方文档中的开辟者工具下载页面(https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html )。这里提供了 Windows、Mac 和 Linux 等差别操作体系版本的下载选项,根据自己的电脑体系选择对应的安装包举行下载。
- 安装:下载完成后,找到安装包并双击运行。在安装向导中,点击 “下一步”,阅读并同意答应协议后,点击 “我继承”。接着可以自定义安装目录,选择好安装位置后点击 “安装”,期待安装过程完成,最后点击 “完成”,微信开辟者工具就乐成安装在你的电脑上了。
项目创建流程
- 登录:打开安装好的微信开辟者工具,利用微信扫码登录。登录乐成后,在欢迎界面点击 “新建项目”。
- 填写项目信息:在弹出的新建项目窗口中,依次填写项目名称,比方 “shopping - mini - program” 。然后填写 AppID,如果没有正式的 AppID,也可以勾选 “测试号” 选项,利用测试号举行开辟测试。接着选择项目标存储位置,最后点击 “新建” 按钮,项目就创建乐成了。
项目目录结构及各文件作用
项目创建完成后,会看到一个清楚的目录结构,主要包罗以下文件和文件夹:
- pages 文件夹:存放小程序的各个页面,每个页面都对应一个独立的文件夹。比方,“pages/home” 文件夹下存放着首页的相关文件,包罗 “home.js”(页面逻辑文件,用于处理页面的数据请求、变乱相应等逻辑)、“home.json”(页面配置文件,可对该页面的导航栏样式、是否答应下拉革新等举行设置,会覆盖全局配置中的相关部分)、“home.wxml”(页面结构文件,利用雷同 HTML 的标签语言来构建页面的布局和元素)、“home.wxss”(页面样式文件,用于定义页面元素的样式,雷同于 CSS )。
- utils 文件夹:通常用于存放一些工具函数,比如日期格式化函数、网络请求封装函数等。比方 “utils/util.js” ,在这个文件中可以定义一个格式化日期的函数,方便在各个页面中利用。
- app.js:小程序的入口文件,主要用于监听和处理小程序的生命周期函数,如小程序启动时触发的 “onLaunch” 函数、小程序显示在前台时触发的 “onShow” 函数等,同时也可以在这里声明一些全局数据,比如 “globalData” ,用于存储用户登录信息等在整个小程序中都大概用到的数据。
- app.json:小程序的全局配置文件,它决定了小程序的页面路径、窗口表现、底部 tab 栏配置等重要信息。比方,“pages” 数组中定义了小程序全部页面的路径,“window” 对象中可以设置小程序窗口的配景颜色、导航栏颜色、标题等。
- app.wxss:小程序的全局样式表文件,在这个文件中定义的样式会作用于整个小程序的全部页面,可用于设置全局的字体、配景颜色等根本样式。
- project.config.json:微信开辟者工具的项目配置文件,包罗项目标名称、appid、编译设置、调试设置等信息。比如可以在这里设置是否开启 ES6 语法支持、是否检查代码中的 URL 等。
- sitemap.json:小程序的站点地图文件,用于 SEO 配置,帮助微信搜刮引擎更好地爬取小程序的页面,通过配置 “rules” 数组,可以指定哪些页面答应被微信索引,哪些页面不答应。
商品展示页面开辟
页面布局(WXML 与 WXSS)
商品展示页面是用户打仗小程序的重要界面,其布局的公道性和美观性直接影响用户体验。下面是一个简朴的商品展示页面的 WXML 结构代码:
- <view class="goods-list">
- <block wx:for="{
-
- {goodsList}}" wx:key="id">
- <view class="goods-item">
- <image class="goods-img" src="{
-
- {item.imgUrl}}" mode="aspectFill"></image>
- <view class="goods-info">
- <text class="goods-name">{
-
- {item.name}}</text>
- <text class="goods-price">¥{
-
- {item.price}}</text>
- <button class="add-cart-btn" bindtap="addToCart" data-goodsId="{
-
- {item.id}}">加入购物车</button>
- </view>
- </view>
- </block>
- </view>
复制代码 在这段代码中,外层的<view class="goods - list">用于包裹整个商品列表。<block wx:for="{ {goodsList}}" wx:key="id">通过wx:for指令循环渲染商品列表,wx:key用于提高渲染性能,确保列表项的唯一性 。每个商品项由<view class="goods - item">包裹,此中<image>标签用于展示商品图片,src="{ {item.imgUrl}}"绑定图片的 URL;<view class="goods - info">中包罗商品名称<text class="goods - name">{ {item.name}}</text>、商品代价<text class="goods - price">¥{ {item.price}}</text>以及参加购物车按钮<button class="add - cart-btn" bindtap="addToCart" data - goodsId="{ { item.id}}">参加购物车</button>,bindtap绑定了点击变乱addToCart,data - goodsId="{ { item.id}}"用于传递商品的 ID。
对应的 WXSS 样式代码如下:
- .goods-list {
- padding: 10px;
- display: flex;
- flex-wrap: wrap;
- justify-content: space-around;
- }
- .goods-item {
- width: 45%;
- margin-bottom: 15px;
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
- border-radius: 5px;
- overflow: hidden;
- background-color: #fff;
- }
- .goods-img {
- width: 100%;
- height: 200px;
- }
- .goods-info {
- padding: 10px;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- }
- .goods-name {
- font-size: 16px;
- color: #333;
- margin-bottom: 5px;
- text-align: center;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .goods-price {
- font-size: 14px;
- color: #f60;
- margin-bottom: 10px;
- }
- .add-cart-btn {
- width: 100%;
- background-color: #f60;
- color: #fff;
- border: none;
- border-radius: 3px;
- padding: 8px 0;
- font-size: 14px;
- }
- .add-cart-btn:active {
- background-color: #ff8c00;
- }
复制代码
在上述 WXSS 代码中,.goods - list设置了内边距、弹性布局,使商品项可以或许灵活排列且在容器内均匀分布;.goods - item定义了商品项的宽度、外边距、阴影、圆角以及配景颜色,使其看起来更加美观和突出;.goods - img设置图片宽度为 100%,高度为 200px,包管图片可以或许完备展示;.goods - info设置了内边距和弹性布局方向,使商品信息垂直居中显示;.goods - name和.goods - price分别设置了商品名称和代价的字体巨细、颜色等样式,而且对商品名称举行了溢出处理,防止名称过长影响页面布局;.add - cart-btn设置了参加购物车按钮的宽度、配景颜色、笔墨颜色、边框、圆角以及点击时的配景颜色变革,增强了按钮的交互性。
数据获取与绑定(JavaScript)
商品展示页面的数据通常来自后端服务器,通过接口调用获取数据后,再将数据绑定到页面上举行展示。在小程序中,利用wx.request方法来发起 HTTP 请求获取数据。假设后端提供了一个获取商品列表的接口https://api.example.com/goods,在页面的 JavaScript 文件中,可以如许编写代码获取数据:
- Page({
- data: {
- goodsList: []
- },
- onLoad: function() {
- this.fetchGoodsList();
- },
- fetchGoodsList: function() {
- wx.request({
- url: 'https://api.example.com/goods',
- method: 'GET',
- success: (res) => {
- if (res.statusCode === 200) {
- this.setData({
- goodsList: res.data
- });
- } else {
- console.error('请求失败,状态码:', res.statusCode);
- }
- },
- fail: (err) => {
- console.error('网络请求出错:', err);
- }
- });
- }
- });
复制代码 在这段代码中,首先在data中定义了一个goodsList数组,用于存储获取到的商品数据。在onLoad生命周期函数中调用fetchGoodsList方法来获取商品列表。fetchGoodsList方法利用wx.request发起 GET 请求,url为后端接口地址。如果请求乐成(statusCode为 200),将后端返回的数据res.data通过setData方法更新到页面的data中,如许页面上绑定的goodsList数据就会更新,从而实现商品列表的动态展示;如果请求失败,在控制台打印错误信息。
参加购物车功能实现
当用户点击商品展示页面的 “参加购物车” 按钮时,必要将对应的商品信息添加到购物车中。在 WXML 中,按钮已经绑定了addToCart点击变乱,接下来在 JavaScript 中实现该变乱处理函数:
- Page({
- // 其他代码...
- addToCart: function(e) {
- const goodsId = e.currentTarget.dataset.goodsId;
- const goods = this.data.goodsList.find(item => item.id === goodsId);
- if (goods) {
- // 获取全局的购物车数据
- const app = getApp();
- let cart = app.globalData.cart;
- const existingGoods = cart.find(cartItem => cartItem.id === goodsId);
- if (existingGoods) {
- existingGoods.count++;
- } else {
- goods.count = 1;
- cart.push(goods);
- }
- app.globalData.cart = cart;
- // 提示用户加入购物车成功
- wx.showToast({
- title: '已加入购物车',
- icon:'success',
- duration: 1500
- });
- }
- }
- });
复制代码 在addToCart函数中,首先通过e.currentTarget.dataset.goodsId获取当前点击按钮所对应的商品 ID。然后利用find方法在goodsList中找到对应的商品信息。接着获取全局的购物车数据app.globalData.cart,检查购物车中是否已经存在该商品,如果存在,则将该商品的数目加 1;如果不存在,则为该商品添加一个数目属性count并设置为 1,然后将商品添加到购物车中。最后更新全局的购物车数据,并利用wx.showToast提示用户参加购物车乐成。
购物车功能开辟
购物车页面布局
购物车页面的布局必要清楚展示商品信息、数目选择、代价计算以及全选、结算等功能。以下是购物车页面的 WXML 代码:
- <view class="cart-container">
- <view class="cart-header">
- <checkbox checked="{
-
- {isAllChecked}}" bindchange="handleAllChecked">全选</checkbox>
- <text>购物车</text>
- </view>
- <block wx:for="{
-
- {cartList}}" wx:key="id">
- <view class="cart-item">
- <checkbox checked="{
-
- {item.checked}}" bindchange="handleItemChecked" data-index="{
-
- {index}}"></checkbox>
- <image class="cart-item-img" src="{
-
- {item.imgUrl}}" mode="aspectFill"></image>
- <view class="cart-item-info">
- <text class="cart-item-name">{
-
- {item.name}}</text>
- <text class="cart-item-price">¥{
-
- {item.price}}</text>
- <view class="quantity-control">
- <button bindtap="decreaseQuantity" data-index="{
-
- {index}}">-</button>
- <text>{
-
- {item.quantity}}</text>
- <button bindtap="increaseQuantity" data-index="{
-
- {index}}">+</button>
- </view>
- </view>
- <button bindtap="deleteItem" data-index="{
-
- {index}}">删除</button>
- </view>
- </block>
- <view class="cart-footer">
- <text>合计: ¥{
-
- {totalPrice}}</text>
- <button bindtap="goToCheckout" disabled="{
-
- {!isSomeChecked}}">去结算</button>
- </view>
- </view>
复制代码 在这段代码中,.cart - container是购物车页面的整体容器。.cart - header包罗全选复选框和 “购物车” 标题。通过wx:for循环渲染购物车中的每个商品项,每个商品项包罗商品的选中复选框、图片、名称、代价、数目控制按钮以及删除按钮。.cart - footer展示了商品的合计代价和去结算按钮,而且根据是否有商品被选中来控制去结算按钮的禁用状态。
对应的 WXSS 样式代码如下:
- .cart-container {
- padding: 10px;
- }
- .cart-header {
- display: flex;
- align-items: center;
- margin-bottom: 10px;
- }
- .cart-header checkbox {
- margin-right: 5px;
- }
- .cart-item {
- display: flex;
- align-items: center;
- border-bottom: 1px solid #eee;
- padding: 10px 0;
- }
- .cart-item checkbox {
- margin-right: 10px;
- }
- .cart-item-img {
- width: 80px;
- height: 80px;
- margin-right: 10px;
- }
- .cart-item-info {
- flex: 1;
- }
- .cart-item-name {
- font-size: 16px;
- margin-bottom: 5px;
- }
- .cart-item-price {
- color: #f60;
- }
- .quantity-control {
- display: flex;
- align-items: center;
- margin-top: 5px;
- }
- .quantity-control button {
- width: 30px;
- height: 30px;
- border: 1px solid #ccc;
- background-color: #fff;
- font-size: 16px;
- text-align: center;
- line-height: 30px;
- margin: 0 5px;
- }
- .cart-footer {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-top: 20px;
- padding-top: 10px;
- border-top: 1px solid #eee;
- }
- .cart-footer button {
- background-color: #f60;
- color: #fff;
- border: none;
- padding: 10px 20px;
- border-radius: 5px;
- }
- .cart-footer button[disabled] {
- background-color: #ccc;
- }
复制代码
上述 WXSS 代码定义了购物车页面各部分的样式。.cart - container设置了内边距,使内容与页面边缘有一定间距。.cart - header采用弹性布局,使全选复选框和标题程度排列,而且设置了底部外边距,使购物车标题与商品列表有显着区分。.cart - item同样利用弹性布局,展示商品的各个信息,通过设置底部边框和内边距,使每个商品项有清楚的分隔。商品图片设置了固定的宽度和高度,并预留了右边距,确保与其他信息有合适的间距。商品信息部分利用flex: 1占据剩余空间,使笔墨内容可以或许自顺应排列。数目控制按钮设置了固定的巨细、边框和配景颜色,以及合适的内外边距和笔墨样式,方便用户操作。.cart - footer采用弹性布局,使合计代价和去结算按钮分别位于两头,通过设置顶部边框和内边距,与商品列表部分区分开来。去结算按钮设置了配景颜色、笔墨颜色、边框和圆角,而且针对禁用状态设置了差别的配景颜色,增强用户交互体验。
购物车数据处理
购物车数据可以存储在本地存储中,也可以从后端获取。在小程序中,利用wx.setStorageSync和wx.getStorageSync方法来操作本地存储。假设购物车数据存储在本地存储的cart字段中,获取购物车数据的代码如下:
- Page({
- data: {
- cartList: [],
- isAllChecked: false,
- totalPrice: 0
- },
- onShow: function() {
- const cart = wx.getStorageSync('cart');
- if (cart) {
- this.setData({
- cartList: cart
- });
- this.calculateTotalPrice();
- }
- },
- calculateTotalPrice: function() {
- const cartList = this.data.cartList;
- let totalPrice = 0;
- cartList.forEach(item => {
- if (item.checked) {
- totalPrice += item.price * item.quantity;
- }
- });
- this.setData({
- totalPrice: totalPrice
- });
- }
- });
复制代码 在上述代码中,onShow生命周期函数在页面显示时被调用,通过wx.getStorageSync('cart')获取本地存储的购物车数据。如果存在数据,则将其设置到页面的cartList数据中,并调用calculateTotalPrice方法计算购物车中已选中商品的总价。calculateTotalPrice方法遍历cartList,累加已选中商品的代价乘以数目,最后通过setData更新页面的totalPrice数据。
添加商品到购物车的代码在之前参加购物车功能实现部分已经提及,这里重点展示购物车中商品数目的增减以及删除商品的代码实现。
增加商品数目的代码:
- Page({
- // 其他代码...
- increaseQuantity: function(e) {
- const index = e.currentTarget.dataset.index;
- let cartList = this.data.cartList;
- cartList[index].quantity++;
- this.setData({
- cartList: cartList
- });
- wx.setStorageSync('cart', cartList);
- this.calculateTotalPrice();
- }
- });
复制代码 在increaseQuantity函数中,通过e.currentTarget.dataset.index获取当前操作商品的索引。然后从页面数据中获取cartList,将对应商品的数目加 1。接着利用setData更新页面数据,同时通过wx.setStorageSync('cart', cartList)将更新后的购物车数据保存到本地存储,最后调用calculateTotalPrice方法重新计算总价。
淘汰商品数目的代码:
- Page({
- // 其他代码...
- decreaseQuantity: function(e) {
- const index = e.currentTarget.dataset.index;
- let cartList = this.data.cartList;
- if (cartList[index].quantity > 1) {
- cartList[index].quantity--;
- this.setData({
- cartList: cartList
- });
- wx.setStorageSync('cart', cartList);
- this.calculateTotalPrice();
- }
- }
- });
复制代码 decreaseQuantity函数与increaseQuantity雷同,首先获取当前操作商品的索引,然后判定商品数目是否大于 1,如果是则将数目减 1。接着更新页面数据和本地存储数据,并重新计算总价。
删除商品的代码:
- Page({
- // 其他代码...
- deleteItem: function(e) {
- const index = e.currentTarget.dataset.index;
- let cartList = this.data.cartList;
- cartList.splice(index, 1);
- this.setData({
- cartList: cartList
- });
- wx.setStorageSync('cart', cartList);
- this.calculateTotalPrice();
- }
- });
复制代码
在deleteItem函数中,通过e.currentTarget.dataset.index获取要删除商品的索引,利用splice方法从cartList中删除对应的商品。然后更新页面数据和本地存储数据,并重新计算总价。
全选、反选与结算功能
全选和反选功能可以通过一个复选框来控制购物车中全部商品的选中状态。结算功能则是在用户点击 “去结算” 按钮时,将已选中的商品信息提交到后端举行处理。
全选、反选功能的 JavaScript 代码如下:
- Page({
- // 其他代码...
- handleAllChecked: function(e) {
- const isChecked = e.detail.value;
- let cartList = this.data.cartList;
- cartList.forEach(item => {
- item.checked = isChecked;
- });
- this.setData({
- cartList: cartList,
- isAllChecked: isChecked
- });
- wx.setStorageSync('cart', cartList);
- this.calculateTotalPrice();
- },
- handleItemChecked: function(e) {
- const index = e.currentTarget.dataset.index;
- let cartList = this.data.cartList;
- cartList[index].checked =!cartList[index].checked;
- let allChecked = true;
- cartList.forEach(item => {
- if (!item.checked) {
- allChecked = false;
- return;
- }
- });
- this.setData({
- cartList: cartList,
- isAllChecked: allChecked
- });
- wx.setStorageSync('cart', cartList);
- this.calculateTotalPrice();
- }
- });
复制代码 在handleAllChecked函数中,通过e.detail.value获取全选复选框的选中状态。然后遍历cartList,将每个商品的选中状态设置为与全选复选框一致。接着更新页面数据和本地存储数据,并重新计算总价。
handleItemChecked函数用于处理单个商品复选框的点击变乱。通过e.currentTarget.dataset.index获取当前点击商品的索引,将该商品的选中状态取反。然后检查购物车中全部商品是否都被选中,如果是则将isAllChecked设置为true,否则设置为false。最后更新页面数据和本地存储数据,并重新计算总价。
结算功能的代码如下:
- Page({
- // 其他代码...
- goToCheckout: function() {
- const cartList = this.data.cartList;
- const selectedGoods = cartList.filter(item => item.checked);
- if (selectedGoods.length === 0) {
- wx.showToast({
- title: '请至少选择一件商品',
- icon: 'none'
- });
- return;
- }
- // 这里可以将selectedGoods数据发送到后端进行结算处理
- wx.navigateTo({
- url: '/pages/checkout/checkout?goods=' + JSON.stringify(selectedGoods)
- });
- }
- });
复制代码 在goToCheckout函数中,首先从cartList中过滤出全部已选中的商品,存储在selectedGoods数组中。如果selectedGoods数组为空,则提示用户至少选择一件商品。否则,可以将selectedGoods数据发送到后端举行结算处理,这里示例通过wx.navigateTo跳转到结算页面,并将选中的商品数据以参数情势传递已往,在实际应用中,必要根据后端接口规范举行数据发送和处理。
订单天生与支付功能开辟
订单天生逻辑
当用户在购物车页面点击 “去结算” 按钮后,就必要收集购物车中已选中商品的信息来天生订单。这部分主要在结算页面的 JavaScript 文件中实现。假设结算页面路径为/pages/checkout/checkout,其对应的 JavaScript 代码如下:
- Page({
- data: {
- selectedGoods: []
- },
- onLoad: function(options) {
- const goods = JSON.parse(options.goods);
- this.setData({
- selectedGoods: goods
- });
- },
- createOrder: function() {
- const selectedGoods = this.data.selectedGoods;
- const orderData = {
- orderItems: selectedGoods.map(item => ({
- goodsId: item.id,
- name: item.name,
- price: item.price,
- quantity: item.quantity
- })),
- totalPrice: selectedGoods.reduce((total, item) => total + item.price * item.quantity, 0),
- // 这里可以添加更多订单相关信息,如收货地址、下单时间等
- // 假设收货地址在全局变量中存储
- shippingAddress: getApp().globalData.userInfo.address
- };
- wx.request({
- url: 'https://api.example.com/createOrder',
- method: 'POST',
- data: orderData,
- success: (res) => {
- if (res.statusCode === 200) {
- const orderId = res.data.orderId;
- wx.navigateTo({
- url: `/pages/orderDetail/orderDetail?orderId=${orderId}`
- });
- } else {
- wx.showToast({
- title: '订单生成失败',
- icon: 'none'
- });
- }
- },
- fail: (err) => {
- wx.showToast({
- title: '网络请求失败',
- icon: 'none'
- });
- }
- });
- }
- });
复制代码 在上述代码中,onLoad函数吸取从购物车页面传递过来的已选中商品数据,并存储在selectedGoods数据中。createOrder函数构建订单数据,将商品信息整理成恰当后端吸取的格式,包罗商品 ID、名称、代价、数目等,同时计算订单总价,并从全局变量中获取收货地址(假设用户信息已在之前登录等操作中存储在全局变量getApp().globalData.userInfo中)。然后通过wx.request向https://api.example.com/createOrder接口发起 POST 请求,将订单数据发送到后端。如果请求乐成且状态码为 200,获取后端返回的订单 ID,跳转到订单详情页面;如果请求失败或状态码不为 200,提示用户订单天生失败。
支付功能接入
在微信小程序中,常用的支付方式是微信支付。接入微信支付的流程如下:
- 准备工作:在微信支付商户平台注册成为商户,获取商户号(MchID)、API 密钥等信息。同时,确保小程序已举行认证,而且在微信公众平台将小程序与商户号举行关联。
- 获取用户 openid:用户在小程序中举行支付时,必要获取用户的唯一标识 openid。可以通过微信登录接口获取用户的 code,然后将 code 发送到后端服务器,后端服务器利用小程序的 AppID、AppSecret 以及获取到的 code 向微信官方服务器发起请求,变更 openid。假设后端利用 Node.js 和 Express 框架搭建服务器,获取 openid 的代码示比方下:
- const express = require('express');
- const router = express.Router();
- const axios = require('axios');
- const { APP_ID, APP_SECRET } = require('../config'); // 假设配置文件中存储了AppID和AppSecret
- router.post('/login', async (req, res) => {
- try {
- const { code } = req.query;
- const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${APP_ID}&secret=${APP_SECRET}&js_code=${code}&grant_type=authorization_code`;
- const { data: { openid } } = await axios.get(url);
- res.status(200).send({ openid });
- } catch (err) {
- res.status(500).send({ message: '获取openid失败' });
- }
- });
- module.exports = router;
复制代码 在小程序端,获取 code 并向后端请求 openid 的代码如下:
- Page({
- loginHandle: function() {
- wx.login({
- success: (res) => {
- const code = res.code;
- wx.request({
- url: 'https://your-server-url.com/login',
- method: 'POST',
- data: { code },
- success: (res) => {
- const openid = res.data.openid;
- // 可以将openid存储在本地或全局变量中
- wx.setStorageSync('openid', openid);
- },
- fail: (err) => {
- console.error('获取openid失败', err);
- }
- });
- },
- fail: (err) => {
- console.error('获取code失败', err);
- }
- });
- }
- });
复制代码
- 发起支付请求:在订单天生后,前端向后端发起支付请求,后端根据订单信息天生预支付订单,并向微信官方服务器请求预支付交易会话标识(prepay_id)。假设后端利用 Node.js 和 Express 框架,天生预支付订单的代码示比方下:
- const express = require('express');
- const router = express.Router();
- const axios = require('axios');
- const crypto = require('crypto');
- const { APP_ID, MCH_ID, API_KEY } = require('../config'); // 假设配置文件中存储了相关信息
- // 生成随机字符串
- function generateNonceStr() {
- return crypto.randomBytes(16).toString('hex');
- }
- // 生成签名
- function generateSign(params) {
- const keys = Object.keys(params).sort();
- const string = keys.reduce((acc, key) => acc + `${key}=${params[key]}&`, '');
- string += `key=${API_KEY}`;
- return crypto.createHash('md5').update(string).digest('hex').toUpperCase();
- }
- router.post('/pay', async (req, res) => {
- const { orderId, totalPrice } = req.body;
- const nonceStr = generateNonceStr();
- const body = '商品购买'; // 商品描述
- const spbillCreateIp = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
- const notifyUrl = 'https://your-server-url.com/notify'; // 支付结果通知地址
- const tradeType = 'JSAPI';
- const openid = req.body.openid; // 从前端传递过来的openid
- const params = {
- appid: APP_ID,
- mch_id: MCH_ID,
- nonce_str: nonceStr,
- body,
- out_trade_no: orderId,
- total_fee: totalPrice * 100, // 金额单位为分
- spbill_create_ip: spbillCreateIp,
- notify_url: notifyUrl,
- trade_type: tradeType,
- openid
- };
- const sign = generateSign(params);
- params.sign = sign;
- const xml = `<?xml version="1.0" encoding="UTF-8"?>
- <xml>
- ${Object.keys(params).map(key => `<${key}>${params[key]}</${key}>`).join('')}
- </xml>`;
- try {
- const response = await axios.post('https://api.mch.weixin.qq.com/pay/unifiedorder', xml, {
- headers: {
- 'Content-Type': 'application/xml'
- }
- });
- const { prepay_id } = response.data;
- const payParams = {
- timeStamp: Math.floor(Date.now() / 1000).toString(),
- nonceStr,
- package: `prepay_id=${prepay_id}`,
- signType: 'MD5'
- };
- payParams.paySign = generateSign(payParams);
- res.status(200).send(payParams);
- } catch (err) {
- res.status(500).send({ message: '预支付订单生成失败' });
- }
- });
- module.exports = router;
复制代码 在小程序端,发起支付请求的代码如下:
- Page({
- payOrder: function() {
- const orderId = '123456'; // 假设订单ID
- const totalPrice = 100; // 假设订单总价
- const openid = wx.getStorageSync('openid');
- wx.request({
- url: 'https://your-server-url.com/pay',
- method: 'POST',
- data: { orderId, totalPrice, openid },
- success: (res) => {
- const payParams = res.data;
- wx.requestPayment({
- ...payParams,
- success: (res) => {
- wx.showToast({
- title: '支付成功',
- icon:'success'
- });
- // 支付成功后可以跳转到订单详情页面或其他操作
- },
- fail: (err) => {
- wx.showToast({
- title: '支付失败',
- icon: 'none'
- });
- }
- });
- },
- fail: (err) => {
- wx.showToast({
- title: '获取支付参数失败',
- icon: 'none'
- });
- }
- });
- }
- });
复制代码
- 处理支付结果:微信支付完成后,微佩服务器会向之前设置的支付结果通知地址(notifyUrl)发送支付结果通知。后端必要在该地址对应的接口中吸取通知,验证通知的合法性,并处理订单状态更新等操作。假设后端利用 Node.js 和 Express 框架,处理支付结果通知的代码示比方下:
- const express = require('express');
- const router = express.Router();
- const axios = require('axios');
- const { APP_ID, MCH_ID, API_KEY } = require('../config');
- const crypto = require('crypto');
- // 验证签名
- function verifySign(xmlData) {
- const data = {};
- const parser = new DOMParser();
- const xmlDoc = parser.parseFromString(xmlData, 'text/xml');
- const elements = xmlDoc.getElementsByTagName('xml')[0].childNodes;
- for (let i = 0; i < elements.length; i++) {
- data[elements[i].nodeName] = elements[i].textContent;
- }
- const keys = Object.keys(data).sort();
- const string = keys.reduce((acc, key) => acc + `${key}=${data[key]}&`, '');
- string += `key=${API_KEY}`;
- const sign = crypto.createHash('md5').update(string).digest('hex').toUpperCase();
- return sign === data.sign;
- }
- router.post('/notify', async (req, res) => {
- const xmlData = req.body;
- if (!verifySign(xmlData)) {
- return res.status(400).send('签名验证失败');
- }
- const parser = new DOMParser();
- const xmlDoc = parser.parseFromString(xmlData, 'text/xml');
- const resultCode = xmlDoc.getElementsByTagName('result_code')[0].textContent;
- const outTradeNo = xmlDoc.getElementsByTagName('out_trade_no')[0].textContent;
- if (resultCode === 'SUCCESS') {
- // 处理订单状态更新等操作,例如将订单状态更新为已支付
- // 这里假设调用后端的更新订单状态接口
- try {
- await axios.post('https://your-server-url.com/updateOrderStatus', {
- orderId: outTradeNo,
- status: '已支付'
- });
- res.send('<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>');
- } catch (err) {
- res.status(500).send('<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[处理订单状态更新失败]]></return_msg></xml>');
- }
- } else {
- res.send('<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[支付失败]]></return_msg></xml>');
- }
- });
- module.exports = router;
复制代码 通过以上步调,就完成了微信小程序中订单天生与支付功能的开辟,实现了用户在小程序中购买商品并完成支付的完备流程。
项目优化与美满
性能优化
分页加载:在商品展示页面,如果商品数据量较大,一次性加载全部数据会导致页面加载缓慢,影响用户体验。可以采用分页加载的方式,每次只加载当前页面所需的数据。比方,在商品列表请求接口时,添加page和pageSize参数,后端根据这两个参数返回对应页码和数目的数据。在小程序端,初始加载第一页数据,当用户滚动到页面底部时,触发加载下一页数据的操作。相关代码如下:
- Page({
- data: {
- goodsList: [],
- currentPage: 1,
- pageSize: 10,
- isLoading: false,
- hasMoreData: true
- },
- onLoad: function() {
- this.loadGoodsList();
- },
- onPageScroll: function(e) {
- const { scrollTop, windowHeight, scrollHeight } = e.detail;
- if (scrollTop + windowHeight >= scrollHeight - 20 &&!this.data.isLoading && this.data.hasMoreData) {
- this.loadGoodsList();
- }
- },
- loadGoodsList: function() {
- if (this.data.isLoading) return;
- this.setData({
- isLoading: true
- });
- wx.request({
- url: 'https://api.example.com/goods',
- method: 'GET',
- data: {
- page: this.data.currentPage,
- pageSize: this.data.pageSize
- },
- success: (res) => {
- if (res.statusCode === 200) {
- const newData = res.data;
- const hasMoreData = newData.length >= this.data.pageSize;
- this.setData({
- goodsList: this.data.goodsList.concat(newData),
- currentPage: this.data.currentPage + 1,
- isLoading: false,
- hasMoreData: hasMoreData
- });
- } else {
- console.error('请求失败,状态码:', res.statusCode);
- this.setData({
- isLoading: false
- });
- }
- },
- fail: (err) => {
- console.error('网络请求出错:', err);
- this.setData({
- isLoading: false
- });
- }
- });
- }
- });
复制代码
- 图片懒加载:商品展示页面通常会有大量商品图片,如果这些图片同时加载,会消耗大量的网络资源和内存,导致页面加载缓慢乃至卡顿。可以利用微信小程序提供的IntersectionObserver组件来实现图片懒加载。首先在 WXML 文件中为图片组件添加data-src属性存储图片真实 URL,初始src属性为空。然后利用IntersectionObserver监听图片是否进入可视地区,当进入可视地区时,将data-src的值赋给src属性,实现图片加载。代码如下:
- <view class="goods-list">
- <block wx:for="{
-
- {goodsList}}" wx:key="id">
- <view class="goods-item">
- <image class="goods-img" src="{
-
- {item.imgUrl? item.imgUrl : ''}}" data-src="{
-
- {item.imgUrl}}" mode="aspectFill"></image>
- <view class="goods-info">
- <text class="goods-name">{
-
- {item.name}}</text>
- <text class="goods-price">¥{
-
- {item.price}}</text>
- <button class="add-cart-btn" bindtap="addToCart" data-goodsId="{
-
- {item.id}}">加入购物车</button>
- </view>
- </view>
- </block>
- </view>
复制代码- Page({
- data: {
- goodsList: []
- },
- onLoad: function() {
- this.fetchGoodsList();
- this.initImageObserver();
- },
- fetchGoodsList: function() {
- // 数据请求代码...
- },
- initImageObserver: function() {
- const query = wx.createIntersectionObserver(this);
- query.selectAll('.goods-img').observe((res) => {
- res.forEach((item) => {
- if (item.intersectionRatio > 0) {
- const index = item.dataset.index;
- this.setData({
- [`goodsList[${index}].imgUrl`]: item.target.dataset.src
- });
- }
- });
- });
- }
- });
复制代码 安全步伐
- 采用 HTTPS:小程序的全部网络请求都必须利用 HTTPS 协议,这是微信官方的强制要求。HTTPS 协议在 HTTP 的根本上参加了 SSL/TLS 加密层,可以或许对数据传输举行加密,确保数据在传输过程中不被窃取、窜改。在配置服务器时,必要申请 SSL 证书,并将其摆设到服务器上,使服务器支持 HTTPS 访问。比方,利用阿里云的 SSL 证书服务,在申请并获取证书后,按照阿里云的文档引导将证书配置到服务器的 Web 服务器(如 Nginx 或 Apache)中。以 Nginx 为例,配置文件中添加如下代码:、
- server {
- listen 443 ssl;
- server_name your_domain.com;
- ssl_certificate /path/to/your_cert.pem;
- ssl_certificate_key /path/to/your_key.pem;
- ssl_session_cache shared:SSL:1m;
- ssl_session_timeout 5m;
- ssl_ciphers HIGH:!aNULL:!MD5;
- ssl_prefer_server_ciphers on;
- location / {
- # 其他配置...
- }
- }
复制代码
- 防止 XSS 攻击:XSS(跨站脚本攻击)是一种常见的前端安全漏洞,攻击者通过在网页中注入恶意脚本,窃取用户信息或控制用户操作。在小程序开辟中,要对用户输入的数据举行严酷的过滤和转义,制止恶意脚本被执行。比方,在用户提交评论等数据时,利用工具函数对输入内容举行过滤。可以利用html-escaper库来举行转义,安装后在代码中利用:
- import escaper from 'html-escaper';
- // 假设inputValue是用户输入的值
- const inputValue = '<script>alert("XSS攻击")</script>';
- const escapedValue = escaper.escape(inputValue);
- // 此时escapedValue的值为 <script>alert("XSS攻击")</script>
- // 将escapedValue存储到数据库或展示到页面上,就可以防止XSS攻击
复制代码 同时,在利用setData更新页面数据时,要确保数据的安全性,制止直接将未经处理的用户输入数据用于渲染页面。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |