vue3 步骤条结合进度条做激活状态显示到一半的效果

333 阅读2分钟

由于el-steps不支持小数,没法实现激活状态走到一半的效果,通过el-progress做样式重合来实现,要做得需求效果如下:

image.png

  1. 我是将示例写在弹窗中进行测试的
  <el-dialog
    v-model="dialogVisible"
    title="Tips"
    width="1000"
    :close-on-click-modal="false"
    @opened="getStepsWidth"
  >
    <div class="steps-container">
      <el-steps :active="1" finish-status="success" ref="stepsRef">
        <el-step title="Step 1"></el-step>
        <el-step title="Step 2"></el-step>
        <el-step title="Step 3"></el-step>
        <el-step title="Step 4"></el-step>
        <el-step title="Step 5"></el-step>
      </el-steps>
      <div class="progress-overlay">
        <div class="custom-progress">
          <div class="progress-label">1.5</div>
        </div>
      </div>
    </div>
  </el-dialog>

补充:如果想要实现动态走到某一节点后的一半,这样实现:

1. progressWidth动态绑定值
 <div class="steps-container">
                <el-steps :active="active" ref="stepsRef">
                  <template v-for="(items, index) in statusInfor.statusBar">
                    <el-step :title="items.period" :description="items.name" />
                  </template>
                </el-steps>
                <div class="progress-overlay" v-if="statusInfor.statusDue">
                  <div
                    class="custom-progress"
                    :style="{ width: progressWidth }"
                  >
                    <div
                      class="progress-label"
                      :style="{ left: progressWidth }"
                    >
                      {{ statusInfor.submitDatePeriod }}
                    </div>
                  </div>
                </div>
              </div>
              
2. 同时,由于无法直接通过 Vue 的 style 绑定来控制伪元素的属性, custom-progress::before 的 width 通过 width: inherit 从父元素继承宽度,父元素的宽度通过动态 style 绑定控制。则动态绑定时直接给 class="custom-progress"来添加width宽度,::before直接继承父级宽度。

// ------steps--------------------
.steps-container {
  position: relative;
}

.el-steps__line {
  position: relative;
  z-index: 1;
}

.el-steps__line-inner {
  background-color: #e5e5e5;
}

.custom-progress::before {
  content: "";
  position: absolute;
  top: -32px;
  left: 0;
  // width: calc(224px * (2 - 1) + 224px / 2); /* 表示从步骤 1 到 1.5 */
  // custom-progress::before 的 width 通过 width: inherit 从父元素继承宽度,父元素的宽度通过动态 style 绑定控制。
  // 因为无法直接通过 Vue 的 style 绑定来控制伪元素的属性
  width: inherit; // 重要*******重要************重要********重要************重要********
  height: 2px;
  background-color: #409eff;
  // z-index: 2; /* 确保蓝色进度条显示在步骤条上方 */
}

.progress-overlay {
  position: relative;
  top: -20px; /* 调整此处,控制进度条相对步骤条的偏移 */
}

.progress-label {
  position: absolute;
  /* 调整文本距离进度条的间距 */
  top: -9px;
  /* 定位到1.5的准确位置 */
  // left: calc(25% / 2);
  // left: calc(224px * (2 - 1) + 224px / 2); /* 表示从步骤 1 到 1.5 */
  transform: translateX(-50%);
  color: #409eff;
  font-size: 12px; /* 控制字体大小 */
}

.el-step.is-process + .el-step::before {
  background-color: transparent; /* 防止步骤条默认的灰色覆盖蓝色进度条 */
}

.el-step__head.is-process::after {
  background-color: #409eff; /* 确保图标处的背景色与进度条一致 */
}

::v-deep .el-step__icon {
  z-index: 10 !important;
}

              
  1. 弹窗打开时获取el-steps的总长度,因为在mounted中获取不到,弹窗没打开。@opened="getStepsWidth" 属性。
const getStepsWidth = () => {
  // Ensure the steps component is rendered before trying to access its width
  if (stepsRef.value) {
    const stepsWidth = stepsRef.value.$el.offsetWidth;
    console.log("Steps total width:", stepsWidth / 4);
  }
};
  1. 添加样式
