微信小程序|开发实战篇之七-steps进度条组件

前言

实战篇内容参考:
1、Lin Ui开源组件源码分析。https://doc.mini.talelin.com/
2、开发过程遇到问题。

0、知识点补充

0.1 $emit()函数

// 点击遮罩层关闭popup
   onClose() {
     // 父组件通知close事件
     this.$emit('close');
   }

0.2 wxs

1、每一个.wxs文件和<wxs>标签都是一个单独的模块。
2、每个模块都有自己独立的作用域。即在一个模块里面定义的变量与函数,默认为私有的,对其他模块不可见。
3、一个模块要想对外暴露其内部的私有变量与函数,只能通过module.exports实现。

  • ⚠️即使将wxs写在wxml中,使用时仍需要使用内置的module对象exports
  • wxs可以写在<wxs></wxs>包裹中和.wxs文件中。
<!-- 导出状态可以被使用 -->
module.exports = {
  setStatus:setStatus,
  statusStyle:statusStyle,
  dotStyle:dotStyle,
  isReverse:isReverse
}

0.3 ⚡组件间关系

详情请参照微信小程序官方文档—组件间关系:
组件间关系详解

⚠️组件间关系可以通过data-传递数据,但是要进行更复杂的联系,就需要使用组件的relations属性。例如下面的steps进度条组件中的子组件step进度条元素。

<x-steps direction="column" step-min-height="160">
  <x-step title="已支付" describe="11:30"></x-step>
  <x-step title="备餐中" describe="12:30"></x-step>
  <x-step title="已出餐" describe="13:30"></x-step>
</x-steps>

使用relations属性解决stepsstep之间的关系:

选项类型是否必填描述
typeString目标组件的相对关系,可选值为parent、child、ancestor、descendant
linkedFunction关系生命周期函数,当关系被建立在页面节点树中时触发,触发时机在组件attached生命周期之后
linkChangedFunction关系生命周期函数,当关系在页面节点树中时发生改变触发,触发时机在组件moved生命周期之后
unlinkedFunction关系生命周期函数,当关系脱离页面节点树中时触发,触发时机在组件detached生命周期之后
// steps.js父组件
relations: {
 '../step/step': {
    type: 'child', // 关联的目标节点为子节点
    linked(target) { // 每次有step被插入时执行,target是子节点实例对象,this还是父组件实例对象,触发在该节点attached之后。
      this._initSteps(); // 调用初始化steps数据的方法。
    },
    unlinked(target) { // 每次有step被移除时执行,触发在该节点detached之后。
      this._initSteps();
    }
  },
},

...
observers: {
   // 手动控制进度条的变化,需要更新每个step的状态,这就需要监听activeIndex的变化。
  'activeIndex': function () {
    this._initSteps();
  }
},

/**
* 组件的方法列表
 */
methods: {
  _initSteps() {
  	// 1、获取自定义组件的实例对象
    const query = wx.createSelectorQuery().in(this);
    // 2、找到steps组件执行{...}中方法
    query.select('.steps-container').boundingClientRect().exec(res => {	
      // 3、获取该steps下所有得到子节点step
      let steps = this.getRelationNodes('../step/step');
      this.data.length = steps.length;
      if (this.data.length > 0) {
         // 4、调用子组件step中更新数据方法updateDataChange()
         
        // index为当前的step,不一定是active的step
        steps.forEach((step, index) => {

          // console.log(steps.length); // 3
          // console.log(res[0].width); // 375
          // console.log(this.data.status);

          step.updateDataChange({
            // 当前index会改变
            index,
            ...this.data,
            stepsWidth: res[0].width
          });
        });
      }
    });
  }
}
// step.js子组件
relations: {
 // 绑定与steps的关系
  '../steps/steps': {
    type: 'parent'
  }
},
/**
 * 组件的方法列表
 */
methods: {
  // 与父组件通信并绑定相关配置数据
  updateDataChange(options) {
    this.setData({
      ...options
    });
  }
}

1、steps进度条组件

在这里插入图片描述

总体分析steps的编写思路:

  • 需求:进度条是由多个进度单元组成(个数应该由用户定制);并且每个进度单元样式应可以自定义;进度应该可以手动控制;数据绑定应该较为简单,尽可能封装样式和属性等,到组件中。
  • 采用整体steps父组件,传入进度条所需的整体大小、当前进度(activeIndex)、当前进度状态statusprocess还是error)、布局方向、进度条节点样式。子组件step主要进行数据绑定、根据父组件设置的布局、当前进度、进度条样式等属性进行设置布局和样式。

