首页、分类、搜索、商品列表、商品详情、购物车、支付
wepy init standard heima_ugo
命令,初始化小程序项目cd heima_ugo
进入项目根目录npm install
安装所有依赖项wepy build --watch
命令,开启 wepy 项目的实时编译功能src -> pages -> index.wpy
首页.prettierrc
配置文件内,新增 "semi": false
配置,防止每次格式化代码,添加分号的问题src -> app.wpy
中的代码,将 style
和 script
标签中,不必要的代码删除掉src -> components
和 src -> mixins
目录新建 src -> pages -> tabs
文件夹,用来存放所有 tabBar 相关的页面
删除 src -> pages -> index.wpy
页面,并在 tabs
目录中,新建 home.wpy
,cates.wpy
,search.wpy
,cart.wpy
,me.wpy
五个 tabBar 相关的页面
将页面路径,记录到 src -> app.wpy
文件的 config -> pages
节点中,示例代码如下:
pages: [
'pages/tabs/home',
'pages/tabs/cates',
'pages/tabs/search',
'pages/tabs/cart',
'pages/tabs/me'
]
新建 src -> assets
目录,并将素材中的 icons
文件夹,拷贝到项目 src -> assets
目录中
在 src -> app.wpy
文件中,新增 tabBar
节点,并做如下配置:
tabBar: {
// 选中的文本颜色
selectedColor: '#D81E06',
// tabBar 的列表
list: [
{
// 页面路径
pagePath: 'pages/tabs/home',
// 显示的文本
text: '首页',
// 默认图标
iconPath: '/assets/icons/home.png',
// 选中图标
selectedIconPath: '/assets/icons/home-active.png'
},
{
pagePath: 'pages/tabs/cates',
text: '分类',
iconPath: '/assets/icons/cates.png',
selectedIconPath: '/assets/icons/cates-active.png'
},
{
pagePath: 'pages/tabs/search',
text: '搜索',
iconPath: '/assets/icons/search.png',
selectedIconPath: '/assets/icons/search-active.png'
},
{
pagePath: 'pages/tabs/cart',
text: '购物车',
iconPath: '/assets/icons/cart.png',
selectedIconPath: '/assets/icons/cart-active.png'
},
{
pagePath: 'pages/tabs/me',
text: '我的',
iconPath: '/assets/icons/my.png',
selectedIconPath: '/assets/icons/my-active.png'
}
]
}
打开 src -> app.wpy
文件,找到 window
节点,并配置如下:
window: {
// 页面背景色
backgroundTextStyle: 'dark',
// 导航条背景色
navigationBarBackgroundColor: '#D81E06',
// 导航条标题文本
navigationBarTitleText: '黑马优购',
// 导航条标题文字颜色
navigationBarTextStyle: 'white'
}
打开 src -> app.wpy
文件
找到 constructor() 构造函数
在构造函数的最后,新增如下代码:
constructor() {
super()
this.use('requestfix')
// 通过下面这一行代码,可以为异步的API,
// 开启Promise功能,这样,异步API调用的结果,返回值是Promise对象
this.use('promisify')
}
获取轮播图数据
// 获取轮播图数据的函数
async getSwiperData() {
const {
data: res } = await wepy.get('/home/swiperdata')
if (res.meta.status !== 200) {
return wepy.baseToast()
}
this.swiperList = res.message
this.$apply()
}
使用 wepy.showToast()
弹框提示
使用 swiper
组件和 swiper-item
组件渲染轮播图效果
使用 navigator
组件将 images
图片包裹起来,从而点击图片实现跳转
<swiper circular indicator-dots>
<swiper-item wx:for="{
{swiperList}}" wx:key="index">
<navigator url="{
{item.navigator_url}}" open-type="{
{item.open_type}}">
<image src="{
{item.image_src}}" />
navigator>
swiper-item>
swiper>
设置 swiper
组件的高度为 350rpx
从而实现轮播图在不同屏幕的自适应
swiper {
height: 350rpx;
navigator,
image {
height: 100%;
width: 750rpx;
}
}
// 获取首页分类相关的数据项
async getCateItems() {
const {
data: res } = await wepy.get('/home/catitems')
if (res.meta.status !== 200) {
return wepy.baseToast()
}
this.cateItems = res.message
this.$apply()
}
<view class="cates">
<block wx:for="{
{cateItems}}" wx:key="index">
<navigator url="/pages/tabs/cates" open-type="{
{item.open_type}}" wx:if="{
{item.navigator_url !== undefined}}" hover-class="none">
<image src="{
{item.image_src}}" />
navigator>
<image src="{
{item.image_src}}" wx:else/>
block>
view>
.cates {
display: flex;
justify-content: space-around;
margin: 40rpx 0;
image {
width: 128rpx;
height: 140rpx;
}
}
onLoad() {
this.getSwiperData()
this.getCateItems()
// 在页面加载完成后,自动获取楼层数据
this.getFloorData()
}
// 获取楼层相关的数据
async getFloorData() {
const {
data: res } = await wepy.get('/home/floordata')
if (res.meta.status !== 200) {
return wepy.baseToast()
}
this.floorData = res.message
// 通知页面,data中数据发生了变化,需要强制页面重新渲染一次
this.$apply()
}
<view class="floor-container">
<view class="floor-item" wx:for="{
{floorData}}" wx:key="index">
<image class="floor-item-title" src="{
{item.floor_title.image_src}}"/>
<view class="floor-img-box">
<image class="floor-item-pic" wx:for="{
{item.product_list}}" wx:key="index" src="{
{item.image_src}}" style="width: {
{
item.image_width}}rpx;" @tap="goGoodsList({
{item.navigator_url}})"/>
view>
view>
view>
.floor-container {
.floor-item {
.floor-item-title {
height: 50rpx;
width: 640rpx;
display: block;
}
.floor-img-box {
.floor-item-pic {
float: left;
height: 190rpx;
margin: 8rpx;
margin-top: 0;
&:nth-child(1) {
height: 390rpx;
}
}
}
}
}
methods = {
// 点击楼层中的每一张图片,都要跳转到商品列表页面
goGoodsList(url) {
wepy.navigateTo({
url
})
}
}
mixin
文件中为了精简每个小程序页面的代码,可以将 script 中的业务逻辑,抽离到对应的 mixin 文件中,具体步骤:
在 src -> mixins
文件夹中,新建与页面路径对应的 .js
文件,并初始化基本的代码结构如下:
import wepy from 'wepy'
// 注意,必须继承自 wepy.mixin
export default class extends wepy.mixin {
}
在对应的页面中,可以导入并使用对应的 mixin,具体代码如下:
<script>
import wepy from 'wepy'
// 1. 导入外界的 mixin 文件,并接受
// @ 就代表 src 这一层路径
import mix from '@/mixins/tabs/home.js'
export default class extends wepy.page {
// 2. 把导入的 mix 对象,挂载到 mixins 这个数据中就行
mixins = [mix]
}
</script>
baseToast
函数提示错误消息为了提高项目的维护性、可用性、扩展性,可以将常用的 js 逻辑,封装到 src -> baseAPI.js
文件中:
import wepy from 'wepy'
/**
* 弹框提示一个无图标的 Toast 消息
* @str 要提示的消息内容
*/
wepy.baseToast = function(str = '获取数据失败!') {
wepy.showToast({
title: str,
// 弹框期间不会携带任何图标
icon: 'none',
duration: 1500
})
}
在 app.wpy
中导入执行 baseAPI.js
文件中的代码:
<script>
import wepy from 'wepy'
import 'wepy-async-function'
// 导入并执行 baseAPI.js 中的所有代码
import '@/baseAPI.js'
</script>
wepy.get
函数发起get请求在小程序项目中,需要经常发起数据请求,因此,可以将 wepy.request()
函数封装,在全局挂在 wepy.get()
函数,从而发起 Get 请求,代码如下:
// src/baseAPI.js
import wepy from 'wepy'
// 请求根路径
const baseURL = 'https://www.zhengzhicheng.cn/api/public/v1'
/**
* 发起 get 请求的 API
* @url 请求的地址,为相对路径,必须以 / 开头
* @data 请求的参数对象
*/
wepy.get = function(url, data = {
}) {
return wepy.request({
url: baseURL + url,
method: 'GET',
data
})
}
wepy.post
函数发起get请求在小程序项目中,需要经常发起数据请求,因此,可以将 wepy.request()
函数封装,在全局挂在 wepy.post()
函数,从而发起 Post 请求,代码如下:
// src/baseAPI.js
import wepy from 'wepy'
// 请求根路径
const baseURL = 'https://www.zhengzhicheng.cn/api/public/v1'
/**
* 发起 post 请求的 API
* @url 请求的地址,为相对路径,必须以 / 开头
* @data 请求的参数对象
*/
wepy.post = function (url, data = {
}) {
return wepy.request({
url: baseURL + url,
method: 'POST',
data
})
}
async getCateList() {
const {
data: res } = await wepy.get('/categories')
if (res.meta.status !== 200) {
return wepy.baseToast()
}
this.cateList = res.message
this.secondCate = res.message[0].children
this.$apply()
}
vant
小程序UI组件库vant-weapp
的 Github 主页 https://github.com/youzan/vant-weappClone or Download
按钮Download ZIP
vant-weapp-dev.zip
lib
目录重命名为 vant
vant
的目录,复制到 src -> assets
目录中打开 app.wpy
文件
在 config
节点内,新增 usingComponents
节点,具体代码如下:
config = {
// 引用并注册全局组件
usingComponents: {
// 徽章组件
'van-badge': './assets/vant/badge/index',
'van-badge-group': './assets/vant/badge-group/index'
}
}
<van-badge-group active="{
{ active }}" bind:change="onChange">
<van-badge title="{
{item.cat_name}}" wx:for="{
{cateList}}" wx:key="index" />
van-badge-group>
scroll-view
优化左侧分类的滚动效果
<scroll-view class="left" scroll-y style="height: 200px;">
<van-badge-group active="{
{ active }}" bind:change="onChange">
<van-badge title="{
{item.cat_name}}" wx:for="{
{cateList}}" wx:key="index" />
van-badge-group>
scroll-view>
onLoad() {
// 动态获取屏幕可用的高度
this.getWindowHeight()
this.getCateList()
}
// 动态获取屏幕可用的高度
async getWindowHeight() {
const res = await wepy.getSystemInfo()
if (res.errMsg === 'getSystemInfo:ok') {
this.wh = res.windowHeight
this.$apply()
}
}
methods = {
onChange(e) {
// e.detail 是点击项的索引
// console.log(e.detail)
this.secondCate = this.cateList[e.detail].children
}
}
<scroll-view class="right" scroll-y style="height: {
{
wh}}px;">
<block wx:for="{
{secondCate}}" wx:key="index">
<van-row>
<van-col span="24" style="text-align:center;">
<text class="cate_title" space="ensp">/ {
{item.cat_name}} /text>
van-col>
van-row>
<van-row>
<block wx:for="{
{item.children}}" wx:key="index">
<van-col span="8" class="cell" @tap="goGoodsList({
{item.cat_id}})">
<image src="{
{item.cat_icon}}" class="thumbImg" />
<view class="thumbTitle">{
{item.cat_name}}view>
van-col>
block>
van-row>
block>
scroll-view>
methods = {
// 点击跳转到商品列表页面,同时把商品分类的 cid 传递过去
goGoodsList(cid) {
wepy.navigateTo({
url: '/pages/goods_list?cid=' + cid
})
}
}
可以使用WePY提供的全局拦截器对原生API的请求进行拦截。
具体方法是配置API的config、fail、success、complete回调函数。参考示例:
import wepy from 'wepy';
export default class extends wepy.app {
constructor () {
// this is not allowed before super()
super();
// 拦截request请求
this.intercept('request', {
// 发出请求时的回调函数
config (p) {
// 对所有request请求中的OBJECT参数对象统一附加时间戳属性
p.timestamp = +new Date();
console.log('config request: ', p);
// 必须返回OBJECT参数对象,否则无法发送请求到服务端
return p;
},
// 请求成功后的回调函数
success (p) {
// 可以在这里对收到的响应数据对象进行加工处理
console.log('request success: ', p);
// 必须返回响应数据对象,否则后续无法对响应数据进行处理
return p;
},
//请求失败后的回调函数
fail (p) {
console.log('request fail: ', p);
// 必须返回响应数据对象,否则后续无法对响应数据进行处理
return p;
},
// 请求完成时的回调函数(请求成功或失败都会被执行)
complete (p) {
console.log('request complete: ', p);
}
});
}
}
打开 app.wpy
,在 constructor()
构造函数中,通过拦截器实现loading效果,具体代码如下:
constructor() {
super()
this.use('requestfix')
// 通过这一行代码,可以为异步的API,开启Promise功能,这样,异步API调用的结果,返回值是Promise对象
this.use('promisify')
// 拦截器
this.intercept('request', {
// 发出请求时的回调函数
config(p) {
// 显示loading效果
wepy.showLoading({
title: '数据加载中...'
})
// 必须返回OBJECT参数对象,否则无法发送请求到服务端
return p
},
// 请求成功后的回调函数
success(p) {
// 必须返回响应数据对象,否则后续无法对响应数据进行处理
return p
},
// 请求失败后的回调函数
fail(p) {
// 必须返回响应数据对象,否则后续无法对响应数据进行处理
return p
},
// 请求完成时的回调函数(请求成功或失败都会被执行)
complete(p) {
// 隐藏loading效果
wepy.hideLoading()
}
})
}
在 app.wpy
中的 config
节点中,找到 usingComponents
并注册搜索组件,代码如下:
export default class extends wepy.app {
config = {
// 引用并注册全局组件
usingComponents: {
// 商品卡片组件
'van-card': './assets/vant/card/index'
}
}
在 search.wpy
中使用刚才注册的组件:
<van-search value="{
{ value }}" placeholder="请输入搜索关键词" show-action bind:change="onChange" bind:search="onSearch" bind:cancel="onCancel" />
监听搜索框组件的 bind:change="onChange"
事件:
// 当搜索关键词发生变化,会触发这个事件处理函数
onChange(e) {
// e.detail 是变化过后最新的内容
console.log(e.detail)
this.getSuggestList(e.detail)
}
定义 getSuggestList()
函数获取搜索建议列表:
// 获取搜索建议列表
async getSuggestList(searchStr) {
const {
data: res } = await wepy.get('/goods/qsearch', {
query: searchStr })
if (res.meta.status !== 200) {
return wepy.baseToast()
}
this.suggestList = res.message
this.$apply()
}
产生 Bug 的原因,是因为用户输入关键词的 length 长度导致的,因此,可以适当修改 onChange
事件处理函数如下:
// 当搜索关键词发生变化,会触发这个事件处理函数
onChange(e) {
// e.detail 是变化过后最新的内容
console.log(e.detail)
if (e.detail.trim().length <= 0) {
this.suggestList = []
return
}
this.getSuggestList(e.detail)
}
全局注册 cell
单元格相关的组件:
export default class extends wepy.app {
config = {
// 引用并注册全局组件
usingComponents: {
// 单元格组件
'van-cell': './assets/vant/cell/index',
'van-cell-group': './assets/vant/cell-group/index'
}
}
在 search.wpy
页面中渲染搜索建议列表结构:
<van-cell-group>
<block wx:for="{
{suggestList}}" wx:key="index">
<van-cell title="{
{item.goods_name}}" />
block>
van-cell-group>
为 vant-cell
组件绑定点击事件处理函数:
<van-cell title="{
{item.goods_name}}" @tap="goGoodsDetail({
{item.goods_id}})" />
定义事件处理函数,并导航到详情页面:
methods = {
// ...
// 点击搜索建议项,导航到商品详情页面
goGoodsDetail(goods_id) {
wepy.navigateTo({
url: '/pages/goods_detail/main?goods_id=' + goods_id
})
}
}
监听 vant-search
组件的 bind:search="onSearch"
事件
导航到商品详情页面:
// 触发了搜索
onSearch(e) {
// e.detail 就是最新的搜索关键字
const kw = e.detail.trim()
// 如果搜索关键词为空,则阻止跳转
if (kw.length <= 0) {
return
}
wepy.navigateTo({
url: '/pages/goods_list?query=' + kw
})
}
onLoad() {
// 调用小程序官方提供的 getStorageSync 函数,可以从本地存储中读取数据
const kwList = wx.getStorageSync('kw') || []
// 将读取的数据挂载到 data 中
this.kwList = kwList
}
// 触发了搜索
onSearch(e) {
// e.detail 就是最新的搜索关键字
const kw = e.detail.trim()
if (kw.length <= 0) {
return
}
// 把用户填写的搜索关键词,保存到 Storage 中
if (this.kwList.indexOf(kw) === -1) {
this.kwList.unshift(kw)
}
// 数组的 slice 方法,不会修改原数组,而是返回一个新的数组
this.kwList = this.kwList.slice(0, 10)
wepy.setStorageSync('kw', this.kwList)
wepy.navigateTo({
url: '/pages/goods_list?query=' + kw
})
}
// 计算属性
computed = {
// true 展示搜索历史区域
// false 展示搜索建议区域
isShowHistory() {
if (this.value.length <= 0) {
return true
}
return false
}
}
全局注册 vant-icon
组件:
export default class extends wepy.app {
config = {
// 引用并注册全局组件
usingComponents: {
// 图标
'van-icon': './assets/vant/icon/index'
}
}
定义历史搜索头部区域的UI结构:
<view wx:else>
<view class="history_title">
<text>历史搜索text>
<van-icon name="delete" @tap="clearHistory" />
view>
view>
定义样式美化对应的UI结构:
.history_title {
display: flex;
justify-content: space-between;
padding: 0 20rpx;
text:nth-child(1) {
font-size: 26rpx;
font-weight: bold;
}
}
全局注册 vant-tag
组件:
export default class extends wepy.app {
config = {
// 引用并注册全局组件
usingComponents: {
// Tag 标签
'van-tag': './assets/vant/tag/index'
}
}
通过循环渲染历史搜索列表的UI结构:
<view wx:else>
<view class="history_title">
<text>历史搜索text>
<van-icon name="delete" @tap="clearHistory" />
view>
<view class="history_body">
<van-tag size="large" wx:for="{
{kwList}}" wx:key="index" class="tag" @tap="goGoodsList({
{item}})">{
{item}}van-tag>
view>
view>
通过样式美化 vant-tag
组件的样式:
.tag {
> view {
margin: 15rpx;
}
}
在 goods_list.js
中定义 data
节点,并定义对应的请求参数:
data = {
// 查询关键词
query: '',
// 商品分类的Id
cid: '',
// 页码值
pagenum: 1,
// 每页显示多少条数据
pagesize: 10
}
在 onLoad()
生命周期函数中,处理 query
和 cid
的值,并发起数据请求:
onLoad(options) {
this.query = options.query || ''
this.cid = options.cid || ''
this.getGoodsList()
}
// 获取商品列表数据
async getGoodsList(cb) {
const {
data: res } = await wepy.get('/goods/search', {
query: this.query,
cid: this.cid,
pagenum: this.pagenum,
pagesize: this.pagesize
})
if (res.meta.status !== 200) {
return wepy.baseToast()
}
this.goodslist = res.message.goods
this.total = res.message.total
this.$apply()
}
全局注册 vant-card
组件:
export default class extends wepy.app {
config = {
// 引用并注册全局组件
usingComponents: {
// 商品卡片组件
'van-card': './assets/vant/card/index'
}
}
循环渲染商品列表对应的UI结构:
<block wx:for="{
{goodslist}}" wx:key="index">
<van-card num="{
{item.goods_number}}" price="{
{item.goods_price}}" title="{
{item.goods_name}}" thumb="{
{ item.goods_small_logo }}" />
<view class="sep_line">view>
block>
美化分割线的样式:
.sep_line {
border-top: 1rpx solid #eee;
}
在 goods_list.wpy
中配置上拉加载更多的距离:
<script>
import wepy from 'wepy'
import mix from '@/mixins/goods_list.js'
export default class extends wepy.page {
// 注意:config 节点只能写到页面的JS中,不能抽离到 mixin 中
config = {
navigationBarTitleText: '商品列表',
// 上拉触底的距离,默认是 50px
onReachBottomDistance: 100
}
mixins = [mix]
}
</script>
在 goods_list.js
中监听上拉触底的事件:
// 触底操作
onReachBottom() {
console.log('触底了')
this.pagenum++
this.getGoodsList()
}
新旧数据拼接合并:
// 获取商品列表数据
async getGoodsList(cb) {
// ...
this.goodslist = [...this.goodslist, ...res.message.goods]
// ...
}
判断数据是否加载完毕的公式为:
当前页码值 * 每页显示的数据条数 >= 总数据条数
pagemun * pagesize >= total
优化 onReachBottom()
函数的业务处理逻辑:
// 触底操作
onReachBottom() {
// 先判断是否有下一页的数据
if (this.pagenum * this.pagesize >= this.total) {
return
}
console.log('触底了')
this.pagenum++
this.getGoodsList()
}
isover
控制数据加载完毕后的提示消息在 data 中定义 isover
布尔值:
data = {
// ...
// 数据是否加载完毕的布尔值,默认为 false
isover: false
}
当所有数据加载完毕之后,把 isover
的值重置为 true
:
if (this.pagenum * this.pagesize >= this.total) {
this.isover = true
return
}
在页面上,渲染数据加载完毕之后的UI结构,并通过 isover
控制其显示与隐藏:
<view class="over_line" hidden="{
{!isover}}">-------- 我是有底线的 --------view>
美化 over_line
的样式:
.over_line {
font-size: 24rpx;
text-align: center;
height: 60rpx;
line-height: 60rpx;
color: #ddd;
}
isloading
防止重复发起数据请求在 data 中定义 isloading
布尔值:
data = {
// ...
// 表示当前数据是否正在请求中
isloading: false
}
优化 getGoodsList()
函数:
// 获取商品列表数据
async getGoodsList(cb) {
// 即将发起请求时,将 isloading 重置为 true
this.isloading = true
const {
data: res } = await wepy.get('/goods/search', {
query: this.query,
cid: this.cid,
pagenum: this.pagenum,
pagesize: this.pagesize
})
if (res.meta.status !== 200) {
return wepy.baseToast()
}
this.goodslist = [...this.goodslist, ...res.message.goods]
this.total = res.message.total
// 当数据请求完成后,将 isloading 重置为 false
this.isloading = false
this.$apply()
}
优化 onReachBottom()
函数:
// 触底操作
onReachBottom() {
// 判断当前是否正在请求数据中,
// 如果 isloading 值为 true,则 return 从而终止后续操作,防止重复发起数据请求
if (this.isloading) {
return
}
// ...
}
在 goods_list.wpy
中启用 下拉刷新
和设置 下拉刷新窗口的背景色
:
export default class extends wepy.page {
// 注意:config 节点只能写到页面的JS中,不能抽离到 mixin 中
config = {
// ...
// 开启下拉刷新
enablePullDownRefresh: true,
// 设置下拉刷新窗口的背景色
backgroundColor: '#eee'
}
mixins = [mix]
}
</script>
在 goods_list.js
中监听下拉刷新的事件处理函数:
// 下拉刷新的操作
onPullDownRefresh() {
// 初始化必要的字段值
this.pagenum = 1
this.total = 0
this.goodslist = []
this.isover = this.isloading = false
// 重新发起数据请求
this.getGoodsList()
}
在 getGoodsList()
函数中,当数据请求完成后,手动调用API关闭下拉刷新效果:
// 获取商品列表数据
async getGoodsList(cb) {
// ...
this.goodslist = [...this.goodslist, ...res.message.goods]
this.total = res.message.total
this.isloading = false
this.$apply()
// 当数据请求成功后,立即关闭下拉刷新效果
wepy.stopPullDownRefresh()
}
优化 onPullDownRefresh
中的代码,在调用 getGoodsList()
函数时,把停止下拉刷新的代码,以回调函数的形式传递进去,示例代码如下:
// 下拉刷新的操作
onPullDownRefresh() {
// 初始化必要的字段值
this.pagenum = 1
this.total = 0
this.goodslist = []
this.isover = this.isloading = false
// 重新发起数据请求
this.getGoodsList(() => {
// 停止下拉刷新的行为
wepy.stopPullDownRefresh()
})
}
优化 stopPullDownRefresh()
中的代码如下:
// 获取商品列表数据
async getGoodsList(cb) {
// ...
this.$apply()
// 只有当外界传递了 cb 回调函数之后,才调用 cb()
cb && cb()
}
为商品列表中的每个 Item 项绑定点击事件处理函数:
<van-card @tap="goGoodsDetail({
{item.goods_id}})" />
定义事件处理函数,导航到商品详情页面:
methods = {
// 点击跳转到商品详情页面
goGoodsDetail(goods_id) {
wepy.navigateTo({
url: '/pages/goods_detail/main?goods_id=' + goods_id
})
}
}
在 data
中定义需要的数据节点:
data = {
// 商品的Id值
goods_id: '',
// 商品的详情
goodsInfo: {
}
}
在 onLoad
生命周期函数中,转存商品Id,并发起数据请求:
onLoad(options) {
// 转存商品Id
this.goods_id = options.goods_id
// 发起数据请求
this.getGoodsInfo()
}
定义 getGoodsInfo
函数,发起数据请求:
// 获取商品详情数据
async getGoodsInfo() {
const {
data: res } = await wepy.get('/goods/detail', {
goods_id: this.goods_id
})
if (res.meta.status !== 200) {
return wepy.baseToast()
}
this.goodsInfo = res.message
this.$apply()
}
绘制UI结构:
<swiper indicator-dots circular>
<block wx:for="{
{goodsInfo.pics}}" wx:key="index">
<swiper-item>
<image src="{
{item.pics_big}}">image>
swiper-item>
block>
swiper>
美化样式:
swiper {
height: 750rpx;
image {
width: 100%;
height: 100%;
}
}
绘制对应的UI结构:
<view class="goods_info">
<view class="box1">
<view class="price">¥{
{goodsInfo.goods_price}}view>
<view class="goods_name">
<view class="left">{
{goodsInfo.goods_name}}view>
<view class="right">
<van-icon name="star-o">van-icon>
<view>收藏view>
view>
view>
<view class="yunfei">快递:免运费view>
view>
view>
通过样式美化价格名称运费区域:
.goods_info {
.sep_line {
border-bottom: 15rpx solid #efefef;
}
.box1 {
padding: 0 10rpx;
.price {
font-size: 40rpx;
color: red;
margin: 20rpx 0;
}
.goods_name {
display: flex;
justify-content: space-between;
.left {
font-size: 26rpx;
padding-right: 40rpx;
}
.right {
width: 200rpx;
text-align: center;
border-left: 1rpx solid #eee;
> view {
font-size: 20rpx;
}
}
}
.yunfei {
font-size: 26rpx;
color: #666;
font-weight: bold;
line-height: 80rpx;
}
}
}
绘制对应的UI结构:
<view class="box2">
<view>
<text>促销text>
<text>满300元减30元text>
view>
<view>
<text>已选text>
<text>黑色/S/1件text>
view>
view>
<view class="sep_line">view>
通过样式美化促销已选区域:
.box2 {
font-size: 24rpx;
padding: 0 10rpx;
> view {
line-height: 80rpx;
text:nth-child(1) {
margin-right: 20rpx;
}
text:nth-child(2) {
color: #666;
}
}
}
绘制对应的UI结构:
<view class="box3" @tap="chooseAddress">
<view>
<text>送至text>
<text>{
{addressStr}}text>
view>
<van-icon name="arrow">van-icon>
view>
<view class="sep_line">view>
通过样式美化收货地址区域:
.box3 {
display: flex;
justify-content: space-between;
font-size: 24rpx;
padding: 25rpx 10rpx 25rpx 10rpx;
> view {
text:nth-child(1) {
margin-right: 20rpx;
}
text:nth-child(2) {
color: #666;
}
}
}
在 app.wpy
中注册对应的组件:
usingComponents: {
// ...
// tab 标签页
'van-tab': './assets/vant/tab/index',
'van-tabs': './assets/vant/tabs/index'
}
通过标签页组件
,在详情页面上渲染商品详情区域:
<van-tabs>
<van-tab title="图文详情">
图文详情
van-tab>
<van-tab title="规格参数" class="tab2">
规格参数
van-tab>
van-tabs>
通过 for 循环,渲染对应的UI结构:
<van-tab title="规格参数" class="tab2">
<block wx:for="{
{goodsInfo.attrs}}" wx:key="index">
<van-row>
<van-col span="10">{
{item.attr_name}}van-col>
<van-col span="14">{
{item.attr_value}}van-col>
van-row>
block>
van-tab>
美化样式:
.tab2 {
font-size: 24rpx;
.van-row {
border-top: 1rpx solid #eee;
.van-col {
padding: 25rpx 0 25rpx 10rpx;
&:nth-child(1) {
border-right: 1rpx solid #eee;
}
}
}
}
.van-tabs {
z-index: 0;
}
.goods_detail_container {
padding-bottom: 50px !important;
}
全局注册 wxparse
组件:
usingComponents: {
// ...
// 把 HTML 代码转换为 WXML 代码的插件
wxparse: './assets/wxparse/wxparse'
}
在详情页使用 wxparse
组件:
<van-tab title="图文详情">
<wxparse data="{
{goodsInfo.goods_introduce}}">wxparse>
van-tab>
为轮播图中的每一张图片,绑定点击事件处理函数:
<image src="{
{item.pics_big}}" @tap="preview({
{item.pics_big}})">image>
在事件处理函数中,调用 wepy.previewImage()
函数,实现轮播图预览效果:
methods = {
// 点击预览图片
preview(current) {
wepy.previewImage({
// 所有图片的路径
urls: this.goodsInfo.pics.map(x => x.pics_big),
// 当前默认看到的图片
current: current
})
}
}
给收货地址对应的 view
组件,绑定点击事件处理函数:
<view class="box3" @tap="chooseAddress">view>
通过 wepy.chooseAddress()
函数,选择收货地址:
// 获取用户的收货地址
async chooseAddress() {
const res = await wepy.chooseAddress().catch(err => err)
if (res.errMsg !== 'chooseAddress:ok') {
return wepy.baseToast('获取收货地址失败!')
}
this.addressInfo = res
// 将选择的收获地址,存储到本地 Storage 中
wepy.setStorageSync('address', res)
this.$apply()
}
定义计算属性节点:
computed = {
addressStr() {
if (this.addressInfo === null) {
return '请选择收货地址'
}
const addr = this.addressInfo
const str =
addr.provinceName + addr.cityName + addr.countyName + addr.detailInfo
return str
}
}
在页面上渲染对应的收货地址:
<view class="box3" @tap="chooseAddress">
<view>
<text>送至text>
<text>{
{addressStr}}text>
view>
<van-icon name="arrow">van-icon>
view>
在 app.wpy
中全局注册 商品导航组件
:
usingComponents: {
// ...
// 商品导航区域
'van-goods-action': './assets/vant/goods-action/index',
'van-goods-action-icon': './assets/vant/goods-action-icon/index',
'van-goods-action-button': './assets/vant/goods-action-button/index'
}
在详情页中使用 商品导航组件
:
<van-goods-action>
<van-goods-action-icon icon="chat-o" text="客服" />
<van-goods-action-icon icon="cart-o" text="购物车" url="/pages/tabs/cart" link-type="switchTab" />
<van-goods-action-button text="加入购物车" type="warning" />
<van-goods-action-button text="立即购买" />
van-goods-action>
为客服按钮,添加 open-type
属性:
<van-goods-action-icon icon="chat-o" text="客服" open-type="contact" />
登录小程序后台,在 功能 -> 客服
面板中,可以维护客服人员列表,也可以登录网页端客服,为用户提供客服服务。
为 加入购物车
按钮,绑定单击事件处理函数:
<van-goods-action-button text="加入购物车" type="warning" bind:click="addToCart" />
在 methods
中定义对应的事件处理函数:
// 点击按钮,把商品添加到购物车列表中
addToCart() {
// 获取到当前商品的所有信息
console.log(this.goodsInfo)
// 提示用户加入购物车成功
wepy.showToast({
title: '已加入购物车',
icon: 'success'
})
}
app.wpy
中定义全局共享的数据和方法在 globalData
中,定义全局的购物车列表:
// 专门存储全局共享的数据
// 只需要通过 this.$parent.globalData 就可以拿到这个全局共享的数据对象
globalData = {
// 全局的购物车列表
cart: []
}
和 globalData
平级,定义全局可调用的函数:
test() {
console.log('ok')
}
在每个小程序页面中,可通过 this.$parent
访问全局的数据或函数:
// 点击按钮,把商品添加到购物车列表中
addToCart() {
// 获取到当前商品的所有信息
// console.log(this.goodsInfo)
console.log(this.$parent.globalData)
console.log(this.$parent)
// 提示用户加入购物车成功
wepy.showToast({
title: '已加入购物车',
icon: 'success'
})
}
点击加入购物车按钮时候,直接调用全局函数:
// 点击按钮,把商品添加到购物车列表中
addToCart() {
// ...
this.$parent.addGoodsToCart(this.goodsInfo)
// ...
}
在 app.wpy
中,定义全局函数 addGoodsToCart()
如下:
// 把商品,添加到购物车列表中
addGoodsToCart(goods) {
this.globalData.cart.push(goods)
}
在将商品添加至购物车期间,不需要用到商品的所有属性,因此可以有选择地将需要的属性,梳理为一个新对象,保存到购物车列表中,具体代码如下:
// 把商品,添加到购物车列表中
addGoodsToCart(goods) {
// 梳理出来的商品信息对象
const info = {
// 商品Id
id: goods.goods_id,
// 名称
name: goods.goods_name,
// 图片
pic: goods.goods_small_logo,
// 价格
price: goods.goods_price,
// 数量
count: 1,
// 是否默认被选中
isCheck: true
}
// 将整理出来的商品信息对象,存储到购物车列表中
this.globalData.cart.push(info)
}
// 把商品,添加到购物车列表中
addGoodsToCart(goods) {
// 先根据 Id 查找购物车列表中,是否已经存储了对应的商品信息
// 如果查找的结果,值为 -1,证明要添加的商品不存在于购物车中,可以直接 push
// 如果查找的结果,值不为 -1,证明要添加的商品,已存在以购物车中,此时直接更新数量即可!!!
const i = this.globalData.cart.findIndex(x => x.id === goods.goods_id)
if (i !== -1) {
this.globalData.cart[i].count++
return
}
// ...
}
在 app.wpy
全局,定义函数 saveCartToStorage()
如下:
// 将购物车中的商品数据,持久化保存到本地
saveCartToStorage() {
wepy.setStorageSync('cart', this.globalData.cart)
}
凡是对购物车中的数据做了操作,在操作完毕后,已经要调用 saveCartToStorage
函数:
// 把商品,添加到购物车列表中
addGoodsToCart(goods) {
const i = this.globalData.cart.findIndex(x => x.id === goods.goods_id)
if (i !== -1) {
this.globalData.cart[i].count++
this.saveCartToStorage()
return
}
console.log(goods)
// 梳理出来的商品信息对象
const info = {
// 商品Id
id: goods.goods_id,
// 名称
name: goods.goods_name,
// 图片
pic: goods.goods_small_logo,
// 价格
price: goods.goods_price,
// 数量
count: 1,
// 是否默认被选中
isCheck: true
}
// 将整理出来的商品信息对象,存储到购物车列表中
this.globalData.cart.push(info)
this.saveCartToStorage()
}
在小程序启动的 onLaunch()
生命周期函数中,加载 storage
中的数据,并赋值给购物车列表:
onLaunch() {
console.log('小程序启动了')
this.globalData.cart = wepy.getStorageSync('cart') || []
}
将购物车页面,添加为新的自定义编译模式
将 素材
中的 [email protected]
图片,拷贝到 项目的 src/assets/images
目录中
打开 cart.wpy
页面,渲染对应的UI结构:
<template>
<view>
<view class="empty_cart">
<image src="/assets/images/[email protected]" />
<view>哎呦,购物车是空的噢~view>
view>
view>
template>
在 cart.wpy
的 style
节点中,美化空白购物车的样式效果:
定义购物车私有数据:
data = {
// 购物车商品列表
cart: []
}
在页面加载期间,转存购物车数据到当前页面中:
onLoad() {
this.cart = this.$parent.globalData.cart
}
定义计算属性:
computed = {
// 判断购物车是否为空
isEmpty() {
if (this.cart.length <= 0) {
return true
}
return false
}
}
按需渲染页面结构:
<view class="empty_cart" wx:if="{
{isEmpty}}">view>
<view class="cart-container" wx:else>view>
需要用到 van-cell
单元格组件:
<van-cell title="购物车列表" icon="shop-o" />
<block wx:for="{
{cart}}" wx:key="id">
<van-card title="{
{item.name}}" thumb="{
{item.pic}}">van-card>
block>
全局注册 van-stepper
组件:
// 引用并注册全局组件
usingComponents: {
// Stepper 步进器
'van-stepper': './assets/vant/stepper/index'
}
通过插槽自定义渲染价格与数量:
<van-card title="{
{item.name}}">
<view slot="desc" class="desc">
<text class="price">¥{
{item.price}}text>
<van-stepper value="{
{item.count}}" />
view>
van-card>
.desc {
display: flex;
justify-content: space-between;
align-items: center;
position: absolute;
bottom: 0;
width: 100%;
.price {
color: red;
font-weight: bold;
font-size: 12px;
}
}
为 van-stepper
组件绑定事件,并通过自定义属性传参:
<van-stepper value="{
{item.count}}" bind:change="countChanged" data-id="{
{item.id}}" />
定义事件处理函数:
countChanged(e) {
// 获取到变化之后最新的数量值
const count = e.detail
// 商品的Id值
const id = e.target.dataset.id
}
在 app.wpy
中定义如下函数:
// 更新商品的数量
updateGoodsCount(id, count) {
const i = this.globalData.cart.findIndex(x => x.id === id)
if (i !== -1) {
// 根据索引值,获取到对应的那个商品,
// 然后更新数量
this.globalData.cart[i].count = count
// 把更新过后的购物车数据,立即存储到Storage中
this.saveCartToStorage()
}
}
当数量改变时,调用这个全局函数:
methods = {
// 监听商品数量变化的事件
countChanged(e) {
// 获取到变化之后最新的数量值
const count = e.detail
// 商品的Id值
const id = e.target.dataset.id
this.$parent.updateGoodsCount(id, count)
}
}
为 van-card
组件添加下边框线:
.van-card {
border-bottom: 1rpx solid #eee;
}
注册复选框组件:
// 引用并注册全局组件
usingComponents: {
// 复选框
'van-checkbox': './assets/vant/checkbox/index'
}
渲染对应的UI结构:
<van-card title="{
{item.name}}">
<view slot="thumb" class="thumb">
<van-checkbox value="{
{ item.isCheck }}">van-checkbox>
<image src="{
{item.pic}}" />
view>
van-card>
.van-card {
border-bottom: 1rpx solid #eee;
padding-left: 7px !important;
}
.thumb {
display: flex;
align-items: center;
width: 118px;
image {
width: 90px;
height: 90px;
margin-left: 8px;
}
}
.van-card__thumb {
width: 118px !important;
}
绑定事件处理函数:
<van-checkbox value="{
{ item.isCheck }}" checked-color="#d81e06" bind:change="statusChanged" data-id="{
{item.id}}">van-checkbox>
定义事件处理函数:
methods = {
// 当商品前面的复选框,选中状态变化,会触发这个函数
statusChanged(e) {
// console.log(e)
// 当前最新的选中状态
const status = e.detail
// 当前点击项对应的商品Id
const id = e.target.dataset.id
}
}
在 app.wpy
中定义如下函数:
// 更新商品的选中状态
updateGoodsStatus(id, status) {
const i = this.globalData.cart.findIndex(x => x.id === id)
if (i !== -1) {
this.globalData.cart[i].isCheck = status
this.saveCartToStorage()
}
}
调用全局定义的函数:
methods = {
// 当商品前面的复选框,选中状态变化,会触发这个函数
statusChanged(e) {
// console.log(e)
// 当前最新的选中状态
const status = e.detail
// 当前点击项对应的商品Id
const id = e.target.dataset.id
this.$parent.updateGoodsStatus(id, status)
}
}
注册 van-swipe-cell
组件:
// 引用并注册全局组件
usingComponents: {
// 滑动单元格组件
'van-swipe-cell': './assets/vant/swipe-cell/index'
}
把商品卡片使用滑动单元格组件包裹起来:
<van-swipe-cell right-width="{
{ 65 }}" left-width="{
{ 0.1 }}">
<van-card title="{
{item.name}}">
van-card>
<view slot="right" class="close">删除view>
van-swipe-cell>
.close {
background-color: #ff4444;
width: 65px;
height: 100%;
color: white;
font-size: 13px;
display: flex;
justify-content: center;
align-items: center;
}
为删除按钮绑定事件处理函数:
<view slot="right" class="close" @tap="close({
{item.id}})">删除view>
定义事件处理函数:
// 点击删除对应的商品
close(id) {
this.$parent.removeGoodsById(id)
}
在 app.wpy
中定义 removeGoodsById()
函数如下:
// 根据Id删除对应的商品
removeGoodsById(id) {
const i = this.globalData.cart.findIndex(x => x.id === id)
if (i !== -1) {
this.globalData.cart.splice(i, 1)
this.saveCartToStorage()
}
}
全局注册 van-submit-bar
组件:
// 引用并注册全局组件
usingComponents: {
// 提交订单
'van-submit-bar': './assets/vant/submit-bar/index'
}
在页面上绘制提交订单的UI结构:
<van-submit-bar price="{
{ amount }}" button-text="提交订单" bind:submit="submitOrder" tip="{
{ false }}">
<van-checkbox class="fullCheck" value="{
{isFullChecked}}" checked-color="#d81e06">全选van-checkbox>
van-submit-bar>
美化全选复选框的样式:
.fullCheck {
margin-left: 7px;
}
// 总价格,单位是 分
amount() {
let total = 0 // 单位是 元
this.cart.forEach(x => {
if (x.isCheck) {
total += x.price * x.count
}
})
return total * 100
}
// 是否全选
isFullChecked() {
// 获取所有商品的个数
const allCount = this.cart.length
let c = 0
this.cart.forEach(x => {
if (x.isCheck) {
c++
}
})
return allCount === c
}
为全选的复选框绑定点击事件:
<van-checkbox class="fullCheck" value="{
{isFullChecked}}" checked-color="#d81e06" bind:change="onFullCheckChanged">全选van-checkbox>
监听全选复选框值改变的事件:
// 监听全选复选框值改变的事件
onFullCheckChanged(e) {
this.$parent.updateAllGoodsStatus(e.detail)
}
在 app.wpy
中定义 updateAllGoodsStatus()
函数:
// 更新购物车中每件商品的选中状态
updateAllGoodsStatus(status) {
this.globalData.cart.forEach(x => {
x.isCheck = status
})
this.saveCartToStorage()
}
在 app.wpy
中定义渲染徽章的函数:
// 渲染购物车的徽章
async renderCartBadge() {
// 计算已勾选的商品数量
let c = 0
this.globalData.cart.forEach(x => {
if (x.isCheck) {
c += x.count
}
})
// 调用 API,将已勾选的商品数量渲染到指定的 TabBar 中
const res = await wepy
.setTabBarBadge({
index: 3,
text: c + ''
})
.catch(err => err)
// 设置 tabBar 的徽章失败!
if (res.errMsg !== 'setTabBarBadge:ok') {
}
}
在 app.wpy
的 onLaunch()
生命周期函数中,调用刚才定义的函数:
onLaunch() {
console.log('小程序启动了')
this.globalData.cart = wepy.getStorageSync('cart') || []
this.renderCartBadge()
}
在 app.wpy
中 saveCartToStorage()
的函数中,新增如下代码:
// 将购物车中的商品数据,持久化保存到本地
saveCartToStorage() {
wepy.setStorageSync('cart', this.globalData.cart)
+ this.renderCartBadge()
}
在 app.wpy
中,定义如下的全局共享数据:
globalData = {
// 全局的购物车列表
cart: [],
// 当前购物车中已经勾选的商品数量
+ total: 0
}
修改 app.wpy
中的 renderCartBadge()
函数:
// 渲染购物车的徽章
async renderCartBadge() {
let c = 0
this.globalData.cart.forEach(x => {
if (x.isCheck) {
c += x.count
}
})
+ // 更新全局的商品数量
+ this.globalData.total = c
const res = await wepy
.setTabBarBadge({
index: 3,
text: c + ''
})
.catch(err => err)
// 设置 tabBar 的徽章失败!
if (res.errMsg !== 'setTabBarBadge:ok') {
}
}
在 src/mixins/goods_detail/main.js
中,定义计算属性如下:
computed = {
// 所有已经勾选的商品的数量
total() {
return this.$parent.globalData.total
}
}
在 src/pages/goods_detail/main.wpy
中,修改UI结构如下:
<van-goods-action>
<van-goods-action-icon icon="cart-o" text="购物车" url="/pages/tabs/cart" link-type="switchTab" info="{
{total}}" />
van-goods-action>
为购物车页面的 提交订单区域
绑定 bind:submit
事件处理函数:
<van-submit-bar price="{
{ amount }}" button-text="提交订单" bind:submit="submitOrder" tip="{
{ false }}">
van-submit-bar>
在 methods
节点中新增如下处理函数:
// 提交订单
submitOrder() {
if (this.amount <= 0) {
return wepy.baseToast('订单金额不能为空!')
}
wepy.navigateTo({
url: '/pages/order'
})
}
在 src/pages
目录中,新建 order.wpy
页面文件,并初始化
在 src/mixins
目录中,新建 order.js
逻辑文件,并初始化
将 order.js
导入并挂在到 order.wpy
中
将新页面的路径,记录到 src/app.wpy
文件的 config -> pages
数组中:
export default class extends wepy.app {
config = {
pages: [
// 省略其他不必要的代码
// 确认订单页面
'pages/order'
]
}
}
<script>
import wepy from 'wepy'
import mix from '@/mixins/order.js'
export default class extends wepy.page {
config = {
// 设置当前页面的标题
navigationBarTitleText: '确认订单'
}
mixins = [mix]
}
</script>
绘制UI结构:
<view class="choose_address_box">
<van-button type="info" size="small">+ 选择收货地址van-button>
view>
<image src="/assets/images/[email protected]" class="sep_line">image>
美化样式:
.choose_address_box {
text-align: center;
padding: 60rpx 0;
}
.sep_line {
height: 7px;
width: 100%;
display: block;
}
为选择收货地址按钮绑定点击事件处理函数:
<van-button type="info" size="small" @tap="chooseAddress">+ 选择收货地址van-button>
在 methods
中定义事件处理函数:
// 选择收货地址
async chooseAddress() {
const res = await wepy.chooseAddress().catch(err => err)
if (res.errMsg !== 'chooseAddress:ok') {
return
}
this.addressInfo = res
wepy.setStorageSync('address', res)
this.$apply()
}
onLoad() {
// 读取收货地址
this.addressInfo = wepy.getStorageSync('address') || null
}
定义计算属性:
computed = {
isHaveAddress() {
if (this.addressInfo === null) {
return false
}
return true
}
}
按需展示:
<view class="choose_address_box" wx:if="{
{isHaveAddress === false}}">
view>
<view class="address_box" wx:else>
view>
渲染收货信息区域的UI结构:
<view class="address_box" wx:else>
<view class="box1">
<text>收货人:{
{addressInfo.userName}}text>
<view @tap="chooseAddress">
<text>联系电话:{
{addressInfo.telNumber}}text>
<van-icon name="arrow" />
view>
view>
<view class="box2">收货地址:{
{addressStr}}view>
view>
美化样式:
.address_box {
font-size: 26rpx;
padding: 0 10rpx;
.box1 {
display: flex;
justify-content: space-between;
padding: 30rpx 0;
}
.box2 {
padding-bottom: 30rpx;
}
}
<view class="box1">
<text>收货人:{
{addressInfo.userName}}text>
<view @tap="chooseAddress">
<text>联系电话:{
{addressInfo.telNumber}}text>
<van-icon name="arrow" />
view>
view>
渲染结构:
<view class="goods_list">
<block wx:for="{
{cart}}" wx:key="id">
<van-card num="{
{item.count}}" price="{
{item.price}}" title="{
{item.name}}" thumb="{
{item.pic}}" />
block>
view>
美化样式:
.van-card {
border-bottom: 1rpx solid #eee;
}
在 app.wpy
中全局注册按钮组件:
// 引用并注册全局组件
usingComponents: {
// 按钮组件
'van-button': './assets/vant/button/index'
}
渲染按钮区域
<van-button type="primary" size="large" class="btnLogin">登录后下单van-button>
美化样式:
.btnLogin {
position: fixed;
bottom: 0;
width: 100%;
}
.order_container {
padding-bottom: 50px;
}
为登录按钮设置 open-type
和 bindgetuserinfo
属性:
<van-button type="primary" size="large" class="btnLogin" open-type="getUserInfo" bindgetuserinfo="getUserInfo">登录后下单van-button>
定义事件处理函数 getUserInfo
,并通过形参接收用户信息:
methods = {
// 获取用户信息
async getUserInfo(userInfo) {
// 判断是否获取用户信息失败
if (userInfo.detail.errMsg !== 'getUserInfo:ok') {
return wepy.baseToast('获取用户信息失败!')
}
console.log(userInfo)
}
}
在 getUserInfo
处理函数中,新增如下代码:
// 获取用户登录的凭证 Code
const loginRes = await wepy.login()
console.log(loginRes)
if (loginRes.errMsg !== 'login:ok') {
return wepy.baseToast('微信登录失败!')
}
// 登录的参数
const loginParams = {
code: loginRes.code,
encryptedData: userInfo.detail.encryptedData,
iv: userInfo.detail.iv,
rawData: userInfo.detail.rawData,
signature: userInfo.detail.signature
}
在 getUserInfo
处理函数中,新增如下代码:
// 发起登录的请求,换取登录成功之后的 Token 值
const {
data: res } = await wepy.post('/users/wxlogin', loginParams)
console.log(res)
if (res.meta.status !== 200) {
return wepy.baseToast('微信登录失败!')
}
// 把登录成功之后的 Token 字符串,保存到 Storage 中
wepy.setStorageSync('token', res.message.token)
this.islogin = true
this.$apply()
通过 wx:if
和 wx:else
按需渲染 登录按钮
和 订单支付
区域:
<van-button type="primary" size="large" class="btnLogin" open-type="getUserInfo" bindgetuserinfo="getUserInfo" wx:if="{
{islogin === false}}">登录后下单van-button>
<van-submit-bar price="{
{amount}}" button-text="支付订单" bind:submit="onSubmit" wx:else>van-submit-bar>
打开 app.wpy
文件,找到 constructor
构造函数中定义的拦截器,从而自定义 header 请求头:
constructor() {
// ...
// 拦截器
this.intercept('request', {
// 发出请求时的回调函数
config(p) {
// 显示loading效果
wepy.showLoading({
title: '数据加载中...'
})
// 自定义请求头
p.header = {
Authorization: wepy.getStorageSync('token')
}
// console.log(p)
// 必须返回OBJECT参数对象,否则无法发送请求到服务端
return p
},
// ...
}
}
通过 bind:submit
为支付订单按钮,绑定事件处理函数:
<van-submit-bar price="{
{amount}}" button-text="支付订单" bind:submit="onSubmit" wx:else>van-submit-bar>
定义事件处理函数 onSubmit
,并实现下单及支付功能:
// 支付订单
async onSubmit() {
if (this.amount <= 0) {
return wepy.baseToast('订单金额不能为0!')
}
if (this.addressStr.length <= 0) {
return wepy.baseToast('请选择收货地址!')
}
// 1. 创建订单
const {
data: createResult } = await wepy.post('/my/orders/create', {
// 订单金额 单位 元
order_price: '0.01',
consignee_addr: this.addressStr,
order_detail: JSON.stringify(this.cart),
goods: this.cart.map(x => {
return {
goods_id: x.id,
goods_number: x.count,
goods_price: x.price
}
})
})
// 创建订单失败
if (createResult.meta.status !== 200) {
return wepy.baseToast('创建订单失败!')
}
// 创建订单成功了
const orderInfo = createResult.message
console.log(orderInfo)
// 2. 生成预支付订单
const {
data: orderResult } = await wepy.post(
'/my/orders/req_unifiedorder',
{
order_number: orderInfo.order_number
}
)
// 生成预支付订单失败
if (orderResult.meta.status !== 200) {
return wepy.baseToast('生成预支付订单失败!')
}
// 走支付的流程
// 3. 调用微信支付的API
// console.log(orderResult)
const payResult = await wepy
.requestPayment(orderResult.message.pay)
.catch(err => err)
// 用户取消了支付
if (payResult.errMsg === 'requestPayment:fail cancel') {
return wepy.baseToast('您已取消了支付!')
}
// 用户完成了支付的过程
// 4. 检查用户支付的结果
const {
data: payCheckResult } = await wepy.post('/my/orders/chkOrder', {
order_number: orderInfo.order_number
})
if (payCheckResult.meta.status !== 200) {
return wepy.baseToast('订单支付失败!')
}
// 5. 提示用户支付成功
wepy.showToast({
title: '支付成功!'
})
// 6. 跳转到订单列表页面
wepy.navigateTo({
url: '/pages/orderlist'
})
}
渲染UI结构:
<van-tabs active="{
{ active }}" bind:change="tabChanged">
<van-tab title="全部">全部van-tab>
<van-tab title="待付款">待付款van-tab>
<van-tab title="已付款">已付款van-tab>
van-tabs>
在 data 中定义 active 属性值:
data = {
// 默认被激活的标签页的索引
active: 0
}
在 methods 中定义事件处理函数 tabChanged
:
methods = {
// 每当切换标签页的时候,都会触发这个函数
tabChanged(e) {
console.log(e)
this.active = e.detail.index
}
}
在 data 中定义数据列表:
data = {
// 默认被激活的标签页的索引
active: 0,
// 全部 订单列表
allOrderList: [],
// 待付款 订单列表
waitOrderList: [],
// 已付款 订单列表
finishOrderList: []
}
在 onLoad
中获取订单列表数据:
onLoad() {
this.getOrderList(this.active)
}
在标签页发生切换时,获取订单列表数据:
// 每当切换标签页的时候,都会触发这个函数
tabChanged(e) {
console.log(e)
this.active = e.detail.index
this.getOrderList(this.active)
}
定义函数 getOrderList
如下:
// 获取订单列表
async getOrderList(index) {
console.log(index)
const {
data: res } = await wepy.get('/my/orders/all', {
type: index + 1 })
if (res.meta.status !== 200) {
return wepy.baseToast('获取订单列表失败!')
}
res.message.orders.forEach(
x => (x.order_detail = JSON.parse(x.order_detail))
)
console.log(res)
if (index === 0) {
this.allOrderList = res.message.orders
} else if (index === 1) {
this.waitOrderList = res.message.orders
} else if (index === 2) {
this.finishOrderList = res.message.orders
} else {
wepy.baseToast('订单类型错误!')
}
this.$apply()
}
全局注册 Panel
面板组件:
usingComponents: {
// Panel 面板
'van-panel': './assets/vant/panel/index'
}
循环渲染订单信息面板:
<block wx:for="{
{allOrderList}}" wx:key="index">
<view class="sep_line">view>
<van-panel title="{
{
'订单号:' + item.order_number}}">
<block wx:for="{
{item.order_detail}}" wx:key="index">
<van-card num="{
{item.count}}" price="{
{item.price}}" title="{
{item.name}}" thumb="{
{item.pic}}" />
block>
<van-cell value="共{
{item.total_count}}件商品,订单金额¥{
{item.order_price}}" />
van-panel>
block>
美化样式:
.sep_line {
border-top: 15rpx solid #eee;
}
.van-card {
border-bottom: 1rpx solid #eee;
}
在 src/components
目录中,新建组件文件 orderItem.wpy
如下:
<template>
<view>
<view class="sep_line">view>
<van-panel title="{
{
'订单号:' + order.order_number}}">
<block wx:for="{
{order.order_detail}}" wx:key="index">
<van-card num="{
{item.count}}" price="{
{item.price}}" title="{
{item.name}}" thumb="{
{item.pic}}" />
block>
<van-cell value="共{
{order.total_count}}件商品,订单金额¥{
{order.order_price}}" />
van-panel>
view>
template>
<script>
import wepy from 'wepy'
export default class extends wepy.component {
data = {
}
// 外界传递过来的数据
props = {
// 外界把订单数据传递过来
order: Object
}
methods = {
}
}
script>
<style lang="less">
.sep_line {
border-top: 15rpx solid #eee;
}
.van-card {
border-bottom: 1rpx solid #eee;
}
style>
在 orderlist.wpy
中,导入自定义的组件:
import orderItem from '@/components/orderItem'
注册自定义组件:
// 注册自定义组件
components = {
'order-item': orderItem
}
通过 WePY
官方提供的辅助组件
循环创建自定义组件:
<van-tabs active="{
{ active }}" bind:change="tabChanged">
<van-tab title="全部">
<repeat for="{
{allOrderList}}" key="index">
<order-item :order="item">order-item>
repeat>
van-tab>
<van-tab title="待付款">
<repeat for="{
{waitOrderList}}" key="index">
<order-item :order="item">order-item>
repeat>
van-tab>
<van-tab title="已付款">
<repeat for="{
{finishOrderList}}" key="index">
<order-item :order="item">order-item>
repeat>
van-tab>
van-tabs>
微信开发者工具
的工具栏中,点击 上传
按钮,填写 版本号
和 项目备注
,将最新的小程序项目代码,上传为 开发版本微信小程序后台主页
,点击 管理 -> 版本管理
开发版本
面板内的 提交审核
按钮,填写相关内容后,将最新的 开发版本
提交为 审核版本,由腾讯官方进行审核,审核过程需要等待若干天