![cover](https://img-blog.csdnimg.cn/img_convert/b7011f7628761c8db54169c0afa40cdf.jpeg)
uni-app黑马优购项目 学习笔记(上)
HBuilderX,uni-app 黑马优购微信小程序项目的学习记录
写在前面
学习uni-app,根据uni-app黑马优购项目,结合自己所遇到的问题和解决方案,写此篇博文为后来回头复盘
打算学习的小伙伴也可以照着此篇博文和视频把项目做出来,上面 uni-app黑马优购
有原文档,项目资料在下面,希望对学习uni-app的你有所帮助!
学习资料链接:百度网盘 请输入提取码
提取码:s9by
目录
在微信开发者工具引用接口调试时报错和解决方法:
在微信开发者工具引用接口调试时报错
解决:
1.起步
1.1 uni-app简介
uni-app是一个使用vue.js开发所有前端应用的框架
详情可见 uni-app官网
1.2 开发工具
-
下载 HBuilderX
- 选择Windows或Mac - 选择下载正式版
-
安装
-
将下载的 zip包 进行解压缩
-
将解压之后的文件夹,存放到纯英文的目录中(且不能包含括号等特殊字符)
-
双击 HBuilderX.exe 即可启动 HBuilderX
-
-
安装 scss/sass 编译
为了方便编写样式(例如:),建议安装 scss/sass 编译 插件。插件下载地址:
进入插件下载页面之后,点击右上角的 使用 HBuilderX 导入插件 按钮进行自动安装,截图如下:
-
快捷键方案切换
操作步骤:工具 -> 预设快捷键方案切换 -> VS Code
-
修改编辑器的基本设置
操作步骤:工具 -> 设置 -> 打开 Settings.json 按需进行配置
1.3 创建 uni-app 项目
- 文件 -> 新建 -> 项目
- 选择uni-app项目,命名,选择存放路径
- 选择uni-ui模板,创建
1.4 目录结构
1.5 项目运行到微信开发者工具
- mainfest.json - 微信小程序配置 -- 填写微信小程序AppID(APPID在微信开发者工具中申请获取)
- 工具 -> 设置 -> 运行配置 -> 小程序运行配置 输入微信开发者工具的路径
- 微信开发者工具中 -> 设置 -> 安全设置 -> 打开服务端口
目的:能够运行到开发者工具看效果
- Hx菜单栏 运行 -> 运行到小程序模拟器 -> 微信开发者工具
1.6 配置checkSiteMap选项
mainfest.json -> 源码视图 -> setting中 加入 "checkSiteMap" : false
1.7 使用Git本地管理uni-app项目
- 在项目根目录中新建 .gitignore 忽略文件,并配置
/*忽略 node_modules 目录 */ /node_modules 因为安装的第三方包都会存储在这目录,第三方包没有必要进行Git管理,因此需要忽略 /unpackage/dist 运行到小程序模拟器,编译生成到的小程序项目,存放到unpackage/dist目录里mp-weixin,因此需要忽略
注意:由于忽略了unpackage目录中仅有的dist目录,因此默认情况下,unpackage目录不会被Git追踪
此时,为了让Git正常追踪unpackage目录,按照惯例,可以在unpackage目录下创建一个 .gitkeep 的文件进行占位
- 打开项目所在文件夹(右键项目根目录,右键,在外部资源管理器打开),打开PowerShell窗口
git init //初始化本地Git仓库 git add . //将所有文件都加入到暂存区 git commit -m "init project" //本地提交更新 可以status查看分支
项目托管到码云
- 进入码云
- 生成配置SSH公钥 -> 在设置里添加已有公钥 C:\Users\DELL.ssh .pub格式是公钥
// 添加完 ,在终端 ssh -T git@gitee.com // 检测是否配置成功
- 创建空白仓库,本地项目上传到空白仓库
- 选择SSH
- 已有仓库 在文件所在位置打开终端输入代码 //关联仓库
2. tabBar
2.0 创建新的分支
- 创建分支 git checkout -b newbranch
- 查看当前项目所有分支 git branch
2.1 创建tabBar页面
- pages目录上鼠标右键, 选择新建页面
- 选择所需模板
- 项目名称命名
- 在pages.json中注册
- 创建同名目录
- 完成创建
shift+alt+f 整理pages.json里的代码格式
2.2 配置tabBar效果
- 先将资料目录下的 static 文件夹替换到项目目录里。所需照片导入
- 修改项目根目录中的 pages.json 配置文件,新增 tabBar 的配置节点如下:
{ "tabBar": { "selectedColor": "#C00000", //设置选中项文本的颜色 "list": [ { "pagePath": "pages/home/home", "text": "首页", "iconPath": "static/tab_icons/home.png", "selectedIconPath": "static/tab_icons/home-active.png" }, { "pagePath": "pages/cate/cate", "text": "分类", "iconPath": "static/tab_icons/cate.png", "selectedIconPath": "static/tab_icons/cate-active.png" }, { "pagePath": "pages/cart/cart", "text": "购物车", "iconPath": "static/tab_icons/cart.png", "selectedIconPath": "static/tab_icons/cart-active.png" }, { "pagePath": "pages/my/my", "text": "我的", "iconPath": "static/tab_icons/my.png", "selectedIconPath": "static/tab_icons/my-active.png" } ] } }
2.3 删除默认的 index 首页
配置完看不到最终的tabbar效果,需要把pages数组第一个index删除 找到 page 数组,把第一项默认的数组删除,再去pages文件夹删除index文件,即可正常显示
- 在 HBuilderX 中,把 pages 目录下的 index首页文件夹 删除掉
- 同时,把 page.json 中记录的 index 首页 路径删除掉
- 为了防止小程序运行失败,在微信开发者工具中,手动删除 pages 目录下的 index 首页文件夹
- 同时,把 components 目录下的 uni-link 组件文件夹 删除掉
2.4 修改导航条的样式效果
打开pages.json -> 修改globalStyle
节点
{
"globalStyle": {
"navigationBarTextStyle": "white", //导航条文本颜色
"navigationBarTitleText": "黑马优购", //导航条文本内容
"navigationBarBackgroundColor": "#C00000", //导航条背景颜色
"backgroundColor": "#FFFFFF"
}
}
2.5 分支的提交与合并
- 将本地的 tabbar 分支进行本地的 commit 提交:
git add . // 将修改的文件加入暂存区 git commit -m "完成了 tabBar 的开发" // 提交信息
- 将本地的 tabbar 分支推送到远程仓库进行保存:
git push -u origin tabbar //分支推送到码云保存
- 将本地的 tabbar 分支合并到本地的 master 分支:
git checkout master //首先切换到主分支 git merge tabbar //合并tabbar代码
到这一步后不能看源文档里的直接删除分支!!!因为还没有推送到master分支
git push
- 最后删除本地的 tabbar 分支:
git branch -d tabbar //想删除当前分支,先跳转到其他分支
3. 首页
微信开发者工具出现 TypeError: Cannot read property 'invoke' of undefined
解决办法:
3.0 创建home分支
打开终端,基于master分支在本地创建home子分支,用来开发首页相关功能
git checkout -b home
3.1 配置网络请求
由于平台的限制,小程序项目中不支持 axios,而且原生的 wx.request()
API 功能较为简单, 不支持拦截器等全局定制的功能。因此,建议在 uni-app 项目中使用 @escook/request-miniprogram
第三方包发起网络数据请求。
安装
- 右键根目录 ->
- 在外部资源管理器打开 ->
- 进入powershell ->
- 初始化一个npm包管理文件 即
npm init -y
生成默认的package.json文件 -> - 安装
npm install @escook/request-miniprogram
导入
在项目的 main.js 入口文件中,通过如下的方式进行配置:
//导入网络请求的包
import { $http } from '@escook/request-miniprogram'
//挂载
uni.$http = $http
//请求拦截器 请求之前展示loading效果
$http.beforeRequest = function(options){
uni.showLoading({
title: '数据加载中...'
})
}
//响应拦截器 请求后隐藏loading效果
$http.afterRequest = function(){
uni.hideLoading()
}
如果当前接口失效,可以:
- 登录你的微信公众平台 https://mp.weixin.qq.com
- 打开服务器域名
- 设置request域名
- 微信小程序工具 -> 详情 -> 项目配置 -> 域名信息 (在此添加修改)
- 重启微信开发工具 (必须要重启)
3.2 轮播图区域
3.2.1请求轮播图数据
实现步骤:
- 在main.js中挂载一下根路径
//请求根路径 $http.baseUrl = 'https://www.uinav.com'
如果在微信开发者工具 报错对应的服务器证书无效。控制台输入 showRequestInfo() 可以获取更详细信息。
解决办法
勾选不校验合法域名
- 在 data 中定义轮播图的数组
- 在 onLoad 生命周期函数中调用获取轮播图数据的方法
- 在 methods 中定义获取轮播图数据的方法
just like
export default {
data() {
return {
// 轮播图的数据列表,默认为空列表
swiperList: []
};
},
onLoad() {
// 在小程序页面刚加载时,调用获取轮播图数据方法
this.getSwiperList()
},
methods: {
async getSwiperList() {
//发起网络数据请求,获取轮播图数据的方法
const {data: res} = await uni.$http.get('/api/public/v1/home/swiperdata')
//请求失败
if (res.meta.status !== 200) {
return uni.showToast({
title: '数据请求失败!',
duration: 1500,
icon: 'none'
})
}
//请求成功 -> 挂载到data的swpierList里,为data中的数据赋值
this.swiperList = res.message
}
}
轮播图的借口 '/api/public/v1/home/swiperdata'
3.2.2 渲染轮播图的 UI 结构
- 渲染 UI 结构:
<template> <view> <!-- 轮播图区域 --> <swiper :indicator-dots="true" :autoplay="true" :interval="3000" :duration="1000" :circular="true"> <!-- 循环渲染轮播图的 item 项 --> <swiper-item v-for="(item, i) in swiperList" :key="i"> <view class="swiper-item"> <!-- 动态绑定图片的 src 属性 --> <image :src="item.image_src"></image> </view> </swiper-item> </swiper> </view> </template> 解释: :indicator-dots="true" //小圆点 :autoplay="true" //自动轮播 :interval="3000" //轮播间隔 3s :duration="1000" //从开始轮播到结束总共耗时 1s :circular="true" //衔接轮播
- 美化 UI 结构:
<style lang="scss">
swiper {
height: 330rpx;
.swiper-item,
image {
width: 100%;
height: 100%;
}
}
</style>
有可能会报错TypeError: Cannot read property ‘$$‘ of undefined
出现以上警告是因为使用
swiper
的 current属性 没有重置,将其重置为0即可在data加入
current: 0
即可
data: {
current: 0
}
3.2.3 配置小程序分包
分包可以减少小程序首次启动时的加载时间
为此,我们在项目中,把 tabBar 相关的 4 个页面放到主包中,
其它页面(例如:商品详情页、商品列表页)放到分包中。在 uni-app 项目中,配置分包的步骤如下:
- 在项目根目录中,创建分包的根目录,命名为 subpkg
- 在 pages.json 中,和 pages 节点平级的位置声明 subPackages 节点,用来定义分包相关的结构:
{ "pages": [ { "path": "pages/home/home", "style": {} }, { "path": "pages/cate/cate", "style": {} }, { "path": "pages/cart/cart", "style": {} }, { "path": "pages/my/my", "style": {} } ], "subPackages": [ { "root": "subpkg", "pages": [] } ] }
- 在 subpkg 目录上鼠标右键,点击 新建页面 选项,并填写页面的相关信息:
3.2.4 点击轮播图跳转到商品详情页面
将 节点内的 view 组件,改造为 navigator 导航组件,并动态绑定 url 属性的值。 改造之前的 UI 结构:
<swiper-item v-for="(item, i) in swiperList" :key="i">
<view class="swiper-item">
<!-- 动态绑定图片的 src 属性 -->
<image :src="item.image_src"></image>
</view>
</swiper-item>
改造之后的 UI 结构:
<swiper-item v-for="(item, i) in swiperList" :key="i">
<navigator class="swiper-item" :url="'/subpkg/goods_detail/goods_detail?goods_id=' + item.goods_id">
<!-- 动态绑定图片的 src 属性 -->
<image :src="item.image_src"></image>
</navigator>
</swiper-item>
url动态绑定 商品路径和商品id
3.2.5封装 uni.$showMsg() 方法
当数据请求失败之后,经常需要调用 uni.showToast({ /* 配置对象 */ }) 方法来提示用户。 此时,可以在全局封装一个 uni.$showMsg() 方法,来简化 uni.showToast() 方法的调用。具体的改造步骤如下:
- 在 main.js 中,为 uni 对象挂载自定义的 $showMsg() 方法:
// 封装弹框消息提示的方法 uni.$showMsg = function (title ='数据请求失败!', duration = 1500) { uni.showToast({ title, duration, icon: 'none', }) }
- 今后,在需要提示消息的时候,直接调用 uni.$showMsg() 方法即可:
async getSwiperList() { <!-- 发起网络数据请求,获取轮播图数据的方法 --> const { data: res } = await uni.$http.get('/api/public/v1/home/swiperdata') <!-- 请求失败 --> if (res.meta.status !== 200) return uni.$showMsg() <!-- 请求成功 -> 挂载到data的swpierList里,为data中数据赋值 --> this.swiperList = res.message }
首页分类导航接口 '/api/public/v1/home/swiperdata'
3.3 分类导航区域
3.3.1 获取分类导航的数据
实现思路:
- 定义 data 数据
- 在 onLoad 中调用获取数据的方法
- 在 methods 中定义获取数据的方法
示例代码如下:
export default {
data() {
return {
// 1. 分类导航的数据列表
navList: [],
}
},
onLoad() {
// 2. 在 onLoad 中调用获取数据的方法
this.getNavList()
},
methods: {
// 3. 在 methods 中定义获取数据的方法
async getNavList() {
const { data: res } = await uni.$http.get('/api/public/v1/home/catitems')
if (res.meta.status !== 200) return uni.$showMsg()
this.navList = res.message
},
},
}
3.3.2 渲染分类导航的 UI 结构
-
定义如下的 UI 结构:
<!-- 分类导航区域 --> <view class="nav-list"> <view class="nav-item" v-for="(item, i) in navList" :key="i"> <image :src="item.image_src" class="nav-img"></image> </view> </view>
-
通过如下的样式美化页面结构:
.nav-list { display: flex; justify-content: space-around; margin: 15px 0; .nav-img { width: 128rpx; height: 140rpx; } }
3.3.3 点击第一项,切换到分类页面
- 为 nav-item 绑定点击事件处理函数:
<!-- 分类导航区域 --> <view class="nav-list"> <view class="nav-item" v-for="(item, i) in navList" :key="i" @click="navClickHandler(item)"> <image :src="item.image_src" class="nav-img"></image> </view> </view>
- 定义 navClickHandler 事件处理函数:
// nav-item 项被点击时候的事件处理函数 navClickHandler(item) { if (item.name === '分类') { uni.swichTab({ url:'/pages/cate/cate' }) } }
注意:分类页面属于tabbar对应页面,要切换tabbar页面,只能调用switchTab
3.4楼层区域
实现思路:
- 定义 data 数据
- 在 onLoad 中调用获取数据的方法
- 在 methods 中定义获取数据的方法 示例代码如下:
export default { data() { return { // 1. 楼层的数据列表 floorList: [], } }, onLoad() { // 2. 在 onLoad 中调用获取楼层数据的方法 this.getFloorList() }, methods: { // 3. 定义获取楼层列表数据的方法 async getFloorList() { const { data: res } = await uni.$http.get('/api/public/v1/home/floordata') if (res.meta.status !== 200) return uni.$showMsg() this.floorList = res.message }, }, }
3.4.1 获取楼层数据
实现思路:
- 定义 data 数据
- 在 onLoad 中调用获取数据的方法
- 在 methods 中定义获取数据的方法 示例代码如下:
export default { data() { return { // 1. 楼层的数据列表 floorList: [], } }, onLoad() { // 2. 在 onLoad 中调用获取楼层数据的方法 this.getFloorList() }, methods: { // 3. 定义获取楼层列表数据的方法 async getFloorList() { const { data: res } = await uni.$http.get('/api/public/v1/home/floordata') if (res.meta.status !== 200) return uni.$showMsg() this.floorList = res.message }, }, }
楼层数据接口 '/api/public/v1/home/floordata'
3.4.2 渲染楼层的标题
- 定义如下的 UI 结构:
<!-- 楼层区域 --> <view class="floor-list"> <!-- 楼层 item 项 --> <view class="floor-item" v-for="(item, i) in floorList" :key="i"> <!-- 楼层标题 --> <image :src="item.floor_title.image_src" class="floor-title"></image> </view> </view>
- 美化楼层标题的样式:
.floor-title { height: 60rpx; width: 100%; display: flex; }
3.4.3 渲染楼层里的图片
-
定义楼层图片区域的 UI 结构:
<!-- 楼层图片区域 --> <view class="floor-img-box"> <!-- 左侧大图片的盒子 --> <view class="left-img-box"> <image :src="item.product_list[0].image_src" :style="{width: item.product_list[0].image_width + 'rpx'}" mode="widthFix"></image> </view> <!-- 右侧 4 个小图片的盒子 --> <view class="right-img-box"> <view class="right-img-item" v-for="(item2, i2) in item.product_list" :key="i2" v-if="i2 !== 0"> <image :src="item2.image_src" mode="widthFix" :style="{width: item2.image_width + 'rpx'}"></image> </view> </view> </view>
图片高度需要自适应 mode="widthFix"
-
美化楼层图片区域的样式:
.right-img-box { display: flex; flex-wrap: wrap; justify-content: space-around; } .floor-img-box { display: flex; padding-left: 10rpx; }
3.4.4 点击楼层图片跳转到商品列表页
-
在 subpkg 分包中,新建 goods_list 页面
-
楼层数据请求成功之后,通过双层 forEach 循环,处理 URL 地址:
// 获取楼层列表数据 async getFloorList() { const { data: res } = await uni.$http.get('/api/public/v1/home/floordata') if (res.meta.status !== 200) return uni.$showMsg() // 通过双层 forEach 循环,处理 URL 地址 res.message.forEach(floor => { floor.product_list.forEach(prod => { prod.url = '/subpkg/goods_list/goods_list?' + prod.navigator_url.split('?')[1] }) }) this.floorList = res.message }
split(“?“)分割字符串
-
把图片外层的 view 组件,改造为 navigator 组件,并动态绑定 url 属性 的值:
<!-- 楼层图片区域 --> <view class="floor-img-box"> <!-- 左侧大图片的盒子 --> <navigator class="left-img-box" :url="item.product_list[0].url"> <image :src="item.product_list[0].image_src" :style="{width: item.product_list[0].image_width + 'rpx'}" mode="widthFix"></image> </navigator> <!-- 右侧 4 个小图片的盒子 --> <view class="right-img-box"> <navigator class="right-img-item" v-for="(item2, i2) in item.product_list" :key="i2" v-if="i2 !== 0" :url="item2.url"> <image :src="item2.image_src" mode="widthFix" :style="{width: item2.image_width + 'rpx'}"></image> </navigator> </view> </view>
3.5 分支的合并与提交
- 将本地的 home 分支进行本地的 commit 提交:
git add . git commit -m "完成了 home 首页的开发"
- 将本地的 home 分支推送到远程仓库进行保存:
git push -u origin home
- 将本地的 home 分支合并到本地的 master 分支,再推送一次:
git checkout master git merge home git push
- 删除本地的 home 分支:
git branch -d home
4 分类
4.0 创建cate分支
git checkout -b cate
可以新建分类页面编译模式,刷新便是分类页面方便查看
4.1渲染分类页面的基本结构
-
定义页面结构如下:
<template> <view> <view class="scroll-view-container"> <!-- 左侧的滚动视图区域 --> <scroll-view class="left-scroll-view" scroll-y :style="{height: wh + 'px'}"> <view class="left-scroll-view-item active">xxx</view> <view class="left-scroll-view-item">xxx</view> <view class="left-scroll-view-item">xxx</view> <view class="left-scroll-view-item">xxx</view> <view class="left-scroll-view-item">xxx</view> <view class="left-scroll-view-item">多复制一些节点,演示纵向滚动效果...</view> </scroll-view> <!-- 右侧的滚动视图区域 --> <scroll-view class="right-scroll-view" scroll-y :style="{height: wh + 'px'}"> <view class="left-scroll-view-item">zzz</view> <view class="left-scroll-view-item">zzz</view> <view class="left-scroll-view-item">zzz</view> <view class="left-scroll-view-item">zzz</view> <view class="left-scroll-view-item">多复制一些节点,演示纵向滚动效果</view> </scroll-view> </view> </view> </template>
-
动态计算窗口的剩余高度:
<script> export default { data() { return { // 窗口的可用高度 = 屏幕高度 - navigationBar高度 - tabBar 高度 wh: 0 }; }, onLoad() { // 获取当前系统的信息 const sysInfo = uni.getSystemInfoSync() // 为 wh 窗口可用高度动态赋值 this.wh = sysInfo.windowHeight } } </script>
调用uni.getSystemInfoSync()获取设备信息
-
美化页面结构:
.scroll-view-container { display: flex; .left-scroll-view { width: 120px; .left-scroll-view-item { line-height: 60px; background-color: #f7f7f7; text-align: center; font-size: 12px; // 激活项的样式 &.active { background-color: #ffffff; position: relative; // 渲染激活项左侧的红色指示边线 伪元素 &::before { content: ' '; display: block; width: 3px; height: 30px; background-color: #c00000; position: absolute; left: 0; top: 50%; transform: translateY(-50%); } } } } }
4.2 获取分类数据
- 在 data 中定义分类数据节点:
data() { return { // 分类数据列表 cateList: [] } }
- 调用获取分类列表数据的方法:
onLoad() { // 调用获取分类列表数据的方法 this.getCateList() }
- 定义获取分类列表数据的方法:
methods: { async getCateList() { // 发起请求 const { data: res } = await uni.$http.get('/api/public/v1/categories') // 判断是否获取失败 if (res.meta.status !== 200) return uni.$showMsg() // 转存数据 this.cateList = res.message } }
4.3 动态渲染左侧的一级分类列表
- 循环渲染列表结构:
<!-- 左侧的滚动视图区域 --> <scroll-view class="left-scroll-view" scroll-y :style="{height: wh + 'px'}"> <block v-for="(item,i) in cateList" :key="i"> <view class="left-scroll-view-item">{{item.cat_name}}</view> </block> </scroll-view>
- 在data中定义默认选中项的索引:
data() { return { active: 0 } }
- 循环渲染结构时,为选中项动态添加
.active
类名<block v-for="(item, i) in cateList" :key="i"> <view :class="['left-scroll-view-item', i === active ? 'active' : '']">{{item.cat_name}}</view> </block>
- 为一级分类的item项绑定点击事件处理函数activeChanged:
<block v-for="(item, i) in cateList" :key="i"> <view :class="['left-scroll-view-item', i === active ? 'active' : '']" @click="activeChanged(i)">{{item.cat_name}}</view> </block>
- 定义
activeChanged
事件处理函数,动态修改选中项的索引:methods: { //选中项 改变的事件处理函数 activeChanged(i) { this.active = i } }
4.4 动态渲染右侧的二级分类列表
- 在 data 中定义二级分类列表的数据节点:
data() { return { // 二级分类列表 cateLevel2: [] } }
- 修改 getCateList 方法,在请求到数据之后,为二级分类列表数据赋值:
async getCateList() { const { data: res } = await uni.$http.get('/api/public/v1/categories') if (res.meta.status !== 200) return uni.$showMsg() this.cateList = res.message // 为二级分类赋值 this.cateLevel2 = res.message[0].children }
- 修改 activeChanged 方法,在一级分类选中项改变之后,为二级分类列表数据重新赋值:
activeChanged(i) { this.active = i // 为二级分类列表重新赋值 this.cateLevel2 = this.cateList[i].children }
- 循环渲染右侧二级分类列表的 UI 结构:
<!-- 右侧的滚动视图区域 --> <scroll-view class="right-scroll-view" scroll-y :style="{height: wh + 'px'}"> <view class="cate-lv2" v-for="(item2, i2) in cateLevel2" :key="i2"> <view class="cate-lv2-title">/ {{item2.cat_name}} /</view> </view> </scroll-view>
- 美化二级分类的标题样式:
.cate-lv2-title { font-size: 12px; font-weight: bold; text-align: center; padding: 15px 0; }
4.5 动态渲染右侧的三级分类列表
-
在二级分类的 组件中,循环渲染三级分类的列表结构:
<!-- 右侧的滚动视图区域 --> <scroll-view class="right-scroll-view" scroll-y :style="{height: wh + 'px'}"> <view class="cate-lv2" v-for="(item2, i2) in cateLevel2" :key="i2"> <view class="cate-lv2-title">/ {{item2.cat_name}} /</view> <!-- 动态渲染三级分类的列表数据 --> <view class="cate-lv3-list"> <!-- 三级分类 Item 项 --> <view class="cate-lv3-item" v-for="(item3, i3) in item2.children" :key="i3"> <!-- 图片 --> <image :src="item3.cat_icon"></image> <!-- 文本 --> <text>{{item3.cat_name}}</text> </view> </view> </view> </scroll-view>
-
美化三级分类的样式:
.cate-lv3-list { display: flex; flex-wrap: wrap; .cate-lv3-item { width: 33.33%; margin-bottom: 10px; display: flex; flex-direction: column; align-items: center; image { width: 60px; height: 60px; } text { font-size: 12px; } } }
4.6 切换一级分类后重置滚动条的位置
-
在 data 中定义 滚动条距离顶部的距离:
data() { return { // 滚动条距离顶部的距离 scrollTop: 0 } }
-
动态为右侧的 组件绑定 :scroll-top 属性
<!-- 右侧的滚动视图区域 --> <scroll-view class="right-scroll-view" scroll-y :style="{height: wh + 'px'}" :scroll-top="scrollTop"></scroll-view>
-
切换一级分类时,动态设置 scrollTop 的值
// 选中项改变的事件处理函数 activeChanged(i) { this.active = i this.cateLevel2 = this.cateList[i].children // 让 scrollTop 的值在 0 与 1 之间切换 this.scrollTop = this.scrollTop === 0 ? 1 : 0 // 可以简化为如下的代码: // this.scrollTop = this.scrollTop ? 0 : 1 }
4.7 点击三级分类跳转到商品列表页面
- 为三级分类的item项绑定点击事件
<view class="cate-lv3-item" v-for="(item3, i3) in item2.children" :key="i3" @click="gotoGoodsList(item3)"> <image :src="item3.cat_icon"></image> <text>{{item3.cat_name}}</text> </view>
- 定义事件处理函数如下
// 点击三级分类项跳转到商品列表页面 gotoGoodsList(item3) { uni.navigateTo({ url: '/subpkg/goods_list/goods_list?cid=' + item3.cat_id }) }
4.8 分支的合并与提交
- 将 cate 分支进行本地提交:
git add . git commit -m "完成了分类页面的开发"
- 将本地的 cate 分支推送到码云:
git push -u origin cate
- 将本地 cate 分支中的代码合并到 master 分支:
git checkout master git merge cate git push
- 删除本地的 cate 分支:
git branch -d cate
5.搜索
5.0 创建 search 分支
git checkout -b search
5.1 自定义搜索组件
5.1.1 自定义 my-search 组件
-
在项目根目录的 components 目录上,鼠标右键,选择 新建组件,填写组件信息后,最后点击 创建 按钮:
-
在分类页面的 UI 结构中,直接以标签的形式使用 my-search 自定义组件:
<!-- 使用自定义的搜索组件 --> <my-search></my-search>
定义好的组件,可在
template
下直接使用 -
定义 my-search 组件的 UI 结构如下:
<template> <view class="my-search-container"> <!-- 使用 view 组件模拟 input 输入框的样式 --> <view class="my-search-box"> <uni-icons type="search" size="17"></uni-icons> <text class="placeholder">搜索</text> </view> </view> </template>
注意:在当前组件中,我们使用 view 组件模拟 input 输入框的效果;并不会在页面上渲染真正的 input 输入框
-
美化自定义 search 组件的样式:
.my-search-container { background-color: #c00000; height: 50px; padding: 0 10px; display: flex; align-items: center; } .my-search-box { height: 36px; background-color: #ffffff; border-radius: 15px; width: 100%; display: flex; align-items: center; justify-content: center; .placeholder { font-size: 15px; margin-left: 5px; } }
-
由于自定义的 my-search 组件高度为 50px,因此,需要重新计算分类页面窗口的可用高度:
onLoad() { const sysInfo = uni.getSystemInfoSync() // 可用高度 = 屏幕高度 - navigationBar高度 - tabBar高度 - 自定义的search组件高度 this.wh = sysInfo.windowHeight - 50 }
加入搜索框的scroll滑动距离需重新计算,于是在cate目录中修改
5.1.2 通过自定义属性增强组件的通用性
为了增强组件的通用性,我们允许使用者自定义搜索组件的 背景颜色
和 圆角尺寸
。
-
通过 props 定义 bgcolor 和 radius 两个属性,并指定值类型和属性默认值:
props: { // 背景颜色 bgcolor: { type: String, default: '#C00000' }, // 圆角尺寸 radius: { type: Number, // 单位是 px default: 18 } }
-
通过属性绑定的形式,为 .my-search-container 盒子和 .my-search-box 盒子动态绑定 style 属性:
<view class="my-search-container" :style="{'background-color': bgcolor}"> <view class="my-search-box" :style="{'border-radius': radius + 'px'}"> <uni-icons type="search" size="17"></uni-icons> <text class="placeholder">搜索</text> </view> </view>
-
移除对应 scss 样式中的 背景颜色 和 圆角尺寸:
.my-search-container { // 移除背景颜色,改由 props 属性控制 // background-color: #C00000; height: 50px; padding: 0 10px; display: flex; align-items: center; } .my-search-box { height: 36px; background-color: #ffffff; // 移除圆角尺寸,改由 props 属性控制 // border-radius: 15px; width: 100%; display: flex; align-items: center; justify-content: center; .placeholder { font-size: 15px; margin-left: 5px; } }
5.1.3 为自定义组件封装 click 事件
- 在 my-search 自定义组件内部,给类名为 .my-search-box 的 view 绑定 click 事件处理函数:
<view class="my-search-box" :style="{'border-radius': radius + 'px'}" @click="searchBoxHandler"> <uni-icons type="search" size="17"></uni-icons> <text class="placeholder">搜索</text> </view>
- 在 my-search 自定义组件的 methods 节点中,声明事件处理函数如下:
methods: { // 点击了模拟的 input 输入框 searchBoxHandler() { // 触发外界通过 @click 绑定的 click 事件处理函数 this.$emit('click') } }
- 在分类页面中使用 my-search 自定义组件时,即可通过 @click 为其绑定点击事件处理函数:
同时在分类页面中,定义 gotoSearch 事件处理函数如下:<!-- 使用自定义的搜索组件 --> <my-search @click="gotoSearch"></my-search>
methods: { // 跳转到分包中的搜索页面 gotoSearch() { uni.navigateTo({ url: '/subpkg/search/search' }) } }
为组件封装 click 事件,以后在哪里调用组件直接 @调用 ,方便
- 此时可能还没有创建 subpkg/search 因此报错,自己右键subpkg根目录创建一下
5.1.4 实现首页搜索组件的吸顶效果
- 在 home 首页定义如下的 UI 结构:
<!-- 使用自定义的搜索组件 --> <view class="search-box"> <my-search @click="gotoSearch"></my-search> </view>
- 在 home 首页定义如下的事件处理函数:
gotoSearch() { uni.navigateTo({ url: '/subpkg/search/search' }) }
- 通过如下的样式实现吸顶的效果:
.search-box { // 设置定位效果为“吸顶” position: sticky; // 吸顶的“位置” top: 0; // 提高层级,防止被轮播图覆盖 z-index: 999; }
5.2 搜索建议
5.2.1 渲染搜索页面的基本结构
- 定义如下的 UI 结构:
<view class="search-box"> <!-- 使用 uni-ui 提供的搜索组件 --> <uni-search-bar @input="input" :radius="100" cancelButton="none"></uni-search-bar> </view>
- 修改 uni_modules -> uni-search-bar -> uni-search-bar.vue 组件,将默认的白色搜索背景改为 #C00000 的红色背景:
.uni-searchbar { /* #ifndef APP-NVUE */ display: flex; /* #endif */ flex-direction: row; position: relative; padding: 16rpx; /* 将默认的 #FFFFFF 改为 #C00000 */ background-color: #c00000; }
教程提到的是
uni-search-bar
在component
目录下,我这里不同,可能是导入插件时一并导入了,根据uni-search-bar
的位置调整就行 - 实现搜索框的吸顶效果:
.search-box { position: sticky; top: 0; z-index: 999; }
- 定义如下的 input 事件处理函数:
methods: { input(e) { // e 是最新的搜索内容 console.log(e) } }
这里注意,和原文档里的不同,可能因为uni-search-bar的更新,这里无需加 value,否则报 undefined
5.2.2 实现搜索框自动获取焦点
- 修改 uni_modules -> uni-search-bar -> uni-search-bar.vue 组件,把 data 数据中的 show 和 showSync 的值,从默认的 false 改为 true 即可:
data() { return { show: true, showSync: true, searchVal: "" } }
- 使用手机扫码预览,即可在真机上查看效果。
5.2.3 实现搜索框的防抖处理
- 在 data 中定义防抖的延时器 timerId 如下:
data() { return { // 延时器的 timerId timer: null, // 搜索关键词 kw: '' } }
- 修改 input 事件处理函数如下:
input(e) { //用户在500ms内连续输入内容,应该清除之前的 clearTimeout(this.timer) //开一个延时器就会有一个返回值 timer this.timer = setTimeout(() => { this.kw = e console.log(e) },500) },
5.2.4 根据关键词查询搜索建议列表
- 在 data 中定义如下的数据节点,用来存放搜索建议的列表数据:
data() { return { // 搜索结果列表 searchResults: [] } }
- 在防抖的 setTimeout 中,调用 getSearchList 方法获取搜索建议列表:
this.timer = setTimeout(() => { this.kw = e this.getSearchList() },500)
- 在 methods 中定义 getSearchList 方法如下:
// 根据搜索关键词,搜索商品建议列表 async getSearchList() { // 判断关键词是否为空 if (this.kw === '') { this.searchResults = [] return } // 发起请求,获取搜索建议列表 const { data: res } = await uni.$http.get('/api/public/v1/goods/qsearch', { query: this.kw }) if (res.meta.status !== 200) return uni.$showMsg() this.searchResults = res.message }
5.2.5 渲染搜索建议列表
-
定义如下的 UI 结构:
<!-- 搜索建议列表 --> <view class="sugg-list"> <view class="sugg-item" v-for="(item, i) in searchResults" :key="i" @click="gotoDetail(item.goods_id)"> <view class="goods-name">{{item.goods_name}}</view> <uni-icons type="arrowright" size="16"></uni-icons> </view> </view>
-
美化搜索建议列表:
.sugg-list { padding: 0 5px; .sugg-item { font-size: 12px; padding: 13px 0; border-bottom: 1px solid #efefef; display: flex; align-items: center; justify-content: space-between; .goods-name { // 文字不允许换行(单行文本) white-space: nowrap; // 溢出部分隐藏 overflow: hidden; // 文本溢出后,使用 ... 代替 text-overflow: ellipsis; margin-right: 3px; } } }
样式要是报错就不嵌套,拿出来写
-
点击搜索建议的 Item 项,跳转到商品详情页面:
gotoDetail(goods_id) { uni.navigateTo({ // 指定详情页面的 URL 地址,并传递 goods_id 参数 url: '/subpkg/goods_detail/goods_detail?goods_id=' + goods_id }) }
5.3 搜索历史
5.3.1 渲染搜索历史记录的基本结构
-
在 data 中定义搜索历史的假数据:
data() { return { // 搜索关键词的历史记录 historyList: ['a', 'app', 'apple'] } }
-
渲染搜索历史区域的 UI 结构:
<!-- 搜索历史 --> <view class="history-box"> <!-- 标题区域 --> <view class="history-title"> <text>搜索历史</text> <uni-icons type="trash" size="17"></uni-icons> </view> <!-- 列表区域 --> <view class="history-list"> <uni-tag :text="item" v-for="(item, i) in historyList" :key="i"></uni-tag> </view> </view>
-
美化搜索历史区域的样式:
.history-box { padding: 0 5px; .history-title { display: flex; justify-content: space-between; align-items: center; height: 40px; font-size: 13px; border-bottom: 1px solid #efefef; } .history-list { display: flex; flex-wrap: wrap; .uni-tag { margin-top: 5px; margin-right: 5px; } } }
5.3.2 实现搜索建议和搜索历史的按需展示
- 当搜索结果列表的长度不为 0的时候(searchResults.length !== 0),需要展示搜索建议区域,隐藏搜索历史区域
- 当搜索结果列表的长度等于 0的时候(searchResults.length === 0),需要隐藏搜索建议区域,展示搜索历史区域
- 使用 v-if 和 v-else 控制这两个区域的显示和隐藏,示例代码如下:
<!-- 搜索建议列表 --> <view class="sugg-list" v-if="searchResults.length !== 0"> <!-- 省略其它代码... --> </view>
```
5.3.3 将搜索关键词存入 historyList
-
直接将搜索关键词
push
到historyList
数组中即可methods: { // 根据搜索关键词,搜索商品建议列表 async getSearchList() { // 省略其它不必要的代码... // 1. 查询到搜索建议之后,调用 saveSearchHistory() 方法保存搜索关键词 this.saveSearchHistory() }, // 2. 保存搜索关键词的方法 saveSearchHistory() { // 2.1 直接把搜索关键词 push 到 historyList 数组中 this.historyList.push(this.kw) } }
-
上述实现思路存在的问题:
- 关键词前后顺序的问题(可以调用数组的 reverse() 方法对数组进行反转)
- 关键词重复的问题(可以使用 Set 对象进行去重操作)
5.3.4 解决关键字前后顺序的问题
-
data中的
historyList
不做任何修改,依然使用push进行末尾追加 -
定义一个计算属性
histories
,将historyList
数组reverse
反转之后,就是此计算属性的值:computed: { histories() { return[...this.historyList].reverse() } }
注意:由于数组是引用类型,所以不要直接基于原数组调用 reverse 方法,以免修改原数组中元素的顺序 而是应该新建一个内存无关的数组,再进行 reverse 反转
-
页面中渲染搜索关键词的时候,不再使用data中的
historyList
,而是使用计算属性histories
:<view> <uni-tag :text="item" v-for="(item,i) in histories" :key="i"></uni-tag> </view>
5.3.5 解决关键词重复的问题
修改 saveSearchHistory
方法如下:
// 保存搜索关键词为历史记录
saveSearchHistory() {
// this.historyList.push(this.kw)
// 1. 将 Array 数组转化为 Set 对象
const set = new Set(this.historyList)
// 2. 调用 Set 对象的 delete 方法,移除对应的元素
set.delete(this.kw)
// 3. 调用 Set 对象的 add 方法,向 Set 中添加元素
set.add(this.kw)
// 4. 将 Set 对象转化为 Array 数组
this.historyList = Array.from(set)
}
5.3.6 将搜索历史记录持久化存储到本地
- 修改 saveSearchHistory 方法如下:
//保存搜索关键词 saveSearchHistory() { const set = new Set(this.historyList) set.delete(this.kw) set.add(this.kw) this.historyList = Array.from(set) //调用uni.setStorageSync(key,value) 将搜索记录持久化存储到本地 uni.setStorageSync('kw',JSON.stringify(this.historyList)) }
- 在
onLoad
生命周期函数中,加载本地存储的搜索历史记录:onLoad() { this.historyList = JSON.parse(uni.getStorageSync('kw') || '[]') }
5.3.7 清空搜索历史记录
- 为清空的图标按钮绑定
click
事件:<uni-icons type="trash" size="17" @click="cleanHistory"></uni-icons>
- 在
methods
中定义cleanHistory
处理函数://清空搜索历史记录 cleanHistory() { //清空 data 保存的搜索历史 this.historyList = [] //清空本地存储中的搜索历史 uni.setStorageSync('kw','[]') }
5.3.8 点击搜索历史跳转到商品列表页面
- 为搜索历史的 item 绑定
click
事件处理函数:<uni-tag :text="item" v-for="(item, i) in historys" :key="i" @click="gotoGoodsList(item)"></uni-tag>
item传进去,是因为要根据关键词来进行商品的展示
- 在
methods
中定义gotoGoodsList
处理函数:gotoGoodsList(kw) { uni.navigateTo({ url:'subpkg/goods_list/goods_list?query=' + kw }) }
5.4 分支的合并与提交
-
将 search 分支进行本地提交:
git add . git commit -m "完成了搜索功能的开发"
-
将本地的 search 分支推送到码云:
git push -u origin search
-
将本地 search 分支中的代码合并到 master 分支:
git checkout master git merge search git push
-
删除本地的 search 分支:
git branch -d search
6.商品列表
6.0 创建 goodslist 分支
git checkout -b goodslist
6.1 定义请求参数对象
- 为了方便发起请求获取商品列表的数据,根据接口的要求, 事先定义一个请求参数对象
data() { return { // 请求参数对象 queryObj: { // 查询关键词 query: '', // 商品分类Id cid: '', // 页码值 pagenum: 1, // 每页显示多少条数据 pagesize: 10 } } }
- 将页面跳转时携带的参数,转存到
queryObj
对象中onLoad(options) { // 将页面参数转存到 this.queryObj 对象中 this.queryObj.query = options.query || '' this.queryObj.cid = options.cid || '' }
6.2 获取商品列表数据
-
在data中新增如下的数据节点:
data() { return { // 商品列表的数据 goodsList: [], // 总数量,用来实现分页 total: 0 } }
-
在
onLoad
生命周期函数中,调用getGoodsList
方法获取商品列表数据:onLoad(options) { // 调用获取商品列表数据的方法 this.getGoodsList() }
-
在
methods
节点中,声明getGoodsList
方法如下:methods: { // 获取商品列表数据的方法 async getGoodsList() { // 发起请求 const { data: res } = await uni.$http.get('/api/public/v1/goods/search', this.queryObj) if (res.meta.status !== 200) return uni.$showMsg() // 为数据赋值 this.goodsList = res.message.goods this.total = res.message.total } }
6.3 渲染商品列表结构
-
在页面中,通过 v-for 指令,循环渲染出商品的 UI 结构:
<template> <view> <view class="goods-list"> <block v-for="(goods, i) in goodsList" :key="i"> <view class="goods-item"> <!-- 商品左侧图片区域 --> <view class="goods-item-left"> <image :src="goods.goods_small_logo || defaultPic" class="goods-pic"></image> </view> <!-- 商品右侧信息区域 --> <view class="goods-item-right"> <!-- 商品标题 --> <view class="goods-name">{{goods.goods_name}}</view> <view class="goods-info-box"> <!-- 商品价格 --> <view class="goods-price">¥{{goods.goods_price}}</view> </view> </view> </view> </block> </view> </view> </template>
使用block包裹:循环时不会被渲染为任何实际的元素,起到包裹性质的作用,结构更加清晰
-
为了防止某些商品的图片不存在,需要在 data 中定义一个默认的图片:
data() { return { // 默认的空图片 defaultPic: 'https://img3.doubanio.com/f/movie/8dd0c794499fe925ae2ae89ee30cd225750457b4/pics/movie/celebrity-default-medium.png' } }
并在页面渲染时按需使用:
<image :src="goods.goods_small_logo || defaultPic" class="goods-pic"></image>
-
美化商品列表的 UI 结构:
.goods-item { display: flex; padding: 10px 5px; border-bottom: 1px solid #f0f0f0; .goods-item-left { margin-right: 5px; .goods-pic { width: 100px; height: 100px; display: block; } } .goods-item-right { display: flex; flex-direction: column; justify-content: space-between; .goods-name { font-size: 13px; } .goods-price { font-size: 16px; color: #c00000; } } }
6.4 把商品 item 项封装为自定义组件
-
在 components 目录上鼠标右键,选择 新建组件:
-
将 goods_list 页面中,关于商品 item 项相关的 UI 结构、样式、data 数据,封装到 my-goods 组件中:
<template> <view class="goods-item"> <!-- 商品左侧图片区域 --> <view class="goods-item-left"> <image :src="goods.goods_small_logo || defaultPic" class="goods-pic"></image> </view> <!-- 商品右侧信息区域 --> <view class="goods-item-right"> <!-- 商品标题 --> <view class="goods-name">{{goods.goods_name}}</view> <view class="goods-info-box"> <!-- 商品价格 --> <view class="goods-price">¥{{goods.goods_price}}</view> </view> </view> </view> </template> <script> export default { // 定义 props 属性,用来接收外界传递到当前组件的数据 props: { // 商品的信息对象 goods: { type: Object, defaul: {}, }, }, data() { return { // 默认的空图片 defaultPic: 'https://img3.doubanio.com/f/movie/8dd0c794499fe925ae2ae89ee30cd225750457b4/pics/movie/celebrity-default-medium.png', } }, } </script> <style lang="scss"> .goods-item { display: flex; padding: 10px 5px; border-bottom: 1px solid #f0f0f0; .goods-item-left { margin-right: 5px; .goods-pic { width: 100px; height: 100px; display: block; } } .goods-item-right { display: flex; flex-direction: column; justify-content: space-between; .goods-name { font-size: 13px; } .goods-price { font-size: 16px; color: #c00000; } } } </style>
-
在 goods_list 组件中,循环渲染 my-goods 组件即可:
<view class="goods-list"> <block v-for="(item, i) in goodsList" :key="i"> <!-- 为 my-goods 组件动态绑定 goods 属性的值 --> <my-goods :goods="item"></my-goods> </block> </view>
6.5 使用过滤器处理价格
- 在 my-goods 组件中,和 data 节点平级,声明 filters 过滤器节点如下:
filters: { // 把数字处理为带两位小数点的数字 tofixed(num) { return Number(num).toFixed(2) } }
- 在渲染商品价格的时候,通过管道符 | 调用过滤器:
<!-- 商品价格 --> <view class="goods-price">¥{{goods.goods_price | tofixed}}</view>
6.6 上拉加载更多
6.6.1 初步实现上拉加载更多
-
打开项目根目录中的 pages.json 配置文件,为 subPackages 分包中的 goods_list 页面配置上拉触底的距离:
"subPackages": [ { "root": "subpkg", "pages": [ { "path": "goods_detail/goods_detail", "style": {} }, { "path": "goods_list/goods_list", "style": { "onReachBottomDistance": 150 } }, { "path": "search/search", "style": {} } ] } ]
-
在 goods_list 页面中,和 methods 节点平级,声明 onReachBottom 事件处理函数,用来监听页面的上拉触底行为:
// 触底的事件 onReachBottom() { // 让页码值自增 +1 this.queryObj.pagenum += 1 // 重新获取列表数据 this.getGoodsList() }
-
改造 methods 中的 getGoodsList 函数,当列表数据请求成功之后,进行新旧数据的拼接处理:
// 获取商品列表数据的方法 async getGoodsList() { // 发起请求 const { data: res } = await uni.$http.get('/api/public/v1/goods/search', this.queryObj) if (res.meta.status !== 200) return uni.$showMsg() // 为数据赋值:通过展开运算符的形式,进行新旧数据的拼接 this.goodsList = [...this.goodsList, ...res.message.goods] this.total = res.message.total }
6.6.2 通过节流阀防止发起额外的请求
-
在 data 中定义 isloading 节流阀如下:
data() { return { // 是否正在请求数据 isloading: false } }
-
修改 getGoodsList 方法,在请求数据前后,分别打开和关闭节流阀:
// 获取商品列表数据的方法 async getGoodsList() { // ** 打开节流阀 this.isloading = true // 发起请求 const { data: res } = await uni.$http.get('/api/public/v1/goods/search', this.queryObj) // ** 关闭节流阀 this.isloading = false // 省略其它代码... }
-
在 onReachBottom 触底事件处理函数中,根据节流阀的状态,来决定是否发起请求:
// 触底的事件 onReachBottom() { // 判断是否正在请求其它数据,如果是,则不发起额外的请求 if (this.isloading) return this.queryObj.pagenum += 1 this.getGoodsList() }
6.6.3 判断数据是否加载完毕
-
如果下面的公式成立,则证明没有下一页数据了:
当前的页码值 * 每页显示多少条数据 >= 总数条数 pagenum * pagesize >= total
记一下公式
-
修改 onReachBottom 事件处理函数如下:
// 触底的事件 onReachBottom() { // 判断是否还有下一页数据 if (this.queryObj.pagenum * this.queryObj.pagesize >= this.total) return uni.$showMsg('数据加载完毕!') // 判断是否正在请求其它数据,如果是,则不发起额外的请求 if (this.isloading) return this.queryObj.pagenum += 1 this.getGoodsList() }
6.7 下拉刷新
-
在 pages.json 配置文件中,为当前的 goods_list 页面单独开启下拉刷新效果:
"subPackages": [{ "root": "subpkg", "pages": [{ "path": "goods_detail/goods_detail", "style": {} }, { "path": "goods_list/goods_list", "style": { "onReachBottomDistance": 150, "enablePullDownRefresh": true, "backgroundColor": "#F8F8F8" } }, { "path": "search/search", "style": {} }] }]
-
监听页面的 onPullDownRefresh 事件处理函数:
// 下拉刷新的事件 onPullDownRefresh() { // 1. 重置关键数据 this.queryObj.pagenum = 1 this.total = 0 this.isloading = false this.goodsList = [] // 2. 重新发起请求 this.getGoodsList(() => uni.stopPullDownRefresh()) }
-
修改 getGoodsList 函数,接收 cb 回调函数并按需进行调用:
// 获取商品列表数据的方法 async getGoodsList(cb) { this.isloading = true const { data: res } = await uni.$http.get('/api/public/v1/goods/search', this.queryObj) this.isloading = false // 只要数据请求完毕,就立即按需调用 cb 回调函数 cb && cb() if (res.meta.status !== 200) return uni.$showMsg() this.goodsList = [...this.goodsList, ...res.message.goods] this.total = res.message.total }
6.8 点击商品 item 项跳转到详情页面
- 将循环时的 block 组件修改为 view 组件,并绑定 click 点击事件处理函数:
<view class="goods-list"> <view v-for="(item, i) in goodsList" :key="i" @click="gotoDetail(item)"> <!-- 为 my-goods 组件动态绑定 goods 属性的值 --> <my-goods :goods="item"></my-goods> </view> </view>
- 在 methods 节点中,定义 gotoDetail 事件处理函数:
// 点击跳转到商品详情页面 gotoDetail(item) { uni.navigateTo({ url: '/subpkg/goods_detail/goods_detail?goods_id=' + item.goods_id }) }
6.9 分支的合并与提交
- 将 goodslist 分支进行本地提交:
git add . git commit -m "完成了商品列表页面的开发"
- 将本地的 goodslist 分支推送到码云:
git push -u origin goodslist
- 将本地 goodslist 分支中的代码合并到 master 分支:
git checkout master git merge goodslist git push
- 删除本地的 goodslist 分支:
git branch -d goodslist
7. 商品详情
7.0 创建 goodsdetail 分支
运行如下的命令,基于 master 分支在本地创建 goodsdetail 子分支,用来开发商品详情页相关的功能:
git checkout -b goodsdetail
7.1 添加商品详情页的编译模式
- 在微信开发者工具中,点击工具栏上的编译模式下拉菜单,选择 添加编译模式 选项:
- 勾选 启动页面 的路径,并填写了 启动参数 之后,点击 确定 按钮,添加详情页面的编译模式:
7.2 获取商品详情数据
- 在 data 中定义商品详情的数据节点:
data() { return { // 商品详情对象 goods_info: {} } }
- 在 onLoad 中获取商品的 Id,并调用请求商品详情的方法:
onLoad(options) { // 获取商品 Id const goods_id = options.goods_id // 调用请求商品详情数据的方法 this.getGoodsDetail(goods_id) }
- 在 methods 中声明 getGoodsDetail 方法:
methods: { // 定义请求商品详情数据的方法 async getGoodsDetail(goods_id) { const { data: res } = await uni.$http.get('/api/public/v1/goods/detail', { goods_id }) if (res.meta.status !== 200) return uni.$showMsg() // 为 data 中的数据赋值 this.goods_info = res.message } }
7.3 渲染商品详情页的 UI 结构
7.3.1 渲染轮播图区域
-
使用 v-for 指令,循环渲染如下的轮播图 UI 结构:
<!-- 轮播图区域 --> <swiper :indicator-dots="true" :autoplay="true" :interval="3000" :duration="1000" :circular="true"> <swiper-item v-for="(item, i) in goods_info.pics" :key="i"> <image :src="item.pics_big"></image> </swiper-item> </swiper>
-
美化轮播图的样式:
swiper { height: 750rpx; image { width: 100%; height: 100%; } }
7.3.2 实现轮播图预览效果
- 为轮播图中的
image
图片绑定click
事件处理函数:<swiper-item v-for="(item, i) in goods_info.pics" :key="i"> <!-- 把当前点击的图片的索引,传递到 preview() 处理函数中 --> <image :src="item.pics_big" @click="preview(i)"></image> </swiper-item>
- 在
methods
中定义preview
事件处理函数:// 实现轮播图的预览效果 preview(i) { // 调用 uni.previewImage() 方法预览图片 uni.previewImage({ // 预览时,默认显示图片的索引 current: i, // 所有图片 url 地址的数组 urls: this.goods_info.pics.map(x => x.pics_big) }) }
7.3.3 渲染商品信息区域
-
定义商品信息区域的 UI 结构如下:
<!-- 商品信息区域 --> <view class="goods-info-box"> <!-- 商品价格 --> <view class="price">¥{{goods_info.goods_price}}</view> <!-- 信息主体区域 --> <view class="goods-info-body"> <!-- 商品名称 --> <view class="goods-name">{{goods_info.goods_name}}</view> <!-- 收藏 --> <view class="favi"> <uni-icons type="star" size="18" color="gray"></uni-icons> <text>收藏</text> </view> </view> <!-- 运费 --> <view class="yf">快递:免运费</view> </view>
-
美化商品信息区域的样式:
// 商品信息区域的样式 .goods-info-box { padding: 10px; padding-right: 0; .price { color: #c00000; font-size: 18px; margin: 10px 0; } .goods-info-body { display: flex; justify-content: space-between; .goods-name { font-size: 13px; padding-right: 10px; } // 收藏区域 .favi { width: 120px; font-size: 12px; display: flex; flex-direction: column; justify-content: center; align-items: center; border-left: 1px solid #efefef; color: gray; } } // 运费 .yf { margin: 10px 0; font-size: 12px; color: gray; } }
7.3.4 渲染商品详情信息
-
在页面结构中,使用 rich-text 组件,将带有 HTML 标签的内容,渲染为小程序的页面结构:
<!-- 商品详情信息 --> <rich-text :nodes="goods_info.goods_introduce"></rich-text>
nodes属性可以渲染html对应标签
-
修改 getGoodsDetail 方法,从而解决图片底部 空白间隙 的问题:
// 定义请求商品详情数据的方法 async getGoodsDetail(goods_id) { const { data: res } = await uni.$http.get('/api/public/v1/goods/detail', { goods_id }) if (res.meta.status !== 200) return uni.$showMsg() // 使用字符串的 replace() 方法,为 img 标签添加行内的 style 样式,从而解决图片底部空白间隙的问题 res.message.goods_introduce = res.message.goods_introduce.replace(/<img /g, '<img style="display:block;" ') this.goods_info = res.message }
-
解决 .webp 格式图片在 ios 设备上无法正常显示的问题:
// 定义请求商品详情数据的方法 async getGoodsDetail(goods_id) { const { data: res } = await uni.$http.get('/api/public/v1/goods/detail', { goods_id }) if (res.meta.status !== 200) return uni.$showMsg() // 使用字符串的 replace() 方法,将 webp 的后缀名替换为 jpg 的后缀名 res.message.goods_introduce = res.message.goods_introduce.replace(/<img /g, '<img style="display:block;" ').replace(/webp/g, 'jpg') this.goods_info = res.message }
7.3.5 解决商品价格闪烁的问题
- 导致问题的原因:在商品详情数据请求回来之前,data 中 goods_info 的值为 {},因此初次渲染页面时,会导致 商品价格、商品名称 等闪烁的问题。
- 解决方案:判断 goods_info.goods_name 属性的值是否存在,存在才执行下面代码,从而使用 v-if 指令控制页面的显示与隐藏:
<template> <view v-if="goods_info.goods_name"> <!-- 省略其它代码 --> </view> </template>
7.4 渲染详情页底部的商品导航区域
7.4.1 渲染商品导航区域的 UI 结构
基于 uni-ui 提供的 GoodsNav 组件来实现商品导航区域的效果
- 在 data 中,通过 options 和 buttonGroup 两个数组,来声明商品导航组件的按钮配置对象:
data() { return { // 商品详情对象 goods_info: {}, // 左侧按钮组的配置对象 options: [{ icon: 'shop', text: '店铺' }, { icon: 'cart', text: '购物车', info: 2 }], // 右侧按钮组的配置对象 buttonGroup: [{ text: '加入购物车', backgroundColor: '#ff0000', color: '#fff' }, { text: '立即购买', backgroundColor: '#ffa200', color: '#fff' } ] } }
- 在页面中使用 uni-goods-nav 商品导航组件:
<!-- 商品导航组件 --> <view class="goods_nav"> <!-- fill 控制右侧按钮的样式 --> <!-- options 左侧按钮的配置项 --> <!-- buttonGroup 右侧按钮的配置项 --> <!-- click 左侧按钮的点击事件处理函数 --> <!-- buttonClick 右侧按钮的点击事件处理函数 --> <uni-goods-nav :fill="true" :options="options" :buttonGroup="buttonGroup" @click="onClick" @buttonClick="buttonClick" /> </view>
- 美化商品导航组件,使之固定在页面最底部:
.goods-detail-container { // 给页面外层的容器,添加 50px 的内padding, // 防止页面内容被底部的商品导航组件遮盖 padding-bottom: 50px; } .goods_nav { // 为商品导航组件添加固定定位 position: fixed; bottom: 0; left: 0; width: 100%; }
因为加入底部 nav功能的原因,原本信息会被挡住,所以在最外层容器赋予类名 添加 padding
7.4.2 点击跳转到购物车页面
- 点击商品导航组件左侧的按钮,会触发
uni-goods-nav
的@click
事件处理函数,事件对象 e 中会包含当前点击的按钮相关的信息:// 左侧按钮的点击事件处理函数 onClick(e) { console.log(e) }
onClick()放在 methods 里 打印的按钮信息对象如下:
- 根据 e.content.text 的值,来决定进一步的操作:
// 左侧按钮的点击事件处理函数 onClick(e) { if (e.content.text === '购物车') { // 切换到购物车页面 uni.switchTab({ url: '/pages/cart/cart' }) } }
7.5 分支的合并与提交
- 将 goodsdetail 分支进行本地提交:
git add . git commit -m "完成了商品详情页面goods_detail的开发"
- 将本地的 goodsdetail 分支推送到码云:
git push -u origin goodsdetail
- 将本地 goodsdetail 分支中的代码合并到 master 分支:
git checkout master git merge goodsdetail git push
- 删除本地的 goodsdetail 分支:
git branch -d goodsdetail
更多推荐
所有评论(0)