对steps父组件和step子组件进行详细分析:

  1. steps组件主要是一个布局steps-container,然后在布局中使用多插槽添加子组件step(⚠️slot非常灵活,可以很好的分割stepsstep,减少stepsstep的耦合度)。
  2. steps组件只用来控制step排列的方向。(flex布局的flex-direction: row-reverse或flex-direction: column-reverse来实现)。当然在step中需要对逆序之后的样式进行调整,具体就不展开说了。
  3. stepsstep的联系使用relations属性关联,数据传递在父组件中使用_initSteps()调用子组件的 updateDataChange()绑定数据。

1.1、step进度条单元的骨架文件wxml

  1. 整体布局:首先根据父组件传入的属性direction指定布局方向。如果是横向,就根据进度条宽度个数,平均分配宽度。纵向就设定最小高度。
  2. 整体基本由三个部分组成:进度条icon、进度条内容、进度条line
  3. 进度条icon可以由custom属性,控制是否自定义设置。如果不需要自定义设置,就按默认样式step-type step-icon,这里对每个step都需要判断当前的状态status,共有“process”、“wait”、“finish”和“error”四种状态,“error”是“process”等价,但由用户设定。

⚠️这里通过Mustache语法判断icon/dot样式,如果是icon是哪一种icon。具体逻辑是“如果设置了dot就按dot的样式,否则就设置成step-icon,空心圆圈的样式。”

{{dot && !icon?'step-dot-'+currentStatus.setStatus(activeIndex,index,status) + ' step-dot':'step-'+currentStatus.setStatus(activeIndex,index,status)+' step-icon'}}

⚠️这里就不得不提到获取状态的方法:setStatus(activeIndex, index, status)
写在<wxs>中的简单判断,如果当前位置index和当前进度位置activeIndex相同,那么,当前状态就是“process/error”。当index大于activeIndex时,那代表已完成进度“finish”,反之小于就是还没轮到处理,就是“wait”。
⚠️下面的逻辑就是有设置icon就按icon,没有就显示对应的序号

  1. 进度条内容title根据direction设定内容方向step-content-direction,然后是titledescribe部分。标题字体颜色,根据是否是activeIndex,置为step-title-processstep-title
  2. 进度条line根据direction设定line的方向step-line-direction,根据是否是最后一个step决定显示与否。
<!--components/step/step.wxml-->
<view class="step {{'step-'+direction}} x-class"
      style="{{direction==='row'?'width:'+(1/length)*stepsWidth+'px':'min-height:'+stepMinHeight+'rpx'}}">
  
  <!-- 进度条icon样式 -->
  <view class="step-container x-step-class {{'step-container-'+direction}}">
    
    <!-- 进度条icon自定义样式 -->
    <view wx:if="{{custom}}" class="step-custom">
      <slot name="dot" />
    </view>
    <!-- 进度条icon样式---进度数字/icon选择 -->
    <view wx:else
          class="x-step-class {{dot && !icon?'step-dot-'+currentStatus.setStatus(activeIndex,index,status) + ' step-dot':'step-'+currentStatus.setStatus(activeIndex,index,status)+' step-icon'}}"
          style="{{dot ? currentStatus.dotStyle(activeIndex,index,color) : currentStatus.statusStyle(activeIndex,index,color,status)}}">
          
      <x-icon wx:if="{{icon}}" name="{{icon}}" size="{{iconSize}}" color="{{(currentStatus.setStatus(activeIndex,index,status)==='process'?'#3963bc':iconColor)}}"/>
      <block wx:if="{{!dot && !icon}}">
        <view wx:if="{{currentStatus.setStatus(activeIndex,index,status)==='error' || currentStatus.setStatus(activeIndex,index,status)==='finish'}}"
              class="iconfont icon-{{currentStatus.setStatus(activeIndex,index,status)}}"></view>
        <block wx:else>{{index+1}}</block>
      </block>
    </view>

  </view>
  
  <!-- 进度条内容title -->
  <view class="step-content {{'step-content-'+direction}}">
    <view class="x-title-class {{activeIndex===index?'step-title-process':'step-title'}}">
      {{title}}
    </view>
    <view class="x-describe-class step-describe">
      {{describe}}
      <slot name="describe"/>
    </view>
  </view>

  <!-- 进度条line -->
  <view class="step-line x-line-class {{'step-line-'+ direction}} {{currentStatus.isReverse(reverse,activeIndex,index)?'step-line-finish':'step-line-wait'}}"
        style="{{activeIndex>index?('background-color:'+color):''}}"
        wx:if="{{reverse?(index!==0):(length!==index+1)}}">
  </view>

