index.vue
<template>
<div class="step">
<div
v-for="(item, index) in stepList"
:key="index"
class="step-item"
:class="{ 'active': index === currentStep, 'clickable': item.status !== 'wait', 'unclickable': item.status === 'wait' }"
@click="selectStep(index)"
>
<div class="step-item-flex">
<div class="step-icon">
<Icon
v-if="item.status === 'success'"
:size="24"
color="success"
>
<Presets.Success />
</Icon>
<div
v-if="item.status === 'process'"
class="process-style"
>
<Text class="process-style-index">{{ index + 1 }}</Text>
</div>
<Icon
v-if="item.status === 'danger'"
:size="24"
color="danger"
>
<Presets.Danger />
</Icon>
<div
v-if="item.status === 'wait'"
class="nostart-style"
>
<Text class="nostart-style-index">{{ index + 1 }}</Text>
</div>
</div>
<div class="step-title-msg">
<Text
class="step-text-title"
color="text-title"
bold
>{{ item.name }}</Text>
<div><Text
class="msg-text"
color="text-description"
ellipsis
>{{ item.content }}</Text><Link
v-if="currentStep === ZERO && index === ZERO"
@click.stop="setClickFn"
>设置</Link></div>
</div>
<!-- 每一步之间 -->
<span
v-if="index < stepList.length - 1"
class="step-divider"
>
<Icon
:icon="DoubleRight"
color="text-description"
/>
</span>
</div>
<div
v-if="item.status"
class="step-underline"
:style="{ 'background-color': underlineColor(index, item.status) }"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps, defineEmits } from 'vue'
import { Icon, Text, Link } from '@antv'
import { Presets, DoubleRight } from '@icons'
import { StepsProps, StepsStatus, ZERO } from './data-source'
const props = withDefaults(
defineProps<{
stepList: StepsProps['steps']
currentStep: number
loading?: boolean
}>(),
{
stepList: () => [],
currentStep: 0,
loading: false,
},
)
const emits = defineEmits(['handleStepNodeAction', 'setCommitId'])
const selectStep = (index: number) => {
if (props.stepList[index].status !== 'wait') {
emits('handleStepNodeAction', index)
}
}
const underlineColor = (index: number, state: StepsStatus) => {
if (index === props.currentStep) {
if (state === 'success') {
return '#00AB46'
} if (state === 'process') {
return '#386BFF'
} if (state === 'danger') {
return '#FB3367'
}
return '#F3F3F6'
// 当前选中的项下划线加深
}
if (state === 'success') {
return '#E1FAEB'
} if (state === 'process') {
return '#ECF0FF'
} if (state === 'danger') {
return '#FFECF2'
}
return '#F3F3F6'
}
const setClickFn = () => {
emits('setCommitId')
}
</script>
<style lang="stylus" scoped>
.step
display grid
grid-template-columns repeat(4, 1fr)
gap 32px
width 100%
.step-item
min-width 214px
position relative
display flex
flex-direction column
align-items center
cursor pointer
&.unclickable
cursor not-allowed
.step-item-flex
position relative
width 100%
display flex
align-items center
margin-bottom 6px
padding 4px 8px
.step-icon
width 32px
display flex
justify-content space-around
align-items center
margin 0 8px
.process-style
width 26px
height 26px
border-radius 50%
background-color #ECF0FF
display flex
align-items center
justify-content center
.process-style-index
color #386BFF
text-align center
font-family Inter
font-size 16px
font-style normal
font-weight 600
line-height 24px
.nostart-style
width 26px
height 26px
border-radius 50%
background-color #F3F3F6
display flex
align-items center
justify-content center
.nostart-style-index
color var(--Text-placeholder, rgba(12, 1, 12, 0.32))
text-align center
font-family Inter
font-size 16px
font-style normal
font-weight 600
line-height 24px
.step-title-msg
flex 1
padding 4px 8px 4px 0
white-space nowrap
.step-text-title
font-size 16px
margin-bottom 4px
.msg-text
width 80%
margin-right 4px
.active
font-weight bold
.clickable:hover
background-color rgba(211, 211, 211, 0.5)
border-radius 4px
.step-divider
position absolute
text-align center
padding 0 8px
min-width 32px
top 50%
transform translateY(-50%)
right -32px
.step-underline
position absolute
bottom -11px
width 100%
height 2px
background-color lightgray
</style>
data-source
import { toast } from ''
export const ZERO = 0
export enum StepsStatus {
PENDING = 'wait',
PROCESS = 'process',
SUCCESS = 'success',
DANGER = 'danger',
}
enum DeploymentFlow {
Success = 'success',
Doing = 'doing',
NoStart = 'nostart',
Failed = 'failed',
}
const stepTitleMapping = ['step1', 'step2', 'step3', 'step4']
type SolutionFlow = {
num: number
state: DeploymentFlow
msg: string
newButton?: {
btnAction?: string
btnTitle?: string
btnType?: string
}[]
}
const stepStatusMapping = {
[DeploymentFlow.NoStart]: StepsStatus.PENDING,
[DeploymentFlow.Doing]: StepsStatus.PROCESS,
[DeploymentFlow.Success]: StepsStatus.SUCCESS,
[DeploymentFlow.Failed]: StepsStatus.DANGER,
}
export interface StepListItem {
name: string
// 每一个步骤的描述
content?: string
// 每一个步骤的状态
status?: StepsStatus
// 按钮列表
actionButtons?: {
// 按钮文字
text: string
// 按钮ovga
type: 'default' | 'primary' | 'secondary' | 'danger' | 'light'
// 点击按钮callback
onClick: (callBack: (val: string) => void) => void
}[]
}
export type ISolutionFlowRes = SolutionFlow[]
export interface StepsProps {
currentStep: number
steps: StepListItem[]
}
export const newButtonType: Record<
string,
'default' | 'danger' | 'primary' | 'secondary' | 'light'
> = {
def: 'default',
danger: 'danger',
primary: 'primary',
secondary: 'secondary',
light: 'light',
link: 'light',
}
export const btnAction: Record<string, 'selectCommit'> = {
SelectCommit: 'selectCommit',
}
export const getStepList = (deploymentFlow: ISolutionFlowRes): StepListItem[] => {
if (!Array.isArray(deploymentFlow) || deploymentFlow.length === 0) {
return []
}
const filteredFlow = deploymentFlow.filter(item => item !== undefined && item !== null)
filteredFlow.sort((a, b) => a.num - b.num)
return filteredFlow?.map((flow: SolutionFlow) => ({
name: stepTitleMapping?.[flow.num - 1] || '',
status: stepStatusMapping?.[flow.state] ?? StepsStatus.SUCCESS,
content: flow.msg,
actionButtons:
flow.newButton?.map(btn => ({
text: btn.btnTitle || '',
type: (btn.btnType && newButtonType[btn.btnType]) || 'default',
onClick: async (callBack: (val: string) => void) => {
try {
if (btn.btnAction === btnAction.SelectCommit) {
callBack(btnAction.SelectCommit)
return
}
} catch (e) {
toast({
type: 'danger',
description: `${e}`,
placement: 'top',
})
}
},
})) || [],
}))
}
export const getCurrentStep = (deploymentFlow: ISolutionFlowRes): number => {
let currentStep = 0
if (!Array.isArray(deploymentFlow)) {
return currentStep
}
currentStep = deploymentFlow.findIndex(item => item.state === 'doing')
return currentStep
}