Vue2步骤条(Steps)
可自定义设置以下属性:
-
步骤数组(steps),类型:Array<{title?: string, description?: string}>,默认 []
-
当前选中的步骤,设置 v-model 后,Steps 变为可点击状态(v-model:current),类型:number,默认 1,从1开始计数
-
步骤条总宽度(width),类型:string|number,默认 '100%'
-
描述文本最大宽度(descMaxWidth),类型:number,默认 140px
效果如下图:
①创建步骤条组件Steps.vue:
<script setup lang="ts">
import { computed } from 'vue'
interface Step {
title?: string, // 标题
description?: string // 描述
}
const props = defineProps({
steps: { // 步骤数组
type: Array<Step>,
default: () => []
},
current: { // 当前选中的步骤(v-model),设置 v-model 后,Steps 变为可点击状态。从1开始计数
type: Number,
default: 1
},
width: { // 步骤条总宽度
type: [String, Number],
default: '100%'
},
descMaxWidth: { // 描述文本最大宽度
type: Number,
default: 140
}
})
const totalWidth = computed(() => {
if (typeof props.width === 'number') {
return props.width + 'px'
} else {
return props.width
}
})
const totalSteps = computed(() => { // 步骤总数
return props.steps.length
})
const currentStep = computed(() => {
if (props.current < 1) {
return 1
} else if (props.current > totalSteps.value + 1) {
return totalSteps.value + 1
} else {
return props.current
}
})
// 若当前选中步骤超过总步骤数,则默认选择步骤1
const emits = defineEmits(['update:current', 'change'])
function onChange (index: number) { // 点击切换选择步骤
if (currentStep.value !== index) {
emits('update:current', index)
emits('change', index)
}
}
</script>
<template>
<div class="m-steps-area" :style="`width: ${totalWidth};`">
<div class="m-steps">
<div :class="['m-steps-item',
{
'finish': currentStep > index + 1,
'process': currentStep === index + 1,
'wait': currentStep < index + 1
}
]"
v-for="(step, index) in steps" :key="index">
<div class="m-info-wrap" @click="onChange(index + 1)">
<div class="m-steps-icon">
<span class="u-num" v-if="currentStep<=index + 1">{{ index + 1 }}</span>
<svg class="u-icon" v-else viewBox="64 64 896 896" data-icon="check" aria-hidden="true" focusable="false"><path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"></path></svg>
</div>
<div class="m-steps-content">
<div class="u-steps-title">{{ step.title }}</div>
<div class="u-steps-description" v-show="step.description" :style="`max-width: ${descMaxWidth}px;`">{{ step.description }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.m-steps-area {
margin: 0px auto;
.m-steps {
display: flex;
.m-steps-item {
display: inline-block;
overflow: hidden;
font-size: 16px;
line-height: 32px;
&:not(:last-child) {
margin-right: 16px;
flex: 1; // 弹性盒模型对象的子元素都有相同的长度,且忽略它们内部的内容
.u-steps-title {
&:after {
background: #e8e8e8;
position: absolute;
top: 16px;
left: 100%;
display: block;
width: 3000px;
height: 1px;
content: "";
cursor: auto;
transition: all .3s;
}
}
}
.m-info-wrap {
display: inline-block;
.m-steps-icon {
display: inline-block;
margin-right: 8px;
width: 30px;
height: 30px;
border-radius: 50%;
text-align: center;
background: #fff;
border: 1px solid rgba(0,0,0,.25);
transition: all .3s;
.u-num {
display: inline-block;
vertical-align: top;
font-size: 16px;
line-height: 1;
margin-top: 7px;
color: rgba(0, 0, 0, .25);
transition: all .3s;
}
.u-icon {
fill: @themeColor;
width: 16px;
height: 16px;
vertical-align: top;
margin-top: 7px;
}
}
.m-steps-content {
display: inline-block;
vertical-align: top;
.u-steps-title {
position: relative;
display: inline-block;
padding-right: 16px;
color: rgba(0,0,0,.45);
transition: all .3s;
}
.u-steps-description {
font-size: 14px;
color: rgba(0,0,0,.45);
word-wrap: break-word;
transition: all .3s;
}
}
}
}
.finish {
.m-info-wrap {
cursor: pointer;
.m-steps-icon {
background: #fff;
border: 1px solid rgba(0,0,0,.25);
border-color: @themeColor;
}
.m-steps-content {
.u-steps-title {
color: rgba(0,0,0,.85);
&:after {
background: @themeColor;
}
}
.u-steps-description {
color: rgba(0,0,0,.45);
}
}
&:hover {
.m-steps-content {
.u-steps-title {
color: @themeColor;
}
.u-steps-description {
color: @themeColor;
}
}
}
}
}
.process {
.m-info-wrap {
.m-steps-icon {
background: @themeColor;
border: 1px solid rgba(0,0,0,.25);
border-color: @themeColor;
.u-num {
color: #fff;
}
}
.m-steps-content {
.u-steps-title {
font-weight: 500;
color: rgba(0,0,0,.85);
}
.u-steps-description {
color: rgba(0,0,0,.85);
}
}
}
}
.wait {
.m-info-wrap {
cursor: pointer;
&:hover {
.m-steps-icon {
border-color: @themeColor;
.u-num {
color: @themeColor;
}
}
.m-steps-content {
.u-steps-title {
color: @themeColor;
}
.u-steps-description {
color: @themeColor;
}
}
}
}
}
}
}
</style>
②在要使用的页面引入:
<script setup lang="ts">
import { Steps } from './Steps.vue'
import { ref, watch } from 'vue'
const steps = ref([
{
title: 'Step 1',
description: 'description 1'
},
{
title: 'Step 2',
description: 'description 2'
},
{
title: 'Step 3',
description: 'description 3'
},
{
title: 'Step 4',
description: 'description 4'
},
{
title: 'Step 5',
description: 'description 5'
}
])
const current = ref(3)
watch(current, (to) => {
console.log('p to:', to)
})
function onChange (index: number) { // 父组件获取切换后的选中步骤
console.log('current:', index)
}
function onPrevious () {
if (current.value > 1) {
current.value--
}
}
function onNext () {
if (steps.value.length >= current.value) {
current.value++
}
}
</script>
<template>
<div>
<h2 class="mb10">Steps 步骤条基本使用</h2>
<Steps
:steps="steps"
:width="'100%'"
:descMaxWidth="160"
:current="current"
@change="onChange" />
<h2 class="mb10">Steps 步骤条,设置(v-model: current后可点击)</h2>
<Steps
:steps="steps"
:width="'100%'"
:descMaxWidth="160"
v-model:current="current"
@change="onChange" />
<Button @click="onPrevious()" size="large" class="mt30 mr30">Previous</Button>
<Button @click="onNext()" size="large">Next</Button>
</div>
</template>
<style lang="less" scoped>
</style>