> 文章列表 > Vue3步骤条(Steps)

Vue3步骤条(Steps)

Vue3步骤条(Steps)

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>