.steps-container {
  position: relative;
}

.el-steps__line {
  position: relative;
  z-index: 1;
}

.el-steps__line-inner {
  background-color: #e5e5e5;
}

.custom-progress::before {
  content: "";
  position: absolute;
  top: -26px;
  left: 0;
  width: calc(233px * 3 / 2); /* 表示从步骤 1 到 1.5 */
  height: 2px;
  background-color: blue;
  z-index: 2; /* 确保蓝色进度条显示在步骤条上方 */
}

.progress-overlay {
  position: relative;
  top: -20px; /* 调整此处,控制进度条相对步骤条的偏移 */
}

.progress-label {
  position: absolute;
  /* 调整文本距离进度条的间距 */
  top: -20px;
  /* 定位到1.5的准确位置 */
  // left: calc(25% / 2);
  left: calc(233px * 3 / 2); /* 表示从步骤 1 到 1.5 */
  transform: translateX(-50%);
  color: blue;
  font-size: 12px; /* 控制字体大小 */
}

.el-step.is-process + .el-step::before {
  background-color: transparent; /* 防止步骤条默认的灰色覆盖蓝色进度条 */
}

.el-step__head.is-process::after {
  background-color: blue; /* 确保图标处的背景色与进度条一致 */
}

::v-deep .el-step__icon {
  z-index: 10;
}

优化后的:

   <div class="steps-container">
                <el-steps :active="active" ref="stepsRef">
                  <template v-for="(items, index) in statusInfor.statusBar">
                    <el-step :description="items.name">
                      <template #title>
                        <span
                          :style="{
                            color: items.markColor === 1 ? 'red' : 'inherit',
                          }"
                        >
                          {{ items.period }}
                        </span>
                      </template>
                    </el-step>
                  </template>
                </el-steps>
                <div class="progress-overlay" v-if="statusInfor.statusDue">
                  <div
                    class="custom-progress"
                    :style="{ width: progressWidth }"
                  >
                    <div
                      class="progress-label"
                      :style="{ left: progressWidth }"
                      :title="`submitted for ${statusInfor.submitDatePeriod}`"
                    >
                      {{ statusInfor.submitDatePeriod }}
                    </div>
                  </div>
                </div>
              </div>
              

方法:

 // 添加类似进度条效果
  if (stepsRef.value) {
    const stepsWidth: any = stepsRef.value.$el.offsetWidth;
    // console.log("Steps total width:", stepsWidth, stepsWidth / 7);

    let greyCount = statusInfor.value.greyIndex + 1;
    let activeCount = 0;
    for (
      let index = statusInfor.value.statusBar.length - 1;
      index >= 0;
      index--
    ) {
      let statusBarItem = statusInfor.value.statusBar[index];
      if (statusBarItem.active) {
        activeCount = index + 1;
        break;
      }
    }

    let singlePx: any =
      parseInt(stepsWidth) / (statusInfor.value.statusBar.length - 1);

    progressWidth.value = `calc(${singlePx}px * (${activeCount} - 1) + ${singlePx}px / 2)`;
    // progressWidth.value = "calc(224px * (6 - 1) + 224px / 2)";

    // --------给跳转的节点添加灰色(节点样式)-----------------------------------------

    const stepIcons = document.querySelectorAll(".el-step__icon-inner");
    // 找到需要添加灰色边框的元素
    const stepIconWithFive = Array.from(stepIcons).find(
      (icon) => icon.textContent.trim() === "" + (greyCount - 1)
    );
    // 如果找到了该元素,则设置其父元素的父元素的边框
    if (stepIconWithFive) {
      const parentElement = stepIconWithFive.closest(".el-step__icon");
      parentElement.style.border = "2px solid gray";
      parentElement.style.color = "gray";
    }

    // --------给跳转的节点添加灰色(描述文本)-----------------------------------------
    const stepDescriptions = document.querySelectorAll(".el-step__description");
    const index = greyCount - 2; // 描述的索引与 greyCount 相同
    if (stepDescriptions[index]) {
      stepDescriptions[index].style.color = "gray"; // 设置描述文本为灰色
    }
  }