通用递进步骤组件的核心原理
一、状态驱动的步骤管理
组件通过响应式数据管理步骤状态,核心状态包括:
- 步骤完成状态:用数组
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>
<!-- 步骤内容 -->
<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>
1078

被折叠的 条评论
为什么被折叠?



