通用递进步骤组件的核心原理
一、状态驱动的步骤管理
组件通过响应式数据管理步骤状态,核心状态包括:
-
步骤完成状态 :用数组
steps存储每个步骤的完成标记(completed: true/false),例如:steps = [{ completed: false }, { completed: false }, ...] -
当前步骤索引 :用
currentStep记录当前显示的步骤位置,通过修改该值切换步骤视图。
二、递进规则的权限控制
实现 "必须完成上一步才能进入下一步" 的核心逻辑是前置步骤校验:
- 定义
canJump(index)方法,判断目标步骤是否可访问:- 第一步默认可访问(
index === 0); - 后续步骤需满足 "上一步已完成"(
steps[index - 1].completed)。
- 第一步默认可访问(
- 点击步骤导航时,通过该方法拦截非法跳转(如未完成步骤 1 时无法直接跳转到步骤 3)。
三、视图与状态的联动
- 导航样式动态绑定 :
- 根据
currentStep标记当前激活步骤; - 根据
completed标记完成状态(显示对勾、绿色样式); - 根据
canJump标记禁用状态(灰色样式、无法点击)。
- 根据
- 内容区域条件渲染 :
- 通过
v-if="currentStep === index"匹配当前步骤,仅渲染对应内容; - 利用插槽(
slot)实现内容自定义,保持组件通用性。
- 通过
四、步骤推进的逻辑闭环
- 下一步操作 :点击 "下一步" 时,先将当前步骤标记为
completed: true,再将currentStep + 1进入下一级; - 完成操作:最后一步点击 "完成" 时,标记最终步骤完成并触发全局完成事件;
- 状态持久化:所有步骤状态保存在响应式数据中,确保页面刷新或操作后状态不丢失。
五、组件解耦与复用
通过插槽机制分离 "步骤框架" 与 "业务内容":
- 组件仅提供递进逻辑和导航 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></template> <script setup> import { ref, computed } from "vue";<!-- 步骤内容 --> <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>// 初始化步骤数据
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");
};// 事件派发
<style scoped> .step-container { padding: 20rpx; }
const emit = defineEmits(["complete"]);
</script>/* 步骤导航 */
.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>