uniapp通用递进式步骤组件

通用递进步骤组件的核心原理

一、状态驱动的步骤管理

组件通过响应式数据管理步骤状态,核心状态包括:

  1. 步骤完成状态 :用数组steps存储每个步骤的完成标记(completed: true/false),例如:

    复制代码
    steps = [{ completed: false }, { completed: false }, ...]
  2. 当前步骤索引 :用currentStep记录当前显示的步骤位置,通过修改该值切换步骤视图。

二、递进规则的权限控制

实现 "必须完成上一步才能进入下一步" 的核心逻辑是前置步骤校验

  • 定义canJump(index)方法,判断目标步骤是否可访问:
    • 第一步默认可访问(index === 0);
    • 后续步骤需满足 "上一步已完成"(steps[index - 1].completed)。
  • 点击步骤导航时,通过该方法拦截非法跳转(如未完成步骤 1 时无法直接跳转到步骤 3)。
三、视图与状态的联动
  1. 导航样式动态绑定
    • 根据currentStep标记当前激活步骤;
    • 根据completed标记完成状态(显示对勾、绿色样式);
    • 根据canJump标记禁用状态(灰色样式、无法点击)。
  2. 内容区域条件渲染
    • 通过v-if="currentStep === index"匹配当前步骤,仅渲染对应内容;
    • 利用插槽(slot)实现内容自定义,保持组件通用性。
四、步骤推进的逻辑闭环
  1. 下一步操作 :点击 "下一步" 时,先将当前步骤标记为completed: true,再将currentStep + 1进入下一级;
  2. 完成操作:最后一步点击 "完成" 时,标记最终步骤完成并触发全局完成事件;
  3. 状态持久化:所有步骤状态保存在响应式数据中,确保页面刷新或操作后状态不丢失。
五、组件解耦与复用

通过插槽机制分离 "步骤框架" 与 "业务内容":

  • 组件仅提供递进逻辑和导航 UI,具体步骤内容由父组件通过插槽传入;
  • 父组件无需关心递进规则,只需调用组件暴露的next/finish方法推进流程。

总结

该组件的核心是 **"状态管理 + 规则校验 + 视图联动"** 的三层架构:

  • 用响应式状态记录步骤进度;

  • 用前置校验保证递进规则;

  • 用条件渲染和样式绑定实现视图与状态同步。这种设计既保证了递进逻辑的严谨性,又通过插槽实现了业务内容的灵活定制,达到 "通用框架 + 个性化内容" 的复用效果。

    <template> <view class="step-container"> <view class="step-nav"> <view v-for="(step, index) in steps" :key="index" class="step-item" :class="{ active: currentStep === index, done: step.completed, disabled: !canJump(index) }" @tap="changeStep(index)" > <view class="step-num">{{ step.completed ? '✓' : index + 1 }}</view> <view class="step-text">步骤{{ index + 1 }}</view> <view class="step-line" v-if="index < steps.length - 1"></view> </view> </view>
    复制代码
      <!-- 步骤内容 -->
      <view class="step-content">
        <view v-if="currentStep === 0" class="step-panel">
          <slot name="step1" :next="goNext" />
        </view>
        <view v-if="currentStep === 1" class="step-panel">
          <slot name="step2" :next="goNext" />
        </view>
        <view v-if="currentStep === 2" class="step-panel">
          <slot name="step3" :next="goNext" />
        </view>
        <view v-if="currentStep === 3" class="step-panel">
          <slot name="step4" :finish="finishAll" />
        </view>
      </view>
    </view>
    </template> <script setup> import { ref, computed } from "vue";

    // 初始化步骤数据
    const steps = ref([
    { completed: false },
    { completed: false },
    { completed: false },
    { completed: false }
    ]);
    const currentStep = ref(0);

    // 判断是否可跳转至目标步骤
    const canJump = (index) => {
    if (index === 0) return true;
    return steps.value[index - 1].completed;
    };

    // 切换步骤
    const changeStep = (index) => {
    if (canJump(index)) {
    currentStep.value = index;
    }
    };

    // 下一步(标记当前步骤完成)
    const goNext = () => {
    steps.value[currentStep.value].completed = true;
    if (currentStep.value < steps.length - 1) {
    currentStep.value += 1;
    }
    };

    // 完成所有步骤
    const finishAll = () => {
    steps.value[currentStep.value].completed = true;
    emit("complete");
    };

    // 事件派发
    const emit = defineEmits(["complete"]);
    </script>

    <style scoped> .step-container { padding: 20rpx; }

    /* 步骤导航 */
    .step-nav {
    display: flex;
    align-items: center;
    margin-bottom: 40rpx;
    }

    .step-item {
    display: flex;
    align-items: center;
    flex: 1;
    }

    .step-num {
    width: 40rpx;
    height: 40rpx;
    border-radius: 50%;
    background: #eee;
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 24rpx;
    }

    .step-text {
    font-size: 24rpx;
    color: #999;
    margin: 0 10rpx;
    }

    .step-line {
    flex: 1;
    height: 2rpx;
    background: #eee;
    }

    /* 状态样式 */
    .step-item.active .step-num {
    background: #138aff;
    }
    .step-item.active .step-text {
    color: #138aff;
    }
    .step-item.done .step-num {
    background: #07c160;
    }
    .step-item.done .step-text {
    color: #07c160;
    }
    .step-item.done .step-line {
    background: #07c160;
    }
    .step-item.disabled .step-num {
    background: #f5f5f5;
    color: #ccc;
    }
    .step-item.disabled .step-text {
    color: #ccc;
    }

    /* 内容区域 */
    .step-panel {
    padding: 20rpx 0;
    }
    </style>

使用示例

复制代码
<template>
  <step-container @complete="handleComplete">
    <!-- 步骤1 -->
    <template #step1="{ next }">
      <view>
        <text>步骤1内容</text>
        <button @tap="next">下一步</button>
      </view>
    </template>
    
    <!-- 步骤2 -->
    <template #step2="{ next }">
      <view>
        <text>步骤2内容</text>
        <button @tap="next">下一步</button>
      </view>
    </template>
    
    <!-- 步骤3 -->
    <template #step3="{ next }">
      <view>
        <text>步骤3内容</text>
        <button @tap="next">下一步</button>
      </view>
    </template>
    
    <!-- 步骤4 -->
    <template #step4="{ finish }">
      <view>
        <text>步骤4内容</text>
        <button @tap="finish">完成</button>
      </view>
    </template>
  </step-container>
</template>

<script setup>
import StepContainer from "@/components/StepContainer.vue";

const handleComplete = () => {
  uni.showToast({
    title: "所有步骤完成",
    icon: "success"
  });
};
</script>
相关推荐
问道飞鱼38 分钟前
【前端知识】从前端请求到后端返回:Gzip压缩全链路配置指南
前端·状态模式·gzip·请求头
小杨累了44 分钟前
CSS Keyframes 实现 Vue 无缝无限轮播
前端
小扎仙森1 小时前
html引导页
前端·html
蜗牛攻城狮1 小时前
JavaScript 尾递归(Tail Recursion)详解
开发语言·javascript·ecmascript
坐吃山猪1 小时前
Electron04-系统通知小闹钟
开发语言·javascript·ecmascript
小飞侠在吗1 小时前
vue toRefs 与 toRef
前端·javascript·vue.js
csuzhucong1 小时前
斜转魔方、斜转扭曲魔方
前端·c++·算法
老华带你飞1 小时前
房屋租赁管理|基于springboot + vue房屋租赁管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·毕设
燃烧的土豆1 小时前
100¥ 实现的React项目 Keep-Alive 缓存控件
前端·react.js·ai编程