3.29 Steps 步骤条

简介

步骤条组件Steps是引导用户按照流程完成任务的分步导航条。当任务复杂或者存在先后关系时,将其分解成一系列步骤,从而简化任务,一半步骤不得少于 2 步。

本文将分析其源码实现,耐心读完,相信会对您有所帮助。🔗 组件文档 Steps 🔗 gitee源码

更多组件剖析详见 👉 📚 Element 2 源码剖析组件总览

组件源码

步骤条功能提供了两个组件:顶层组件el-steps和 子组件el-step

各组件的 prop 声明,各属性功能说明详见官方文档 Steps#attributes

顶层组件 steps.vue

顶层组件基本上就是一个容器,包含着子组件el-step用到的共享状态。在el-step中直接通过$parent获取父实例,改变和同步其共享状态。

模板渲染成一个类名el-steps的简单div元素,同时使用匿名插槽渲染步骤元素。

  • 声明props 用于外部传入的属性。

  • 属性 steps 用于保存当前实例下子组件el-step的实例数组。

  • 属性 stepOffset 用于设置步骤元素的间隔。

  • 定义了侦听器,监听属性 active用于触发的自定义change事件,监听属性 steps 更新每个子组件实例步骤索引。

// packages\steps\src\steps.vue
<template>
  <div
    class="el-steps"
    :class="[
       !simple && 'el-steps--' + direction,
       simple && 'el-steps--simple'
     ]">
      <slot></slot>
  </div>
</template>

<script> 
export default {  
  // props ... 
  data() {
    return {
      steps: [],
      stepOffset: 0
    };
  }, 
  watch: {
    active(newVal, oldVal) {
      this.$emit('change', newVal, oldVal);
    }, 
    steps(steps) {
      steps.forEach((child, index) => {
        child.index = index;
      });
    }
  }
};
</script> 

步骤组件状态初始化

为了更好理解步骤组件一些状态初始化操作, 接下结合父子组件的生命周期流程进行讲解。

父子组件实例在创建时经历一系列的初始化步骤:父beforeCreate -> 父created -> 父beforeMount ->子beforeCreate -> 子created -> 子beforeMount -> 子mounted-> 父mounted

首先,顶层组件el-steps初始化实例、 解析props、data()和侦听器等选项设置完毕。

其次,子组件el-step初始化实例、 解析props、data() 、计算属性、方法和侦听器等选项设置完毕。定义了beforeCreate钩子函数,当子组件实例初始化完成之后立即将该实例添加至父组件的属性steps数组中。

再次,定义了mounted钩子函数,当子组件el-step被挂载之后,使用命令式的 $watch() 创建侦听器。用于监听属性index更改,执行侦听回调一次后,调用方法unwatch()就会停止该侦听器。

然后,顶层组件的属性steps数组长度大于0,会执行侦听回调,更新每个子组件实例的属性 index值,即实例在数组steps中索引值。

然后,触发子组件的index的属性侦听回调,此时会在子组件创建侦全局状态activeprocessStatus的侦听器,用于更新各个子组件的状态显示。用于触发回调方法updateStatus,参数是当前激活步骤的index。

最后,执行方法 updateStatus,初始化子组件状态。因为定义了immediate: true,在侦听器创建时立即方法,所以会调用两次方法。

子组件有三个内部状态(隐式) 激活/已完成/未激活,使用属性internalStatus表示内部状态值(显式)。

  • 已完成 当前激活步骤索引大于此步骤所在数组索引值,internalStatus值为属性finishStatus值。

  • 激活 当前激活步骤索引等于步骤所在数组索引值,若非首元素,则上一步骤元素的状态值不能为 error,也就是 error会中断步骤流程。 internalStatus值为属性processStatus值。

  • 未激活 当前激活步骤索引小于此步骤所在数组索引值,internalStatus值为wait

根据当前子组件的索引,获取它的上一元素prevChild,调用prevChild的方法prevChildcalcProgress计算步骤间轴线样式,此方法稍后会详细介绍。

步骤的状态

上面介绍了整个初始化的流程,也许大家会有疑惑,动态更新的属性internalStatus值作用是什么?

步骤组件提供了属性status值用于设置当前步骤的状态值;如果未设置, 就会根据属性internalStatus确定状态值。

计算属性currentStatus返回当前步骤的状态值,计算属性 prevStatus 返回上一步骤的状态值。

步骤组件 step.vue

步骤条主要功能实现都在该组件中。

HTML

模板渲染成一个类名el-step的div元素,元素包含两部分内容

  1. 用于图标、轴线的渲染。

  2. 用于标题、描述的渲染。

下图是不同状态步骤的展示效果:

image.png

步骤元素通过内联样式和动态类名渲染不同配置下组件样式。

根元素自定义类名

文档中提到当设置 simple 可应用简洁风格,该条件下 align-centerdirectionspace 都将失效。

接下将一一分析为什么设置会失效。

