【步骤条轮子】迎合业务的步骤条

基于Vue的步骤条组件技术解析与实现


一、组件概述

本文将深入解析一个基于Vue 3的水平步骤条组件实现方案,该组件支持动态渲染步骤项、状态标识(进行中/已完成/未开始)、自定义图标及标题,并提供响应式布局能力。组件设计兼顾可复用性与扩展性,适用于表单流程引导、审批进度展示等场景。


二、核心功能特性

  1. 动态渲染与状态管理

    • 通过stepOptions数组动态生成步骤项,每项包含flowId(唯一标识)和flowName(显示名称)。

    • 支持两种状态标识:

      • 进行中:当前激活步骤(currentStep
      • 已完成:已完成的步骤(nodeStepIndex
      • 未开始:未触发的步骤。
  2. 交互设计

    • 点击步骤项可切换激活状态,触发setHandleStep事件并传递当前步骤信息。
    • 提供只读模式提供只读模式(onlyView),禁用点击但保留状态标识。
  3. 样式定制

    • 支持主题色切换(默认蓝色系),通过修改SCSS变量--active-color可快速适配不同设计需求。
    • 引导线自适应布局,利用flex和伪元素::after实现动态宽度。

三、代码实现解析

1. 模板结构(<template>
ini 复制代码
<template>
  <div class="step-container">
    <div
      class="content"
      v-for="(item, index) in stepOptions"
      :key="item.flowId"
      @click="handleStepClick(item, index)"
    >
      <div
        class="step-item"
        :class="{
          activeBlue: isActive(index),
        }"
      >
        <div class="step-circle">
          <span v-if="index + 1 >= nodeStepIndex" class="step-number">{{
            index + 1
          }}</span>
          <span v-if="index + 1 < nodeStepIndex">
            <img
              class="w-[1.125rem] h-[1.125rem]"
              :src="getAssetUrl(currentStep == index + 1)"
            />
          </span>
        </div>
        <div v-if="index < stepOptions.length - 1" class="step-line"></div>
        <div class="step-title">{{ item.flowName }}</div>
      </div>
    </div>
  </div>
</template>
  • 关键点:

    • 使用v-for动态生成步骤项,key绑定唯一标识flowId
    • 通过:class动态添加activeBlue类,控制激活状态的样式。
2. 脚本逻辑(<script setup>
javascript 复制代码
import { ref, watch } from 'vue'

const props = defineProps({
  stepOptions: Array,
  handleStep: Number,
  nodeStepIndex: Number
})

const currentStep = ref(props.handleStep)

// 状态判断函数
const isActive = (index) => {
  return currentStep.value === index + 1 || index + 1 === props.nodeStepIndex
}

// 图标路径生成
const getAssetUrl = (isCurrent) => {
  return new URL(`/src/assets/images/${isCurrent ? 'white_correct' : 'blue_correct'}.png`, import.meta.url).href
}

// 监听外部传入的步骤变化
watch(() => props.handleStep, (val) => {
  currentStep.value = val
})
  • 核心逻辑:

    • currentStep响应式跟踪当前激活步骤,与父组件通过propsemit双向绑定。
    • getAssetUrl函数根据步骤状态返回对应图标路径,支持动态加载。
3. 样式设计(<style scoped>
css 复制代码
.step-container {
  display: flex;
  align-items: center;
  justify-content: center;
}

.step-item {
  position: relative;
  margin-right: 127px; // 127/16rem
  width: 100%;
}

.step-circle {
  width: 48px; // 48/16rem
  height: 48px;
  border-radius: 50%;
  border: 2px solid #1181fd;
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgba(17, 129, 253, 0.1);
  &:hover {
    background: rgba(17, 129, 253, 0.24);
  }
}

.activeBlue {
  .step-circle {
    background: #1181fd;
    border-color: #1181fd;
    .step-number {
      color: #fff;
    }
  }
  .step-title {
    color: #1181fd !important;
  }
}
  • 布局技巧:

    • 使用flex实现水平排列,通过margin-right控制步骤间距。
    • 引导线利用伪元素::afterflex: 1实现自适应宽度。

4. 完整源码
xml 复制代码
<template>
  <div class="step-container">
    <div
      class="content"
      v-for="(item, index) in stepOptions"
      :key="item.flowId"
      @click="handleStepClick(item, index)"
    >
      <div
        class="step-item"
        :class="{
          activeBlue: isActive(index),
        }"
      >
        <div class="step-circle">
          <span v-if="index + 1 >= nodeStepIndex" class="step-number">{{
            index + 1
          }}</span>
          <span v-if="index + 1 < nodeStepIndex">
            <img
              class="w-[1.125rem] h-[1.125rem]"
              :src="getAssetUrl(currentStep == index + 1)"
            />
          </span>
        </div>
        <div v-if="index < stepOptions.length - 1" class="step-line"></div>
        <div class="step-title">{{ item.flowName }}</div>
      </div>
    </div>
  </div>
</template>

<script setup name="StepBar">
import { ref, watch } from 'vue'
const props = defineProps({
  stepOptions: {
    type: Array,
    default: () => [],
    example: [
      { flowId: 1, flowName: '电量填报' },
      { flowId: 2, flowName: '电费填报' },
      { flowId: 3, flowName: '审核' },
    ],
  },
  handleStep: {
    // 前端操作的节点
    type: Number,
    default: 1,
  },
  nodeStepIndex: {
    // 工单当前的节点
    type: Number,
    default: 1,
  },
})

const currentStep = ref(props.handleStep)

const emit = defineEmits(['setHandleStep'])

/**
 * 处理步骤点击事件
 * @param {Object} item - 当前步骤数据
 * @param {Number} index - 步骤索引
 */
const handleStepClick = (item, index) => {
  currentStep.value = index + 1
  emit('setHandleStep', currentStep.value, item)
}

/**
 * 判断当前步骤是否激活
 * @param {Number} index - 步骤索引
 * @returns {Boolean}
 */
const isActive = (index) => {
  return currentStep.value === index + 1 || index + 1 === props.nodeStepIndex
}

/**
 * 获取步骤图标路径
 * @param {Number} index - 步骤索引
 * @returns {String}
 */
const getAssetUrl = (isCurrent) => {
  return new URL(
    `/src/assets/images/${isCurrent ? 'white_correct' : 'blue_correct'}.png`,
    import.meta.url,
  ).href
}

watch(
  () => props.handleStep,
  (val) => {
    currentStep.value = val
  },
)
</script>

<style lang="scss" scoped>
/* 步骤条样式 */
.step-container {
  display: flex;
  align-items: center;
  justify-content: center;

  .divider-line {
    border-top: 2px solid #dde2ed;
    position: relative;
    top: -64px;
    left: 64px;
    width: 128px;
    margin: 0;
  }
  .content {
    &:last-child {
      .divider-line {
        display: none;
      }
    }
  }

  .step-item {
    position: relative;
    margin-right: 127px /* 127/16 */;
    width: 100%;
  }

  .step-circle {
    width: 48px /* 48/16 */;
    height: 48px /* 48/16 */;
    border-radius: 50%;
    border: 2px solid #1181fd;
    display: flex;
    justify-content: center;
    align-items: center;
    background: rgba(17, 129, 253, 0.1);
    margin: 0 auto;
    &:hover {
      background: rgba(17, 129, 253, 0.24);
    }
  }

  .step-circle:hover ~ .step-title {
    color: #1181fd;
  }

  .step-number {
    font-family: 'SourceHanSansCN-Medium';
    font-weight: 500;
    font-size: 20px /* 20/16 */;
    color: #1181fd;
  }

  .step-title {
    font-family: 'SourceHanSansCN-Regular';
    font-weight: 400;
    font-size: 16px /* 16/16 */;
    color: #9ca2af;
    margin-top: 16px /* 16/16 */;
    text-align: center;
    position: absolute;
    left: 0;
    right: 0;
  }
  .step-line {
    width: calc(100% - 90px);
    height: 2px /* 2/16 */;
    background-color: #dde2ed;
    position: absolute;
    top: 18px;
    right: -36px;
  }

  .onlyViewStep {
    background: rgba(17, 129, 253, 0.1);
    border-color: #1181fd;
    .step-number {
      color: #1181fd;
    }
    &:hover {
      background: rgba(17, 129, 253, 0.24);
      .step-title {
        color: #1181fd;
      }
    }
  }

  .activeBlue .step-circle {
    border-color: #1181fd;
    background: #1181fd;
    .step-number {
      color: #fff;
    }
  }
  .activeBlue .step-title {
    color: #1181fd !important;
  }

  .backActive .step-circle {
    background: rgba(17, 129, 253, 0.24) !important;
  }

  .onlyView {
    border-color: #1181fd;
    .step-circle {
      border: 2px solid #1181fd;
      border-color: #1181fd !important;
    }
    .step-number {
      color: #1181fd;
    }
    &:hover {
      .step-title {
        color: #1181fd;
      }
    }
  }

  .onlyViewActive {
    .step-circle {
      background: #1181fd;
    }
    .step-title {
      color: #1181fd !important;
    }
    .step-number {
      color: #fff;
    }
  }
}
</style>

四、应用场景示例

ini 复制代码
<template>
  <StepBar
    :stepOptions="steps"
    :handleStep="currentStep"
    :nodeStepIndex="completedStep"
    @setHandleStep="handleStepChange"
  />
</template>

<script setup>
import StepBar from '@/components/StepBar.vue'

const steps = [
  { flowId: 1, flowName: '电量填报' },
  { flowId: 2, flowName: '电费计算' },
  { flowId: 3, flowName: '审核' }
]

const currentStep = ref(1)
const completedStep = ref(1)

const handleStepChange = (newStep) => {
  currentStep.value = newStep
  completedStep.value = Math.min(newStep, 3) // 假设最多3步
}
</script>

五、总结

本文通过拆解一个Vue步骤条组件的实现细节,展示了如何结合动态渲染、状态管理和样式设计构建可复用组件。实际开发中可进一步扩展功能,如支持国际化、集成表单验证等。

该文章仅用于学习记录,不涉及商业传播。

相关推荐
生产队队长8 分钟前
Vue+SpringBoot:整合JasperReport作PDF报表,并解决中文不显示问题
vue.js·spring boot·pdf
软件技术NINI16 分钟前
html css 网页制作成品——HTML+CSS非遗文化扎染网页设计(5页)附源码
前端·css·html
fangcaojushi17 分钟前
npm常用的命令
前端·npm·node.js
阿丽塔~29 分钟前
新手小白 react-useEffect 使用场景
前端·react.js·前端框架
程序猿大波41 分钟前
基于Java,SpringBoot和Vue高考志愿填报辅助系统设计
java·vue.js·spring boot
鱼樱前端1 小时前
Rollup 在前端工程化中的核心应用解析-重新认识下Rollup
前端·javascript
m0_740154671 小时前
SpringMVC 请求和响应
java·服务器·前端
加减法原则1 小时前
探索 RAG(检索增强生成)
前端
计算机-秋大田1 小时前
基于Spring Boot的个性化商铺系统的设计与实现(LW+源码+讲解)
java·vue.js·spring boot·后端·课程设计
禁止摆烂_才浅2 小时前
前端开发小技巧 - 【CSS】- 表单控件的 placeholder 如何控制换行显示?
前端·css·html