</view>

<!-- step status设置 -->
<wxs module="currentStatus">
var setStatus = function(activeIndex,index,status){
  if(activeIndex === index){
    return status || 'process'
  }else if(activeIndex > index){
    return 'finish'
  }else {
    return 'wait'
  }
}


var statusStyle = function(activeIndex,index,color,status) {
  if(activeIndex == index){
    return status=='error'?'':('background-color:' + color)
  }else if(activeIndex > index){
    return ('border-color:' + color + ';color:' + color)
  }else {
    return ''
  }
}


var dotStyle = function(activeIndex,index,color) {
  if(activeIndex>=index){
    return ('background-color:' + color)
  }else {
    return ''
  }
}

var isReverse = function(reverse,activeIndex,index) {
  if(reverse){
    return (activeIndex>=index)
  }
  return (activeIndex>index)
}

module.exports = {
  setStatus:setStatus,
  statusStyle:statusStyle,
  dotStyle:dotStyle,
  isReverse:isReverse
}
</wxs>

1.2、step进度条单元的js

// components/step/step.js
Component({
  /**
   * 组件的属性列表
   */
  externalClasses: [
    'x-class',
    'x-step-class',
    'x-title-class',
    'x-describe-class',
    'x-line-class'
  ],
  options: {
    multipleSlots: true
  },
  relations: {
    // 绑定与steps的关系
    '../steps/steps': {
      type: 'parent'
    }
  },
  properties: {
    icon: String,
    title: String,
    describe: String,
    iconSize: {
      type: Number,
      value: 24
    },
    iconColor: String,
    // 是否自定义步骤条元素step
    custom: Boolean
  },

  /**
   * 组件的初始数据
   */
  data: {

  },

  /**
   * 组件的方法列表
   */
  methods: {
    // 与父组件通信并绑定相关配置数据
    // 父组件调用绑定数据
    updateDataChange(options) {
      // console.log(options.status);
      this.setData({
        ...options
      });
    }
  }
})

1.3、step进度条单元的样式文件wxss

/* components/step/step.wxss */
.step {
  display: flex;
  position: relative
}

.step-custom {
  z-index: 2
}

.step-row {
  flex-direction: column;
  align-items: center
}

.step-column {
  flex-direction: row;
  padding-left: 30rpx;
  box-sizing: border-box
}

.step-container {
  display: flex;
  justify-content: center;
  z-index: 2;
  align-items: center;
  background-color: #fff
}

.step-container-row {
  width: 70rpx;
  height: 40rpx
}

.step-container-column {
  height: 60rpx;
  width: 40rpx
}

.step-icon {
  width: 40rpx;
  height: 40rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22rpx;
  transition: .5s;
  box-sizing: border-box
}

.step-process {
  color: #fff;
  background-color: #3963bc;
  border-radius: 50%
}

.step-error {
  background-color: #fff;
  border: 1px solid #f4516c;
  color: #f4516c;
  border-radius: 50%
}

.step-finish {
  background-color: #fff;
  color: #3963bc;
  border: 1px solid #3963bc;
  border-radius: 50%
}

.step-wait {
  background-color: #fff;
  color: #c4c9d2;
  border: 1px solid #c4c9d2;
  border-radius: 50%
}

.step-dot {
  width: 24rpx;
  height: 24rpx;
  border-radius: 50%;
  transition: .5s
}

.step-dot-process {
  background-color: #3963bc
}

.step-dot-wait {
  background-color: #c4c9d2
}

.step-dot-error {
  background-color: #f4516c
}

.step-dot-finish {
  background-color: #3963bc
}

.step-line {
  background: #c4c9d2;
  transition: .5s;
  position: absolute;
  z-index: 0
}

.step-line-row {
  height: 2rpx;
  width: 100%;
  left: 50%;
  top: 18rpx
}

.step-line-column {
  width: 2rpx;
  height: 100%;
  top: 20rpx;
  left: 48rpx
}

