小程序和普通网页开发的区别:
注册账号后获取小程序的 AppID:小程序、开发、开发设置。
安装好开发者工具后设置外观和代理:
超链接
、WXML
在页面对应的 .js 文件中,把数据定义到 data 对象中即可:
把data中的数据绑定到页面中渲染,使用 Mustache 语法(双大括号)将变量包起来即可。语法格式为:
<view>{{ 要绑定的数据名称 }}view>
页面的数据如下:
Page({
data: {
info: 'init data'
}
})
页面的结构如下:
<view>{{ info }}view>
页面的数据如下:
Page({
data: {
imgSrc:'http://www.itheima.com/images/logo.png',
},
页面的结构如下:
<image src="{{imgSrc}}">image>
事件是渲染层到逻辑层的通讯方式。通过事件可以将用户在渲染层产生的行为,反馈到逻辑层进行业务的处理。
当事件回调触发的时候,会收到一个事件对象 event,它的详细属性如下表所示:
target 是触发该事件的源头组件,而 currentTarget 则是当前事件所绑定的组件。举例如下:
点击内部的按钮时,点击事件以冒泡的方式向外扩散,也会触发外层 view 的 tap 事件处理函数。
对于外层的 view:
小程序中通过 tap 事件来响应用户的触摸行为。
通过调用 this.setData(dataObject) 方法,可以给页面 data 中的数据重新赋值,示例如下:
小程序中的事件传参比较特殊,不能在绑定事件的同时为事件处理函数传递参数。上面的代码小程序会把 bindtap 的属性值,统一当作事件名称来处理,相当于要调用一个名称为 btnHandler(123)的事件处理函数。
可以为组件提供 data-* 自定义属性传参,其中 * 代表的是参数的名字,示例代码如下:
最终:
小程序中,通过 input 事件来响应文本框的输入事件
实现步骤:
wx:if
小程序中,使用 wx:if="{{condition}}"
来判断是否需要渲染该代码块:
也可以用 wx:elif
和 wx:else
来添加 else
判断:
使用 wx:if
如果要一次性控制多个组件的展示与隐藏,可以使用一个
标签将多个组件包装起来,并在
标签上使用 wx:if
控制属性,示例如下:
注意: 并不是一个组件,它只是一个包裹性质的容器,不会在页面中做任何渲染。
小程序中,直接使用 hidden="{{ condition }}"
也能控制元素的显示与隐藏:
wx:if
以动态创建和移除元素的方式,控制元素的展示与隐藏hidden
以切换样式的方式(display: none/block
;),控制元素的显示与隐藏hidden
wx:if
搭配 wx:elif
、wx:else
进行展示与隐藏的切换wx:for
通过 wx:for
可以根据指定的数组,循环渲染重复的组件结构,语法示例如下:
默认情况下,当前循环项的索引用 index 表示;当前循环项用 item 表示。
wx:key
的使用类似于 Vue 列表渲染中的 :key
,小程序在实现列表渲染时,也建议为渲染出来的列表项指定唯一的 key 值,从而提高渲染的效率
WXSS (WeiXin Style Sheets)是一套样式语言,用于美化 WXML 的组件样式,类似于网页开发中的 CSS。
WXSS 具有 CSS 大部分特性,同时,WXSS 还对 CSS 进行了扩充以及修改,以适应微信小程序的开发。与 CSS 相比,WXSS 扩展的特性有:
rpx(responsive pixel)是微信小程序独有的,用来解决屏适配的尺寸单位。
rpx 的实现原理非常简单:鉴于不同设备屏幕的大小不同,为了实现屏幕的自动适配,rpx 把所有设备的屏幕,在宽度上等分为 750 份(即:当前屏幕的总宽度为 750rpx)。
小程序在不同设备上运行的时候,会自动把 rpx 的样式单位换算成对应的像素单位来渲染,从而实现屏幕适配。
在 iPhone6 上,屏幕宽度为375px,共有 750 个物理像素,等分为 750rpx。则:
750rpx = 375px = 750 物理像素
1rpx = 0.5px = 1物理像素
官方建议:开发微信小程序时,设计师可以用 iPhone6 作为视觉稿的标准。
开发举例:在 iPhone6 上如果要绘制宽100px,高20px的盒子,换算成rpx单位,宽高分别为 200rpx 和 40rpx。
使用 WXSS 提供的 @import
语法,可以导入外联的样式表。
@import
后跟需要导入的外联样式表的相对路径,用 ; 表示语句结束。示例如下:
定义在 app.wxss 中的样式为全局样式,作用于每一个页面。
在页面的 .wxss 文件中定义的样式为局部样式,只作用于当前页面。
注意:
小程序根目录下的 app.json 文件是小程序的全局配置文件。常用的配置项如下:
设置步骤:app.json
-> window
-> navigationBarTitleText
需求:把导航栏上的标题,从默认的 “WeChat”修改为“黑马程序员”,效果如图所示:
设置步骤:app.json
-> window
-> navigationBarBackgroundColor
需求:把导航栏标题的背景色,从默认的 #fff
修改为 #2b4b6b
,效果如图所示:
设置步骤:app.json
-> window
-> navigationBarTextStyle
需求:把导航栏上的标题颜色,从默认的 black
修改为 white
,效果如图所示:
注意: navigationBarTextStyle 的可选值只有 black
和 white
概念:下拉刷新是移动端的专有名词,指的是通过手指在屏幕上的下拉滑动操作,从而重新加载页面数据的行为。
设置步骤:app.json
-> window
-> 把 enablePullDownRefresh
的值设置为 true
注意:在 app.json 中启用下拉刷新功能,会作用于每个小程序页面!
当全局开启下拉刷新功能之后,默认的窗口背景为白色。如果自定义下拉刷新窗口背景色,设置步骤为:
app.json
-> window
-> 为 backgroundColor
指定16进制的颜色值 #efefef
。效果如下:
当全局开启下拉刷新功能之后,默认窗口的 loading 样式为白色,如果要更改 loading 样式(加载的三个小圆点)的效果,设置步
骤为 app.json
-> window
-> 为 backgroundTextStyle
指定 dark
值。效果如下:
概念:上拉触底是移动端的专有名词,通过手指在屏幕上的上拉滑动操作,从而加载更多数据的行为。
设置步骤: app.json
-> window
-> 为 onReachBottomDistance
设置新的数值
注意:默认距离为50px,如果没有特殊需求,建议使用默认值即可
tabBar 是移动端应用常见的页面效果,用于实现多页面的快速切换。
通常分为:
app.json
配置文件,和 pages、window 平级,新增 tabBar 节点pagePath
指定当前 tab 对应的页面路径【必填】text
指定当前 tab 上按钮的文字【必填】iconPath
指定当前 tab 未选中时候的图片路径【可选】selectedIconPath
指定当前 tab 被选中后高亮的图片路径【可选】小程序中,每个页面都有自己的 .json
配置文件,用来对当前页面的窗口外观、页面效果等进行配置。
出于安全性方面的考虑,小程序官方对数据接口的请求做出了如下两个限制:
调用微信小程序提供的 wx.request() 方法,可以发起 GET 数据请求,示例代码如下:
调用微信小程序提供的 wx.request() 方法,可以发起 POST 数据请求,示例代码如下:
在很多情况下,我们需要在页面刚加载的时候,自动请求一些初始化的数据。此时需要在页面的 onLoad 事件中调用获取数据的函数,示例代码如下:
为了不耽误开发的进度,我们可以在微信开发者工具中,临时
开启「开发环境不校验请求域名、TLS 版本及 HTTPS 证书」选项,跳过 request 合法域名的校验。
注意:跳过 request 合法域名校验的选项,仅限在开发与调试阶段使用!
新建 local_life 项目;
在 app.json 文件中创建三个页面 —— 首页、消息、联系
"pages":[
"pages/home/home",
"pages/message/message",
"pages/contact/contact"
],
删除原来默认的index和log页面,先删除文件再删除app.json中的内容;
在 app.json 的 window 中修改导航栏配置:
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#2b4b6b",
"navigationBarTitleText": "本地生活",
"navigationBarTextStyle":"white"
},
将图标文件放在项目根目录中;
配置tabBar:
"tabBar": {
"list": [{
"pagePath": "pages/home/home",
"text": "首页",
"iconPath": "/images/tabs/home.png",
"selectedIconPath": "/images/tabs/home-active.png"
},
{
"pagePath": "pages/message/message",
"text": "消息",
"iconPath": "/images/tabs/message.png",
"selectedIconPath": "/images/tabs/message-active.png"
},
{
"pagePath": "pages/contact/contact",
"text": "联系我们",
"iconPath": "/images/tabs/contact.png",
"selectedIconPath": "/images/tabs/contact-active.png"
}]
},
在 .js 文件中 Page – data 定义存放轮播图数据的列表:
data: {
//存放轮播图数据的列表
swiperList:[]
},
定义获取轮播图数据的方法,获得数据封装在res.data中,赋给swiperList
getSwiperList(){
wx.request({
url: 'https://www.escook.cn/slides',
method:'GET',
success:(res)=>{
// console.log(res)
this.setData({
swiperList:res.data
})
}
})
},
onLoad 生命周期函数中调用方法
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.getSwiperList(),
this.getGridList()
},
在 home.wxml 中使用 swiper 组件,item项中image为图片地址:
<swiper indicator-dots circular>
<swiper-item wx:for="{{swiperList}}" wx:key="id">
<image src="{{item.image}}">image>
swiper-item>
swiper>
在 home.wxss 中修改轮播图组件高度和图片显示效果:
swiper{
height: 350rpx;
}
.swiper image{
height: 100%;
width: 100%;
}
在 .js 文件中 Page – data 定义存放九宫格数据的列表:
data: {
//存放九宫格数据的列表
gridList:[]
},
定义获取九宫格数据的方法,获得数据封装在res.data中,赋给swiperList
//获取九宫格列表数据的方法
getGridList(){
wx.request({
url: 'https://www.escook.cn/categories',
method:'GET',
success:(res)=>{
// console.log(res)
this.setData({
gridList:res.data
})
}
})
},
onLoad 生命周期函数中调用方法
onLoad(options) {
this.getGridList()
},
home.wxml文件中定义九宫格区域,item项中icon为图片地址,name为名称
<view class="grid-list">
<view class="grid-item" wx:for="{{gridList}}" wx:key="id">
<image src="{{item.icon}}">image>
<text>{{item.name}}text>
view>
view>
wxss 设置九宫格样式
/* 九宫格 */
.grid-list{
/* felx布局 */
display: flex;
/* 允许换行 */
flex-wrap: wrap;
/* 为外边容器添加左、上边界,结合item的边界,使每个item都有边界包围 */
border-left: 1rpx solid #efefef;
border-top: 1rpx solid #efefef;
}
.grid-item{
width: 33.33%;
height: 200rpx;
/* 每个item项内图片和文字flex布局 */
display: flex;
/* flex布局默认按行 改为按列 */
flex-direction: column;
/* 纵向居中 */
align-items: center;
/* 横向居中 */
justify-content: center;
/* 为每个item项添加右、下边界 */
border-right: 1rpx solid #efefef;
border-bottom: 1rpx solid #efefef;
/* 添加边界后默认的 content-box不包含边界,添加边界一行不够放33.3%的三个,改为border-box把边界包含在内 */
box-sizing: border-box;
}
.grid-item image{
/* 图片大小——30像素 */
width: 60rpx;
height: 60rpx;
}
.grid-item text{
/* 文本高度:12磅 */
font-size: 24rpx;
/* 文字图片相距5像素 */
margin-top: 10rpx;
}
wxml 文件中定义图片区域
<view class="img-box">
<image src="/images/link-01.png" mode="widthFix">image>
<image src="/images/link-02.png" mode="widthFix">image>
view>
wxss 文件中定义图片显示样式
/* 图片 */
.img-box{
display: flex;
padding: 20rpx 10rpx;
/* 让两张图片的空间均匀分布而不是留在右侧 */
justify-content: space-around;
}
.img-box image{
width: 45%;
}
页面导航指的是页面之间的相互跳转。例如,浏览器中实现页面导航的方式有如下两种:
导航组件
组件实现页面跳转tabBar 页面指的是被配置为 tabBar 的页面。
在使用
组件跳转到指定的 tabBar 页面时,需要指定 url 属性和 open-type 属性,其中:
非 tabBar 页面指的是没有被配置为 tabBar 的页面。
在使用
组件跳转到普通的非 tabBar 页面时,也需要指定 url 属性和 open-type 属性,其中:
如果要后退到上一页面或多级页面,则需要指定 open-type 属性和 delta 属性,其中:
open-type
的值必须是 navigateBack
,表示要进行后退导航delta
的值必须是数字,表示要后退的层级调用 wx.switchTab(Object object)
方法,可以跳转到 tabBar 页面。其中 Object 参数对象的属性列表如下:
调用 wx.navigateTo(Object object)
方法,可以跳转到非 tabBar 的页面。其中 Object 参数对象的属性列表如下:
调用 wx.navigateBack(Object object)
方法,可以返回上一页面或多级页面。其中 Object 参数对象可选的属性列表如下:
navigator
组件的 url 属性用来指定将要跳转到的页面的路径。同时,路径的后面还可以携带参数:
调用 wx.navigateTo(Object object)
方法跳转页面时,也可以携带参数,代码示例如下:
通过声明式导航传参或编程式导航传参所携带的参数,可以直接在 onLoad 事件中直接获取到,示例代码如下:
下拉刷新是移动端的专有名词,指的是通过手指在屏幕上的下拉滑动操作,从而重新加载页面数据的行为。
启用下拉刷新有两种方式:
app.json
的 window
节点中,将 enablePullDownRefresh
设置为 true
.json
配置文件中,将 enablePullDownRefresh
设置为 true
在全局或页面的 .json
配置文件中,通过 backgroundColor
和 backgroundTextStyle
来配置下拉刷新窗口的样式,其中:
backgroundColor
用来配置下拉刷新窗口的背景颜色,仅支持16 进制的颜色值backgroundTextStyle
用来配置下拉刷新 loading 的样式,仅支持 dark 和 light在页面的 .js 文件中,通过 onPullDownRefresh()
函数即可监听当前页面的下拉刷新事件。
例如,在页面的 wxml 中有如下的 UI 结构,点击按钮可以让 count 值自增 +1:
在触发页面的下拉刷新事件的时候,如果要把 count 的值重置为 0,示例代码如下:
当处理完下拉刷新后,下拉刷新的 loading 效果会一直显示,不会主动消失,所以需要手动隐藏下拉刷新的 loading 效果。此时,调用 wx.stopPullDownRefresh()
可以停止当前页面的下拉刷新。示例代码如下:
上拉触底是移动端的专有名词,通过手指在屏幕上的上拉滑动操作,从而加载更多数据的行为。
在页面的 .js 文件中,通过 onReachBottom()
函数即可监听当前页面的上拉触底事件。示例代码如下:
上拉触底距离指的是触发上拉触底事件时,滚动条距离页面底部的距离。
可以在全局或页面的 .json 配置文件中,通过 onReachBottomDistance
属性来配置上拉触底的距离。
小程序默认的触底距离是 50px,在实际开发中,可以根据自己的需求修改这个默认值。
上拉触底可以加载出随机颜色值,颜色值 rgb 参数为文本,颜色为背景
① 定义获取随机颜色的方法
② 在页面加载时获取初始数据
③ 渲染 UI 结构并美化页面效果
④ 在上拉触底时调用获取随机颜色的方法
⑤ 添加 loading 提示效果
⑥ 对上拉触底进行节流处理
colorList
数组赋值采用展开运算符 ...
进行字符串拼接生命周期(Life Cycle)是指一个对象从创建 -> 运行 -> 销毁的整个阶段,强调的是一个时间段。
每个小程序运行的过程,也概括为生命周期:
小程序中,生命周期分为两类,分别是:
小程序中的生命周期函数分为两类,分别是:
小程序的应用生命周期函数需要在 app.js 中进行声明,示例代码如下:
小程序的页面生命周期函数需要在页面的 .js 文件中进行声明,示例代码如下:
WXS(WeiXin Script)是小程序独有的一套脚本语言,结合 WXML,可以构建出页面的结构。
wxml 中无法调用在页面的 .js 中定义的函数,但是,wxml 中可以调用 wxs 中定义的函数。因此,小程序中wxs 的典型应用场景就是==“过滤器”==。
wxs 代码可以编写在 wxml 文件中的
标签内,就像 Javascript 代码可以编写在 html 文件中的 标签内一样。
wxml 文件中的每个
标签,必须提供 module 属性,用来指定当前 wxs 的模块名称,方便在 wxml 中访问模块中的成员:
wxs 代码还可以编写在以 .wxs 为后缀名的文件内,就像 javascript 代码可以编写在以 .js 为后缀名的文件中一样。示例代码如下:
在 wxml 中引入外联的 wxs 脚本时,必须为
标签添加 module 和 src 属性,其中:
为了降低 wxs(WeiXin Script)的学习成本, wxs 语言在设计时借大量鉴了 JavaScript 的语法。但是本质上,wxs 和 JavaScript 是完全不同的两种语言!
wxs 典型的应用场景就是“过滤器”,经常配合 Mustache 语法进行使用,例如:
但是,在 wxs 中定义的函数不能作为组件的事件回调函数。例如,下面的用法是错误的:
隔离性指的是 wxs 的运行环境和其他 JavaScript 代码是隔离的。体现在如下两方面:
将day02中创建好的本地生活导入项目,包含了准备好的首页;
在 app.json 的 “pages” 中增加列表页面:“pages/shoplist/shoplist”;
将九宫格中的每一项由 view 组件更改为 navigator 组件,并指定url、根据当前item项传递id和标题参数;
<view class="grid-list">
<navigator class="grid-item" wx:for="{{gridList}}" wx:key="id" url="/pages/shoplist/shoplist?id={{item.id}}&title={{item.name}}">
<image src="{{item.icon}}">image>
<text>{{item.name}}text>
navigator>
view>
在 shoplist.js 的 data 中定义 query 转存页面参数;
data: {
query:{}
},
在 onLoad 声明周期函数中将传递过来的参数由局部参数 options 转存到 query
onLoad(options) {
this.setData({
query:options
})
},
在 onReady 生命周期函数中动态设置页面标题
onReady() {
wx.setNavigationBarTitle({
title: this.data.query.title,
})
},
data: {
query:{},
// 存放所有商铺的列表
shopList:[],
// 当前请求页面
page: 1,
// 每页条数
pageSize: 10,
// 总条目数
total: 0
},
// 获取商铺列表的函数
getShopList(){
wx.request({
// 模板字符串,反引号``,内部用${}传入变量
url: `https://www.escook.cn/categories/${this.data.query.id}/shops`,
method:"GET",
data:{
_page:this.data.page,
_limit:this.data.pageSize
},
success:(res)=>{
console.log(res)
this.setData({
// 扩展运算符 拼接商铺列表
shopList:[...this.data.shopList,...res.data],
// header里面的属性带-,不能用点而是用中括号,返回的是一个字符串,用-'0'来得到数字
total:res.header['X-Total-Count'] - 0
})
}
})
},
在 onLoad() 函数中调用 getShopList()
onLoad(options) {
this.setData({
query:options
})
this.getShopList()
},
在 shoplist.wxml 中定义UI结构
<view class="shop-item" wx:for="{{shopList}}" wx:key="id">
<view class="thumb">
<image src="{{item.images[0]}}">image>
view>
<view class="info">
<text class="shop-title">{{item.name}}text>
<text>电话:{{item.phone}}text>
<text>地址:{{item.address}}text>
<text>营业时间:{{item.businessHours}}text>
view>
view>
在 wxss 文件中美化样式
/* 每个item项 */
.shop-item{
/* 图片和文本flex布局 */
display: flex;
/* 每项之间不紧挨着边框 */
padding: 15rpx;
/* 边框 */
border: 1rpx solid #efefef;
border-radius: 8rpx;
/* 两个item项之间隔开 */
margin: 15rpx;
/* 加阴影 */
box-shadow: 1rpx 1rpx 15rpx #ddd;
}
/* 左侧图片样式 */
.thumb image{
width: 250rpx;
height: 250rpx;
/* 消除两个图片之间的间隔 */
display: block;
/* 文字和图片隔开 */
margin-right: 15rpx;
}
/* 右侧文本样式 */
.info{
/* 文本之间纵向flex布局 */
display: flex;
flex-direction: column;
/* 文字之间均匀分散 */
justify-content: space-around;
/* 调小字好 */
font-size: 24rpx;
}
/* 店铺标题加粗 */
.shop-title{
font-weight: bold;
}
在 getShopList() 函数中展示、隐藏loading效果
getShopList(){
// 展示loading效果
wx.showLoading({
title: '数据加载中...',
})
wx.request({
......
complete:()=>{
// 隐藏loading效果
wx.hideLoading()
}
})
},
在 sholist.json 中定义下拉触底距离:
"onReachBottomDistance": 200
在 js 的页面上拉触底事件处理函数中将页面值+1,重新请求数据
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
this.setData({
page:this.data.page + 1
})
this.getShopList()
},
在 data 中定义节流阀
data: {
...
// 节流阀
isloading:false
},
在进入 getShopList() 函数将 isloading 置位 false,请求完成重新置为 true
// 获取商铺列表的函数
getShopList(){
this.setData({
isloading:true
})
...
wx.request({
...
complete:()=>{
...
// 关闭节流阀
this.setData({
isloading:false
})
}
})
},
在上拉触底事件处理函数中先判断是否正在加载
onReachBottom() {
// 先判断是否需要节流
if(this.data.isloading) return
...
},
判断公式:如果 页码值 * 每页显示多少条数据 >= 总数据条数,证明没有下一页数据,那么不应该再发起请求;
在页面上拉触底事件的处理函数中先进行判断是否还有下一页的数据,如果没有了,调用 wx.showToast() 方法弹出提示框:
onReachBottom() {
// 判断是否请求完了所有数据
if(this.data.page * this.data.pageSize >= this.data.total){
return wx.showToast({
title: '数据加载完毕!',
// 不使用图标,默认是success的对勾
icon: 'none'
})
}
...
},
在 .json 文件中定义三个节点,开启下拉刷新、设置背景为浅灰色、设置加载的小圆点
"enablePullDownRefresh": true,
"backgroundColor": "#efefef",
"backgroundTextStyle": "dark"
在 .js 文件下拉刷新函数中编写下拉刷新功能的实现,重置关键数据、重新请求参数
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
// 重置关键的数据
this.setData({
page:1,
shopList:[],
total:0
})
// 重新发起数据请求
this.getShopList(()=>{
wx.stopPullDownRefresh()
})
},
解决下拉刷新不会自动关闭的问题——在重新发起数据请求完成时关闭下拉刷新,但是获取数据列表请求功能不止在下拉刷新用到,也会在页面初始化、上拉触底用到,而这些地方并不需要关闭下拉刷新——借助cb回调函数,使 getShopList() 只有被下拉刷新调用时才会关闭下拉刷新,其中cb回调函数已在重新发起数据请求传递好了,对请求完成做如下添加:
getShopList(cb){
...
wx.request({
...
complete:()=>{
...
// 借助cb回调函数,利用短路运算符,使下拉刷新使调用重新请求数据时能够执行回调函数中的关闭下拉刷新的操作
cb && cb()
}
})
},
在 uitls 文件夹定义wxs脚本文件,实现将11位手机号分割的功能,将该方法以 splitPhone 分享出去
function splitPhone(str){
if(str === null) return
if(str.length !== 11) return str
var arr = str.split('')
arr.splice(3, 0, '-')
arr.splice(8, 0, '-')
return arr.join('')
}
module.exports = {
splitPhone: splitPhone
}
在 wxml 文件中导入 wxs 脚本
<wxs src='../../utils/tools.wxs' module="tools">wxs>
将 wxml 的UI结构中的电话使用 wxs 进行处理
<text>电话:{{tools.splitPhone(item.phone)}}text>
组件的引用方式分为“局部引用”和“全局引用”
局部引用组件:在页面的 .json 配置文件中引用组件的方式,叫做“局部引用”。
{
"usingComponents": {
"my-test": "/components/test/test"
}
}
// 在页面的 .wxml 文件中,使用组件
<my-test></my-test>
全局引用组件:在 app.json 全局配置文件中引用组件的方式,叫做“全局引用”。
全局引用 VS 局部引用,根据组件的使用频率和范围,来选择合适的引用方式:
从表面来看,组件和页面都是由 .js、.json、.wxml 和 .wxss 这四个文件组成的。但是,组件和页面的 .js 与.json 文件有明显的不同:
默认情况下,自定义组件的样式只对当前组件生效,不会影响到组件之外的
UI 结构,如图所示:
好处:
建议:在组件和引用组件的页面中建议使用 class 选择器,不要使用 id、属性、标签选择器!
默认情况下,自定义组件的样式隔离特性能够防止组件内外样式互相干扰的问题。但有时,我们希望在外界能够控制组件内部的样式,此时,可以通过 styleIsolation 修改组件的样式隔离选项,用法如下:
在小程序组件中,用于组件模板渲染的私有数据,需要定义到 data 节点中
在小程序组件中,事件处理函数和自定义方法需要定义到 methods 节点中
在小程序组件中,properties 是组件的对外属性,用来接收外界传递到组件中的数据
在小程序的组件中,properties 属性和 data 数据的用法相同,它们都是可读可写的,只不过:
由于 data 数据和 properties 属性在本质上没有任何区别,因此 properties 属性的值也可以用于页面渲染,或使用 setData 为 properties 中的属性重新赋值
数据监听器用于监听和响应任何属性和数据字段的变化,从而执行特定的操作。它的作用类似于 vue 中的watch 侦听器。在小程序组件中,数据监听器的基本语法格式如下:
.colorBox {
line-height: 200rpx;
font-size: 24rpx;
color: white;
text-align: center;
text-shadow: 0rpx 0rpx 2rpx black;
}
如果某个对象中需要被监听的属性太多,为了方便,可以使用通配符 ** 来监听对象中所有属性的变化
在 Component 构造器的 options 节点中,指定 pureDataPattern 为一个正则表达式,字段名符合这个正则表达式的字段将成为纯数据字段
小程序组件可用的全部生命周期如下表所示:
在小程序组件中,最重要的生命周期函数有 3 个,分别是 created、attached、detached。它们各自的特点如下:
在小程序组件中,生命周期函数可以直接定义在 Component 构造器的第一级参数中,可以在 lifetimes 字段内进行声明(这是推荐的方式,其优先级最高)
有时,自定义组件的行为依赖于页面状态的变化,此时就需要用到组件所在页面的生命周期。
例如:每当触发页面的 show 生命周期函数的时候,我们希望能够重新生成一个随机的 RGB 颜色值。
在自定义组件中,组件所在页面的生命周期函数有如下 3 个,分别是:
组件所在页面的生命周期函数,需要定义在 pageLifetimes 节点中
在自定义组件的 wxml 结构中,可以提供一个 节点(插槽),用于承载组件使用者提供的 wxml 结构
在小程序的自定义组件中,需要使用多 插槽时,可以在组件的 .js 文件中,通过如下方式进行启用
在组件的 .wxml 中使用多个 标签,以不同的 name 来区分不同的插槽。
在使用带有多个插槽的自定义组件时,需要用 slot 属性来将节点插入到不同的 中。
父组件:调用组件的页面(或组件);
子组件:被调用的组件本身。
属性绑定用于实现父向子传值,而且只能传递普通类型的数据,无法将方法传递给子组件。
父组件代码:
子组件在 properties 节点中声明对应的属性并使用:
事件绑定用于实现子向父传值,可以传递任何类型的数据。使用步骤如下:
可在父组件里调用 this.selectComponent(“id或class选择器”) ,获取子组件的实例对象,从而直接访问子组件的任意数据和方法。调用时需要传入一个选择器,例如 this.selectComponent(“.my-component”)
behaviors 是小程序中,用于实现组件间代码共享的特性,类似于 Vue.js 中的 “mixins”
每个 behavior 可以包含一组属性、数据、生命周期函数和方法。
组件引用它时,它的属性、数据和方法会被合并到组件中。
每个组件可以引用多个 behavior,behavior 也可以引用其它 behavior。
根目录下创建 behaviors 文件夹,创建 my-behavior.js 文件
在组件中,使用 require() 方法导入需要的 behavior,挂载后即可访问 behavior 中的数据或方法
关于详细的覆盖和组合规则,大家可以参考微信小程序官方文档给出的说明:https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/behaviors.html
小程序中已经支持使用 npm 安装第三方包,从而来提高小程序的开发效率。但是,在小程序中使用npm 包有如下 3 个限制:
Vant Weapp 是有赞前端团队开源的一套小程序 UI 组件库,使用MIT 开源许可协议,对商业使用比较友好。
官方文档地址:https://youzan.github.io/vant-weapp
在小程序项目中,安装 Vant 组件库主要分为如下 3 步:
注意,要使用 npm 需要安装 node.js
安装完 Vant 组件库之后,可以在 app.json 的 usingComponents
节点中引入需要的组件,即可在 wxml 中直接使用组件。示例代码如下:
Vant Weapp 使用 CSS 变量来实现定制主题。 关于 CSS 变量的基本用法,请参考 MDN 文档:
https://developer.mozilla.org/zh-CN/docs/Web/CSS/Using_CSS_custom_properties
在 app.wxss 中,写入 CSS 变量,即可对全局生效:
所有可用的颜色变量,请参考 Vant 官方提供的配置文件:
https://github.com/youzan/vant-weapp/blob/dev/packages/common/style/var.less
默认情况下,小程序官方提供的异步 API 都是基于回调函数实现的,例如,网络请求的 API 需要按照如下的方式调用:
缺点:容易造成回调地狱的问题,代码的可读性、维护性差。
API Promise化,指的是通过额外的配置,将官方提供的、基于回调函数的异步 API,升级改造为基于Promise 的异步 API,从而提高代码的可读性、维护性,避免回调地狱的问题。
在小程序中,实现 API Promise 化主要依赖于 miniprogram-api-promise 这个第三方的 npm 包。它的安装和使用步骤如下:
全局数据共享(又叫做:状态管理)是为了解决组件之间数据共享的问题。
开发中常用的全局数据共享方案有:Vuex、Redux、MobX 等。
在小程序中,可使用 mobx-miniprogram 配合 mobx-miniprogram-bindings 实现全局数据共享。其中:
在项目中运行如下的命令,安装 MobX 相关的包:
注意:MobX 相关的包安装完毕之后,记得删除 miniprogram_npm 目录后,重新构建 npm。
分包指的是把一个完整的小程序项目,按照需求划分为不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。
分包前,小程序项目中所有的页面和资源都被打包到了一起,导致整个项目体积过大,影响小程序首次启动的下载时间。
分包后,小程序项目由 1 个主包 + 多个分包组成:
独立分包本质上也是分包,只不过它比较特殊,可以独立于主包和其他分包而单独运行。
最主要的区别:是否依赖于主包才能运行
开发者可以按需,将某些具有一定功能独立性的页面配置到独立分包中。原因如下:
独立分包和普通分包以及主包之间,是相互隔绝的,不能相互引用彼此的资源!例如:
在进入小程序的某个页面时,由框架自动预下载可能需要的分包,从而提升进入后续分包页面时的启动速度。
预下载分包的行为,会在进入指定的页面时触发。在 app.json 中,使用 preloadRule 节点定义分包的预下载规则,示例代码如下
不同分包中的页面享有共同的预下载大小限额 2M,例如:
分包 ABC 总体积大于 2M,不允许:
分包 ABC 总体积小于2M,允许:
参考小程序自定义 tabBar 文档:https://developers.weixin.qq.com/miniprogram/dev/framework/ability/custom-tabbar.html
"tabBar": {
"custom": true,
"list": [
{
"pagePath": "pages/mp5-2/mp5-2",
"text": "首页",
"iconPath": "/images/tabs/home.png",
"selectedIconPath": "/images/tabs/home-active.png"
},
{
"pagePath": "pages/message/message",
"text": "消息",
"iconPath": "/images/tabs/message.png",
"selectedIconPath": "/images/tabs/message-active.png"
},
{
"pagePath": "pages/contact/contact",
"text": "联系我们",
"iconPath": "/images/tabs/contact.png",
"selectedIconPath": "/images/tabs/contact-active.png"
}
]
},
"usingComponents": {
"van-button": "@vant/weapp/button/index",
"my-numbers": "./components/numbers/numbers"
},
注意,按照 tabBar 配置要求,应将配置为 tabBar 的页面在 app.json 中 pages 节点配置为前几项,否则会影响后面自定义 tabBar的渲染。
进入 Vant Weapp 页面:https://youzan.github.io/vant-weapp/#/tabbar,参照引入 tabBar组件
"usingComponents": {
"van-tabbar": "@vant/weapp/tabbar/index",
"van-tabbar-item": "@vant/weapp/tabbar-item/index"
}
<van-tabbar active="{{ active }}" bind:change="onChange">
<van-tabbar-item icon="home-o">标签van-tabbar-item>
<van-tabbar-item icon="search">标签van-tabbar-item>
<van-tabbar-item icon="friends-o">标签van-tabbar-item>
<van-tabbar-item icon="setting-o">标签van-tabbar-item>
van-tabbar>
Component({
data: {
active: 0,
},
methods: {
onChange(event) {
// event.detail 的值为当前选中项的索引
this.setData({ active: event.detail });
},
}
});
最终渲染效果如下:
注意,如果项目移动位置后重新导入,需要重新构建 npm,不然可能会报错找不到组件。
将 Vant Weapp - tabBar 自定义图标中的示例代码拷贝至 index.wxml 中进行修改:slot = “icon” 为未选中时的图标,将其src属性修改为指定的首页图标路径;slot = “icon-active” 为选中时的图标,将其src属性修改为指定的首页图标路径;控制显示消息的属性 info 可以先删掉;将显示的文本由自定义修改为首页
<van-tabbar-item>
<image slot="icon" src="/images/tabs/home.png" mode="aspectFit" style="width: 30px; height: 18px;" />
<image slot="icon-active" src="/images/tabs/home-active.png" mode="aspectFit" style="width: 30px; height: 18px;" />
首页
van-tabbar-item>
将 app.json 中 tabBar 节点定义的 list 数组拷贝到 index.js 的数据 data 中:
data: {
active: 0,
list: [
{
"pagePath": "pages/mp5-2/mp5-2",
"text": "首页",
"iconPath": "/images/tabs/home.png",
"selectedIconPath": "/images/tabs/home-active.png"
},
{
"pagePath": "pages/message/message",
"text": "消息",
"iconPath": "/images/tabs/message.png",
"selectedIconPath": "/images/tabs/message-active.png"
},
{
"pagePath": "pages/contact/contact",
"text": "联系我们",
"iconPath": "/images/tabs/contact.png",
"selectedIconPath": "/images/tabs/contact-active.png"
}
]
},
在 index.wxml 中通过 van-tabbar-item 组件遍历 list 数组渲染 tabBar
<van-tabbar-item wx:for="{{list}}" wx:key="index">
<image slot="icon" src="{{item.iconPath}}" mode="aspectFit" style="width: 25px; height: 25px;" />
<image slot="icon-active" src="{{item.selectedIconPath}}" mode="aspectFit" style="width: 25px; height: 25px;" />
{{item.text}}
van-tabbar-item>
在 wxml 文件中 组件添加 info 属性,并赋值为2,便能够渲染出数字徽标;
<van-tabbar-item wx:for="{{list}}" wx:key="index" info="2">
...
van-tabbar-item>
但此时因为 tabbar 图标与文字之间有一个margin,使数字徽标顶部部分超出 tabbar 范围,要去掉 margin 使数字徽标回到正常;
通过检查元素找到 icon 图标的 margin-bottom 控制为(注意审查元素时选择一个未被选中的可以跳出想要看到的icon):
margin-bottom: var(--tabbar-item-margin-bottom,5px);
表明,如果提供了 --tabbar-item-margin-bottom css变量,那么就采取该值,否则为默认5px,所以要做的是定义出来这个 css 变量;
审查元素找到 icon 的父节点有 class 类 van-tabbar-item,如图
因此在 index.wxss 通过父节点定义css变量:
.van-tabbar-item {
--tabbar-item-margin-bottom: 0;
}
参照 Vant Weapp 样式覆盖,还需解除样式隔离,在 js 文件中定义如下:
Component({
//开启样式覆盖
options: {
styleIsolation: 'shared',
},
为需要渲染数字徽标的消息页面在 list 数组中对应项引入 info: 2属性
list: [
{
...
},
{
"pagePath": "pages/message/message",
"text": "消息",
"iconPath": "/images/tabs/message.png",
"selectedIconPath": "/images/tabs/message-active.png",
"info": 2
},
{
"pagePath": "pages/contact/contact",
"text": "联系我们",
"iconPath": "/images/tabs/contact.png",
"selectedIconPath": "/images/tabs/contact-active.png"
}
]
修改 wxml 根据 item 项是否有 info 字段来决定是否渲染,并根据 info 的值显示数字;如果 item 项没有 info 属性,属于 undefined,结果为空字符串,就不会渲染
<van-tabbar-item wx:for="{{list}}" wx:key="index" info="{{item.info ? item.info : ''}}">
...
van-tabbar-item>
在 js 文件中导入数据绑定
import { storeBindingsBehavior } from 'mobx-miniprogram-bindings'
import {store} from '../store'
挂载 behaviors (与 data 平齐)
// 挂载 behaviors
behaviors: [storeBindingsBehavior],
将 store 中的数据映射到当前组件:
behaviors: [storeBindingsBehavior],
// 数据映射
storeBindings: {
store,
fields: {
sum: 'sum'
},
actions: {
},
},
使用数据监听器监听 sum 值的变化,做到将 sum 的值转存到 list 中的 info (与behaviors、storeBindings平齐)
// 数据监听
observers: {
'sum': function(val) {
this.setData({
'list[1].info': val
})
}
},
当点击 tabbar 会触发 tabbar 的 onchange 处理函数,在事件处理函数中使用 wx.switchTab 跳转,注意为了要使用这个跳转功能,要将 list中的 pagePath 路径都补全,即增加一个起始反斜线
methods: {
onChange(event) {
// event.detail 的值为当前选中项的索引
this.setData({ active: event.detail })
wx.switchTab({
url: this.data.list[event.detail].pagePath,
})
},
}
解决选中项出错的问题:
删除原来定义在 data 中的 action;
到 store.js 中定义选中项索引和更改该索引的方法
activeTabBarIndex: 0,
updateActiveTabBarIndex: action(function(index){
this.activeTabBarIndex = index
})
在 index.js 引入字段和方法
storeBindings: {
store,
fields: {
sum: 'sum',
active: 'activeTabBarIndex',
},
actions: {
updateActive: 'updateActiveTabBarIndex'
},
},
将事件处理函数中原来的修改 data 中 acitve 的方法 改成用 store 中的全局方法修改全局索引
methods: {
onChange(event) {
// event.detail 的值为当前选中项的索引
// console.log(event.detail)
this.updateActive(event.detail)
wx.switchTab({
url: this.data.list[event.detail].pagePath,
})
},
}
默认选中时文本为淡蓝色,如上图,如果希望修改该颜色,参照 Vant Weapp Tabbar 标签栏最下面的 API 介绍,active-color 参数可以修改选中标签的颜色,该参数放在 van-tabbar 标签
<van-tabbar active="{{active}}" bind:change="onChange" active-color="#13A7A0">
<van-tabbar-item wx:for="{{list}}" wx:key="index" info="{{item.info ? item.info : ''}}">
<image slot="icon" src="{{item.iconPath}}" mode="aspectFit" style="width: 25px; height: 25px;" />
<image slot="icon-active" src="{{item.selectedIconPath}}" mode="aspectFit" style="width: 25px; height: 25px;" />
{{item.text}}
van-tabbar-item>
van-tabbar>
uni-app 是一个使用 Vue.js 开发所有前端应用的框架。可以编写一套代码,发布到多个平台。
uni-app 官方文档:https://uniapp.dcloud.net.cn/
uni-app 官方推荐使用 HBuilderX 来开发 uni-app 类型的项目。
为了方便编写样式(例如:),建议安装 scss/sass 编译 插件。插件下载地址:https://ext.dcloud.net.cn/plugin?name=compile-node-sass
进入插件下载页面之后,点击右上角的 使用 HBuilderX 导入插件 按钮进行自动安装:
1.2.5 修改编辑器的基本设置
源码视图下可用的参考配置:
{
"editor.colorScheme": "Default",
"editor.fontSize": 12,
"editor.fontFamily": "Consolas",
"editor.fontFmyCHS": "微软雅黑 Light",
"editor.insertSpaces": true,
"editor.lineHeight": "1.5",
"editor.minimap.enabled": false,
"editor.mouseWheelZoom": true,
"editor.onlyHighlightWord": false,
"editor.tabSize": 2,
"editor.wordWrap": true,
"explorer.iconTheme": "vs-seti",
"editor.codeassist.px2rem.enabel": false,
"editor.codeassist.px2upx.enabel": false
}
┌─components uni-app组件目录
│ └─comp-a.vue 可复用的a组件
├─pages 业务页面文件存放的目录
│ ├─index
│ │ └─index.vue index页面
│ └─list
│ └─list.vue list页面
├─static 存放应用引用静态资源(如图片、视频等)的目录,注意:静态资源只能存放于此
├─main.js Vue初始化入口文件
├─App.vue 应用配置,用来配置小程序的全局样式、生命周期函数等
├─manifest.json 配置应用名称、appid、logo、版本等打包信息
└─pages.json 配置页面路径、页面窗口样式、tabBar、navigationBar 等页面类信息
.gitignore
忽略文件,并配置如下:# 忽略 node_modules 目录
/node_modules
/unpackage/dist
git init
命令,初始化本地 Git 仓库。正常运行结果如下:Initialized empty Git repository in D:/***/uni-proj1/.git/
git status
命令,查询当前仓库文件状态:On branch master
No commits yet
Untracked files:
(use "git add ..." to include in what will be committed)
.gitignore
App.vue
components/
main.js
manifest.json
pages.json
pages/
static/
uni.scss
unpackage/
nothing added to commit but untracked files present (use "git add" to track)
git add.
命令,将文件加入到暂存区git status
命令,会看到一堆 绿色提示 new filegit commit -m "init project"
命令进行本地提交更新Author identity unknown
*** Please tell me who you are.
Run
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
to set your account's default identity.
Omit --global to set the identity only in this repository.
fatal: unable to auto-detect email address (got 'lenovo@DESKTOP-JSMAJ0G.(none)')
git config --global user.email "**@**com"
git config --global user.name "***"
git status
命令检查文件状态,得到 working tree clean 结果On branch master
nothing to commit, working tree clean
在项目根目录中,shift + 鼠标右键 启动 powerShell 窗口基于 master 分支在本地创建 tabBar 子分支,用于开发和 tabBar 相关的功能
git switch -c tabbar
在 pages
目录中,创建四个 tabBar 页面。在 HBuilderX 中,可以通过如下步骤快速新建页面
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"
}
]
},
pages.json
配置文件中,删除 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 switch master
git merge tabbar
git push
git branch -d tabbar
git switch -c home
wx.request()
API 功能较为简单,不支持拦截器等全局定制的功能。因此在 uni-app 项目中使用 @escook/request-miniprogram
第三方包来发起网络数据请求。npm install @escook/request-miniprogram
import { $http } from '@escook/request-miniprogram'
// 配置网络请求
uni.$http = $http
// 请求根路径
$http.baseUrl = 'https://www.uinav.com'
// 请求开始前做一些事情
$http.beforeRequest = function(options) {
uni.showLoading({
title: '数据加载中...'
})
}
// 请求完成后做一些事情
$http.afterRequest = function() {
uni.hideLoading()
}
<script>
export default {
data() {
return {
// 轮播图的数据列表
swiperList: [],
};
},
...
methods: {
// 获取轮播图数据的方法
async getSwiperList() {
// 发起请求
const {
data: res
} = await uni.$http.get('/api/public/v1/home/swiperdata')
// 判断请求结果-请求失败
if (res.meta.status !== 200) {
return uni.$showMsg()
}
// 请求成功,为 data 中的 swiperlist 赋值
this.swiperList = res.message
},
},
}
onLoad() {
// 小程序页面刚加载时,调用获取轮播图数据的方法
this.getSwiperList()
},
部分渲染 UI 结构<template>
<view>
<swiper :indicator-dots="true" :autoplay="true" :interval="3000" :duration="1000" :circular="true">
<swiper-item v-for="(item, i) in swiperList" :key="i">
<view class="swiper-item">
<image :src="item.image_src">image>
view>
swiper-item>
swiper>
view>
template>
中美化 UI 结构
分包好处:减少小程序首次启动时的加载时间;
页面分布: tabBar 相关的4个页面放到主包中,其他页面(如商品详情页、商品列表页)放到分包中;
配置分包步骤如下:
"pages": [
...
],
"subPackages": [{
"root": "subpkg",
"pages": []
}],
将每个 item 项中包裹图片的组件由 view 改为 navigator,并动态绑定其 url 属性
<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">
<image :src="item.image_src">image>
navigator>
此时,点击轮播图可跳转到商品详情页面。
数据请求失败后,经常需要调用 uni.showToast() 方法来提示用户,并且需要手动填入标题、持续时间参数;此时,可以全局封装一个 uni.$showMsg() 方法来简化调用,如下:
// 封装的展示消息提示的方法
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()
}
// 请求成功,为 data 中的 swiperlist 赋值
this.swiperList = res.message
},
uni.switchTab()
跳转到 tabBar 页面中的分类。/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()
// 处理传过来数据的每个小分类中的 url 地址 ——> 两层foreach循环
res.message.forEach(floor => {
// 第一层,取出楼层列表中的每一层,记作 floor
floor.product_list.forEach(prod => {
// 第二层,取出当前 floor 层中的每一种产品,记作prod
prod.url = '/subpkg/goods_list/goods_list?' + prod.navigator_url.split('?')[1]
})
})
this.floorList = res.message
},
对于拿到的数据,第一层 forEach 循环取出每一个楼层,第二个 forEach 循环依次拿到每一层楼里面的5类商品,对 URL 的处理是将原来的 navigator_url 通过 ? 进行分割,然后取出分割后数组索引为 1 的那一项,即页面跳转时的请求参数,然后在前面拼接上正确格式的页面路径:/subpkg/goods_list/goods_list
。将改路径作为一个新的属性 url 存入楼层数据中,跳转时直接取 url 即可。
git push
,而是git push -u origin home
基于 master 分支在本地创建 cate 子分支,用来开发分类页面相关的功能;
uni.getSystemInfoSync()
获取当前系统的信息,然后从返回值中取出 windowHeight
属性,即窗口可用高度,动态绑定在页面中。.active
类名:<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>
通过判断当前索引 i 是否等于 被选中索引 active ,如果是被选中项,那么当前 item 项额外有一个 active 类名,后面就可以根据是否有这个类名来为其美化样式;
activeChanged
;cateLevel2
;getCateList()
,请求到数据后,将一级分类列表中的第一项的 children
属性赋值给二级分类列表,即默认让二级分类对应第一个一级分类;activeChanged(i)
事件处理函数,当选中项改变即一级分类改变时,为二级分类列表数据重新赋值;cateLevel2
中取出数据作为 item 项,先将每个 item 项的 cat_name
属性渲染为二级分类,即二级分类是一级分类下的标题;modifyCatIcon
方法,将本来动态绑定给 image
组件的 src
属性的 item3.cat_icon
进行处理,如下:// 处理三级分类图标地址的方法--过滤器
modifyCatIcon(s){
return s.replace('dev','web')
},
前面基本实现了页面效果,但是有一个小bug :在一个一级分类下,将右侧滚动条向下拉,此时切换一级分类,二级分类并不会重置滚动条到起始位置,而是保留前面的位置。
scrollTop
,默认为0;
组件绑定 scroll-top
属性,绑定值为 scrollTop
;scrollTop
的值,因为设置 scrollTop
时,前后如果没变化并不会生效,所以这里的操作是:每次切换时使 scrollTop
在 0 和 1 之间来回赋值,虽然每次有 1 个像素的偏差,但是对用户使用并没有差异,实现如下: // 一级分类选中项被改变的事件处理函数
activeChanged(i) {
...
// 切换一级分类时,重置二级分类滚动条顶部距离
this.scrollTop = this.scrollTop === 0 ? 1 : 0
},
gotoGoodslist(item3)
;uni.navigateTo()
导航至商品列表页面,同时将 item3.cat_id
,作为页面参数传递过去。components
目录上,右键 - 新建组件 my-search
;cate
页面的 UI 结构中,直接以标签的形式使用 my-search
自定义组件;my-search
组件的 UI 结构:包含一个 uni-icon 的搜索图标,和 “搜索”两个字 —— 当前组件中是模拟出 input 输入框的外形,并不会真正在页面上渲染出 input 输入框;my-search
组件占据了一定高度,分类页面本来可用的窗口高度需要重新计算,其中50是自定义组件的高度:onLoad() {
// 计算窗口可用高度
// 获取当前系统信息
const sysInfo = uni.getSystemInfoSync()
// 给窗口可用高度赋值
this.wh = sysInfo.windowHeight - 50
...
}
目的:允许使用者自定义搜索组件的背景颜色和圆角尺寸;
中,与 data
节点平行,增加 props
属性,定义 bgcolor
和 radius
两个属性;.my-search-container
盒子和 my-search-box
动态绑定上面定义的两个属性;scss
样式中定义的背景颜色和圆角尺寸;my-search
组件内部,给 .my-search-container
所在的 view
绑定 click
事件处理函数 searchBoxHandler
;methods
节点中,声明事件处理函数,在其内部出发外界通过 @click
绑定的 click
事件处理函数:methods: {
searchBoxHandler(){
// 触发外界通过 @click 绑定的 click 事件处理函数
this.$emit('click')
}
}
cate
中使用 my-search
自定义组件时,即可通过 @click
为其绑定点击事件处理函数 gotoSearch
;search
搜索页面,并在 cate
页面中,定义 gotoSearch()
事件处理函数跳转到搜索页面。分类页面因为搜索组件下面就是适应屏幕的滑动窗口,所以不存在在下滑时将搜索组件覆盖的现象;
而首页整个顺序往下排,当下滑时,搜索组件会被覆盖,现在需要解决该问题。
// 实现搜索组件的吸顶效果
.search-box {
// 设置定位效果为 “吸顶”
position: sticky;
// 吸顶的位置
top: 0;
// 提高搜索狂的层级,防止被轮播i图覆盖
z-index: 999;
}
uni-ui
提供的组件实现搜索页面的搜索框;components -> uni-search-bar -> uni-search-bar.vue
修改组件,将默认的白色搜索背景改为红色;e.value
是输入的最新搜索内容。components -> uni-search-bar -> uni-search-bar.vue
修改组件,将其 data
数据中的 show
和 showSync
的值由默认的 false
改为 true
,便可实现跳转到搜索界面,会自动获取焦点等待输入;目的:目前,当在搜索框输入内容时,只要输入就会传入关键词参数,如果不做防抖处理直接进行数据请求,那么会多出许多不必要的请求,用户中间输入的内容没必要去请求参数,因此需要防抖处理;
data() {
return {
// 延时器的 timerId
timer: null,
// 搜索关键词 - 即搜索框的输入
kw: ''
}
}
input(e){
// 搜索框的防抖处理
// 清除 timer 对应的延时器
clearTimeout(this.timer)
// 重新启动一个延时器,并把 timerId 赋值给 this.timer
this.timer = setTimeout(() => {
// 500ms 内,没有触发新的输入事件,才给关键词赋值,避免连续输入时不必要地读取赋值
this.kw = e.value
// e.value 是输入框最新的搜索内容
// console.log(e.value)
// 根据关键词,查询搜索建议列表
this.getSearchList()
},500)
},
即实现了防抖效果,每次 500ms 内连续输入的结果不会为关键词赋值。
searchResult
;setTimeout
中,调用 getSearchList
方法获取搜索建议列表;methods
节点中定义 getSearchList
方法,发起请求,获取搜索建议列表数据。// 让商品标题只显示一行,并且多余的文本溢出后用...代替
.goods-name {
// 文字不允许换行
white-space: nowrap;
// 溢出部分隐藏
overflow: hidden;
// 文本溢出后,用...代替
text-overflow: ellipsis;
margin-right: 3px;
}
historyList
;historyList
中取出历史关键词显示;(searchResults.length !== 0)
时,展示搜索建议区域,隐藏搜索历史区域;v-if
和 v-else
控制这两个区域的显示和隐藏。saveSearchHistory()
保存搜索关键词,并定义该方法,将关键词 push 到搜索历史列表中;historyList
不做修改,依然使用 push
进行数组的末尾追加;computed
与 data
平行,在里面定义一个计算属性,该计算属性是拿到数组反转之后的值:// 计算属性
computed: {
// 搜索历史数组反转,实现新搜索的在前面,但是数组中存储的还是往后追加
historys() {
return [...this.hitoryList].reverse()
}
},
data
中的 historyList
,而是使用计算属性 historys
;解决方案:使用 Set 集合的去重效果;
// 保存搜索关键词的方法
saveSearchHistory() {
// 借助 Set 去重
const set = new Set(this.hitoryList)
// 删除当前传进来的关键词,有才删除
set.delete(this.kw)
// 增加新加的关键词
set.add(this.kw)
// 将 set 恢复为数组
this.hitoryList = Array.from(set)
},
uni.setStorageSync()
; // 保存搜索关键词的方法
saveSearchHistory() {
// 借助 Set 去重
const set = new Set(this.hitoryList)
// 删除当前传进来的关键词,有才删除
set.delete(this.kw)
// 增加新加的关键词
set.add(this.kw)
// 将 set 恢复为数组
this.hitoryList = Array.from(set)
// 持久化存储到本地
uni.setStorageSync('kw',JSON.stringify(this.hitoryList))
},
onLoad
生命周期函数中,加载本地存储的搜索历史记录onLoad() {
this.hitoryList = JSON.parse(uni.getStorageSync('kw') || '[]')
},
click
事件;historyList
数组,清空本地存储中记录的搜索历史。新建 goodslist
分支
queryObj
,包含以下属性:查询关键词、商品分类Id、页码值、每页显示条目数;queryObj
对象中,如果传入为空,通过或运算赋一个空字符串;data
中新增商品列表的数据节点 goodsList
和总数量节点 total
;onLoad
生命周期函数中,调用 getGoodsList
方法获取商品列表数据;methods
节点中,声明 getGoodsList
方法发起请求获取参数;v-for
指令,循环渲染出商品的 UI 结构,每一项左侧是商品图片,右侧是商品标题和商品价格;data
中定义一个默认图片,并在 UI 结构中通过或运算 || 绑定;components
目录上新建组件 my-goods
;goods-list
页面中有关商品 item
项的 UI 结构、样式、data 数据,封装到 my-goods
组件中:UI结构和样式整个迁移,data 数据需要为组件定义一个 props 节点中的 goods
对象,用于从外界接收,迁移默认的空图片地址;goods_list
页面中,循环渲染 my-goods
组件即可:动态为组建的 goods
属性绑定循环的 item
。my-goods
组件中,和 data
节点平级,声明 filters
过滤器节点,里面定义 toFixed
把价格数字处理为带两位小数点的数字;|
调用过滤器: <view class="goods-price">¥{{goods.goods_price | toFixed}}view>
pages.json
配置文件中,为 subPackages
分包中的 goods_list
页面配置上拉触底的距离;goods_list
页面中,声明 onReachBottom
事件处理函数,监听页面的上拉触底行为:页码之+1,重新获取列表数据;methods
节点中的 getGoodsList
函数,每次请求数据成功后,新旧数据通过展开运算符进行拼接。data
中定义 isLoading
节流阀,默认赋值为 false
;getGoodsList
函数,请求数据前打开节流阀,请求数据后关闭节流阀,即分别赋值 true
、false
;onReachBottom
事件处理函数中,先判断节流阀是否打开,如果打开表明正在请求数据,不再发起额外请求,直接 return 掉。pagenum * pagesize >= total
;onReachBottom
事件处理函数,先判断是否还有下一页数据,如果满足上式,直接 return 掉,并弹框提示“数据加载完毕!”。pages.json
配置文件中,为当前的 goods_list
页面单独开启下拉刷新效果;onPullDownRefresh
事件处理函数:下拉刷新时,重置关键数据、重新发起数据请求并传入回调函数关闭下拉刷新;getGoodsList
函数,请求数据完毕,根据是否传入 cb 回调函数来进行对应的关闭下拉刷新操作。block
组件改为 view
组件,并绑定 click
事件处理函数;goods_id
参数。goodslist
分支进行本地提交;goodslist
分支推送到码云;goodslist
分支合并到 master
主分支并推送主分支;goodslist
分支。创建商品详情页的编译模式,注意填写启动参数 goods_id=266
data
中定义商品详情的数据节点;onLoad
生命周期函数中获取商品的 id,并调用请求商品详情的方法 getGoodsDetail()
;methods
节点中声明 getGoodsDetail()
方法。v-for
指令,渲染 UI 结构;image
绑定 click
事件处理函数 preview
;methods
中定义 preview
方法,实现轮播图的预览效果:调用 uni.previewImage()
方法预览图片。rich-text
富文本组件,将带有 HTML 标签的内容,渲染为小程序的页面结构;replace()
方法,为 img 标签添加行内的 style 样式,解决图片底部空白间隙的问题,替换时使用正则表达式:res.message.goods_introduce = res.message.goods_introduce.replace(//g, ')
.webp
格式图片在 ios 设备上无法正常显示的问题:将 .webp
后缀替换为 .jpg
。v-if
判断属性值是否存在,存在再显示。基于 uni-ui 提供的 GoodsNav 组件来实现商品导航区域的效果。
最终效果如下:
标签中,按需导入 mapState 辅助方法,映射购物车模块中的数据;需求:从商品详情页导航到购物车页面,需要为 tabBar 中的购物车动态设置数字徽标。
目的:tabBar 徽标的渲染,在四个页面显示时都需要渲染,需要在其他三个页面都重新把方法和在 onShow 中的调用写一遍。可以使用 Vue 提供的 mixins,提高代码的可维护性。
NumberBox 组件时 uni-ui 提供,可以直接为其绑定商品的数量值,作为商品数量的显示组件,可以让用户方便进行加减与自定义输入操作。
用户修改 numberBox 数值以后,需要将商品数量更新到购物车中。
用户可能会输入非数字、小数,需要到组件源代码进行处理。
用户每次输入数据都会触发内部监听器,触发侦听,而如果输入不合法不应该被触发,到源代码 inputValue 侦听器进行判断处理。
滑动删除使用的是 uni-ui 的 uni-swipe-action 组件和 uni-swipe-action-item。
绑定事件处理函数,当点击滑动操作按钮,将对应购物车数组中的数据删除。
如果收货地址节点有数据,展示收货信息,没有数据展示选择收货地址效果。
调用小程序提供的 chooseAddress() API 实现选择收货地址的功能,注意需要在 manifest.json 中注册该方法。
定义 user.js 的用户 vuex 模块,存储 address 信息。
抽离可以提高代码的复用性,方便在多个页面和组件之间复用。
检测到用户没有授权时,通过定义 reAuth 重新获取授权。
增加检测用户没有授权时错误信息的判断项即可。
在 store/cart.js 中,定义名为 checkedCount 的 getters 统计已勾选商品的总数量:
购物车数组先 filter 过滤筛选出勾选的的商品,再 reduce 进行累加。
将商品总数量映射到结算组件,定义 isFullCheck 的计算属性:
通过比较购物车商品总数量和已勾选商品的总数量是否相等来判断当前是否全选。
根据 isFullCheck 确定当前状态,点击事件处理函数将其取反就是最新的勾选状态。
购物车数组:filter 过滤出已勾选商品,reduce 累加计算总价,toFixed 保留两位小数。
问题:修改商品数量后,tabBar 上的徽标不会自动更新。
解决:在 mixins 中使用 watch 监听器,监听 total 总数量的变化。
依次判断:是否勾选了商品、是否选择了收货地址、是否完成了登陆(通过定义 token 是否存在判断)
分别创建登陆组件和用户信息组件,通过判断 token 是否有值来选择展示的内容。
api 已被废弃:wx.getUserInfo wx.getUserProfile
调用登录接口 uni.login() ,发送请求换取 token
收藏区域
订单待收货等区域
收货地址、联系客服、退出登陆功能区域
清空 vuex 中的 userinfo、token、address
结算按钮当没登陆时,需要提示用户3秒后自动跳转到登陆页面
定义延迟导航函数,里面设置定时器,每1秒刷新一次展示框,同时注意要在计完3秒后,清除定时器,实现调转功能;每次进入方法时重置计时秒数。
声明返回页面的信息对象 redirectInfo,包含 openType 和 from 两个属性,在跳转到登陆页面成功之后的回调函数中,将 redirectInfo 信息更新;
登录成功之后通过 redirectInfo 里面的数据返回原来的页面,并在导航成功后将 redirectInfo 重置为 null。
判断请求的路径包含需要身份认证,为其请求头添加身份认证字段,值为 token