属性 direction 用于设置显示方向,生成类名 is-verticalis-horizontal。根据计算属性isSimple判断设置简洁风格时,只会生成类名 is-simple

顶层组件中也会根据属性simpledirection生成根元素类名el-steps--simpleel-steps--verticalel-steps--horizontal

如果未设置间距 space和居中对齐 alignCenter,步骤条末元素会生成类名 is-flex用于自适应宽度。 如果设置了简洁风格,计算属性space中属性space设置无效。

非简洁模式和竖直方向下,设置居中对齐 alignCenter才会生效,生成类名is-center

根元素内联样式

顶层组件 steps 的根元素采用 flex布局。

计算属性style根据属性 space 设置flex-basis,指定了 flex 元素在主轴方向上的初始大小,也就是内容盒(content-box)的尺寸。相当于设置了步骤元素的 widthheight

当一个元素同时被设置了 flex-basis (除值为 auto 外) 和 width (或者在 flex-direction: column 情况下设置了height) , flex-basis 具有更高的优先级。

传入的属性 space值类型为 number时生成格式 {20}px。不设置根据步骤个数自动计算百分比实现自适应间距。

水平方向时,步骤末元素设置属性max-width值,其余设置属性margin-right值(属性stepOffset值没有相关计算或赋值,始终为 0)。

属性 space 可设置值可以参考以下内容:

下面通过运行实例对比分析下,各属性设置的渲染效果。

步骤元素内容个数相同情况下,不同设置的效果如下: image.png

各步骤元素DOM结构如下(此处不考虑内部内容样式区别),除了第一个示例,其余每个步骤都是等分。 image.png

那问题来了,第一个示例中 space 未设置,组件会自适应宽度操作,那么末步骤元素发生了什么导致其宽度跟其他步骤元素不一致?

第一个示例DOM结构渲染如下,虽然都设置了flex-basis: 33.3333%;,但是末元素的未生效。

因为末元素添加了样式类名is-flex,覆盖了flex-basis,等同于 flex: noneflex: 0 0 auto。元素会根据自身宽高来设置尺寸。它是完全非弹性的:既不会缩短,也不会伸长来适应 flex 容器。

上文中提到非简洁模式下,只有未设置间距 space和居中对齐 alignCenter时,步骤条末元素会生成类名 is-flex。这也是第二、三示例末元素跟其他元素宽度一致原因。

竖直方向样式逻辑与水平一致(alignCenter设置无效),此处不再赘述。

图标 & 轴线

根据计算属性currentStatus生成当前步骤状态对应的主题样式 is-[wait/process/finish/error/success]

图标

图标元素是类名el-step__icon的div元素,提供了具名icon插槽自定义步骤节点图标。插槽内容默认展示Icon图标或表示节点顺序圆环数字(从1开始)。简洁风格下,只有Icon设置生效。

当组件状态值为 successerror 时,使用系统提供图标。

数字圆环的样式is-text设置的。

不同设置组件效果对比如下:

image.png

设置居中对齐 alignCenter时,图标的居中效果是通过样式控制的。

轴线

轴线元素是类名el-step__line的div元素,使用了绝对布局。

通过偏移量、height、width等属性设置轴线位置、长度以及粗细。

使用了伪类:last-of-type 设置最后一个步骤元素中轴线不显示。

居中对齐下的轴线偏移量有些特殊,渲染效果如下。 image.png

实际元素DOM结构如下: image.png

轴线进度状态效果通过类名el-step__line-inner元素实现,该元素内联样式绑定属性lineStyle

属性lineStyle通过方法calcProgress根据当前步骤的状态计算而来。 方法updateStatus作用在生命周期中详细介绍过。

当前步骤元素不是第一个,此时步骤的状态为已完成,就会更新其上一元素的 lineStyle 值,显示进度效果。

标题 & 描述

标题是类名el-step__title的div元素,提供了具名title插槽用于自定义标题,后备插槽内容为属性 title值。

描述类名el-step__description的div元素,提供了具名description插槽用于自定义描述性文字,后备插槽内容为属性 description值。简洁风格下,description 设置失效。

它们根据计算属性currentStatus生成当前步骤状态对应的主题样式 is-[wait/process/finish/error/success]

简洁风格

前面章节中介绍了简洁风格会导致很多设置无效,接下来整体的看下简洁风格的实现效果。

image.png

此时DOM结构渲染如下。

此时步骤元素使用flex布局,所以图标、标题、 箭头元素都在一行内展示。

轴线元素DOM渲染了,但是没有设置宽、高、边框粗细,页面就无法展示。

箭头样式使用伪类:before:after定义。

样式实现

组件样式源码 packages\theme-chalk\src\steps.scss 使用混合指令嵌套生成组件样式。

组件样式源码 packages\theme-chalk\src\step.scss 使用混合指令嵌套生成组件样式。

📚参考&关联阅读

'CSS/flex',MDN 'CSS/:last-of-type',MDN '生命周期',vuejs 'watch',vuejs

最后更新于