文档管理中心
我的

步骤条组件

简介

步骤条(steps)组件常用于描述一件事情从起始到结束的过程,电商购物物流场景使用居多。步骤条一般具有如下特点:

  • 可设置横向排列或纵向排列
  • 可突出显示当前步骤
  • 可显示事件轴

效果图如下:

基本布局代码如下:

<template>
  <div class="container">
    <text class="section">横向排列</text>
    <steps class="stepcss" options="{{list2}}" active-color="#007AFF" active="{{active}}" direction="row"></steps>
    <text class="section">纵向排列</text>
    <steps if="{{isShow}}}" class="stepcss" options="{{list2}}" active-color="#007AFF" active="{{active}}" direction="column"></steps>
  </div>
</template>

开发指引

自定义子组件

  1. 定义布局样式。

    步骤条整体外观都是通用的,但是内部具体的样式有区别,在设计的时候,不能把内部样式固定,否则一旦步骤条的UI有变化,会导致子组件也要修改,违背了对外开源的初衷。

    所以,在子组件steps.ux中,开放多种样式属性,方便开发者自定义样式。

    <template>
      <div class="rootcontainer">
        <block if="{{direction=='column'}}">
          <div style="width: 100%;">
            <!--垂直分割线 -->
            <div style="flex-direction: column; align-items: center; padding-top: 16px; width: 60px;">
              <div if="{{computed}}" class="steps_column-line-item" for="{{listoptions}}">
                <image if="$idx==active" class="steps_column-imgcheck"></image>
                <image else class="steps_column-imgcircle"></image>
                <div if="$idx!=options.length-1 && computed" class="steps_column-line" style="height: {{$item.lineHeight}}px;"></div>
                 <!-- <div if="$idx!=options.length-1 && computed" class="steps_column-line" style="height:100%;"></div> -->
              </div>
            </div>
    
            <!--垂直事件描述-->
            <div style="flex-direction: column; width: 100%;">
              <block for="{{listoptions}}">
                <div if="$idx==active" class="steps_column_text_div" id="{{$item.itemId}}">
                  <text class="steps_txt_state" style="color:{{activeColor}};">{{ $item.title }}</text>
                  <text class="steps_txt_des" style="color:{{activeColor}};">{{ $item.desc }}</text>
                  <text class="steps_txt_time" style="color:{{activeColor}};">{{ $item.time }}</text>
                  <text class="steps_row-line"></text>
                </div>
                <div else class="steps_column_text_div" id="{{$item.itemId}}">
                  <text class="steps_txt_state" style="color:{{deactiveColor}};">{{ $item.title }}</text>
                  <text class="steps_txt_des" style="color:{{deactiveColor}};">{{ $item.desc }}</text>
                  <text class="steps_txt_time" style="color:{{deactiveColor}};">{{ $item.time }}</text>
                  <text class="steps_row-line"></text>
                </div>
              </block>
            </div>
          </div>
        </block>
    
        <block else>
          <!--水平方向事件 -->
          <div id="rowroot" style="flex-direction: column;width: 100%;">
            <!--事件文字布局 -->
            <div class="steps_row">
              <div class="steps_row_text_div" style="width: {{row_list_item_width}}px;" id="{{$item.itemId}}" for="{{listoptions}}">
                <block if="$idx==active">
                  <text class="steps_txt_state" style="color:{{activeColor}};">{{ $item.title }}</text>
                  <text class="steps_txt_des" style="color:{{activeColor}};">{{ $item.desc }}</text>
                  <text class="steps_txt_time" style="color:{{activeColor}};">{{ $item.time }}</text>
                </block>
    
                <block else>
                  <text class="steps_txt_state" style="color:{{deactiveColor}};">{{ $item.title }}</text>
                  <text class="steps_txt_des" style="color:{{deactiveColor}};">{{ $item.desc }}</text>
                  <text class="steps_txt_time" style="color:{{deactiveColor}};">{{ $item.time }}</text>
                </block>
              </div>
            </div>
    
            <!--分割线布局 -->
            <div class="steps_row_line_root" style=" margin-left: {{lineleft}}px; margin-right:{{lineleft}}px;" >
              <div if="{{computed}}" class="steps_row-line-item" for="{{listoptions}}">
                <image if="$idx==active" class="steps_column-imgcheck"></image>
                <image else class="steps_column-imgcircle"></image>
                <block if="$idx!=options.length-1">
                  <div class="steps_column-line" style="width:{{row_list_item_width+5}}px; height: 1px;"></div>
                </block>
              </div>
            </div>
          </div>
        </block>
      </div>
    </template>
  2. 规划属性和支持的事件。
    支持的属性:
    展开

    属性

    类型

    默认值

    描述

    direction

    String

    column

    步骤排列方向。

    options

    Array

    -

    事件源。

    active

    Number

    0

    当前步骤。

    activeColor

    Color

    #1aad19

    激活状态的颜色。

    deactiveColor

    Color

    #999999

    未激活状态的颜色。

    支持的事件:

    无

