学习uni-app,根据uni-app黑马优购项目,结合自己所遇到的问题和解决方案,写此篇博文为后来回头复盘
Headline | uniapp - 黑马优购
打算学习的小伙伴也可以照着此篇博文和视频把项目做出来,上面 uni-app黑马优购
有原文档,项目资料在下面,希望对学习uni-app的你有所帮助!
学习资料链接:百度网盘 请输入提取码
提取码:s9by
目录
HBuilderX的uni-app学习笔记
写在前面
在微信开发者工具引用接口调试时报错和解决方法:
1.起步
1.1 uni-app简介
1.2 开发工具
1.3 创建 uni-app 项目
1.4 目录结构
1.5 项目运行到微信开发者工具
1.6 配置checkSiteMap选项
1.7 使用Git本地管理uni-app项目
项目托管到码云
2. tabBar
2.0 创建新的分支
2.1 创建tabBar页面
2.2 配置tabBar效果
2.3 删除默认的 index 首页
2.4 修改导航条的样式效果
2.5 分支的提交与合并
3. 首页
3.0 创建home分支
3.1 配置网络请求
3.2 轮播图区域
3.3 分类导航区域
3.4楼层区域
3.5 分支的合并与提交
4 分类
4.0 创建cate分支
4.1渲染分类页面的基本结构
4.2 获取分类数据
4.3 动态渲染左侧的一级分类列表
4.4 动态渲染右侧的二级分类列表
4.5 动态渲染右侧的三级分类列表
4.6 切换一级分类后重置滚动条的位置
4.7 点击三级分类跳转到商品列表页面
4.8 分支的合并与提交
5.搜索
5.0 创建 search 分支
5.1 自定义搜索组件
5.2 搜索建议
5.3 搜索历史
5.4 分支的合并与提交
6.商品列表
6.0 创建 goodslist 分支
6.1 定义请求参数对象
6.2 获取商品列表数据
6.3 渲染商品列表结构
6.4 把商品 item 项封装为自定义组件
6.5 使用过滤器处理价格
6.6 上拉加载更多
6.7 下拉刷新
6.8 点击商品 item 项跳转到详情页面
6.9 分支的合并与提交
7. 商品详情
7.0 创建 goodsdetail 分支
7.1 添加商品详情页的编译模式
7.2 获取商品详情数据
7.3 渲染商品详情页的 UI 结构
7.4 渲染详情页底部的商品导航区域
7.5 分支的合并与提交
在微信开发者工具引用接口调试时报错
解决:
uni-app是一个使用vue.js开发所有前端应用的框架
详情可见 uni-app官网
下载 HBuilderX
安装
将下载的 zip包 进行解压缩
将解压之后的文件夹,存放到纯英文的目录中(且不能包含括号等特殊字符)
双击 HBuilderX.exe 即可启动 HBuilderX
安装 scss/sass 编译
为了方便编写样式(例如:),建议安装 scss/sass 编译 插件。插件下载地址:
scss/sass编译 - DCloud 插件市场
进入插件下载页面之后,点击右上角的 使用 HBuilderX 导入插件 按钮进行自动安装,截图如下:
快捷键方案切换
操作步骤:工具 -> 预设快捷键方案切换 -> VS Code
修改编辑器的基本设置
操作步骤:工具 -> 设置 -> 打开 Settings.json 按需进行配置
目的:能够运行到开发者工具看效果
mainfest.json -> 源码视图 -> setting中 加入 "checkSiteMap" : false
/*忽略 node_modules 目录 */
/node_modules
因为安装的第三方包都会存储在这目录,第三方包没有必要进行Git管理,因此需要忽略
/unpackage/dist
运行到小程序模拟器,编译生成到的小程序项目,存放到unpackage/dist目录里mp-weixin,因此需要忽略
注意:由于忽略了unpackage目录中仅有的dist目录,因此默认情况下,unpackage目录不会被Git追踪
此时,为了让Git正常追踪unpackage目录,按照惯例,可以在unpackage目录下创建一个 .gitkeep 的文件进行占位
git init //初始化本地Git仓库
git add . //将所有文件都加入到暂存区
git commit -m "init project" //本地提交更新 可以status查看分支
// 添加完 ,在终端
ssh -T [email protected]
// 检测是否配置成功
shift+alt+f 整理pages.json里的代码格式
{
"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"
}
]
}
}
配置完看不到最终的tabbar效果,需要把pages数组第一个index删除 找到 page 数组,把第一项默认的数组删除,再去pages文件夹删除index文件,即可正常显示
打开pages.json -> 修改globalStyle
节点
{
"globalStyle": {
"navigationBarTextStyle": "white", //导航条文本颜色
"navigationBarTitleText": "黑马优购", //导航条文本内容
"navigationBarBackgroundColor": "#C00000", //导航条背景颜色
"backgroundColor": "#FFFFFF"
}
}
git add . // 将修改的文件加入暂存区
git commit -m "完成了 tabBar 的开发" // 提交信息
git push -u origin tabbar //分支推送到码云保存
git checkout master //首先切换到主分支
git merge tabbar //合并tabbar代码
到这一步后不能看源文档里的直接删除分支!!!因为还没有推送到master分支
git push
git branch -d tabbar //想删除当前分支,先跳转到其他分支
微信开发者工具出现 TypeError: Cannot read property 'invoke' of undefined
解决办法:
打开终端,基于master分支在本地创建home子分支,用来开发首页相关功能
git checkout -b home
由于平台的限制,小程序项目中不支持 axios,而且原生的 wx.request()
API 功能较为简单, 不支持拦截器等全局定制的功能。因此,建议在 uni-app 项目中使用 @escook/request-miniprogram
第三方包发起网络数据请求。
@escook/request-miniprogram官方文档
安装
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.1请求轮播图数据
实现步骤:
//请求根路径
$http.baseUrl = 'https://www.uinav.com'
如果在微信开发者工具 报错对应的服务器证书无效。控制台输入 showRequestInfo() 可以获取更详细信息。
解决办法
勾选不校验合法域名
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 结构
解释:
:indicator-dots="true" //小圆点
:autoplay="true" //自动轮播
:interval="3000" //轮播间隔 3s
:duration="1000" //从开始轮播到结束总共耗时 1s
:circular="true" //衔接轮播
有可能会报错TypeError: Cannot read property ‘$$‘ of undefined
出现以上警告是因为使用
swiper
的 current属性 没有重置,将其重置为0即可在data加入
current: 0
即可
data: {
current: 0
}
3.2.3 配置小程序分包
分包可以减少小程序首次启动时的加载时间
为此,我们在项目中,把 tabBar 相关的 4 个页面放到主包中,
其它页面(例如:商品详情页、商品列表页)放到分包中。在 uni-app 项目中,配置分包的步骤如下:
{
"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": []
}
]
}
3.2.4 点击轮播图跳转到商品详情页面
将 节点内的 view 组件,改造为 navigator 导航组件,并动态绑定 url 属性的值。 改造之前的 UI 结构:
改造之后的 UI 结构:
url动态绑定 商品路径和商品id
3.2.5封装 uni.$showMsg() 方法
当数据请求失败之后,经常需要调用 uni.showToast({ /* 配置对象 */ }) 方法来提示用户。 此时,可以在全局封装一个 uni.$showMsg() 方法,来简化 uni.showToast() 方法的调用。具体的改造步骤如下:
// 封装弹框消息提示的方法
uni.$showMsg = function (title ='数据请求失败!', duration = 1500) {
uni.showToast({
title,
duration,
icon: 'none',
})
}
async getSwiperList() {
const { data: res } = await uni.$http.get('/api/public/v1/home/swiperdata')
if (res.meta.status !== 200) return uni.$showMsg()
this.swiperList = res.message
}
首页分类导航接口 '/api/public/v1/home/swiperdata'
3.3.1 获取分类导航的数据
实现思路:
示例代码如下:
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 结构:
通过如下的样式美化页面结构:
.nav-list {
display: flex;
justify-content: space-around;
margin: 15px 0;
.nav-img {
width: 128rpx;
height: 140rpx;
}
}
3.3.3 点击第一项,切换到分类页面
// nav-item 项被点击时候的事件处理函数
navClickHandler(item) {
if (item.name === '分类') {
uni.swichTab({
url:'/pages/cate/cate'
})
}
}
注意:分类页面属于tabbar对应页面,要切换tabbar页面,只能调用switchTab
实现思路:
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 获取楼层数据
实现思路:
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 渲染楼层的标题
.floor-title {
height: 60rpx;
width: 100%;
display: flex;
}
3.4.3 渲染楼层里的图片
定义楼层图片区域的 UI 结构:
图片高度需要自适应 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 属性 的值:
git add .
git commit -m "完成了 home 首页的开发"
git push -u origin home
git checkout master
git merge home
git push
git branch -d home
git checkout -b cate
可以新建分类页面编译模式,刷新便是分类页面方便查看
定义页面结构如下:
xxx
xxx
xxx
xxx
xxx
多复制一些节点,演示纵向滚动效果...
zzz
zzz
zzz
zzz
多复制一些节点,演示纵向滚动效果
动态计算窗口的剩余高度:
调用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%);
}
}
}
}
}
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
}
}
{{item.cat_name}}
data() {
return {
active: 0
}
}
.active
类名
{{item.cat_name}}
{{item.cat_name}}
activeChanged
事件处理函数,动态修改选中项的索引: methods: {
//选中项 改变的事件处理函数
activeChanged(i) {
this.active = i
}
}
data() {
return {
// 二级分类列表
cateLevel2: []
}
}
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(i) {
this.active = i
// 为二级分类列表重新赋值
this.cateLevel2 = this.cateList[i].children
}
/ {{item2.cat_name}} /
.cate-lv2-title {
font-size: 12px;
font-weight: bold;
text-align: center;
padding: 15px 0;
}
在二级分类的 组件中,循环渲染三级分类的列表结构:
/ {{item2.cat_name}} /
{{item3.cat_name}}
美化三级分类的样式:
.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;
}
}
}
在 data 中定义 滚动条距离顶部的距离:
data() {
return {
// 滚动条距离顶部的距离
scrollTop: 0
}
}
动态为右侧的 组件绑定 :scroll-top 属性
切换一级分类时,动态设置 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
}
{{item3.cat_name}}
// 点击三级分类项跳转到商品列表页面
gotoGoodsList(item3) {
uni.navigateTo({
url: '/subpkg/goods_list/goods_list?cid=' + item3.cat_id
})
}
git add .
git commit -m "完成了分类页面的开发"
git push -u origin cate
git checkout master
git merge cate
git push
git branch -d cate
git checkout -b search
5.1.1 自定义 my-search 组件
在项目根目录的 components 目录上,鼠标右键,选择 新建组件,填写组件信息后,最后点击 创建 按钮:
在分类页面的 UI 结构中,直接以标签的形式使用 my-search 自定义组件:
定义好的组件,可在
template
下直接使用
定义 my-search 组件的 UI 结构如下:
搜索
注意:在当前组件中,我们使用 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 属性:
搜索
移除对应 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 事件
搜索
methods: {
// 点击了模拟的 input 输入框
searchBoxHandler() {
// 触发外界通过 @click 绑定的 click 事件处理函数
this.$emit('click')
}
}
同时在分类页面中,定义 gotoSearch 事件处理函数如下: methods: {
// 跳转到分包中的搜索页面
gotoSearch() {
uni.navigateTo({
url: '/subpkg/search/search'
})
}
}
为组件封装 click 事件,以后在哪里调用组件直接 @调用 ,方便
5.1.4 实现首页搜索组件的吸顶效果
gotoSearch() {
uni.navigateTo({
url: '/subpkg/search/search'
})
}
.search-box {
// 设置定位效果为“吸顶”
position: sticky;
// 吸顶的“位置”
top: 0;
// 提高层级,防止被轮播图覆盖
z-index: 999;
}
5.2.1 渲染搜索页面的基本结构
.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;
}
methods: {
input(e) {
// e 是最新的搜索内容
console.log(e)
}
}
这里注意,和原文档里的不同,可能因为uni-search-bar的更新,这里无需加 value,否则报 undefined
5.2.2 实现搜索框自动获取焦点
data() {
return {
show: true,
showSync: true,
searchVal: ""
}
}
5.2.3 实现搜索框的防抖处理
data() {
return {
// 延时器的 timerId
timer: null,
// 搜索关键词
kw: ''
}
}
input(e) {
//用户在500ms内连续输入内容,应该清除之前的
clearTimeout(this.timer)
//开一个延时器就会有一个返回值 timer
this.timer = setTimeout(() => {
this.kw = e
console.log(e)
},500)
},
5.2.4 根据关键词查询搜索建议列表
data() {
return {
// 搜索结果列表
searchResults: []
}
}
this.timer = setTimeout(() => {
this.kw = e
this.getSearchList()
},500)
// 根据搜索关键词,搜索商品建议列表
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 结构:
{{item.goods_name}}
美化搜索建议列表:
.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.1 渲染搜索历史记录的基本结构
在 data 中定义搜索历史的假数据:
data() {
return {
// 搜索关键词的历史记录
historyList: ['a', 'app', 'apple']
}
}
渲染搜索历史区域的 UI 结构:
搜索历史
美化搜索历史区域的样式:
.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 实现搜索建议和搜索历史的按需展示
```
5.3.3 将搜索关键词存入 historyList
直接将搜索关键词 push
到 historyList
数组中即可
methods: {
// 根据搜索关键词,搜索商品建议列表
async getSearchList() {
// 省略其它不必要的代码...
// 1. 查询到搜索建议之后,调用 saveSearchHistory() 方法保存搜索关键词
this.saveSearchHistory()
},
// 2. 保存搜索关键词的方法
saveSearchHistory() {
// 2.1 直接把搜索关键词 push 到 historyList 数组中
this.historyList.push(this.kw)
}
}
上述实现思路存在的问题:
5.3.4 解决关键字前后顺序的问题
data中的 historyList
不做任何修改,依然使用push进行末尾追加
定义一个计算属性 histories
,将historyList
数组reverse
反转之后,就是此计算属性的值:
computed: {
histories() {
return[...this.historyList].reverse()
}
}
注意:由于数组是引用类型,所以不要直接基于原数组调用 reverse 方法,以免修改原数组中元素的顺序 而是应该新建一个内存无关的数组,再进行 reverse 反转
页面中渲染搜索关键词的时候,不再使用data中的historyList
,而是使用计算属性histories
:
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() {
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
事件:
methods
中定义 cleanHistory
处理函数: //清空搜索历史记录
cleanHistory() {
//清空 data 保存的搜索历史
this.historyList = []
//清空本地存储中的搜索历史
uni.setStorageSync('kw','[]')
}
5.3.8 点击搜索历史跳转到商品列表页面
click
事件处理函数:
item传进去,是因为要根据关键词来进行商品的展示
methods
中定义 gotoGoodsList
处理函数: gotoGoodsList(kw) {
uni.navigateTo({
url:'subpkg/goods_list/goods_list?query=' + kw
})
}
将 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
git checkout -b goodslist
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 || ''
}
在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
}
}
在页面中,通过 v-for 指令,循环渲染出商品的 UI 结构:
{{goods.goods_name}}
¥{{goods.goods_price}}
使用block包裹:循环时不会被渲染为任何实际的元素,起到包裹性质的作用,结构更加清晰
为了防止某些商品的图片不存在,需要在 data 中定义一个默认的图片:
data() {
return {
// 默认的空图片
defaultPic: 'https://img3.doubanio.com/f/movie/8dd0c794499fe925ae2ae89ee30cd225750457b4/pics/movie/celebrity-default-medium.png'
}
}
并在页面渲染时按需使用:
美化商品列表的 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;
}
}
}
在 components 目录上鼠标右键,选择 新建组件:
将 goods_list 页面中,关于商品 item 项相关的 UI 结构、样式、data 数据,封装到 my-goods 组件中:
{{goods.goods_name}}
¥{{goods.goods_price}}
在 goods_list 组件中,循环渲染 my-goods 组件即可:
filters: {
// 把数字处理为带两位小数点的数字
tofixed(num) {
return Number(num).toFixed(2)
}
}
¥{{goods.goods_price | tofixed}}
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()
}
在 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
}
// 点击跳转到商品详情页面
gotoDetail(item) {
uni.navigateTo({
url: '/subpkg/goods_detail/goods_detail?goods_id=' + item.goods_id
})
}
git add .
git commit -m "完成了商品列表页面的开发"
git push -u origin goodslist
git checkout master
git merge goodslist
git push
git branch -d goodslist
运行如下的命令,基于 master 分支在本地创建 goodsdetail 子分支,用来开发商品详情页相关的功能:
git checkout -b goodsdetail
data() {
return {
// 商品详情对象
goods_info: {}
}
}
onLoad(options) {
// 获取商品 Id
const goods_id = options.goods_id
// 调用请求商品详情数据的方法
this.getGoodsDetail(goods_id)
}
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.1 渲染轮播图区域
使用 v-for 指令,循环渲染如下的轮播图 UI 结构:
美化轮播图的样式:
swiper {
height: 750rpx;
image {
width: 100%;
height: 100%;
}
}
7.3.2 实现轮播图预览效果
image
图片绑定 click
事件处理函数:
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 结构如下:
¥{{goods_info.goods_price}}
{{goods_info.goods_name}}
收藏
快递:免运费
美化商品信息区域的样式:
// 商品信息区域的样式
.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 标签的内容,渲染为小程序的页面结构:
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(/
解决 .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(/
7.3.5 解决商品价格闪烁的问题
7.4.1 渲染商品导航区域的 UI 结构
基于 uni-ui 提供的 GoodsNav 组件来实现商品导航区域的效果
data() {
return {
// 商品详情对象
goods_info: {},
// 左侧按钮组的配置对象
options: [{
icon: 'shop',
text: '店铺'
}, {
icon: 'cart',
text: '购物车',
info: 2
}],
// 右侧按钮组的配置对象
buttonGroup: [{
text: '加入购物车',
backgroundColor: '#ff0000',
color: '#fff'
},
{
text: '立即购买',
backgroundColor: '#ffa200',
color: '#fff'
}
]
}
}
.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 里 打印的按钮信息对象如下:
// 左侧按钮的点击事件处理函数
onClick(e) {
if (e.content.text === '购物车') {
// 切换到购物车页面
uni.switchTab({
url: '/pages/cart/cart'
})
}
}
git add .
git commit -m "完成了商品详情页面goods_detail的开发"
git push -u origin goodsdetail
git checkout master
git merge goodsdetail
git push
git branch -d goodsdetail