基于Vue實(shí)現(xiàn)一個(gè)"蛇形"步驟條
前言
在現(xiàn)代Web應(yīng)用中,步驟條(Step Progression)作為一種常見的UI組件,廣泛應(yīng)用于表單提交、任務(wù)進(jìn)度以及多步驟操作等場景。為了提升用戶體驗(yàn),設(shè)計(jì)師往往會采用更加直觀和有趣的方式展示步驟進(jìn)度。今天,我們將通過 Vue 實(shí)現(xiàn)一個(gè)“蛇形”步驟圖組件,這種獨(dú)特的設(shè)計(jì)方式不僅能清晰地表達(dá)步驟的完成狀態(tài),還能給用戶帶來更加流暢和動態(tài)的交互體驗(yàn)。
前幾天在做一個(gè)需求時(shí)就需要寫一個(gè)步驟圖組件,大致原型如下:
看到這個(gè),內(nèi)心無一點(diǎn)波瀾,直接去項(xiàng)目配套 Ant Desin Vue
組件庫中找對應(yīng)組件拿來改改就 ojbk 了。
But,后端同事告訴我業(yè)務(wù)場景后發(fā)現(xiàn)還有操作空間,因?yàn)?UI 組件的 Step 步驟條不太符合當(dāng)前業(yè)務(wù),里面所有步驟都是連續(xù)的,但是當(dāng)前業(yè)務(wù)場景是可以跳過其中一些步驟項(xiàng),于是打算自己寫一個(gè),最后根據(jù)后端返回的數(shù)據(jù)來決定步驟項(xiàng)是否執(zhí)行了。
customStep(V1)
customStep.vue
完整代碼:
<template> <div class="custom-step"> <div v-for="(item, index) in stepList" :key="index" class="step-item"> <div class="item-content"> <div class="step-title" @click="handleStepClick(index)"> <div class="step-num" :class="{ 'step-num-finished': item.status === 'finished' }">{{ index + 1 }} </div> <div class="setp-txt" :class="{ 'step-txt-finished': item.status === 'finished' }">{{ item.title }} </div> </div> <div class="split-line" v-if="!item.isLast" :class="{ 'split-line-finished': isFinished(index) }"></div> </div> </div> </div> </template> <script setup> import { ref, computed } from 'vue' const props = defineProps({ stepList: { type: Array, default: () => [] } }) // 步驟列表 // const stepList = ref([ // { title: '確定意向', status: 'finished', isLast: false }, // { title: '對接洽談', status: 'finished', isLast: false }, // { title: '項(xiàng)目報(bào)價(jià)', status: 'unfinished', isLast: false }, // { title: '投標(biāo)對比', status: 'unfinished', isLast: false }, // { title: '合同擬定', status: 'unfinished', isLast: false }, // { title: '轉(zhuǎn)化完成', status: 'finished', isLast: false }, // { title: '轉(zhuǎn)化完成1', status: 'finished', isLast: false }, // { title: '轉(zhuǎn)化完成2', status: 'finished', isLast: true }, // ]) const isFinished = computed(() => index => { const prevStep = props.stepList[index]; const nextStep = props.stepList[index + 1]; return prevStep.status === 'finished' && nextStep.status === 'finished'; }) const emit = defineEmits(['stepClick']) const handleStepClick = index => { emit('stepClick', index) } </script> <style lang="less" scoped> .custom-step { display: flex; align-items: center; flex-wrap: wrap; row-gap: 20px; width: 100%; padding: 0 30px; .step-item { width: calc(100% / 6); .item-content { box-sizing: border-box; display: flex; align-items: center; } .step-title { width: 80px; text-align: center; font-weight: 600; color: rgb(153, 153, 166); cursor: pointer; .step-num { box-sizing: content-box; width: 35px; margin: 0 auto; line-height: 35px; font-size: 16px; border: 3px solid #e3e8ec; border-radius: 100%; } .setp-txt { margin-top: 10px; } .step-num-finished { color: rgb(26, 188, 156); border: 3px solid rgb(26, 188, 156); } .step-txt-finished { color: rgb(26, 188, 156); } } .split-line { width: calc(100% - 80px); height: 3px; margin-top: -25px; background-color: #e3e8ec; border-radius: 5px; } .split-line-finished { background-color: rgb(26, 188, 156); } } } </style>
最后效果如下:
頁面初始渲染步驟,然后可以點(diǎn)擊某個(gè)步驟項(xiàng),進(jìn)行業(yè)務(wù)操作后刷新頁面重新渲染步驟條,這樣可以根據(jù)業(yè)務(wù)需求跳過某些步驟項(xiàng)。
But,還有事,我這固定了一行顯示6個(gè)步驟項(xiàng),超過6個(gè)會換行,但是顯示就有一點(diǎn)瑕疵,因?yàn)檎T夭季侄际菑淖蟮接?,所以超過6個(gè)步驟項(xiàng)就會這樣顯示:
1 -> 2 -> 3 -> 4 -> 5 -> 6 ->
7 -> 8 -> ...
產(chǎn)品給我說這樣看著不太連貫,想實(shí)現(xiàn)一個(gè)類似 "S" 形的,看著會連貫一些,后面找了個(gè)例子改了改,算是符合當(dāng)前需求了。
customStep(V2)
customStep_.vue
完整代碼:
<template> <div class="container"> <div v-for="(item, index) in stepList" class="grid-item" :key="index"> <div class="step" :class="{ 'step-finished': item.status === 'finished' }" @click="handleStepClick(index)"> {{ item.title }}</div> </div> </div> </template> <script setup> import { ref } from 'vue'; const props = defineProps({ stepList: { type: Array, default: () => [] } }) // 步驟列表 // const stepList = ref([ // { title: '確定意向', status: 'finished' }, // { title: '對接洽談', status: 'finished' }, // { title: '項(xiàng)目報(bào)價(jià)', status: 'unfinished' }, // { title: '投標(biāo)對比', status: 'unfinished' }, // { title: '合同擬定', status: 'unfinished' }, // { title: '轉(zhuǎn)化完成', status: 'finished' }, // { title: '制定方案', status: 'unfinished' }, // { title: '合同簽訂', status: 'finished' }, // { title: '合同跟蹤', status: 'finished' }, // { title: '合同回款', status: 'unfinished' }, // { title: '項(xiàng)目交付', status: 'unfinished' }, // { title: '項(xiàng)目驗(yàn)收', status: 'unfinished' }, // { title: '項(xiàng)目結(jié)束', status: 'unfinished' }, // ]) const emit = defineEmits(['stepClick']) const handleStepClick = index => { emit('stepClick', index) } </script> <style lang="less" scoped> @colNum: 6; // 單行排列的步驟項(xiàng)個(gè)數(shù)(2、3、4、5、6、...) @colEven: @colNum * 2; // 兩行元素?cái)?shù) @lineWidth: 35px; // 步驟間連線長度 @rowDistance: 50px; // 行間距 @colDistance: @lineWidth; // 列間距 @arrowSize: 6px; // 箭頭大小 @stepColor: #9e9e9e; // 步驟顏色 .container { width: 100%; display: grid; padding: 30px 0; grid-template-columns: repeat(@colNum, 1fr); gap: @rowDistance @colDistance; } .grid-item { position: relative; display: flex; align-items: center; justify-content: center; cursor: pointer; &::before { position: absolute; content: ''; right: -@lineWidth; width: @lineWidth; height: 0; border-top: 1px dashed @stepColor; } &::after { content: ''; position: absolute; right: (-@colDistance / 2); transform: translateX(50%); border-top: (@arrowSize / 1.4) solid transparent; border-left: @arrowSize solid @stepColor; border-bottom: (@arrowSize / 1.4) solid transparent; } // 給每行最后一個(gè)步驟(除最后一行)添加向下的連接箭頭 &:nth-child(@{colNum}n) { &:not(:last-child) { .step { &::before { content: ''; position: absolute; left: 50%; bottom: -(@rowDistance / 2); height: @lineWidth; border-left: 1px dashed @stepColor; transform: translate(-50%, 50%); } &::after { content: ''; position: absolute; left: 50%; bottom: -(@rowDistance / 2); border-top: @arrowSize solid @stepColor; border-left: (@arrowSize / 1.4) solid transparent; border-right: (@arrowSize / 1.4) solid transparent; transform: translate(-50%, 50%); } } } } each(range(@colEven), { &:nth-child(@{colEven}n+@{value}) { @isEvenLine: boolean(@value > @colNum); @modNum: mod(@value, @colEven); // 余數(shù) 1、2、3、4、5、0 /** 偶數(shù)行旋轉(zhuǎn)箭頭,步驟倒序排列(使用transform交換位置) */ & when (@isEvenLine) { @transN: (@colNum + 1 + @colEven - @value - @value); transform: translateX(calc(@transN * 100% + @transN * @colDistance)); &::after { transform: translateX(50%) rotate(180deg) !important; // 旋轉(zhuǎn)箭頭 } } // 最右排(n & n + 1 位)隱藏多余的箭頭(如果container設(shè)置了overflow:hidden 則不用處理) & when (@modNum=@colNum), (@modNum=@colNum+1) { &::before, &::after { display: none; } } // 最后一個(gè)步驟在奇數(shù)行 需要隱藏連線箭頭 & when not (@isEvenLine) { &:last-child { &::before, &::after { display: none; } } } } }) } .step { position: relative; width: 100px; line-height: 40px; font-size: 16px; text-align: center; border-radius: 5px; color: #9e9e9e; border: 2px solid #9e9e9e; } .step-finished { background-color: #4caf50; color: #fff; border: 2px solid #4caf50; } </style>
兩種對比效果:
到此這篇關(guān)于基于Vue實(shí)現(xiàn)一個(gè)"蛇形"步驟條的文章就介紹到這了,更多相關(guān)Vue步驟條內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Vue組件之間的數(shù)據(jù)通信實(shí)例
本篇文章主要介紹了詳解Vue組件之間的數(shù)據(jù)通信實(shí)例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-06-06vue結(jié)合axios實(shí)現(xiàn)restful風(fēng)格的四種請求方式
這篇文章主要介紹了vue結(jié)合axios實(shí)現(xiàn)restful風(fēng)格的四種請求方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06實(shí)用的 vue tags 創(chuàng)建緩存導(dǎo)航的過程實(shí)現(xiàn)
這篇文章主要介紹了實(shí)用的 vue tags 創(chuàng)建緩存導(dǎo)航的過程實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12