实现横向排列

  1. 整体布局是“事件源布局”+“事件轴布局”组合起来的竖向布局。
  2. “事件轴布局”是横向布局,“事件源布局”是横向布局。
  3. 使用for循环形成列表布局。
  4. 事件轴的起点(灰色小圆点)设置于起始事件的中心位置,然后计算事件轴左右距离根节点的宽度。
  5. 两个小圆点之间的宽度和每个事件的宽度相同,需要计算所有事件布局的宽度之和,然后计算每个事件轴的宽度,代码如下:
    calRowItemWidth(len) {
      var that = this;
      this.$element('rowroot').getBoundingClientRect({
        success: function (data) {
          console.log('calRowItemWidth  getBoundingClientRect succeeded, result data =' 
          + JSON.stringify(data));
          that.row_list_item_width = data.width / len ;
          that.lineleft = that.row_list_item_width / 2 ;
          console.info("calRowItemWidth= " + that.row_list_item_width);
          that.computed = true;
        },
        fail() {
          console.log('getBoundingClientRect failed');
        },
        complete: function () {
          console.log('getBoundingClientRect completed');
        }
      });
    }

实现纵向排列

  1. 整体布局是“事件轴布局”+“事件源布局”组合起来的横向布局。
  2. “事件轴布局”是竖向布局,“事件源布局”是竖向布局。
  3. 使用for循环形成列表布局。
  4. 左侧事件轴的高度取决于右侧内容所占用的空间,若要保证事件轴上每个小圆点都与事件源水平对齐,可通过组件的getBoundingClientRect方法获取右侧每个事件源的高度,然后赋值给相应变量,代码如下:
    calLineHeight(optionItem) {
      var that = this;
      this.$element(optionItem.itemId).getBoundingClientRect({
        success: function (data) {
          console.log('getBoundingClientRect succeeded, result data =' 
          + JSON.stringify(data));
          let lineHeight = data.height - 10;
          optionItem.lineHeight = lineHeight;
          that.computed = true;
        },
        fail() {
          console.log('getBoundingClientRect failed');
        },
        complete: function () {
          console.log('getBoundingClientRect completed');
        }
      });
    },
    注意

    getBoundingClientRect( )方法只能在页面渲染后才可以计算出组件的宽高值,在页面生命周期调用时,一般要延时200ms左右。

说明

步骤条每个事件源高度和事件轴高度是根据自身内容来动态适配的,没有将高度固定。由于轴线高度、宽度值计算受限于渲染后,当计算出结果时,界面刷新,可能会出现界面闪一下的效果。

如果您的每个事件源内容少,建议固定高度,省去一些计算步骤。

示例代码

步骤条组件steps.ux代码:

<template>
  <div class="rootcontainer">
    <block if="{{direction=='column'}}">
      <div style="width: 100%;">
        <!--垂直分割线 -->
        <div style="flex-direction: column; align-items: center; padding-top: 16px; width: 60px;">
          <div if="{{computed}}" class="steps_column-line-item" for="{{listoptions}}">
            <image if="$idx==active" class="steps_column-imgcheck"></image>
            <image else class="steps_column-imgcircle"></image>
            <div if="$idx!=options.length-1 && computed" class="steps_column-line" style="height: {{$item.lineHeight}}px;"></div>
             <!-- <div if="$idx!=options.length-1 && computed" class="steps_column-line" style="height:100%;"></div> -->
          </div>
        </div>

        <!--垂直事件描述-->
        <div style="flex-direction: column; width: 100%;">
          <block for="{{listoptions}}">
            <div if="$idx==active" class="steps_column_text_div" id="{{$item.itemId}}">
              <text class="steps_txt_state" style="color:{{activeColor}};">{{ $item.title }}</text>
              <text class="steps_txt_des" style="color:{{activeColor}};">{{ $item.desc }}</text>
              <text class="steps_txt_time" style="color:{{activeColor}};">{{ $item.time }}</text>
              <text class="steps_row-line"></text>
            </div>
            <div else class="steps_column_text_div" id="{{$item.itemId}}">
              <text class="steps_txt_state" style="color:{{deactiveColor}};">{{ $item.title }}</text>
              <text class="steps_txt_des" style="color:{{deactiveColor}};">{{ $item.desc }}</text>
              <text class="steps_txt_time" style="color:{{deactiveColor}};">{{ $item.time }}</text>
              <text class="steps_row-line"></text>
            </div>
          </block>
        </div>
      </div>
    </block>	

    <block else>
      <!--水平方向事件 -->
      <div id="rowroot" style="flex-direction: column;width: 100%;">
        <!--事件文字布局 -->
        <div class="steps_row">
          <div class="steps_row_text_div" style="width: {{row_list_item_width}}px;" id="{{$item.itemId}}" for="{{listoptions}}">
            <block if="$idx==active">
              <text class="steps_txt_state" style="color:{{activeColor}};">{{ $item.title }}</text>
              <text class="steps_txt_des" style="color:{{activeColor}};">{{ $item.desc }}</text>
              <text class="steps_txt_time" style="color:{{activeColor}};">{{ $item.time }}</text>
            </block>

            <block else>
              <text class="steps_txt_state" style="color:{{deactiveColor}};">{{ $item.title }}</text>
              <text class="steps_txt_des" style="color:{{deactiveColor}};">{{ $item.desc }}</text>
              <text class="steps_txt_time" style="color:{{deactiveColor}};">{{ $item.time }}</text>
            </block>
          </div>
        </div>

        <!--分割线布局 -->
        <div class="steps_row_line_root" style=" margin-left: {{lineleft}}px; margin-right:{{lineleft}}px;" >
          <div if="{{computed}}" class="steps_row-line-item" for="{{listoptions}}">
            <image if="$idx==active" class="steps_column-imgcheck"></image>
            <image else class="steps_column-imgcircle"></image>
            <block if="$idx!=options.length-1">
              <div class="steps_column-line" style="width:{{row_list_item_width+5}}px; height: 1px;"></div>
            </block>
          </div>
        </div>
      </div>
    </block>
  </div>
</template>

<style>
  .rootcontainer {
    flex-direction: column;
    justify-content: center;
    align-items: center;
    width: 100%;
  }
  .steps_column {
    display: flex;
    flex-direction: row;
    justify-content: center;
    padding-top: 16px;
  }
  .steps_column-line-item {
    flex-direction: column;
    align-items: center;
    justify-content: center;
  }

  .steps_column_text_div {
    flex-direction: column;
    align-items: flex-start;
    width: 100%;
    padding-top: 16px;
    padding-left: 30px;
  }

  .steps_row-line {
    height: 1px;
    width: 100%;
    margin-top: 20px;
    background-color: #999;
  }

  .steps_column-line {
    width: 1px;
    background-color: #999;
  }
  .steps_row-circle {
    width: 10px;
    height: 10px;
    border-radius: 1px;
    background-color: #999;
  }

  .steps_column-imgcircle {
    width: 10px;
    height: 10px;
    border-radius: 5px;
    background-color: #999;
  }

  .steps_column-imgcheck {
    width: 30px;
    height: 30px;
    border-radius: 15px;
    background-color: #00bfff;
  }

  .steps_txt_state {
    font-size: 30px;
    font-weight: bold;
  }

  .steps_txt_des {
    font-size: 25px;
    margin-top: 6px;
  }

  .steps_txt_time {
    font-size: 25px;
    margin-top: 6px;
  }

  .steps_row {
    display: flex;
    flex-direction: row;
    justify-content: center;
  }

  .steps_row_text_div {
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    width: 100%;
  }
  .steps_row-line-item {
    flex-direction: row;
    align-items: center;
    justify-content: center;
  }

  .steps_row_line_root {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
  }
</style>

