index.vue 8.13 KB
<template>
  <div class="zui-steps" :class="stepsClassRender">
    <template v-for="(step, index) of steps">
      <div class="step-wrapper" :class="[$_getStepStatusClass(index)]" :key="stepKey(index)">
        <div v-if="$scopedSlots.icon" class="icon-wrapper">
          <slot name="icon" :index="index" :current-index="currentLength" :step="step"></slot>
        </div>
        <div v-else class="icon-wrapper">
          <template v-if="index < currentLength">
            <slot v-if="$scopedSlots.reached || $slots.reached" name="reached" :index="index"></slot>
            <div v-else class="step-node-default">
              <div class="step-node-default-icon" style="width: 8px;height: 8px;border-radius: 50%;"></div>
            </div>
          </template>
          <template v-else-if="index == currentLength">
            <slot v-if="$scopedSlots.current || $slots.current" name="current" :index="index"></slot>
            <zui-icon v-else name="checkcircle"></zui-icon>
          </template>
          <template v-else>
            <slot v-if="$scopedSlots.unreached || $slots.unreached" name="unreached" :index="index"></slot>
            <div v-else class="step-node-default">
              <div class="step-node-default-icon" style="width: 8px;height: 8px;border-radius: 50%;"></div>
            </div>
          </template>
        </div>
        <div class="text-wrapper">
          <slot v-if="$scopedSlots.content" name="content" :index="index" :step="step"></slot>
          <template v-else>
            <div class="name">{{step.name}}</div>
            <div class="desc" v-if="step.text">{{step.text}}</div>
          </template>
        </div>
      </div>
      <div class="bar" :class="barClassRender" :style="$_getStepSizeForStyle(index)" :key="barKey(index)">
        <i class="bar-inner" v-if="progress[index]" :style="$_barInnerStyle(index)"></i>
      </div>
    </template>
  </div>
</template>