.step-line-wait {
  background-color: #e8e8e8
}

.step-line-finish {
  background-color: #3963bc
}

.step-content {
  display: flex;
  flex-direction: column
}

.step-content-row {
  align-items: center;
  width: 100%
}

.step-content-column {
  margin-left: 15rpx
}

.step-title {
  color: #595959;
  font-size: 26rpx;
  line-height: 40rpx;
  margin: 10rpx 0
}

.step-title-process {
  color: #333;
  font-size: 28rpx;
  line-height: 40rpx;
  margin: 10rpx 0
}

.step-describe {
  color: #8c98ae;
  font-size: 22rpx
}

@font-face {
  font-family: iconfont;
  src: url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAK8AAsAAAAABpQAAAJvAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDBgp8gQsBNgIkAwwLCAAEIAWEbQc2G9EFyK4wbuGJaCgrSmkBCSe/ZeUtviBzEXztx/bd3f+oNEiuzaerJhKN6WQSJFqlZELxTOZqTS9l0hsflgVC/bD3z/dzv8lMXAksyeryeByfVkhgV93EvwdIwC+b/hetH5jPyAA+z+X0JtCBXONbltMcw496AcYBBbYnRoEEXEQBcAqyOwK15PsEmg2KiewMTy6yCoWzLBD3lqGwilJKUaRqo1C37C3iRaUxPaUnPMffj18LUUFSZc7a3bshnfV++DD/2GVISBPo4QoZU5hCXLeWThULiSvWbCrpb64VIS2VhYlVpqU+6B8vETWnsxWM0XLiI19G8OGABDKoy5VBzDRje98gRHertt+2fLx2cdC6ZJwcnK226Ifh0uzy4vzK8sLSytxprqHAHexGbz8/6za6C3ePjwW29viP3ailnm7Zz+SntAd5IV0IBHuvvy3Xd/41thTw/osdBRahcefh4Gepwb5iah13LrrKV9mRrBsIJKhCjvrnxLq9Trf0wb2ERn0TIg0GcmSNhsjCTqHSYgm1RutoNmnkcIseUrEoTZiwRRA63SBp94as0zNZ2BdU+v2h1hnFaHYczWe2GAmrakCiQqiDmgdLEz73vFSVdM+jkXUVWdTG/DLK0MpCOpGqVkfQR7nEhjBnZIg4cCk8GCaPoesKCKSwUaOESRS0J5O87UUJTXisyjYJKQjSAVUesGgEHw+Hk6ra5+chQ5ZLIXsEDfllSApZ44O0hBRAjph8SPAoz4RyDBmEcICTBA8YJrOQyyWAoH2WDWlIgjmhEGiXdAdx1JTY3uL93xFo5kzMkSJHUT+g+5BNxVpWMAYAAA==') format('woff2')
}

// finish和error图标使用iconfont图标

.iconfont {
  font-family: iconfont !important;
  font-size: 24rpx;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale
}

.icon-finish:before {
  content: "\e600"
}

.icon-error:before {
  content: "\e6cf"
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
微信小程序是一种基于微信平台的应用的开发模式,可以快速的开发出符合用户需求的小程序。在小程序开发中,组件是一个非常重要的概念,通过组件可以实现复用性和模块化编程思想。 组件应用是小程序开发的基础。通过组件可以将某一模块化并封装起来,使得组件可以在不同的页面间得到复用,大大提升了开发效率并减少了代码冗余。微信小程序提供了丰富的自带组件,包括文本、图片、按钮、输入框等等,开发者也可以自己开发组件来满足自己的需求。实际开发中,通过组件可以快速搭建页面框架和业务逻辑。 Demo是一个演示小程序的示例程序。在小程序的实际开发过程中,一个好的Demo非常重要。通过Demo,开发人员可以更深入的了解小程序开发流程、组件的应用和实际的业务开发等等。在Demo中,通常会包括小程序的一些基础操作,如页面跳转、数据绑定、组件的使用等。而在实际开发中,Demo还会包括一些复杂的业务场景,如支付、登录、数据列表展示等等。Demo不仅为开发者提供了学习和实践的机会,也方便了使用者了解该小程序的功能和特点。 总之,微信小程序组件的应用和Demo的开发都是小程序开发过程中非常重要的两个部分。良好的组件应用和精心设计的Demo,可以在极短的时间内实现小程序开发

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值