一、概述
在微信小程序开发中,以“慕尚花坊”为主题的项目是一个很好的实践案例。以下是根据微信小程序开发者工具的使用和花店项目开发的实践经验整理的笔记。
二、项目初始化与准备
1.注册与登录:
在微信公众平台(mp.weixin.qq.com)注册小程序账号,获取AppID。
下载并安装微信开发者工具,使用注册D的微信号扫码登录.
2.创建项目:
在开发者工具中点击“添加项目”,填写AppID和项目名称。
选择项目目录,并设置项目配置(如:项目名称、AppID、项目描述等)。
3.项目初始化
首先找到app.js,将App({})里面的代码全部删除。
删除 app.json 中 pages 下的 "rendererOptions" 以及 "componentFramework" 字段。
重置 app.wxss 中的代码。
删除 app.json 中 pages 下的 "pages/logs/logs" 路径,同时删除 pages/logs ⽂件夹。
删除 components 中的⾃定义组件。
重置 pages/index ⽂件夹下的 index.js 、 index.wxss 、 index.html 以及 index.json ⽂件。
更新 utils 下 util.js 的⽂件名为 formatTime.js。
三、开发工具使用
1.编辑器:
微信开发者工具提供了代码编辑器,支持JS、WXML、WXSS和JSON文件的编辑。
支持语法高亮、自动补全等特性,提高编码效率。
2.调试器:
可以预览小程序效果,模拟用户操作。
提供Console、Sources、NetworkStorage等面板,便于调试。
3.模拟器:
支持多种设备和屏幕尺寸的模拟,便于适配不同设备。
提供多种网络环境模拟,测试网络请求的稳定性和速度。
四、项目开发
1.页面设计:
根据花店业务设计首页、商品分类、商品详情、购物车、结算等页面。
使用WXML和WXSS定义页面结构和样式。
2.数据衣百 :
通过wx.request或wx.cloud.callFunction等方式实现与服务器的数据交互。
使用JSON格式传输数据,保证数据的正确性和安全性。
3.组件使用:
结合Vant组件库实现页面结构的搭建。
2.CSS拓展语言:
使用Scss绘制页面的结构增强样式的灵活性和可维护性。
3.小程序内置API:
涉及交豆、支付、文件上传、地图走位、网络请求、预览图片、本地存储等功能,确保小程序功能的全面性和易用性。
4.小程序分包加载:
采用分包技术,降低小程序的启动时间和包体积,提升用户体验度。
5.自定义组件开发:
将页面内的功能模块抽象成自定义组件,如banner组件、entrance组件等,实现代码的复用,提高开发效率。
6.网络请求封装:
包括request方法封装、快捷方式封装、响应拦载器、请求拦截器等确保网络请求的安全性和稳走性。
7.LBS服务:
使用腾讯地图服务进行LBS逆地址解析,实现选择收货地址功能,增强用户体验。
五、项目首页
1. 轮播图区域
2. 商品导航区域
3. 活动宣传区域
4. 猜你喜欢区域
5. 人气推荐区域
在实现这些功能之前,我们需要先获取数据,在获取数据以后,然后进行页面的渲染,同时完成进行功能的开发。
因为需要同时获取 5 个接口的数据,所以我们使用并发请求来实现。这样能够提升页面的渲染速度。
实现步骤
1. 封装接口请求函数,可以一个个封装,也可以直接使用 `all` 方法进行封装
2. 在页面 .js 文件中导入封装的接口 `API` 函数
3. 在 `onLoad` 钩子函数中调用方法,获取首页轮播图数据
<!-- 引入骨架屏 -->
<import src="./skeleton/index.skeleton.wxml" />
<!-- 使用骨架屏 -->
<template is="skeleton" wx:if="{{ loading }}" />
<view wx:else class="index-container">
<!--首页背景图-->
<view class="window-bgc"></view>
<!-- 页面主体区域 -->
<view class="container">
<!-- 轮播图区域 -->
<banner bannerList="{{ bannerList }}" />
<!-- 导航分类 -->
<entrance cateList="{{ categoryList }}" />
<!-- 广告区域 -->
<view class="adver">
<view class="adver-left">
<navigator
url="/modules/goodModule/pages/goods/list/list?category2Id={{ activeList[0].category2Id }}"
>
<image src="{{ activeList[0].imageUrl }}" mode="widthFix" />
</navigator>
</view>
<view class="adver-right">
<view>
<navigator
url="/modules/goodModule/pages/goods/list/list?category2Id={{ activeList[1].category2Id }}"
>
<image src="{{ activeList[1].imageUrl }}" mode="widthFix" />
</navigator>
</view>
<view>
<navigator
url="/modules/goodModule/pages/goods/list/list?category2Id={{ activeList[2].category2Id }}"
>
<image src="{{ activeList[2].imageUrl }}" mode="widthFix" />
</navigator>
</view>
</view>
</view>
<!-- 商品列表 -->
<goods-list title="猜你喜欢" list="{{ guessList }}"></goods-list>
<goods-list title="人气推荐" list="{{ hotList }}"></goods-list>
</view>
</view>
首页展示
六、项目分类
01.商品分类
在商品分类页面我们主要实现三个功能:
1. 一级分类的渲染
2. 一级分类的切换
3. 二级分类的渲染
02.实现步骤
1. 在项目根目录下 `api` 目录下新建 `category.js` 文件,用来管理分类页面接口请求
2. 在该文件中导入封装的网络请求模块,根据接口文档,创建获取分类数据的 `API` 函数 `reqCategoryData`
3. 在 `/pages/category/category.js` 中导入封装好的获取分类数据的 `API` 函数
4. 页面数据需要在页面加载的时候进行调用,因此需要在 `onLoad` 钩子函数中调用 `reqCategoryData` 方法
5. 在获取到数据以后,使用后端返回的数据对页面进行渲染
03.代码
` api/category.js`
// 导入封装的网络请求模块实例
import http from '../utils/http'
/**
* @description 获取商品分类的数据
* @returns Promise
*/
export const reqCategoryData = () => {
return http.get('/index/findCategoryTree')
}
// 导入封装的接口 API
import { reqCategoryData } from '../../api/category'
Page({
/**
* 页面的初始数据
*/
data: {
categoryList: [] // 分类数据列表
},
// 生命周期函数--监听页面加载
onLoad(options) {
// 获取页面中使用的
this.getCategoryData()
},
// 获取页面初始化时,页面中使用的数据
async getCategoryData() {
// 调用接口获取分类的数据
const res = await reqCategoryData()
this.setData({
categoryList: res.data
})
},
// 导航分类点击事件
// coding...
})
滚动视图区域
<!-- 右侧的滚动视图区域 -->
<scroll-view class="right-view" scroll-y enable-flex="true">
<view class="right-view-item" wx:for="{{ category[activeIndex].children }}" wx:key="id">
<navigator class="navigator" url="/pages/goods/list/list?category2Id={{item.id}}">
<image class="" src="{{ item.imageUrl }}"></image>
<text class='goods_item_name'>{{ item.name }}</text>
</navigator>
</view>
</scroll-view>
六、框架扩展
01.mobx-miniprogram 介绍
目前已经学习了 6 种小程序页面、组件间的数据通信方案,分别是:
1. 数据绑定:`properties`
2. 获取组件实例:`this.selectComponent()`
3. 事件绑定:`this.triggerEvent()`
4. 获取应用实例:`getApp()`
5. 页面间通信:`EventChannel`
6. 事件总线:`pubsub-js`
`mobx-miniprogram` 是针对微信小程序开发的一个简单、高效、轻量级状态管理库,它基于`Mobx`状态管理框架实现。
使用 `mobx-miniprogram` 定义管理的状态是响应式的,当状态一旦它改变,所有关联组件都会自动更新相对应的数据
通过该扩展工具库,开发者可以很方便地在小程序中全局共享的状态,并自动更新视图组件,从而提升小程序的开发效率
需要注意:在使用 `mobx-miniprogram` 需要安装两个包:`mobx-miniprogram` 和 `mobx-miniprogram-bindings`
1. `mobx-miniprogram` 的作用:创建 `Store` 对象,用于存储应用的数据
2. `mobx-miniprogram-bindings` 的作用:将状态和组件、页面进行绑定关联,从而在组件和页面中操作数据
七、用户管理
01.用户登录-什么是 token
`Token` 是服务器生成的一串字符串,用作客户端发起请求的一个身份令牌。当第一次登录成功后,服务器生成一个 `Token` 便将此 `Token` 返回给客户端,客户端在接收到 `Token` 以后,会使用某种方式将 `Token` 保存到本地。以后客户端发起请求,只需要在请求头上带上这个 `Token` ,服务器通过验证 `Token` 来确认用户的身份,而无需再次带上用户名和密码。
02.用户登录-实现小程序登录功能
1.当用户没有登录的时候,需要点击个人中心的头像,跳转到登录页面进行登录。在登录成功以后,需要再次返回到个人中心页面
在登录页面我们使用了 `Vant` 提供的两个组件来进行页面结构的绘制
给登录按钮绑定点击事件,在事件处理程序中,调用 `wx.login` 获取 **临时登录凭证code** ,
然后调用后端接口,将 **临时登录凭证code** 传递给后端
根据接口文档封装接口 `API` 函数,当点击授权登录按钮的时候调用 `API` 函数,在获取到 `token` 以后,将 `token` 存储到本地,然后跳转到登录之前的页面。
2.实现步骤:
1. 给 `form` 表单绑定 `bindsubmit` 事件,用来获取输入框最新的值
2. 给 `input` 组件绑定 `type` 属性,属性值为 `nickname`,获取微信昵称
3. 给 `input` 组件绑定 `bindinput` 事件,获取用户输入最新的昵称
4. 将 `formType` 设置为 `submit` 当用户点击确定后,触发 `form` 表单的 `bindsubmit` 事件
5. 在 `form` 表单的 `bindsubmit` 事件中进行赋值
6. 给 `form` 表单的取消按钮绑定事件,取消弹框
代码
` modules/settingModule/pages/profile/profile.wxml`
<van-dialog
custom-style="position: relative"
use-slot
title="修改昵称"
show="{{ isShowPopup }}"
showConfirmButton="{{ false }}"
showCancelButton="{{ false }}"
transition="fade"
>
+ <form bindsubmit="getNewName">
<!-- type 设置为 nickname 是为了获取微信昵称 -->
<input
class="input-name"
+ type="nickname"
+ bindinput="getNewName"
name="nickname"
value="{{ userInfo.nickname }}"
/>
<view class="dialog-content">
+ <button class="cancel" bindtap="cancelForm">取消</button>
+ <!-- 将 formType 设置为 submit 当用户点击确定后,触发 form 表单的 bindsubmit 事件 -->
+ <button class="confirm" type="primary" formType="submit">确定</button>
</view>
</form>
</van-dialog>
` modules/settingModule/pages/profile/profile.js`
import { reqUpdateUserInfo, reqUserInfo } from '../../../../api/user'
import { createStoreBindings } from 'mobx-miniprogram-bindings'
import store from '../../../../stores/index'
Page({
// 页面的初始数据
data: {
avatarUrl: '/static/images/avatar.png',
isShowPopup: false,
userInfo: {
nickname: '',
headimgurl: ''
}
},
// 生命周期函数--监听页面加载
onLoad(options) {
createStoreBindings(this, {
store,
fields: ['userInfo'],
actions: ['setUserInfo']
})
},
getAvatar(e) {
// coding...
},
// 更新用户信息
async updateUserInfo() {
// coding...
},
// 显示修改昵称弹框
onUpdateNickName() {
this.setData({
isShowPopup: true
})
},
// 获取最新的用户昵称
getNewName(e) {
// 获取用户输入的最新的昵称
const { nickname } = e.detail.value
this.setData({
'userInfo.nickname': nickname,
isShowPopup: false
})
},
// 取消更新用户昵称
cancelForm() {
this.setData({
isShowPopup: false
})
}
})
八、地址管理
01.收货地址
1. 收货地址列表
2. 新增收货地址
3. 编辑收货地址
4. 删除收货地址
02.步骤
1. 在新增收货地址页面 `data` 中声明所需要的字段
2. 定义收货地址所需要的全部接口 `API` 函数
03.代码
Page{{
// 页面的初始数据
data: {
name: '', // 收货人
phone: '', // 手机号
provinceName: '', // 省
provinceCode: '', // 省 编码
cityName: '', // 市
cityCode: '', // 市 编码
districtName: '', // 区
districtCode: '', // 区 编码
address: '', // 详细地址
fullAddress: '', // 完整地址 (省 + 市 + 区 + 详细地址)
isDefault: 0 // 设置默认地址,是否默认地址 → 0:否 1:是
}
}}
04.展示图
九、商品管理
01.配置商品管理分包
随着项目功能的增加,项目体积也随着增大,从而影响小程序的加载速度,影响用户的体验。
因此我们需要将 `商品列表` 和 `商品详情` 功能配置成一个分包,
当用户在访问设置页面时,还预先加载 `商品列表` 和 `商品详情` 所在的分包
02.实现步骤
1. 在 `modules` 目录下创建 `goodModule` 文件夹,用来存放商品管理分包
2. 在 `app.json` 的 `subpackages` 进行商品管理分包配置
5. 在 `app.json` 的 `preloadRule` 进行商品管理分包配置
04.代码
{
"subPackages": [
{
"root": "modules/settingModule",
"name": "settingModule",
"pages": [
"pages/address/add/index",
"pages/address/list/index",
"pages/profile/profile"
]
},
{
"root": "modules/goodModule",
"name": "goodModule",
"pages": ["pages/goods/list/list", "pages/goods/detail/detail"]
}
],
"preloadRule": {
"pages/settings/settings": {
"network": "all",
"packages": ["settingModule"]
},
"pages/category/category": {
"network": "all",
"packages": ["goodModule"]
}
}
}
05.商品详情-详情图片预览功能
如果想实现该功能,需要使用小程序提供的 `API`:`wx.previewImage()`,用来在新页面中全屏预览图片。预览的过程中用户可以进行保存图片、发送给朋友等操作。
06.实现步骤
1. 给展示大图的 `image` 组件绑定点击事件,同时通过自定义属性的方式,传递当前需要显示的图片http 链接
2. 同时商品详情的数组数据传递给 `urls` 数组即可
<!-- 商品大图 -->
<view class="banner-img">
<image
class="img"
src="{{ goodsInfo.imageUrl }}"
bindtap="previewImg"
/>
</view>
// 预览商品图片
previewImg() {
// 调用预览图片的 API
wx.previewImage({
urls: this.data.goodsInfo.detailList
})
}
十、购物车
01.购物车-封装购物车接口 API
为了方便后续进行购物车模块的开发,我们在这一节将购物车所有的接口封装成接口 API 函数
代码
import http from '../utils/http'
/**
* @description 获取购物车列表数据
* @returns Promise
*/
export const reqCartList = () => {
return http.get('/mall-api/cart/getCartList')
}
/**
* @description 加入购物车
* @param {*} data
* @returns Promise
*/
export const reqAddCart = (data) => {
return http.get(`/cart/addToCart/${data.goodsId}/${data.count}`, data)
}
/**
* @description 更新商品的选中状态
* @param {*} goodsId 商品 id
* @param {*} isChecked 商品的选中状态
* @returns Promise
*/
export const reqUpdateChecked = (goodsId, isChecked) => {
return http.get(`/cart/checkCart/${goodsId}/${isChecked}`)
}
/**
* @description 全选和全不选
* @param {*} isChecked 商品的选中状态
* @returns Promise
*/
export const reqCheckAllCart = (isChecked) => {
return http.get(`/cart/checkAllCart/${isChecked}`)
}
/**
* @description 删除购物车商品
* @param {*} goodsId 商品 id
* @returns Promise
*/
export const reqDelCart = (goodsId) => {
return http.get(`/cart/delete/${goodsId}`)
}
02. 加入购物车-模板分析和渲染
点击加入购物车和立即购买的时候,展示购物弹框,在弹框中需要用户选择购买数量和祝福语
点击加入购物车和立即购买,触发的是同一个弹框。
因此点击弹框中的确定按钮时,我们需要区分当前是加入购物车操作还是立即购买操作。
这时候定义一个状态 `buyNow` 做区分,`buyNow` 等于 1 代表是立即购买,否则是加入购物车
03.产品需求
1. 如果点击的是加入购物车,需要将当前商品加入到购物车
2. 如果点击的是立即购买,需要跳转到结算支付页面,立即购买该商品
3. 如果是立即购买,不支持购买多个商品
代码
<!-- 商品的底部商品导航 -->
<van-goods-action>
<!-- coding... -->
<van-goods-action-button text="加入购物车" type="warning" bindtap="handleAddcart" />
<van-goods-action-button text="立即购买" bindtap="handeGotoBuy" />
</van-goods-action>
<!-- 加入购物车、立即购买弹框 -->
<!-- show 控制弹框的隐藏和展示 -->
<!-- bind:close 点击关闭弹框时触发的回调 -->
<van-action-sheet show="{{ show }}" bind:close="onClose">
<view class="sheet-wrapper">
<!-- 代码略... -->
<!-- 购买数量弹框 -->
<view class="buy-btn" wx:if="{{ buyNow === 0 }}">
<!-- Stepper 步进器,由增加按钮、减少按钮和输入框组成,控制购买数量 -->
<van-stepper value="{{ count }}" bind:change="onChangeGoodsCount" />
</view>
<!-- 代码略... -->
</view>
</van-action-sheet>
04.项目展示
05. 购物车-删除购物车中的商品
点击删除按钮的时候,需要将对应的购物车商品进行删除
06.实现步骤
1. 导入封装的接口 `API` 函数,同时导入处理删除自动关闭效果的 `behaviors` 并进行注册
3. 在点击删除以后,调用 `API` 函数,在删除购物车商品成功以后,给用户提示
代码
<view bindtap="onSwipeCellPage">
<!-- 代码略 -->
<van-swipe-cell
class="goods-swipe"
right-width="{{ 65 }}"
id="swipe-cell-{{ item.goodsId }}"
bind:open="swipeCellOpen"
bind:click="onSwipeCellClick"
>
<van-cell-group border="{{ false }}">
<view class="goods-info">
<view class="left">
<van-checkbox
checked-color="#FA4126"
value="{{ item.isChecked }}"
bindchange="updateChecked"
data-id="{{ item.goodsId }}"
data-index="{{ index }}"
></van-checkbox>
</view>
<view class="mid">
<image class="img" src="{{ item.imageUrl }}" />
</view>
<view class="right">
<view class="title"> {{ item.name }} </view>
<view class="buy">
<view class="price">
<view class="symbol">¥</view>
<view class="num">{{ item.price }}</view>
</view>
<view class="buy-btn">
<van-stepper
min="1"
max="200"
integer
value="{{ item.count }}"
data-id="{{ item.goodsId }}"
data-index="{{ index }}"
data-oldbuynum="{{ item.count }}"
bindchange="changeBuyNum"
/>
</view>
</view>
</view>
</view>
</van-cell-group>
<view
slot="right"
class="van-swipe-cell__right"
bindtap="delCartGoods"
data-id="{{ item.goodsId }}"
>
删除
</view>
</van-swipe-cell>
<!-- 代码略 -->
</view>
十一、结算支付
01. 配置分包并跳转到结算页面
随着项目功能的增加,项目体积也随着增大,从而影响小程序的加载速度,影响用户的体验。
因此我们需要将 `结算支付` 功能配置成一个分包,
当用户在访问设置页面时,还预先加载 `结算支付` 所在的分包
代码
"subPackages": [
{
"root": "modules/settingModule",
"name": "settingModule",
"pages": [
"pages/address/add/index",
"pages/address/list/index",
"pages/profile/profile"
]
},
{
"root": "modules/goodModule",
"name": "goodModule",
"pages": ["pages/goods/list/list", "pages/goods/detail/detail"]
},
{
"root": "modules/orderPayModule",
"name": "orderPayModule",
"pages": [
"pages/order/detail/detail",
"pages/order/list/list"
]
}
],
"preloadRule": {
"pages/settings/settings": {
"network": "all",
"packages": ["settingModule"]
},
"pages/category/category": {
"network": "all",
"packages": ["goodModule"]
},
"pages/cart/cart": {
"network": "all",
"packages": ["orderPayModule"]
}
}
02. 小程序支付-支付状态查询
通过调用后端接口获取支付状态,如果支付成功,需要给用户提示,同时跳转到订单列表页面。
公司后端开始向微信服务器发送请求,查询支付结果
公司服务器会将微信服务器返回的支付结果,返回到客户端
客户端根据查询结果跳转到订单列表页面
代码
async advancePay() {
try {
const payParams = await reqPrePayInfo(this.orderNo)
if (payParams.code === 200) {
// payParams.data 就是获取的支付参数
// 调用 wx.requestPayment 发起微信支付
const payInfo = await wx.requestPayment(payParams.data)
// 获取支付结果
if (payInfo.errMsg === 'requestPayment:ok') {
// 查询订单的支付状态
const payStatus = await reqPayStatus(this.orderNo)
if (payStatus.code === 200) {
wx.redirectTo({
url: '/pages/order/list/index',
success: () => {
wx.toast({
title: '支付成功',
icon: 'success
})
}
})
}
}
}
} catch (error) {
wx.toast({
title: '支付失败,请联系客服',
icon: 'error'
})
}
},