<script>
export default {
  name: "Steps",
  props: {
    steps: {
      type: Array,
      default: function() {
        /* istanbul ignore next */
        return [];
      }
    },
    current: {
      type: Number,
      default: 0,
      validator(val) {
        return val >= 0;
      }
    },
    direction: {
      type: String,
      default: "horizontal"
    },
    transition: {
      type: Boolean,
      default: false
    },
    verticalAdaptive: {
      type: Boolean,
      default: false
    }
  },
  data: function() {
    return {
      initialed: false,
      progress: [],
      stepsSize: [],
      currentLength: 0,
      duration: 0.3,
      timer: null
    };
  },
  computed: {
    stepsClassRender: function() {
      return {
        "zui-steps-vertical": this.direction == "vertical",
        "zui-steps-horizontal": this.direction == "horizontal",
        "vertical-adaptive":
          this.direction == "vertical" && this.verticalAdaptive,
        "no-current": this.currentLength % 1 !== 0
      };
    },
    barClassRender: function() {
      return [this.direction == 'horizontal' ? 'horizontal-bar' : 'vertical-bar']
    },
    $_barInnerStyle: function() {
      var progress = this.progress;
      var self = this;
      return function(index) {
        var transform =
          self.direction == "horizontal"
            ? '(' + (progress[index]["len"] - 1) * 100 + '%, 0, 0)'
            : '(0, ' + (progress[index]["len"] - 1) * 100 + '%, 0)';
        return {
          transform: 'translate3d' + transform,
          transition: 'all ' + progress[index]["time"] + 's linear'
        };
      };
    }
  },
  watch: {
    current: function(val, oldVal) {
      var currentStep = this.$_formatValue(val);
      var newProgress = this.$_sliceProgress(currentStep);
      if (this.transition) {
        var isAdd = currentStep >= oldVal;
        var self = this;
        this.timer && clearTimeout(this.timer);
        this.timer = setTimeout(function() {
          self.$_doTransition(function(newProgress, isAdd, len) {
            if (
              (isAdd && len > self.currentLength) ||
              (!isAdd && len < self.currentLength)
            ) {
              self.currentLength = len;
            }
          });
        }, 100);
      } else {
        this.progress = newProgress;
        this.currentLength = currentStep;
      }
    }
  },
  created: function() {
    var currentStep = this.$_formatValue(this.current);
    this.currentLength = currentStep;
    this.progress = this.$_sliceProgress(currentStep);
  },
  mounted: function() {
    this.$_initStepSize();
  },
  updated: function() {
    this.$nextTick(function() {
      this.$_initStepSize();
    });
  },
  methods: {
    stepKey: function(index) {
      return "steps-" + index;
    },
    barKey: function(index) {
      return "bar-" + index;
    },
    toArray: function(list, start) {
      start = start || 0;
      var i = list.length - start;
      var ret = [];
      while (i--) {
        ret.unshift(list[i + start]);
      }
      return ret;
    },
    // MARK: private methods
    $_initStepSize: function() {
      if (this.direction != "vertical" || this.verticalAdaptive) {
        return;
      }
      var iconWrappers = this.$el.querySelectorAll(".icon-wrapper");
      var textWrappers = this.$el.querySelectorAll(".text-wrapper");
      var self = this;
      var stepsSize = this.toArray(textWrappers).map(function(wrapper, index) {
        var stepHeight = wrapper.clientHeight;
        var iconHeight = iconWrappers[index].clientHeight;
        if (index == textWrappers.length - 1) {
          // The last step needs to subtract floated height
          stepHeight -= iconHeight;
        } else {
          // Add spacing between steps to prevent distance too close
          // stepHeight += 40
          // 这里减小空白间隙,使其看起来更紧凑
          stepHeight += self.stepHeightFix == 0 ? self.stepHeightFix : self.stepHeightFix || -15;
        }
        return stepHeight > 0 ? stepHeight : 0;
      });
      if (stepsSize.toString() != this.stepsSize.toString()) {
        this.stepsSize = stepsSize;
      }
    },
    $_getStepSizeForStyle: function(index) {
      var size =
        this.direction == "vertical" && !this.verticalAdaptive
          ? this.stepsSize[index]
          : 0;
      return size
        ? {
            height: size + 'px'
          }
        : null;
    },
    $_getStepStatusClass: function(index) {
      var currentLength = this.currentLength;
      var status = [];
      if (index < currentLength) {
        status.push("reached");
      }
      if (index == Math.floor(currentLength)) {
        status.push("current");
      }
      return status.join(" ");
    },
    $_formatValue: function(val) {
      if (val < 0) {
        return 0;
      } else if (val > this.steps.length - 1) {
        return this.steps.length - 1;
      } else {
        return val;
      }
    },
    $_sliceProgress: function(current) {
      var self = this;
      return this.steps.slice(0, this.steps.length - 1).map(function(step, index) {
        var offset = current - index;
        var progress = self.progress[index];
        var isNewProgress = progress == undefined;
        var len, time;
        if (offset <= 0) {
          len = 0;
        } else if (offset >= 1) {
          len = 1;
        } else {
          len = offset;
        }
        time =
          (isNewProgress ? len : Math.abs(progress.len - len)) * self.duration;
        return {
          len,
          time
        };
      });
    },
    $_doTransition: function(progress, isAdd, step) {
      var currentLength = isAdd ? 0 : this.currentLength;
      var self = this;
      var walk = function(index) {
        if ((index < progress.length) & (index > -1) && progress[index]) {
          if (isAdd) {
            currentLength += progress[index].len;
          } else {
            currentLength -= self.progress[index].len - progress[index].len;
          }
          setTimeout(function() {
            index += isAdd ? 1 : -1;
            step(currentLength);
            walk(index);
          }, progress[index].time * 1000);
        }
        self.$set(self.progress, index, progress[index]);
      };
      walk(isAdd ? 0 : progress.length - 1);
    }
  }
};
</script>

<style>
@import "./index.css";
</style>