自学内容网 自学内容网

微信小程序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'
}, 
  • **页面生命周期回调函数:**在微信小程序中,页面的生命周期是指每个页面“加载→渲染→销毁”的过程,每个页面都有生命周期。如果想要在某个特定的时机进行特定的处理,则可以通过页面生命周期回调函数来完成。

image-20240909104604518

  • **页面事件处理函数:**在微信小程序中,用户可能会在页面上进行一些操作,例如上拉、下拉、滚动页面等,这些可以通过页面事件处理函数来完成。
    • 使用onPullDownRefresh()函数前,需要在app.json配置文件中将enablePullDownRefresh配置项设为true。

image-20240909110116637

(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)知识储备-事件绑定
  • **事件绑定:**事件是视图层到逻辑层的通信方式,通过给组件绑定事件,可以监听用户的操作行为,然后在对应的事件处理函数中进行相应的业务处理。例如,为页面中的按钮绑定事件,当用户点击按钮时,就触发了事件。
  • 常见的事件如下,分为两类
    • **冒泡事件:**当一个组件上的事件被触发后,该事件会向父组件传递,比如点击事件、长按事件、触摸事件。
    • **非冒泡事件:**当一个组件上的事件被触发后,该事件不会向父组件传递,比如其他事件。

image-20240910105924434

  • 事件绑定实现

    • 可以通过为组件添加“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:**获取当前组件的一些属性值集合。

image-20240910120150849

  • 事件对象代码案例

    • 修改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])

image-20240910131113983

  • 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文件中配置本项目的页面样式。

    image-20240910133752969

  • **实现页面结构:**在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
    })
  }
})
  • 页面实现效果

image-20240910134136623

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()
    })
  }
})
  • 页面实现效果

image-20240910145157233

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

    image-20240910153417687

    image-20240910153433681

  • 网络请求代码演示

    • 每个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()方法的常用选项如下

    image-20240910154355623

    image-20240910154417434

    • 代码演示如下
      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选项中文本最多可显示两行。

    image-20240910154623895

    • 代码演示如下
    wx.showToast({
      title: '成功',
      icon: 'success',
      duration: 2000
    }) 
    
(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语法

    • WXS代码可以写在页面的WXML文件的<wxs>标签内(内嵌WXS脚本)。
    • WXS代码可以写在以.wxs为后缀名的文件中(外联WXS脚本)。
    • 每一个.wxs文件和<wxs>标签均为一个单独的模块,有自己独立的作用域,即在一个模块内定义的变量和函数默认为私有的,对其他模块不可见。
    • 在页面的WXML文件中使用<wxs>双标签语法时,必须提供module属性,用于指定当前WXS的模块名称,以便于在WXML中访问模块中的成员;当<wxs>标签为单闭合标签或标签中内容为空时需提供src属性,src属性的属性值为引用的.wxs文件的相对路径。
  • **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
    

    image-20240911095215275

  • **获取初始数据:**在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"
}
  • 页面实现效果

image-20240911111928548

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
    

    image-20240911134812392

  • **获取初始数据:**在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>
  • 页面实现效果

image-20240911135134297


原文地址:https://blog.csdn.net/liyou123456789/article/details/142356838

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!