微信小程序03-页面交互
零、文章目录
微信小程序03-页面交互
1、案例:比较数字大小
(1)案例分析
-
需求:本案例将实现“比较数字大小”微信小程序,它的功能是当用户输入两个数字后,点击“比较”按钮可以自动比较这两个数字的大小。
-
两个输入框,可以输入数字,输入后点击“比较”按钮,按钮下方会显示比较结果。
-
比较结果有3种情况
- 如果第1个数字比第2个数字大,则比较结果为“第1个数大”;
- 如果第2个数字比第1个数字大,则比较结果为“第2个数大”;
- 如果第1个数字和第2个数字相等,则比较结果为“两数相等”。
(2)知识储备-Page()函数
- 在微信小程序中,页面交互的代码写在页面的JS文件中,每个页面都需要通过Page()函数进行注册。需要注意的是,Page()函数只能写在微信小程序每个页面对应的JS文件中,并且每个页面只能注册一个。
- Page()函数的参数是一个对象,通过该对象可以指定页面初始数据、页面生命周期回调函数和页面事件处理函数。
- data:页面初始数据
- onLoad:页面生命周期回调函数
- onPullDownRefresh:页面事件处理函数
Page({
// 页面初始数据
data: {},
// 页面生命周期回调函数,以onLoad()为例
onLoad: function () {
console.log('onLoad()函数执行了')
},
// 页面事件处理函数,以onPullDownRefresh()为例
onPullDownRefresh: function () {
console.log('onPullDownRefresh()函数执行了')
}
})
- **页面初始数据:**页面初始数据是指页面第一次渲染时所用到的数据。
data: {
msg1: 'Hello',
msg2: 'World'
},
- **页面生命周期回调函数:**在微信小程序中,页面的生命周期是指每个页面“加载→渲染→销毁”的过程,每个页面都有生命周期。如果想要在某个特定的时机进行特定的处理,则可以通过页面生命周期回调函数来完成。
- **页面事件处理函数:**在微信小程序中,用户可能会在页面上进行一些操作,例如上拉、下拉、滚动页面等,这些可以通过页面事件处理函数来完成。
- 使用onPullDownRefresh()函数前,需要在app.json配置文件中将enablePullDownRefresh配置项设为true。
(2)知识储备-数据绑定
-
数据绑定概念
- 在微信小程序开发过程中,一般会将页面中的数据从WXML文件中分离出来,通过JS文件操作页面中的数据。
- 假如有一个电商类的微信小程序,每个商品的详情页面的结构是相同的,区别是页面展示的数据不同。在实际开发中,开发者只编写一个页面,通过更改页面中的数据来实现不同的商品详情页面。这种开发方式是将页面中的数据分离出来,放到页面的JS文件中,通过程序控制页面中数据的展示。
-
**数据绑定实现:**微信小程序提供了Mustache语法(又称为双大括号语法)用于实现数据绑定,可将data中的数据通过Mustache语法输出到页面上。
-
数据绑定代码案例
-
在pages/index/index.js文件中,在data中定义一个message数据
-
Page({ data: { message: 'Hello World' } })
-
-
在pages/index/index.wxml文件中编写页面结构
-
<view>{{ message }}</view>
-
-
**运行结果:**页面上显示了message变量对应的值,也就是把“HelloWorld”渲染到页面代码中{{ message }}所在的位置,实现了从逻辑层到视图层的数据显示。
-
(2)知识储备-事件绑定
- **事件绑定:**事件是视图层到逻辑层的通信方式,通过给组件绑定事件,可以监听用户的操作行为,然后在对应的事件处理函数中进行相应的业务处理。例如,为页面中的按钮绑定事件,当用户点击按钮时,就触发了事件。
- 常见的事件如下,分为两类
- **冒泡事件:**当一个组件上的事件被触发后,该事件会向父组件传递,比如点击事件、长按事件、触摸事件。
- **非冒泡事件:**当一个组件上的事件被触发后,该事件不会向父组件传递,比如其他事件。
-
事件绑定实现
- 可以通过为组件添加“bind+事件名称”属性或“catch+事件名称”属性来完成,属性的值为事件处理函数,当组件的事件被触发时,会主动执行事件处理函数。
- bind和catch的区别在于,bind不会阻止冒泡事件向上冒泡,而catch可以阻止冒泡事件向上冒泡。
- 为组件绑定事件后,可以将事件处理函数定义在Page({})中。
-
事件绑定代码案例
-
在pages/index/index.wxml文件中为button组件绑定tap事件
-
<button bindtap="compare">比较</button>
-
-
在pages/index/index.js文件的Page({})中定义compare()函数
-
compare: function () { console.log('比较按钮被单击了') }, //可以写成简写形式 compare () { console.log('比较按钮被单击了') },
-
-
**运行结果:**单击“比较”按钮,在控制台输出“比较按钮被单击了”。
-
(2)知识储备-事件对象
- 事件对象
- 在微信小程序的开发过程中,有时需要获取事件发生时的一些信息,例如事件类型、事件发生的时间、触发事件的对象等,此时可以通过事件对象来获取。
- 当事件处理函数被调用时,微信小程序会将事件对象以参数的形式传给事件处理函数。
- 事件对象的属性如下
- **target:**获取触发事件的组件的一些属性值集合。
- **currentTarget:**获取当前组件的一些属性值集合。
-
事件对象代码案例
-
修改pages/index/index.js文件中的compare()函数,通过参数接收事件对象,并将事件对象输出到控制台
-
compare: function (e) { console.log(e) },
-
-
**运行结果:**单击“比较”按钮,控制台输出参数e表示事件对象。
-
-
事件对象代码案例-演示target和currentTarget区别
-
在pages/index/index.wxml文件中编写页面结构
-
<view bindtap="viewtap" id="outer"> outer <view id="inner"> inner </view> </view>
-
-
在pages/index/index.js文件中添加viewtap()事件处理函数
-
viewtap: function (e) { console.log(e.target.id + '-' + e.currentTarget.id) },
-
-
**运行结果:**当单击outer时,控制台中的输出结果为outer-outer,而单击inner时,控制台中的输出结果为inner-outer。由此可见,e.target获取的是子元素的属性值集合,而e.currentTarget获取的是父元素的属性值集合。
-
(2)知识储备-this关键字
- **this关键字:**在微信小程序开发过程中,有时需要在函数中访问页面中定义的一些数据,或者调用页面中定义的一些函数,此时可以通过this关键字来实现。this关键字代表当前页面对象。
- **代码演示:**在onLoad()函数中通过this关键字访问data中的num数据并调用test()函数。程序运行后,在控制台中可以看到程序输出了this.data.num的值“1”和“test()函数执行了”。
Page({
data: { num: 1 }, // 定义data数据
test: function () { // 定义test()函数
console.log('test()函数执行了')
},
onLoad: function () {
console.log(this.data.num) // 通过this关键字访问data中的num数据
this.test() // 通过this关键字调用test()函数
}
})
(2)知识储备-setData()方法
-
setData()方法
- 通过数据绑定可以将data中定义的数据渲染到页面,但是如果数据发生了变化,页面并不会同步更新数据。
- 为了实现在数据变化时使页面同步更新,微信小程序提供了setData()方法,该方法可以立即改变data中的数据,并通过异步的方式将数据渲染到页面上。
-
setData()语法
this.setData(data[, callback])
-
setData()代码案例
-
在pages/index/index.js文件中编写页面中所需的数据message和事件处理函数changeText()
-
Page({ data: { message: 'Hello World' }, changeText: function () { this.setData({ message: 'hello微信小程序' }) } })
-
-
在pages/index/index.wxml文件中编写页面结构
-
<view bindtap="changeText">{{ message }}</view>
-
-
**运行结果:**单击前页面中显示的文字为“Hello World”,单击后页面中显示的文字为“hello微信小程序”。
-
(2)知识储备-条件渲染
-
条件渲染
- 在微信小程序开发过程中,如果需要根据不同的判断结果显示不同的组件,可以使用条件渲染来实现。
- 条件渲染通过标签的wx:if控制属性来完成。使用wx:if="{{ val }}"来判断是否需要渲染标签对应的组件,如果变量val的值为true,则渲染组件并输出;变量val的值为false,则不渲染组件。
-
**代码演示:**通过变量condition的值来控制是否渲染view组件。
<view wx:if="{{ condition }}">True</view>
- 代码演示:
- 给标签设置了wx:if控制属性后,可以为后面的标签设置wx:elif、wx:else控制属性。
- wx:elif控制属性表示当前面标签的if条件不满足时,继续判断elif(else if)的条件;
- wx:else控制属性表示当前面的if条件不满足时,渲染else对应的组件。
- wx:else控制属性也可以直接出现在wx:if控制属性的后面。
<view wx:if="{{ count < 1 }}">0</view>
<view wx:elif="{{ count == 1 }}">1</view>
<view wx:else>2</view>
(2)知识储备-<block>
标签
- **
<block>
标签:**当使用一个判断条件决定是否显示或者隐藏多个组件时,通常会在其外部包裹一个<block>
标签,这样可直接控制这个外部标签的显示或隐藏。该标签并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,比view能耗低。
<block wx:if="{{ true }}">
<view>view1</view>
<view>view2</view>
</block>
(2)知识储备-hidden属性
- hidden属性
- 除wx:if控制属性外,hidden属性也可以控制组件的显示与隐藏,条件为true时隐藏组件里面的内容,条件为false时显示组件里面的内容
- hidden属性和wx:if控制属性不同之处
- wx:if控制属性的初始渲染条件为false,只有条件第一次变为true的时候才开始渲染。
- hidden属性所在的组件始终会被渲染,只是简单的控制显示与隐藏。
<text hidden="{{ hidden }}">hidden为true时不显示</text>
(3)案例实现
-
准备工作
-
①创建项目。在微信开发者工具中创建一个新的微信小程序项目,项目名称为“比较数字大小”,模板选择“不使用模板”。
-
②配置导航栏。在pages/index/index.json文件中配置页面导航栏。
-
③配置样式。在pages/index/index.wxss文件中配置本项目的页面样式。
-
-
**实现页面结构:**在pages/index/index.wxml文件中编写“比较数字大小”微信小程序的页面结构。
<!--index.wxml-->
<view>
<text>请输入第1个数字:</text>
<input type="number" bindinput="num1Input" />
</view>
<view>
<text>请输入第2个数字:</text>
<input type="number" bindinput="num2Input" />
</view>
<button bindtap="compare">比较</button>
<view>
<text wx:if="{{ result }}">比较结果:{{ result }}</text>
</view>
- **实现事件处理函数:**在pages/index/index.js文件的Page({})中编写事件处理函数
// index.js
Page({
data: {
result: ''
},
num1: 0, // 保存第1个数字
num2: 0, // 保存第2个数字
num1Input: function (e) {
this.num1 = Number(e.detail.value)
},
num2Input: function (e) {
this.num2 = Number(e.detail.value)
},
compare: function () {
var str = ''
if (this.num1 > this.num2) {
str = '第1个数大'
} else if (this.num1 < this.num2) {
str = '第2个数大'
} else {
str = '两数相等'
}
this.setData({
result: str
})
}
})
- 页面实现效果
2、案例:计算器
(1)案例分析
- 需求:在日常生活中,计算器是人们广泛使用的工具,可以帮助我们快速且方便地计算金额、成本、利润等。
- 在计算器中可以进行整数和小数的加(+)、减(-)、乘(×)、除(÷)运算。
- “C”按钮为清除按钮,表示将输入的数字全部清空;
- “DEL”按钮为删除按钮,表示删除前面输入的一个数字;
- “+/-”按钮为正负号切换按钮,用于实现正负数切换;
- “.”按钮为小数点按钮,表示在计算过程中可以输入小数进行计算;
- “=”按钮为等号按钮,表示对输入的数字进行计算。
(2)知识储备-data-*自定义属性
-
自定义属性
- 在组件中,有时需要为事件处理函数传递参数。在Vue.js中可以直接使用函数进行传参,但是这种写法在微信小程序中并不适用。微信小程序可以通过自定义属性来进行传参。
- 微信小程序中的
data-*
是一个自定义属性,data-*
自定义属性实际上是由data-前缀加上一个自定义的属性名组成的,属性名中如果有多个单词,用连字符“-”连接。 data-*
自定义属性的属性值表示要传参的数据。在事件处理函数中通过target或currentTarget对象的dataset属性可以获取数据。dataset属性是一个对象,该对象的属性与data-*
自定义属性相对应。需要注意的是,自定义属性名的连字符写法会被转换成驼峰写法,并且大写字母会自动转换成小写字母,例如,data-element-type会被转换为dataset对象的elementType属性,data-elementType会被转换为dataset对象的elementtype属性。
-
自定义属性代码实现
-
在pages/index/index.wxml文件中编写页面结构
-
<view bindtap="demo" data-name="xiaochengxu" data-age="6"> 获取姓名和年龄 </view> <view>姓名:{{ name }}</view> <view>年龄:{{ age }}</view>
-
-
在pages/index/index.js文件中编写页面逻辑
-
Page({ data: { name: '初始名字', age: 0 }, demo: function (e) { this.setData({ name: e.target.dataset.name, age: e.target.dataset.age }) } })
-
-
运行结果:单击“获取姓名和年龄”,name编程xiaochengxu,age变成6
-
(2)知识储备-模块
- **模块:**在微信小程序中,为了提高代码的可复用性,通常会将一些公共的代码抽离成单独的JS文件,作为模块使用,每个JS文件均为一个模块。
- **模块语法:**微信小程序提供了模块化开发的语法,可以使用module.exports语法对外暴露接口,然后在需要使用模块的地方通过require()函数引入模块。
- **创建模块:**在项目的根目录下创建一个utils目录,用于保存项目中的模块,然后在该目录下创建welcome.js文件
module.exports = {
message: 'welcome'
}
- **引入模块:**在页面的JS文件中使用require()函数将模块引入
var welcome = require('../../utils/welcome.js')
Page({
onLoad: function () {
console.log(welcome.message)
}
})
(3)案例实现
-
准备工作
-
①创建项目。在微信开发者工具中创建一个新的微信小程序项目,项目名称为“计算器”,模板选择“不使用模板”。
-
②配置导航栏。在pages/index/index.json文件中配置页面导航栏。
-
③配置样式。在pages/index/index.wxss文件中配置本项目的页面样式。
-
④创建utils文件夹。utils文件夹保存了项目中用到的两个公共文件,分别是math.js和calc.js。
-
math.js文件实现了数字的精确计算,用于解决JavaScript浮点型数据计算精度不准确的问题;
-
// 精准计算功能,用于解决JavaScript浮点数运算精度不准确的问题 module.exports = { add: function(arg1, arg2) { var r1, r2, m try { r1 = arg1.toString().split('.')[1].length } catch (e) { r1 = 0 } try { r2 = arg2.toString().split('.')[1].length } catch (e) { r2 = 0 } m = Math.pow(10, Math.max(r1, r2)) return (arg1 * m + arg2 * m) / m }, sub: function(arg1, arg2) { var r1, r2, m, n try { r1 = arg1.toString().split('.')[1].length } catch (e) { r1 = 0 } try { r2 = arg2.toString().split('.')[1].length } catch (e) { r2 = 0 } m = Math.pow(10, Math.max(r1, r2)) // 动态控制精度长度 n = (r1 >= r2) ? r1 : r2 return ((arg1 * m - arg2 * m) / m).toFixed(n) }, mul: function(arg1, arg2) { var m = 0, s1 = arg1.toString(), s2 = arg2.toString() try { m += s1.split('.')[1].length } catch (e) {} try { m += s2.split('.')[1].length } catch (e) {} return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m) }, div: function(arg1, arg2) { var t1 = 0, t2 = 0, r1, r2 try { t1 = arg1.toString().split('.')[1].length } catch (e) {} try { t2 = arg2.toString().split('.')[1].length } catch (e) {} r1 = Number(arg1.toString().replace('.', '')) r2 = Number(arg2.toString().replace('.', '')) return (r1 / r2) * Math.pow(10, t2 - t1) } }
-
calc.js文件提供了一个计算器对象,用于简化开发逻辑。
-
const math = require('./math.js') // 计算器中的数字处理 module.exports = { target: 'num1', // 表示当前正在输入哪个数字 num1: '0', // 保存第1个数字 num2: '0', // 保存第2个数字 op: '', // 运算符,值可以是 + - × ÷ // 设置当前数字 setNum (num) { this[this.target] = num }, // 获取当前数字 getNum () { return this[this.target] }, // 切换到第2个数字 changeNum2 () { this.target = 'num2' }, // 重置 reset () { this.num1 = this.num2 = '0' this.target = 'num1' this.op = '' }, // 进行计算 getResult () { if (this.op === '+') { return math.add(this.num1, this.num2) + '' } else if (this.op === '-') { return math.sub(this.num1, this.num2) + '' } else if (this.op === '×') { return math.mul(this.num1, this.num2) + '' } else if (this.op === '÷') { return math.div(this.num1, this.num2) + '' } } }
-
-
-
-
**实现页面结构:**在pages/index/index.wxml文件中编写“计算器”微信小程序的页面结构
<!--index.wxml-->
<view class="result">
<!-- 结果区域 -->
<view class="result-sub">{{ sub }}</view>
<view class="result-num">{{ num }}</view>
</view>
<view class="btns">
<!-- 按钮区域 -->
<!-- 按钮区域第1行按钮的结构 -->
<view>
<view hover-class="bg" hover-stay-time="50" bindtap="resetBtn">C</view>
<view hover-class="bg" hover-stay-time="50" bindtap="delBtn">DEL</view>
<view hover-class="bg" hover-stay-time="50" bindtap="negBtn">+/-</view>
<view hover-class="bg" hover-stay-time="50" bindtap="opBtn" data-val="÷">÷</view>
</view>
<!-- 按钮区域第2行按钮的结构 -->
<view>
<view hover-class="bg" hover-stay-time="50" bindtap="numBtn" data-val="7">7</view>
<view hover-class="bg" hover-stay-time="50" bindtap="numBtn" data-val="8">8</view>
<view hover-class="bg" hover-stay-time="50" bindtap="numBtn" data-val="9">9</view>
<view hover-class="bg" hover-stay-time="50" bindtap="opBtn" data-val="×">×</view>
</view>
<!-- 按钮区域第3行按钮的结构 -->
<view>
<view hover-class="bg" hover-stay-time="50" bindtap="numBtn" data-val="4">4</view>
<view hover-class="bg" hover-stay-time="50" bindtap="numBtn" data-val="5">5</view>
<view hover-class="bg" hover-stay-time="50" bindtap="numBtn" data-val="6">6</view>
<view hover-class="bg" hover-stay-time="50" bindtap="opBtn" data-val="-">-</view>
</view>
<!-- 按钮区域第4行按钮的结构 -->
<view>
<view hover-class="bg" hover-stay-time="50" bindtap="numBtn" data-val="1">1</view>
<view hover-class="bg" hover-stay-time="50" bindtap="numBtn" data-val="2">2</view>
<view hover-class="bg" hover-stay-time="50" bindtap="numBtn" data-val="3">3</view>
<view hover-class="bg" hover-stay-time="50" bindtap="opBtn" data-val="+">+</view>
</view>
<!-- 按钮区域第5行按钮的结构 -->
<view>
<view hover-class="bg" hover-stay-time="50" bindtap="numBtn" data-val="0">0</view>
<view hover-class="bg" hover-stay-time="50" bindtap="dotBtn">.</view>
<view hover-class="bg" hover-stay-time="50" bindtap="execBtn">=</view>
</view>
</view>
- **实现页面逻辑:**在pages/index/index.js文件的Page({})中编写页面逻辑
// index.js
const calc = require('../../utils/calc.js')
Page({
data: {
sub: '',
num: '0'
},
// 设置3个变量标识
numChangeFlag: false,
execFlag: false,
resultFlag: false,
numBtn: function (e) {
// 点击数字按钮,获取对应的数字,将其值赋给num
var num = e.target.dataset.val
if (this.resultFlag) {
this.resetBtn()
}
if (this.numChangeFlag) {
this.numChangeFlag = false
this.execFlag = true // 代表已输入第2个数字
this.data.num = '0' // 将num设为0,避免数字进行拼接
calc.changeNum2() // 将target切换到第2个数字
}
// 设置输入的数字
calc.setNum(this.data.num === '0' ? num : this.data.num + num)
// 在页面中显示输入的数字
this.setData({
num: calc.getNum()
})
},
opBtn: function (e) {
calc.op = e.target.dataset.val
this.numChangeFlag = true
// 判断是否已经输入第2个数字
if (this.execFlag) {
this.execFlag = false
// 已经输入第2个数字,再判断当前是否为计算结果状态
if (this.resultFlag) {
// 当前是计算结果状态,需要在计算结果的基础上计算
this.resultFlag = false
} else {
// 连续计算,将计算结果作为第1个数字
calc.num1 = calc.getResult()
}
}
this.setData({
sub: calc.num1 + ' ' + calc.op + ' ',
num: calc.num1
})
},
// “=”按钮的事件处理函数
execBtn: function () {
if (this.numChangeFlag) {
this.numChangeFlag = false
this.execFlag = true
calc.num2 = this.data.num
}
// 如果已经输入第2个数字,执行计算操作
if (this.execFlag) {
this.resultFlag = true
var result = calc.getResult()
this.setData({
sub: calc.num1 + ' ' + calc.op + ' ' + calc.num2 + ' = ',
num: result
})
calc.num1 = result
}
},
resetBtn: function () {
calc.reset() // 调用reset()实现数字、运算符的重置
this.execFlag = false
this.numChangeFlag = false
this.resultFlag = false
this.setData({
sub: '',
num: '0'
})
},
// “.”按钮(小数点按钮)的事件处理函数
dotBtn: function () {
// 如果当前是计算结果状态,则重置计算器
if (this.resultFlag) {
this.resetBtn()
}
// 如果等待输入第2个数字且还没有输入第2个数字,设为“0.”
if (this.numChangeFlag) {
this.numChangeFlag = false
calc.setNum('0.')
} else if (this.data.num.indexOf('.') < 0) {
// 如果当前数字中没有“.”,需要加上“.”
calc.setNum(this.data.num + '.')
}
this.setData({
num: calc.getNum()
})
},
// DEL按钮(删除按钮)的事件处理函数
delBtn: function () {
// 如果当前是计算结果状态,则重置计算器
if (this.resultFlag) {
return this.resetBtn()
}
// 非计算结果状态,删除当前数字中最右边的一个字符
var num = this.data.num.substr(0, this.data.num.length - 1)
calc.setNum(num === '' || num === '-' || num === '-0.' ? '0' : num)
this.setData({
num: calc.getNum()
})
},
// “+/-”按钮(正负切换按钮)的事件处理函数
negBtn: function () {
// 如果是0,不加正负号
if (this.data.num === '0' || this.data.num === '0.') {
return
}
// 如果当前是计算结果状态,则重置计算器
if (this.resultFlag) {
this.resetBtn()
} else if (this.data.num.indexOf('-') < 0) {
// 当前没有负号,加负号
calc.setNum('-' + this.data.num)
} else {
// 当前有负号,去掉负号
calc.setNum(this.data.num.substr(1))
}
this.setData({
num: calc.getNum()
})
}
})
- 页面实现效果
3、案例:美食列表
(1)案例分析
- 需求:“美食列表”微信小程序是一个展示美食名称、美食图片及美食商家的电话、地址和营业时间等信息的微信小程序。
- 美食列表包含多条美食信息
- 每条美食信息左侧为美食图片
- 右侧为美食详细信息,包括美食名称、电话、地址和营业时间。
- 该页面具有上拉触底加载数据和下拉刷新两个功能
- 用户上拉美食列表页时,如果页面即将到达底部,会自动加载更多数据;
- 用户下拉页面时,如果到达顶部后进行下拉操作,可以刷新页面。
(2)知识储备-列表渲染
-
列表渲染
- 为了方便用户查找美食信息,微信小程序的页面通常以列表的形式展示美食信息。
- 在实际开发中,通常将列表数据保存为数组或对象,然后在页面中通过列表渲染的方式输出数据。
-
列表渲染语法
- 列表渲染通过wx:for控制属性来实现。微信小程序进行列表渲染时,会根据列表中数据的数量渲染相应数量的内容。
- 在wx:for控制属性所在标签的内部
- 使用item变量获取当前项的值
- 使用index变量获取当前项的数组索引或对象属性名。
- 如果不想使用item和index这两个变量名,还可以通过wx:for-item控制属性更改item的变量名;通过wx:for-index控制属性更改index的变量名。
- wx:for控制属性通常搭配wx:key控制属性使用
- wx:key控制属性用于为每一项设置唯一标识,这样可以在数据改变后页面重新渲染时,使原有组件保持自身的状态。
- 在设置wx:key的值时,如果item本身就是一个具有唯一性的字符串或数字,则可以将wx:key的值设置为
*this
,*this
表示item本身。
-
代码案例演示
-
在pages/index/index.js文件的Page({})中编写页面数据
-
data: { arr: [ 'a', 'b', 'c'] }
-
-
在pages/index/index.wxml文件中编写页面结构,通过列表渲染的方式将arr数组渲染到页面中
-
<view wx:for="{{ arr }}" wx:key="*this"> {{ index }} {{ item }} </view>
-
-
-
代码案例演示:对象数组
-
在pages/index/index.js文件的Page({})中编写页面数据
-
data: { list: [ { message: '梅' , id: 1 }, { message: '兰' , id: 2 }, { message: '竹' , id: 3 }, { message: '菊' , id: 4 } ] }
-
-
在pages/index/index.wxml文件中编写页面结构,将list数组中的数据在页面中显示出来
-
<view wx:for="{{ list }}" wx:key="id"> {{ index }}-----{{ item.message }}======={{ item.id }} </view>
-
-
-
代码案例演示:通过wx:for-item、wx:for-index更改item和index的变量名
-
<view wx:for="{{ list }}" wx:for-item="item2" wx:for-index="index2" wx:key="id"> {{ index2 }}:{{ item2.message }} </view>
-
(2)知识储备-网络请求
-
网络请求
- 客户端与服务器进行交互时,客户端请求服务器的过程称为网络请求。
- 例如,获取用户的头像信息,需要客户端向服务器发送请求,服务器查询到数据后把数据传递给客户端。
- 在微信小程序中实现网络请求时,需要服务器给微信小程序提供服务器接口。
-
微信对接口的安全限制
- ①只能请求HTTPS协议的服务器接口。
- ②必须登录微信小程序管理后台,将服务器接口的域名添加到信任列表中。
- 当服务器接口不满足以上两个条件时,可以在微信开发者工具的本地设置中勾选“不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书”复选框,跳过对服务器接口的校验。但是此做法仅限在开发与调试阶段使用。
-
网络请求语法
- 在微信小程序中发起网络请求可以通过调用wx.request()方法来实现。
- wx.request()方法的常见选项如下
- method选项的合法值包括OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE和CONNECT
-
网络请求代码演示
-
每个wx.request()方法都是一个请求任务,可以通过abort()方法将其取消
-
// 发起网络请求 var requestTask = wx.request({ url: 'URL地址', // wx.request()的常见参数…… }) // 取消请求任务 requestTask.abort()
-
-
通过wx.request()方法发起一个GET方式的请求
-
wx.request({ url: 'URL地址', method: 'GET', data: { name: 'zs' }, success: res => { console.log(res) } })
-
-
(2)知识储备-提示框
-
wx.showLoading()方法
- wx.showLoading()方法用于弹出加载提示框,加载提示框弹出后,不会自动关闭,需要手动调用wx.hideLoading()方法才能关闭载提示框。
- wx.showLoading()方法的常用选项如下
- 代码演示如下
wx.showLoading({ title: '加载中', }) setTimeout(function () { wx.hideLoading() }, 2000)
-
wx.showToast()方法
- wx.showToast()方法用于显示消息提示框,该方法的常用选项如下
- icon选项的合法值包括success(成功图标)、error(失败图标)、loading(加载图标)和none(无图标)。
- 当icon的值为success、error、loading时,title选项中文本最多显示7个汉字长度;
- 当icon的值为none时,title选项中文本最多可显示两行。
- icon选项的合法值包括success(成功图标)、error(失败图标)、loading(加载图标)和none(无图标)。
- 代码演示如下
wx.showToast({ title: '成功', icon: 'success', duration: 2000 })
- wx.showToast()方法用于显示消息提示框,该方法的常用选项如下
(2)知识储备-WXS
-
WXS
- WXS(WeiXin Script)是微信小程序独有的一套脚本语言,可以结合WXML构建出页面结构。
- WXS的典型应用场景是“过滤器”,所谓的过滤器是指在渲染数据之前,对数据进行处理,处理结果最终会显示在页面上。
-
WXS的4个特点
- WXS与JavaScript不同
- ①WXS有8种数据类型,包括number(数值)、string(字符串)、boolean(布尔)、object(对象)、function(函数)、array(数组)、date(日期)、regexp(正则)。
- ②WXS不支持let、const、解构赋值、展开运算符、箭头函数、对象属性简写等语法,WXS支持var定义变量、普通function函数等语法。
- ③WXS遵循CommonJS规范。在每个模块内部,module变量代表当前模块,这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。在使用require()函数引用其他模块时,得到的是被引用模块中module.exports所指的对象。
- WXS不能作为组件的事件回调:WXS经常与Mustache语法配合使用,但是在WXS中定义的函数不能作为组件的事件回调函数。
- 具有隔离性:隔离性是指WXS代码的运行环境和其他JavaScript代码是隔离的,体现在以下两个方面。
- ①在WXS代码中不能调用页面的JS文件定义的函数。
- ②在WXS代码中不能调用微信小程序提供的API。
- 在iOS设备上效率高:在iOS设备上,微信小程序内WXS代码的执行速度比JavaScript代码快2~20倍;在Android设备上,两者的运行效率无差异。
- WXS与JavaScript不同
-
WXS语法
- WXS代码可以写在页面的WXML文件的
<wxs>
标签内(内嵌WXS脚本)。 - WXS代码可以写在以.wxs为后缀名的文件中(外联WXS脚本)。
- 每一个.wxs文件和
<wxs>
标签均为一个单独的模块,有自己独立的作用域,即在一个模块内定义的变量和函数默认为私有的,对其他模块不可见。 - 在页面的WXML文件中使用
<wxs>
双标签语法时,必须提供module属性,用于指定当前WXS的模块名称,以便于在WXML中访问模块中的成员;当<wxs>
标签为单闭合标签或标签中内容为空时需提供src属性,src属性的属性值为引用的.wxs文件的相对路径。
- WXS代码可以写在页面的WXML文件的
-
**WXS代码演示-内嵌WXS脚本:**在pages/index/index.wxml文件中编写页面结构并内嵌WXS脚本
<wxs module="m1">
function toUpper(str) {
return str.toUpperCase()
}
module.exports = {
toUpper: toUpper
}
</wxs>
<view>
{{ m1.toUpper('weixin') }}
</view>
-
WXS代码演示-外联WXS脚本:
- 通常情况下,将以.wxs为后缀名的文件存放在utils目录下,该目录用于存放工具类函数或公共模块。首先在utils/tool.wxs文件中编写外链WXS脚本。
var msg = 'hello world' module.exports = { message: msg }
- 在pages/index/index.wxml文件中编写页面结构。
<wxs module="m2" src="../../utils/tool.wxs"></wxs> <view> {{ m2.message }} </view>
(2)知识储备-上拉触底
-
上拉触底
- 在原生应用或者网页的交互中,经常会有上拉加载这个功能。用户在浏览列表页面时,手指在手机屏幕上进行上拉滑动操作,通过上拉加载请求数据,增加列表数据。
- 微信小程序提供了onReachBottom()事件处理函数,即页面上拉触底事件处理函数,用于监听当前页面的上拉触底事件。
- onReachBottom()事件处理函数的示例代码如下
onReachBottom: function () { console.log('触发了上拉触底的事件') },
-
上拉触底默认距离
-
在默认情况下,触发上拉触底事件时,滚动条距离页面底部的距离为50px,即上拉触底距离为50px。
-
在实际开发中,开发人员可以根据实际需求,在全局或页面的JSON配置文件中,通过onReachBottomDistance属性修改上拉触底的距离。
-
(2)知识储备-下拉刷新
-
下拉刷新
-
在原生应用的交互中,经常会有下拉刷新操作,即当用户下拉页面到达顶部时,再进行下拉可以将数据重新加载。
-
在微信小程序中,也可以实现下拉刷新的效果。启用下拉刷新有2种方式。
- 全局开启下拉刷新:在app.json文件的window节点中,将enablePullDownRefresh设置为true。
- 局部开启下拉刷新:在页面的JSON文件中,将enablePullDownRefresh设置为true。
-
开启下拉刷新后,当下拉操作执行时,就会触发onPullDownRefresh()事件处理函数。
onPullDownRefresh: function () { console.log('触发了下拉刷新的事件') }
-
-
加载提示弹回时机设定
- 当执行了下拉刷新操作后,页面顶部会出现加载提示,并且页面需要延迟一段时间才会弹回去。
- 为了优化用户体验,可以在完成下拉刷新的数据加载后,立即调用wx.stopPullDownRefresh()方法停止使用当前页面的下拉刷新加载效果。
wx.stopPullDownRefresh()
(3)案例实现
-
准备工作
- ①创建项目。在微信开发者工具中创建一个新的微信小程序项目,项目名称为“美食列表”,模板选择“不使用模板”。
- ②配置页面。项目创建完成后,在app.json文件中配置一个shoplist页。
"pages": [ "pages/shoplist/shoplist" ],
- ③配置导航栏。在pages/shoplist/shoplist.json文件中配置页面导航栏。
- ④配置页面样式。在pages/shoplist/shoplist.wxss文件中配置页面样式。
- ⑤启动服务器。切换工作目录到nodejs服务程序目录,打开命令提示符,然后在命令提示符中执行如下命令,启动服务器。
node index.js
-
**获取初始数据:**在pages/shoplist/shoplist.js文件的Page({})中编写页面逻辑。
// pages/shoplist/shoplist.js
Page({
data: {
shopList: [], // 保存美食列表信息
},
listData: {
page: 1, // 默认请求第1页的数据
pageSize: 10, // 默认每页请求10条数据
total: 0 // 数据总数,默认为0
},
isLoading: false, // 当前是否正在加载数据
getShopList: function (cb) {
this.isLoading = true
// 请求数据之前,展示加载效果,接口调用结束后,停止加载效果
wx.showLoading({
title: '数据加载中...'
})
wx.request({
url: 'http://127.0.0.1:3000/data',
method: 'GET',
data: {
page: this.listData.page,
pageSize: this.listData.pageSize
},
success: res => {
console.log(res)
this.setData({
shopList: [...this.data.shopList, ...res.data],
})
this.listData.total = res.header['X-Total-Count'] - 0
},
complete: () => {
// 隐藏加载效果
wx.hideLoading()
this.isLoading = false
cb && cb()
}
})
},
onLoad() {
this.getShopList()
},
onReachBottom: function () {
if (this.listData.page * this.listData.pageSize >= this.listData.total) {
// 没有下一页的数据了
return wx.showToast({
title: '数据加载完毕!',
icon: 'none'
})
}
if (this.isLoading) {
return
}
// 页码自增
++this.listData.page
// 请求下一页数据
this.getShopList()
},
onPullDownRefresh: function () {
// 需要重置的数据
this.setData({
shopList: []
})
this.listData.page = 1
this.listData.total = 0
// 重新发起数据请求
this.getShopList(() => {
wx.stopPullDownRefresh()
})
}
})
- **实现页面渲染:**在pages/shoplist/shoplist.wxml文件中进行页面渲染。
<!--pages/shoplist/shoplist.wxml-->
<wxs src="../../utils/tools.wxs" module="tools"></wxs>
<view class="shop-item" wx:for="{{ shopList }}" wx:key="id">
<view class="thumb">
<image src="{{ item.image }}"></image>
</view>
<view class="info">
<text class="shop-title">{{ item.name }}</text>
<text>电话:{{ tools.splitPhone(item.phone) }}</text>
<text>地址:{{ item.address }}</text>
<text>营业时间:{{ item.businessHours }}</text>
</view>
</view>
- **处理电话格式:**在项目根目录下创建utils文件夹,将处理电话函数封装到utils/tools.wxs文件中。
function splitPhone(str) {
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
}
- **设置上拉触底距离和下拉刷新及样式:**在pages/shoplist/shoplist.json文件中配置上拉触底的距离为200px。
{
"navigationBarTitleText": "美食",
"onReachBottomDistance": 200,
"enablePullDownRefresh": true,
"backgroundColor": "#efefef",
"backgroundTextStyle": "dark"
}
- 页面实现效果
4、案例:问卷调查
(1)案例分析
- 需求:调查问卷又称调查表或询问表,是以问题的形式系统地记载调查内容的一种印件。传统的调查问卷是纸质的,发布和收集都不太方便,而通过微信小程序制作调查问卷,可以在短时间内快速收集反馈信息,相比纸质调查问卷极大地提高了效率。假设有一位大学老师,想通过调查问卷来了解同学们的专业技能、对开设公开课的意见等信息,从而根据同学们的建议制订下一步的教学计划。
- 调查问卷需要填写的信息包括姓名、性别、专业技能和您的意见。
- “姓名”通过单行输入框填写。
- “性别”通过单选框选择。
- “专业技能”通过多选框选择。
- “您的意见”通过多行输入框填写。
- 页面底部的“提交”按钮用于将用户输入的信息提交。
(2)知识储备-双向数据绑定
-
单向数据绑定
- 普通属性的绑定都是单向的,如果使用this.setData({ value:‘leaf’ })来更新value,则this.data.value和输入框中显示的值都会被更新为leaf
<input value="{{ value }}" />
- 但是如果用户在页面中修改了输入框里的值,则this.data.value的值不会发生改变。
-
双向数据绑定:
- 双向数据绑定的实现方式是在对应属性之前添加model:前缀。
<input model:value="{{ value }}" />
- 如果输入框的值被更改了,this.data.value也会随之更改。同时,页面的WXML文件中所有绑定了value的位置也会被一同更新,数据监听器也会被正常触发。
(3)案例实现
-
准备工作
- ①创建项目。在微信开发者工具中创建一个新的微信小程序项目,项目名称为“调查问卷”,模板选择“不使用模板”。
- ②配置页面。项目创建完成后,在app.json文件中配置1个form页面。
"pages": [ "pages/form/form" ],
- ③配置导航栏。在pages/form/form.json文件中配置页面导航栏。
- ④配置页面样式。在pages/form/form.wxss文件中配置页面样式。
- ⑤启动服务器。切换工作目录到nodejs服务程序目录,打开命令提示符,然后在命令提示符中执行如下命令,启动服务器。
node index.js
-
**获取初始数据:**在pages/form/form.js文件的onLoad()事件处理函数中实现页面加载完成时自动向服务器发送请求,获取表单中的初始数据。
// pages/form/form.js
Page({
data: {
},
onLoad: function () {
wx.showLoading({
title: '数据加载中'
})
wx.request({
url: 'http://127.0.0.1:3000/',
success: res => {
// statusCode为HTTP状态码,200表示网络请求成功,数据获取成功
if (res.statusCode === 200) {
this.setData(res.data)
console.log(res.data)
} else {
wx.showModal({
title: '服务器异常'
})
}
setTimeout(() => {
wx.hideLoading()
}, 500)
},
fail: function () {
wx.hideLoading()
wx.showModal({
title: '网络异常,无法请求服务器'
})
},
})
},
radioChange: function (e) {
var val = e.detail.value
this.data.gender.forEach((v) => {
v.value === val ? v.checked = true : v.checked = false
})
},
checkboxChange: function (e) {
var val = e.detail.value
this.data.skills.forEach((v) => {
val.includes(v.value) ? v.checked = true : v.checked = false
})
},
submit: function () {
wx.request({
url: 'http://127.0.0.1:3000',
method: 'POST',
data: this.data,
success: res => {
wx.showModal({
title: '提交完成',
showCancel: false
})
}
})
}
})
- **实现页面渲染:**在pages/form/form.wxml文件中编写内容区域的整体结构。
<!--pages/form/form.wxml-->
<view class="container">
<!-- 姓名区域 -->
<view>
<text>姓名:</text>
<input type="text" model:value="{{ name }}" />
</view>
<!-- 性别区域 -->
<view>
<text>性别:</text>
<radio-group bindchange="radioChange">
<label wx:for="{{ gender }}" wx:key="value">
<radio value="{{ item.value }}" checked="{{ item.checked }}" />
{{ item.name }}
</label>
</radio-group>
</view>
<!-- 专业技能区域 -->
<view>
<text>专业技能:</text>
<checkbox-group bindchange="checkboxChange">
<label wx:for="{{ skills }}" wx:key="value">
<checkbox value="{{ item.value }}" checked="{{ item.checked }}" />
{{ item.name }}
</label>
</checkbox-group>
</view>
<!-- 意见区域 -->
<view>
<text>您的意见:</text>
<textarea model:value="{{ opinion }}" />
</view>
<button type="primary" bindtap="submit">提交</button>
</view>
- 页面实现效果