微信小程序入门练习-过程条

606 阅读9分钟

最近学习了微信小程序开发,创建了一个过程条组件作为入门小练习。 实现的效果如下:

实现思路

首先捋一下怎样实现。 如图,将实现内容按图中蓝线从上到下分为三大部分,过程说明部分(A),代表步骤及过程的线(B),以及下面的步骤说明部分(C)。每个大的部分由多个相同的小组件组成。将A往下移到合适的位置就能实现一个简单的过程条了。

  • 组成A部分的每个小组件(a)包含说明文本和小圆点。
  • 组成B部分的每个小组件(b)包含左线条,右线条,中间的圆点以及圆点中的数字。
  • 组成C部分的每个小组件只是简单的文本。
  • 我设置a、b、c小组件的宽度都是固定的170rpx,这样便于A、B、C的对齐(目前没有考虑小组件宽度不固定的情况)。
  • 通过传入的属性来控制每个小组件的样式。
  • 通过一个整体的状态值来控制整个组件的展示情况。例如状态值为1的时候就是走完第一步没有走完第二步,组件应该展示为本文第一幅图的那样。

rpx是微信小程序支持的尺寸单位,使用这个单位就不用我们自己做屏幕自适应了:

rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。

开发过程

创建项目

打开开发者工具,使用测试号创建一个项目:

创建组件

在根目录下,创建一个components文件夹,components文件夹中有steps组件,steps组件包含代表a组件(是上文的A的子组件)和b组件(是上文B的子组件)两个文件夹。创建好之后文件结构如下:

processDescription文件夹和step文件夹中也包含了相应的四个文件。在js文件中用Component定义好组件,在json文件中设置component属性值为true。在wxml文件中添加组件内容。在index页面引入组件。

项目代码

添加完内容后各个文件的代码如下:

首页代码

index.json

{
  "usingComponents": {
    "steps": "/components/steps/steps"
  }
}

在json文件中通过usingComponents属性引入过程条组件。

index.wxml

<view class="container">
  <steps state="{{stepsState}}"/>
</view>

在wxml中引入组件,设置组件的state属性,用于从父组件传值到子组件。在steps组件中定义组件的properties属性中的state属性,就能获取到从父组件传来的值。可以看下文steps.js中的代码。

index.js

Page({
  data: {
    // 状态,0表示没开始,1表示走完第一步,没走完第二步,2表示走完第二步,没走完第三步,依此类推
    stepsState: 3
  }
})

在js文件中通过Page注册页面,并定义过程条的状态值。这个状态值会控制整个进度条展示成什么样子。

过程条组件代码

steps.json

{
  "component": true,
  "usingComponents": {
    "process-description": "./components/processDescription/processDescription",
    "step-component": "./components/step/step"
  }
}

通过设置component属性为true表明当前模块为一个组件,通过usingComponents定义当前模块引用的子组件。

steps.wxml

<view>
  <view class="description">
    <block wx:for="{{description}}" wx:key="index" wx:for-item="item">
      <process-description hide="{{item.hide}}" text="{{item.text}}"/>
    </block>
  </view>
  <view class="steps-line">
    <block wx:for="{{steps}}" wx:key="index" wx:for-item="item" >
      <step-component type="{{item.type}}" state="{{item.state}}" stepText="{{item.text}}"/>
    </block>
  </view>
  <view class="steps-names">
    <view class="step-name" wx:for="{{stepsNames}}" wx:for-item="item" wx:key="index">{{item}}</view>
  </view>
</view>

整个过程条组件按照之前的思路分为了三大部分,每个部分的展示通过相应的数组数据渲染。这个过程中用到了wx:for,wx:key和wx:for-item。wx:for里面是一个数组,代表用于渲染列表的数组数据。wx:key是用于提高渲染效率的,

当数据改变触发渲染层重新渲染的时候,会校正带有 key 的组件,框架会确保他们被重新排序,而不是重新创建,以确保使组件保持自身的状态,并且提高列表渲染时的效率。

wx:for-item按照目前的写法来说是不需要的,因为微信小程序默认用item代表数组的元素,如果要使用item以外的值,就必须要用wx:for-item,例如:

<block wx:for="{{description}}" wx:key="index" wx:for-item="item1">
  <process-description hide="{{item1.hide}}" text="{{item1.text}}"/>
