Steps 步骤条
概述
Steps 步骤条是一个用于引导用户按照流程完成任务的分步导航组件。它通过可视化的方式展示任务进度,帮助用户了解当前所处的步骤位置以及整个流程的完成情况。
主要特性
- 流程可视化:清晰展示任务的各个步骤和当前进度
- 状态管理:支持多种步骤状态(等待、进行中、完成、错误等)
- 布局灵活:支持水平和垂直两种布局方向
- 样式丰富:提供普通和简洁两种风格
- 高度定制:支持自定义图标、标题和描述
- 响应式设计:适配不同屏幕尺寸
适用场景
- 表单向导:多步骤表单填写流程
- 订单流程:购物车、确认订单、支付、完成等步骤
- 注册流程:账号信息、验证邮箱、完善资料等
- 安装向导:软件或系统的安装配置流程
- 审批流程:文档审批、工作流程等
- 教程指导:新手引导、操作教程等
学习目标
基础知识
- 掌握 Steps 的基本概念和使用场景
- 学会基础步骤条的创建和配置
- 了解步骤状态的管理方法
- 掌握水平和垂直布局的使用
进阶技能
- 学会自定义步骤图标和样式
- 掌握简洁风格的应用场景
- 了解步骤条的交互设计
- 学会响应式步骤条的实现
实战应用
- 能够设计完整的多步骤流程
- 掌握步骤条与表单的集成
- 了解步骤条的性能优化
- 学会步骤条的可访问性优化
基础用法
基础用法
简单的步骤条。
设置 active
属性,接受一个 Number,表明步骤的 index,从 0 开始。需要定宽的步骤条时,设置 space
属性即可,它接受 Number,单位为 px,如果不设置,则为自适应。设置 finish-status
属性可以改变已经完成的步骤的状态。
<template>
<el-steps :active="active" finish-status="success">
<el-step title="Step 1" />
<el-step title="Step 2" />
<el-step title="Step 3" />
</el-steps>
<el-button style="margin-top: 12px" @click="next">Next step</el-button>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const active = ref(0)
const next = () => {
if (active.value++ > 2) active.value = 0
}
</script>
含状态的步骤条
每一步骤显示出该步骤的状态。
也可以使用 title
具名插槽,可以用 slot 的方式来取代属性的设置。
<template>
<el-steps :space="200" :active="1" finish-status="success">
<el-step title="Done" />
<el-step title="Processing" />
<el-step title="Step 3" />
</el-steps>
</template>
居中的步骤条
标题和描述可以居中。
<template>
<el-steps :active="2" align-center>
<el-step title="Step 1" description="Some description" />
<el-step title="Step 2" description="Some description" />
<el-step title="Step 3" description="Some description" />
<el-step title="Step 4" description="Some description" />
</el-steps>
</template>
带描述的步骤条
每一步都有描述。
<template>
<el-steps :active="1">
<el-step title="Step 1" description="Some description" />
<el-step title="Step 2" description="Some description" />
<el-step title="Step 3" description="Some description" />
</el-steps>
</template>
带图标的步骤条
可以在步骤栏中使用各种自定义图标。
通过 icon
属性来设置图标,图标的类型可以参考 Icon 组件的文档,除此以外,还能通过具名 slot 来使用自定义的图标。
<template>
<el-steps :active="1">
<el-step title="Step 1" :icon="Edit" />
<el-step title="Step 2" :icon="Upload" />
<el-step title="Step 3" :icon="Picture" />
</el-steps>
</template>
<script lang="ts" setup>
import { Edit, Picture, Upload } from '@element-plus/icons-vue'
</script>
垂直的步骤条
垂直方向的步骤条。
只需要在 el-steps
元素中设置 direction
属性为 vertical
即可。
<template>
<div style="height: 300px">
<el-steps direction="vertical" :active="1">
<el-step title="Step 1" />
<el-step title="Step 2" />
<el-step title="Step 3" />
</el-steps>
</div>
</template>
简洁风格的步骤条
设置 simple
可应用简洁风格,该条件下 align-center
/ description
/ direction
/ space
都将失效。
<template>
<el-steps :space="200" :active="1" simple>
<el-step title="Step 1" :icon="Edit" />
<el-step title="Step 2" :icon="UploadFilled" />
<el-step title="Step 3" :icon="Picture" />
</el-steps>
<el-steps :active="1" finish-status="success" simple style="margin-top: 20px">
<el-step title="Step 1" />
<el-step title="Step 2" />
<el-step title="Step 3" />
</el-steps>
</template>
<script lang="ts" setup>
import { Edit, Picture, UploadFilled } from '@element-plus/icons-vue'
</script>
插槽的使用
在 Vue 3.0 项目中使用插槽的方式。
<template>
<el-steps :active="2" direction="vertical" align-center>
<el-step title="步骤1" description="描述">
<template v-slot:description>
<span>这是测试1</span>
</template>
</el-step>
<el-step title="步骤2" description="描述">
<template v-slot:description>
<span>这是测试2</span>
</template>
</el-step>
</el-steps>
</template>
实际应用示例
订单提交流程
电商网站的订单提交步骤条:
<template>
<div class="order-process">
<el-steps :active="currentStep" finish-status="success" align-center>
<el-step
title="购物车"
description="确认商品信息"
:icon="ShoppingCart"
/>
<el-step
title="确认订单"
description="填写收货信息"
:icon="Document"
/>
<el-step
title="支付"
description="选择支付方式"
:icon="CreditCard"
/>
<el-step
title="完成"
description="订单提交成功"
:icon="Check"
/>
</el-steps>
<!-- 步骤内容 -->
<div class="step-content">
<div v-if="currentStep === 0" class="cart-step">
<h3>购物车</h3>
<div class="cart-items">
<div v-for="item in cartItems" :key="item.id" class="cart-item">
<el-image :src="item.image" class="item-image" />
<div class="item-info">
<h4>{{ item.name }}</h4>
<p>价格:¥{{ item.price }}</p>
<el-input-number v-model="item.quantity" :min="1" size="small" />
</div>
</div>
</div>
<div class="step-actions">
<el-button type="primary" @click="nextStep">确认商品</el-button>
</div>
</div>
<div v-if="currentStep === 1" class="order-step">
<h3>确认订单</h3>
<el-form :model="orderForm" label-width="100px">
<el-form-item label="收货人">
<el-input v-model="orderForm.receiver" placeholder="请输入收货人姓名" />
</el-form-item>
<el-form-item label="联系电话">
<el-input v-model="orderForm.phone" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item label="收货地址">
<el-input
v-model="orderForm.address"
type="textarea"
placeholder="请输入详细地址"
/>
</el-form-item>
</el-form>
<div class="step-actions">
<el-button @click="prevStep">返回</el-button>
<el-button type="primary" @click="nextStep">确认订单</el-button>
</div>
</div>
<div v-if="currentStep === 2" class="payment-step">
<h3>选择支付方式</h3>
<el-radio-group v-model="paymentMethod" class="payment-methods">
<el-radio label="alipay" class="payment-option">
<div class="payment-info">
<span class="payment-name">支付宝</span>
<span class="payment-desc">推荐使用支付宝快捷支付</span>
</div>
</el-radio>
<el-radio label="wechat" class="payment-option">
<div class="payment-info">
<span class="payment-name">微信支付</span>
<span class="payment-desc">使用微信扫码支付</span>
</div>
</el-radio>
<el-radio label="bank" class="payment-option">
<div class="payment-info">
<span class="payment-name">银行卡</span>
<span class="payment-desc">支持各大银行储蓄卡及信用卡</span>
</div>
</el-radio>
</el-radio-group>
<div class="order-summary">
<p>订单总额:<span class="total-price">¥{{ totalPrice }}</span></p>
</div>
<div class="step-actions">
<el-button @click="prevStep">返回</el-button>
<el-button type="primary" @click="submitOrder" :loading="submitting">
立即支付
</el-button>
</div>
</div>
<div v-if="currentStep === 3" class="success-step">
<div class="success-content">
<el-icon class="success-icon" color="#67c23a" :size="60">
<Check />
</el-icon>
<h3>订单提交成功!</h3>
<p>订单号:{{ orderNumber }}</p>
<p>我们将尽快为您处理订单</p>
</div>
<div class="step-actions">
<el-button @click="goToOrders">查看订单</el-button>
<el-button type="primary" @click="continueShopping">继续购物</el-button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { useRouter } from 'vue-router'
import { ShoppingCart, Document, CreditCard, Check } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
const router = useRouter()
const currentStep = ref(0)
const submitting = ref(false)
const paymentMethod = ref('alipay')
const orderNumber = ref('')
const cartItems = reactive([
{
id: 1,
name: 'iPhone 15 Pro',
price: 7999,
quantity: 1,
image: '/images/iphone15.jpg'
},
{
id: 2,
name: 'AirPods Pro',
price: 1999,
quantity: 1,
image: '/images/airpods.jpg'
}
])
const orderForm = reactive({
receiver: '',
phone: '',
address: ''
})
const totalPrice = computed(() => {
return cartItems.reduce((total, item) => total + item.price * item.quantity, 0)
})
const nextStep = () => {
if (currentStep.value < 3) {
currentStep.value++
}
}
const prevStep = () => {
if (currentStep.value > 0) {
currentStep.value--
}
}
const submitOrder = async () => {
submitting.value = true
try {
// 模拟提交订单
await new Promise(resolve => setTimeout(resolve, 2000))
orderNumber.value = 'ORD' + Date.now()
nextStep()
ElMessage.success('订单提交成功!')
} catch (error) {
ElMessage.error('订单提交失败,请重试')
} finally {
submitting.value = false
}
}
const goToOrders = () => {
router.push('/orders')
}
const continueShopping = () => {
router.push('/')
}
</script>
<style scoped>
.order-process {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.step-content {
margin-top: 40px;
padding: 30px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.cart-items {
margin: 20px 0;
}
.cart-item {
display: flex;
align-items: center;
padding: 15px;
border: 1px solid #ebeef5;
border-radius: 6px;
margin-bottom: 10px;
}
.item-image {
width: 60px;
height: 60px;
margin-right: 15px;
}
.item-info {
flex: 1;
}
.item-info h4 {
margin: 0 0 5px 0;
color: #303133;
}
.payment-methods {
margin: 20px 0;
}
.payment-option {
display: block;
width: 100%;
padding: 15px;
border: 1px solid #ebeef5;
border-radius: 6px;
margin-bottom: 10px;
}
.payment-info {
display: flex;
flex-direction: column;
margin-left: 10px;
}
.payment-name {
font-weight: 500;
color: #303133;
}
.payment-desc {
font-size: 12px;
color: #909399;
margin-top: 2px;
}
.order-summary {
text-align: right;
margin: 20px 0;
padding: 15px;
background: #f5f7fa;
border-radius: 6px;
}
.total-price {
font-size: 18px;
font-weight: 600;
color: #f56c6c;
}
.success-content {
text-align: center;
padding: 40px 0;
}
.success-icon {
margin-bottom: 20px;
}
.step-actions {
text-align: center;
margin-top: 30px;
}
.step-actions .el-button {
margin: 0 10px;
}
</style>
用户注册向导
多步骤用户注册流程:
<template>
<div class="register-wizard">
<el-steps :active="currentStep" direction="vertical" class="register-steps">
<el-step
title="账号信息"
description="设置登录账号和密码"
:icon="User"
:status="getStepStatus(0)"
/>
<el-step
title="个人信息"
description="完善个人基本信息"
:icon="UserFilled"
:status="getStepStatus(1)"
/>
<el-step
title="邮箱验证"
description="验证邮箱地址"
:icon="Message"
:status="getStepStatus(2)"
/>
<el-step
title="完成注册"
description="注册成功"
:icon="Check"
:status="getStepStatus(3)"
/>
</el-steps>
<div class="wizard-content">
<!-- 步骤1:账号信息 -->
<div v-show="currentStep === 0" class="step-panel">
<h3>设置账号信息</h3>
<el-form :model="accountForm" :rules="accountRules" ref="accountFormRef">
<el-form-item label="用户名" prop="username">
<el-input v-model="accountForm.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="accountForm.password"
type="password"
placeholder="请输入密码"
show-password
/>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input
v-model="accountForm.confirmPassword"
type="password"
placeholder="请再次输入密码"
show-password
/>
</el-form-item>
</el-form>
<div class="step-actions">
<el-button type="primary" @click="validateAndNext('accountFormRef')">
下一步
</el-button>
</div>
</div>
<!-- 步骤2:个人信息 -->
<div v-show="currentStep === 1" class="step-panel">
<h3>完善个人信息</h3>
<el-form :model="profileForm" :rules="profileRules" ref="profileFormRef">
<el-form-item label="真实姓名" prop="realName">
<el-input v-model="profileForm.realName" placeholder="请输入真实姓名" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="profileForm.phone" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="profileForm.email" placeholder="请输入邮箱地址" />
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="profileForm.gender">
<el-radio label="male">男</el-radio>
<el-radio label="female">女</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<div class="step-actions">
<el-button @click="prevStep">上一步</el-button>
<el-button type="primary" @click="validateAndNext('profileFormRef')">
下一步
</el-button>
</div>
</div>
<!-- 步骤3:邮箱验证 -->
<div v-show="currentStep === 2" class="step-panel">
<h3>验证邮箱</h3>
<p>我们已向 <strong>{{ profileForm.email }}</strong> 发送了验证码</p>
<el-form :model="verifyForm" ref="verifyFormRef">
<el-form-item label="验证码" prop="code">
<div class="verify-input">
<el-input
v-model="verifyForm.code"
placeholder="请输入6位验证码"
maxlength="6"
/>
<el-button
@click="sendVerifyCode"
:disabled="countdown > 0"
class="send-code-btn"
>
{{ countdown > 0 ? `${countdown}s后重发` : '发送验证码' }}
</el-button>
</div>
</el-form-item>
</el-form>
<div class="step-actions">
<el-button @click="prevStep">上一步</el-button>
<el-button type="primary" @click="verifyEmail" :loading="verifying">
验证邮箱
</el-button>
</div>
</div>
<!-- 步骤4:完成注册 -->
<div v-show="currentStep === 3" class="step-panel">
<div class="success-content">
<el-icon class="success-icon" color="#67c23a" :size="80">
<Check />
</el-icon>
<h3>注册成功!</h3>
<p>欢迎加入我们,{{ profileForm.realName }}!</p>
<p>您现在可以使用账号 <strong>{{ accountForm.username }}</strong> 登录系统</p>
</div>
<div class="step-actions">
<el-button type="primary" @click="goToLogin">立即登录</el-button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { User, UserFilled, Message, Check } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
const router = useRouter()
const currentStep = ref(0)
const verifying = ref(false)
const countdown = ref(0)
const accountForm = reactive({
username: '',
password: '',
confirmPassword: ''
})
const profileForm = reactive({
realName: '',
phone: '',
email: '',
gender: 'male'
})
const verifyForm = reactive({
code: ''
})
const accountRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '用户名长度在 3 到 20 个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码长度不能少于 6 位', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请确认密码', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value !== accountForm.password) {
callback(new Error('两次输入密码不一致'))
} else {
callback()
}
},
trigger: 'blur'
}
]
}
const profileRules = {
realName: [
{ required: true, message: '请输入真实姓名', trigger: 'blur' }
],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
]
}
const getStepStatus = (step) => {
if (step < currentStep.value) return 'finish'
if (step === currentStep.value) return 'process'
return 'wait'
}
const validateAndNext = async (formRef) => {
const form = formRef === 'accountFormRef' ? accountFormRef.value : profileFormRef.value
try {
await form.validate()
nextStep()
} catch (error) {
ElMessage.error('请完善必填信息')
}
}
const nextStep = () => {
if (currentStep.value < 3) {
currentStep.value++
}
}
const prevStep = () => {
if (currentStep.value > 0) {
currentStep.value--
}
}
const sendVerifyCode = () => {
countdown.value = 60
const timer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
clearInterval(timer)
}
}, 1000)
ElMessage.success('验证码已发送')
}
const verifyEmail = async () => {
if (!verifyForm.code) {
ElMessage.error('请输入验证码')
return
}
verifying.value = true
try {
// 模拟验证
await new Promise(resolve => setTimeout(resolve, 1500))
nextStep()
ElMessage.success('邮箱验证成功!')
} catch (error) {
ElMessage.error('验证码错误')
} finally {
verifying.value = false
}
}
const goToLogin = () => {
router.push('/login')
}
</script>
<style scoped>
.register-wizard {
display: flex;
max-width: 1000px;
margin: 0 auto;
padding: 20px;
gap: 40px;
}
.register-steps {
width: 300px;
flex-shrink: 0;
}
.wizard-content {
flex: 1;
background: #fff;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.step-panel h3 {
margin: 0 0 20px 0;
color: #303133;
}
.verify-input {
display: flex;
gap: 10px;
}
.send-code-btn {
white-space: nowrap;
}
.success-content {
text-align: center;
padding: 40px 0;
}
.success-icon {
margin-bottom: 20px;
}
.step-actions {
text-align: center;
margin-top: 30px;
}
.step-actions .el-button {
margin: 0 10px;
}
</style>
API
Steps Attributes
属性名 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
space | 每个 step 的间距,不填写将自适应间距。支持百分比。 | number / string | — | — |
direction | 显示方向 | string | vertical/horizontal | horizontal |
active | 设置当前激活步骤 | number | — | 0 |
process-status | 设置当前步骤的状态 | string | wait / process / finish / error / success | process |
finish-status | 设置结束步骤的状态 | string | wait / process / finish / error / success | finish |
align-center | 进行居中对齐 | boolean | — | false |
simple | 是否应用简洁风格 | boolean | — | false |
Steps Slots
插槽名 | 说明 | 子标签 |
---|---|---|
— | 默认插槽 | Step |
Step Attributes
属性名 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
title | 标题 | string | — | — |
description | 描述文案 | string | — | — |
icon | Step 组件的自定义图标。也支持 slot 方式写入 | string / Component | — | — |
status | 设置当前步骤的状态,不设置则根据 steps 确定状态 | string | wait / process / finish / error / success | — |
Step Slots
插槽名 | 说明 |
---|---|
icon | 自定义图标 |
title | 自定义标题 |
description | 自定义描述文案 |
最佳实践
- 步骤数量:步骤不得少于 2 步,建议不超过 6 步以保持清晰
- 状态管理:合理使用
process-status
和finish-status
来表示不同的步骤状态 - 响应式设计:在移动端考虑使用垂直布局或简洁风格
- 图标选择:选择语义明确的图标来增强用户理解
- 描述信息:为复杂流程提供必要的描述信息
- 交互反馈:可以通过点击事件实现步骤间的跳转
常见问题
Q: 如何实现步骤条的点击跳转?
A: 可以通过 @click.native
事件监听步骤的点击,然后更新 active
属性值。
Q: Vue 3.0 中如何使用插槽?
A: 在 Vue 3.0 中需要使用 <template v-slot:slotName>
的方式,不能再使用 slot-scope
。
Q: 简洁风格下哪些属性会失效?
A: 设置 simple
为 true 时,align-center
、description
、direction
、space
属性都将失效。
Q: 如何自定义步骤条的样式?
A: 可以通过 CSS 覆盖默认样式,如修改 .el-steps
、.el-step__head
、.el-step__title
等类名的样式。
总结
Steps 步骤条是一个功能强大的流程导航组件,具有以下特点:
- 流程清晰:通过可视化方式展示任务进度和步骤关系
- 状态丰富:支持多种步骤状态,准确反映当前进度
- 布局灵活:支持水平和垂直布局,适应不同场景
- 高度定制:支持自定义图标、标题、描述等内容
- 交互友好:可配合表单验证实现智能步骤跳转
- 响应式设计:在不同设备上都能提供良好的用户体验
适用于多步骤流程的场景,如表单向导、订单流程、注册流程等。通过合理的步骤设计和状态管理,可以大大提升用户完成复杂任务的体验。