<script>

  module.exports = {
    props: {
      direction: {
        // 排列方向 row column
        type: String,
        default: 'column'
      },
      activeColor: {
        // 激活状态颜色
        type: String,
        default: '#1aad19'
      },
      deactiveColor: {
        // 未激活状态颜色
        type: String,
        default: '#999999'
      },
      active: {
        // 当前步骤
        type: Number,
        default: 0
      },
      options: {
        type: Array,
        default() {
          return []
        }
      }
    },
    data() {
      return {
        txtColor: '#1aad19',
        listoptions: [],
        lineoptions: [],
        row_list_item_width: 200,
        lineleft: 0,
        computed: false
      }
    },
    onInit() {
      this.initListOptions();
      setTimeout(() => {
        this.cal();
      }, 200);

    },

    initListOptions() {
      let len = this.options.length;
      for (var i = 0; i < len; i++) {
        let item = {};
        item.desc = this.options[i].desc;
        item.title = this.options[i].title;
        item.time = this.options[i].time;
        item.itemId = "item" + i;
        item.lineHeight = 0;
        this.listoptions.push(item);
      }
    },
    cal() {
      let len = this.listoptions.length;
      if (this.direction == "column") {
        for (var i = 0; i < len; i++) {
          let optionItem = this.listoptions[i];
          this.calLineHeight(optionItem);
        }
      } else {
        this.calRowItemWidth(len);
      }
    },

    calLineHeight(optionItem) {
      var that = this;
      this.$element(optionItem.itemId).getBoundingClientRect({
        success: function (data) {
          console.log('getBoundingClientRect succeeded, result data =' 
          + JSON.stringify(data));
          let lineHeight = data.height - 10;
          optionItem.lineHeight = lineHeight;
          that.computed = true;
        },
        fail() {
          console.log('getBoundingClientRect failed');
        },
        complete: function () {
          console.log('getBoundingClientRect completed');
        }
      });
    },
    calRowItemWidth(len) {
      var that = this;
      this.$element('rowroot').getBoundingClientRect({
        success: function (data) {
          console.log('calRowItemWidth  getBoundingClientRect succeeded, result data =' 
          + JSON.stringify(data));
          that.row_list_item_width = data.width / len;
          that.lineleft = that.row_list_item_width / 2;
          console.info("calRowItemWidth= " + that.row_list_item_width);
          that.computed = true;
        },
        fail() {
          console.log('getBoundingClientRect failed');
        },
        complete: function () {
          console.log('getBoundingClientRect completed');
        }
      });
    }
  }
</script>

页面hello.ux代码:

<import name="steps" src="../Steps/steps.ux"></import>
<template>
  <div class="container">
    <text class="section">横向排列</text>
    <steps class="stepcss" options="{{list2}}" active-color="#007AFF" active="{{active}}" direction="row"></steps>
    <text class="section">纵向排列</text>
    <steps if="{{isShow}}}" class="stepcss" options="{{list2}}" active-color="#007AFF" active="{{active}}" direction="column"></steps>
  </div>
</template>

<style>
  .container {
    flex-direction: column;
    align-items: center;
    padding-left: 20px;
    padding-right: 20px;
    padding-top: 16px;
  }

  .section {
    background-color: #00bfff;
    width: 100%;
    height: 80px;
    margin-top: 25px;
  }

  .stepcss {
    padding-left: 30px;
    padding-right: 30px;
    width: 100%;
  }
</style>

<script>
  module.exports = {
    data() {
      return {
        isShow: true,
        active: 1,
        list1: [{
          title: '事件一',
          desc: '2020-11-11'
        }, {
          title: '事件二',
          desc: '2020-11-12'
        }, {
          title: '事件二',
          desc: '2020-11-12'
        },
        {
          title: '事件3',
          desc: '2020-11-12'
        },
        {
          title: '事件4',
          desc: '2020-11-12'
        },
        {
          title: '事件5',
          desc: '2020-11-12'
        }, {
          title: '事件6',
          desc: '2020-11-12'
        }],
        list11: [{
          title: '事件一',
          desc: '2020-11-11'
        }, {
          title: '事件二',
          desc: '2020-11-12'
        }, {
          title: '事件二',
          desc: '2020-11-12'
        },
        {
          title: '事件3',
          desc: '2020-11-12'
        },
        {
          title: '事件4',
          desc: '2020-11-12'
        }],
        list2: [{
          title: '买家下单',
          desc: '待打包',
          time: '2020-11-11'
        }, {
          title: '卖家发货',
          desc: '待快递上门取货',
          time: '2020-11-12'
        }, {
          title: '运输中',
          desc: '快件已到达深圳',
          time: '2020-11-13'
         }, 
        {
          title: '派件中',
          desc: '【深圳市】【福田华强】业务员王明正在派件中。小哥体温今日正常,将佩戴口罩为您投递,也可以联系小哥将您的快递放到指定代收点,祝您身体健康!',
          time: '2020-11-14'
        }, {
          title: '已签收',
          desc: '签收人:本人。如有疑问,请联系132xxxxxx。您的快递已经妥投,如果您对我们的服务感到满意,请给个五星好评,鼓励一下我们!您的快递已经妥投,如果您对我们的服务感到满意,请给个五星好评,鼓励一下我们!',
          time: '2020-11-14 17:00'
        }
        ]
      }
    },
    onInit() {
      console.info("hello onInit ");
      this.$page.setTitleBar({
        text: 'steps',
        textColor: '#ffffff',
        backgroundColor: '#007DFF',
        backgroundOpacity: 0.5,
        menu: true
      });
    },

    onShow(options) {
      console.info("hello onShow ");
    },
    deal: function () {
      this.isShow = !this.isShow;
      console.info(" this.isShow=" + this.isShow);
    }
  }
</script>