</block>

block不是一个像view那样的组件,他只是用来包装内容的,不会在页面中渲染。

这部分的知识点: 微信小程序列表渲染

steps.js

Component({
  properties: {
    state: Number
  },
  data: {
    description: [],
    steps: [],
    stepsNames: ['测试测试测试1', '2', '3']
  },
  lifetimes: {
    attached: function () {
      this.diffStateData()
    }
  },
  methods: {
    getStepType: function (index, stepsData) {
      let lastIndex = stepsData.length - 1
      switch (true) {
        case index === 0:
          return 0
        case index === lastIndex:
          return 2
        default:
          return 1
      }
    },
    initStepsData: function (stepsData, stateArr) {
      let arr = stepsData.map((item, index) => {
        let type = this.getStepType(index, stepsData)
        return {
          type,
          state: stateArr[index],
          text: item
        }
      })
      this.setData({
        steps: arr
      })
    },
    initDescriptionData: function (description, hideArr) {
      let descArr = description.map((item, index) => {
        return {
          hide: hideArr[index],
          text: item
        }
      })
      this.setData({
        description: descArr
      })
    },
    // 根据不同的状态使用不同的数据,目前假设有三个步骤
    diffStateData: function () {
      let stepsData = [1, 2, 3]
      let description = ['过程1内容比较长的时候呢过程1内容比较长的时候呢', '过程2']
      let state = this.properties.state
      let stateArr, 
          hideArr
      switch (state) {
        case 0: 
          stateArr = [0, 0, 0]
          hideArr = [true, true]
          break
        case 1: 
          stateArr = [2, 0, 0]
          hideArr = [false, true]
          break
        case 2:
          stateArr = [2, 2, 0]
          hideArr = [false, false]
          break
        case 3:
          stateArr = [2, 2, 2]
          hideArr = [false, false]
          break
      }
      this.initStepsData(stepsData, stateArr)
      this.initDescriptionData(description, hideArr)
    }
  }
})
  • 首先使用Component定义组件。
  • properties用于获取组件的属性值。
  • data用于定义组件的数据。
  • lifetimes是组件的生命周期。当组件已经初始化完毕后,就会触发attached生命周期函数,会执行里面的代码,也就是会调用diffStateData函数。
  • methods里面定义组件需要使用到的函数。
  • 函数说明:
    • diffStateData函数,用于初始化过程条的数据。首先可以自定义步骤的数据和描述的数据,再根据从index页面传来的属性值this.properties.state,设置好子组件的状态数组。
    • initStepsData函数,根据数据数组和状态数组,生成步骤线条实际用到的数据。
    • initDescriptionData函数,根据数据数组和状态数组,生成过程说明部分实际要用到的数据。
    • getStepType函数,用于判断组成步骤线条的小组件是第一个(只有右线条)、最后一个(只有左线条)、或者是中间部分(有左右线条)。

steps.wxss

.steps-line {
  display: flex;
  position: relative;
  flex-direction: row;
  justify-content: center;
  z-index: 1;
}
.description {
  display: flex;
  position: relative;
  flex-direction: row;
  align-items: stretch;
  justify-content: center;
  margin-bottom: -34rpx;
  z-index: 10;
  min-height: 120rpx;
}
.steps-names {
  display: flex;
  flex-direction: row;
  justify-content: center;
  margin-top: 20rpx;
}
.step-name {
  width: 170rpx;
  overflow: hidden;
  font-size: 26rpx;
  text-align: center;
}

下面部分为组成过程条的两个小组件的代码:

(1)过程描述组件

processDescription.json

{
  "component": true
}

processDescription.wxml

<view class="{{hide ? 'hide-description description': 'description'}}">
  <view class="content">
    <text class="description-text">{{text}}</text>
  </view>
  <view class="triangle"></view>
  <view class="dot"></view>
</view>

wxml使用{{}}来进行数据绑定,里面的内容可以是一个表达式,所以可以使用?:来进行判断。

processDescription.js

Component({
  properties: {
    /**
     * hide表示是否隐藏描述标签
     */
    hide: {
      type: Boolean,
      value: true
    },
    /**
     * text表示描述文本
     */
    text: {
      type: String,
      value: '测试'
    }
  }
})

过程描述小组件接受两个属性值,hide和text,type表示数据类型,value表示默认值。

processDescription.wxss

.description {
  display: flex;
  justify-content: center;
  flex-direction: column;
  align-self: stretch;
  height: 100%;
  width: 170rpx;
  box-sizing: border-box;
  padding: 0 10rpx;
}
.content {
  background-color: green;
  color: #fff;
  font-size: 28rpx;
  width: 100%;
  border-radius: 4rpx;
  flex: 1;
  padding: 6rpx;
}
.triangle {
  align-self: center;
  margin-top: -10rpx;
  width: 0;
  height: 0;
  border: 10rpx solid green;
  border-color: transparent green green transparent;
  transform: rotate(45deg);
}
.dot {
  width: 16rpx;
  height: 16rpx;
  border-radius: 50%;
  background-color: green;
  margin-top: 30rpx;
  align-self: center;
}
.description-text {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}
.hide-description {
  visibility: hidden;
}

(2)步骤组件 step.json

{
  "component": true
}

step.wxml

<view class="step {{stateClass}}">
  <view class="left-line" wx:if="{{type!==0}}"></view>
  <view class="center">
    <text class="center-text">{{stepText}}</text>
  </view>
  <view class="right-line" wx:if="{{type!==2}}"></view>
</view>

可以使用wx:if和hidden来控制内容的显示隐藏。一般来说当需要频繁切换显示隐藏的时候用hidden,只判断一次就确定展示内容时用v-if。这里不需要频繁地切换,因为传入小组件的数据是在一开始就确定,所以这里我使用了v-if。

因为 wx:if 之中的模板也可能包含数据绑定,所以当 wx:if 的条件值切换时,框架有一个局部渲染的过程,因为它会确保条件块在切换时销毁或重新渲染。 同时 wx:if 也是惰性的,如果在初始渲染条件为 false,框架什么也不做,在条件第一次变成真的时候才开始局部渲染。 相比之下,hidden 就简单的多,组件始终会被渲染,只是简单的控制显示与隐藏。

一般来说,wx:if 有更高的切换消耗而 hidden 有更高的初始渲染消耗。因此,如果需要频繁切换的情景下,用 hidden 更好,如果在运行时条件不大可能改变则 wx:if 较好。

举个例子说明hidden的使用方式:

<view class="left-line" hidden="{{type===0}}"></view>

step.js

Component({
  properties: {
    /**
     * type有三个值
     * 0 表示是第一个步骤(只有右边的线)
     * 1 表示是中间步骤(有左右两边的线)
     * 2 表示是最后一个步骤(只有左边的步骤)
     */
    type: {
      type: Number,
      value: 1
    },
    /**
     * state有三个值
     * 0 表示左右的线都为灰色
     * 1 表示左边的线为完成态的颜色,右边的线为灰色
     * 2 表示左边的线和右边的线都为完成态的颜色
     */
    state: {
      type: Number,
      value: 0    
    },
    stepText: {
      type: String,
      value: '1'
    }
  },
  data: {
    stateClass: ''
  },
  lifetimes: {
    attached: function () {
      let state = this.properties.state
      let stateClass
      switch (true) {
        case state === 1: 
          stateClass = 'state1'
          break
        case state === 2:
          stateClass = 'state2'
          break
        default:
          stateClass = ''
      }
      this.setData({
        stateClass
      })
    }
  }
})

通过属性获取到type和state如代码所示,根据属性值的不同设置组件的展示。

step.wxss

.step {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  align-items: center;
}
.state1 .left-line {
  background-color: green;
}
.state2 .left-line, 
.state2 .right-line {
  background-color: green;
}
.state2 .center {
  background-color: green;
}
.left-line {
  width: 50rpx;
  height: 5rpx;
  background-color: #999;
}
.right-line {
  width: 50rpx;
  height: 5rpx;
  background-color: #999;
}
.center {
  display: flex;
  width: 50rpx;
  height: 50rpx;
  border-radius: 50%;
  color: #fff;
  background-color: #999;
  margin: 0 10rpx;
}
.center-text {
  margin: auto;
}

其他

以上就是整个组件的实现方法了,还有很多可以进一步完善的地方,比如样式的完善,比如当过程条很长,一行无法展示全时的处理。

源码地址,可以使用DownGit下载git仓库中某文件夹下的代码。