|
@@ -1,137 +1,353 @@
|
|
|
<template>
|
|
|
- <div class="steps flex">
|
|
|
- <div v-for="(step, index) in steps" :key="index" class="node">
|
|
|
- <div v-if="step.flag" class="node-status-success">
|
|
|
- <span class="icon"></span>
|
|
|
- </div>
|
|
|
- <div v-else class="node-status-default"></div>
|
|
|
- <template v-if="index < steps.length - 1">
|
|
|
+ <div class="steps">
|
|
|
+ <div
|
|
|
+ v-for="(step, index) in steps"
|
|
|
+ :key="index"
|
|
|
+ class="step"
|
|
|
+ :style="stepStyle(index)"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ v-if="index > 0"
|
|
|
+ :class="[
|
|
|
+ 'step-line',
|
|
|
+ 'step-line-before',
|
|
|
+ step.preActive ? 'step-line-active' : 'step-line-inactive',
|
|
|
+ ]"
|
|
|
+ ></div>
|
|
|
+ <div
|
|
|
+ v-if="steps.length > 1 && index < steps.length - 1"
|
|
|
+ :class="[
|
|
|
+ 'step-line',
|
|
|
+ 'step-line-after',
|
|
|
+ step.nextActive ? 'step-line-active' : 'step-line-inactive',
|
|
|
+ ]"
|
|
|
+ ></div>
|
|
|
+ <template v-if="'name' in step">
|
|
|
<div
|
|
|
- v-if="step.flag && steps[index + 1] && steps[index + 1].flag"
|
|
|
- class="node-status-success-line"
|
|
|
- ></div>
|
|
|
- <div v-else class="node-status-default-line"></div>
|
|
|
+ :class="[
|
|
|
+ 'step-state',
|
|
|
+ step.active ? 'step-state-active' : 'step-state-inactive',
|
|
|
+ ]"
|
|
|
+ >
|
|
|
+ <i class="step-state-icon" />
|
|
|
+ </div>
|
|
|
+ <div class="step-info">
|
|
|
+ <div class="step-info-name">{{ step.name }}</div>
|
|
|
+ <div class="step-info-desc">
|
|
|
+ <span
|
|
|
+ class="step-info-desc-item"
|
|
|
+ v-for="(p, i) in step.descriptions"
|
|
|
+ :key="i"
|
|
|
+ >{{ p }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
- <div
|
|
|
- :style="{
|
|
|
- width: step.labelWidth ? step.labelWidth + 'px' : 60 + 'px',
|
|
|
- transform: `translateX(-${
|
|
|
- step.labelWidth ? step.labelWidth / 3 : 60 / 3
|
|
|
- }px)`,
|
|
|
- }"
|
|
|
- class="node-cap"
|
|
|
- >
|
|
|
- <div class="node-name">{{ step.name }}</div>
|
|
|
- <div class="node-info">
|
|
|
- <span
|
|
|
- class="node-info-list"
|
|
|
- v-for="(p, i) in step.descriptions"
|
|
|
- :key="i"
|
|
|
- >{{ p }}<br />
|
|
|
- <!-- <br v-if="i == 1" /> -->
|
|
|
- <!-- <i v-if="i % 2 == 0">/</i> -->
|
|
|
- </span>
|
|
|
+ <template v-else>
|
|
|
+ <div
|
|
|
+ v-for="(subStep, i) in step.children"
|
|
|
+ :key="i"
|
|
|
+ :class="[
|
|
|
+ 'step',
|
|
|
+ 'sub-step',
|
|
|
+ i === 0 ? 'sub-step-up' : 'sub-step-down',
|
|
|
+ subStep.active ? 'sub-step-active' : 'sub-step-inactive',
|
|
|
+ ]"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ v-if="index > 0"
|
|
|
+ :class="[
|
|
|
+ 'step-line',
|
|
|
+ 'step-line-before',
|
|
|
+ subStep.active ? 'step-line-active' : 'step-line-inactive',
|
|
|
+ ]"
|
|
|
+ ></div>
|
|
|
+ <div
|
|
|
+ v-if="index < steps.length - 1"
|
|
|
+ :class="[
|
|
|
+ 'step-line',
|
|
|
+ 'step-line-after',
|
|
|
+ subStep.active ? 'step-line-active' : 'step-line-inactive',
|
|
|
+ ]"
|
|
|
+ ></div>
|
|
|
+ <div
|
|
|
+ :class="[
|
|
|
+ 'step-state',
|
|
|
+ subStep.active ? 'step-state-active' : 'step-state-inactive',
|
|
|
+ ]"
|
|
|
+ >
|
|
|
+ <i class="step-state-icon" />
|
|
|
+ </div>
|
|
|
+ <div class="step-info">
|
|
|
+ <div class="step-info-name">{{ subStep.name }}</div>
|
|
|
+ <div class="step-info-desc">
|
|
|
+ <span
|
|
|
+ class="step-info-desc-item"
|
|
|
+ v-for="(p, i) in subStep.descriptions"
|
|
|
+ :key="i"
|
|
|
+ >{{ p }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ </template>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { PropType } from 'vue'
|
|
|
+import { CSSProperties, PropType } from 'vue'
|
|
|
|
|
|
type StepData = {
|
|
|
name: string
|
|
|
- flag: boolean
|
|
|
+ active: boolean
|
|
|
+ preActive?: boolean
|
|
|
+ nextActive?: boolean
|
|
|
labelWidth?: number
|
|
|
descriptions: string[]
|
|
|
}
|
|
|
+type CombinedStepData = {
|
|
|
+ active: boolean
|
|
|
+ children: [StepData, StepData]
|
|
|
+ labelWidth?: number
|
|
|
+ preActive?: boolean
|
|
|
+ nextActive?: boolean
|
|
|
+}
|
|
|
|
|
|
const props = defineProps({
|
|
|
steps: {
|
|
|
- type: Array as PropType<StepData[]>,
|
|
|
+ type: Array as PropType<(StepData | CombinedStepData)[]>,
|
|
|
default: () => [],
|
|
|
},
|
|
|
})
|
|
|
+
|
|
|
+const hasMultiple = ref(false)
|
|
|
+
|
|
|
+watchEffect(() => {
|
|
|
+ const steps = props.steps
|
|
|
+ const { length } = steps
|
|
|
+ for (let i = 0; i < length - 1; i++) {
|
|
|
+ const currentStep = steps[i]
|
|
|
+ const nextStep = steps[i + 1]
|
|
|
+ const allActive = currentStep.active && nextStep.active
|
|
|
+ currentStep.nextActive = nextStep.preActive = allActive
|
|
|
+
|
|
|
+ if ('children' in currentStep || 'children' in nextStep) {
|
|
|
+ hasMultiple.value = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const stepStyle = (index: number) => {
|
|
|
+ const style: CSSProperties = {}
|
|
|
+ const steps = props.steps
|
|
|
+ const { length } = steps
|
|
|
+ let width: string | number = 0
|
|
|
+ if (length === 1) {
|
|
|
+ width = '100%'
|
|
|
+ }
|
|
|
+ if (length === 2) {
|
|
|
+ width = '50%'
|
|
|
+ }
|
|
|
+ if (length > 2) {
|
|
|
+ const splited = 100 / ((length - 2) * 2 + 2)
|
|
|
+ if (index === 0 || index === length - 1) {
|
|
|
+ width = `${splited}%`
|
|
|
+ } else {
|
|
|
+ width = `${splited * 2}%`
|
|
|
+ }
|
|
|
+ }
|
|
|
+ style.width = width
|
|
|
+ if (!hasMultiple.value) {
|
|
|
+ style.transform = 'translate(0, -30%)'
|
|
|
+ }
|
|
|
+ return style
|
|
|
+}
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
.steps {
|
|
|
+ height: calc(100% - 16px - 10px);
|
|
|
padding: 0 13px;
|
|
|
- .node {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ .step {
|
|
|
+ height: 100%;
|
|
|
position: relative;
|
|
|
- width: 100%;
|
|
|
+ &-line {
|
|
|
+ height: 6px;
|
|
|
+ position: absolute;
|
|
|
+ width: 50%;
|
|
|
+ top: 50%;
|
|
|
+ transform: translate(0, -50%);
|
|
|
+ z-index: 1;
|
|
|
+ &-before {
|
|
|
+ left: 0;
|
|
|
+ }
|
|
|
+ &-after {
|
|
|
+ right: 0;
|
|
|
+ }
|
|
|
+ &-active {
|
|
|
+ background: #d5327b;
|
|
|
+ }
|
|
|
+ &-inactive {
|
|
|
+ background: #b7b1b4;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &:first-child,
|
|
|
&:last-child {
|
|
|
- width: 2%;
|
|
|
+ .step-line {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
}
|
|
|
- &-name {
|
|
|
- font-size: 14px;
|
|
|
- font-family: Microsoft YaHei;
|
|
|
- font-weight: bold;
|
|
|
- color: #101116;
|
|
|
+ &-state {
|
|
|
+ position: absolute;
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ background: #ffffff;
|
|
|
+ border: 2px solid;
|
|
|
+ border-radius: 50%;
|
|
|
+ z-index: 2;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ .step-state-icon {
|
|
|
+ width: 14px;
|
|
|
+ height: 14px;
|
|
|
+ border-radius: 50%;
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ margin-top: -7px;
|
|
|
+ margin-left: -7px;
|
|
|
+ }
|
|
|
+ &-active {
|
|
|
+ border-color: #d5327b;
|
|
|
+ .step-state-icon {
|
|
|
+ background-color: #d5327b;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &-inactive {
|
|
|
+ border-color: #b7b1b4;
|
|
|
+ .step-state-icon {
|
|
|
+ background-color: #b7b1b4;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- &-cap {
|
|
|
+ &-info {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, 24px);
|
|
|
text-align: center;
|
|
|
- margin-top: 10px;
|
|
|
- .node-info {
|
|
|
+ &-name {
|
|
|
+ font-size: 14px;
|
|
|
+ font-family: Microsoft YaHei;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #101116;
|
|
|
+ }
|
|
|
+ &-desc {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
- &-list {
|
|
|
- // font-size: 12px;
|
|
|
+ &-item {
|
|
|
+ height: 22px;
|
|
|
+ line-height: 22px;
|
|
|
font-family: DIN, Microsoft YaHei;
|
|
|
font-weight: 400;
|
|
|
color: #101116;
|
|
|
- margin-top: 8px;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
- .node-status-success {
|
|
|
- position: relative;
|
|
|
- width: 24px;
|
|
|
- height: 24px;
|
|
|
- background: #ffffff;
|
|
|
- border: 2px solid #d5327b;
|
|
|
- border-radius: 50%;
|
|
|
- z-index: 2;
|
|
|
- .icon {
|
|
|
- width: 14px;
|
|
|
- height: 14px;
|
|
|
- background: #d5327b;
|
|
|
- border-radius: 50%;
|
|
|
- position: absolute;
|
|
|
- top: 50%;
|
|
|
- left: 50%;
|
|
|
- margin-top: -7px;
|
|
|
- margin-left: -7px;
|
|
|
+
|
|
|
+ &:first-child:not(.sub-step) {
|
|
|
+ .step-state,
|
|
|
+ .step-info {
|
|
|
+ left: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &:last-child:not(.sub-step) {
|
|
|
+ .step-state {
|
|
|
+ left: unset;
|
|
|
+ right: 0;
|
|
|
+ transform: translate(50%, -50%);
|
|
|
+ }
|
|
|
+ .step-info {
|
|
|
+ left: unset;
|
|
|
+ right: 0;
|
|
|
+ transform: translate(50%, 24px);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.sub-step {
|
|
|
+ margin: 0 auto;
|
|
|
+ width: 80%;
|
|
|
+ height: 50%;
|
|
|
+ z-index: 2;
|
|
|
+ background-color: #fff;
|
|
|
+ &.sub-step-active {
|
|
|
+ z-index: 11;
|
|
|
+ }
|
|
|
+ &.sub-step-inactive {
|
|
|
+ z-index: 10;
|
|
|
+ }
|
|
|
+ .step-line::after {
|
|
|
+ content: '';
|
|
|
+ display: block;
|
|
|
+ width: 6px;
|
|
|
+ height: 20px;
|
|
|
+ position: absolute;
|
|
|
+ }
|
|
|
+ .step-line-active::after {
|
|
|
+ background-color: #d5327b;
|
|
|
+ }
|
|
|
+ .step-line-inactive::after {
|
|
|
+ background-color: #b7b1b4;
|
|
|
+ }
|
|
|
+ .step-line-before::after {
|
|
|
+ left: 0;
|
|
|
+ }
|
|
|
+ .step-line-after::after {
|
|
|
+ right: 0;
|
|
|
+ }
|
|
|
+ &.sub-step-up {
|
|
|
+ .step-line {
|
|
|
+ top: unset;
|
|
|
+ bottom: 20px;
|
|
|
+ transform: translate(0, 50%);
|
|
|
+ border-radius: 6px 6px 0 0;
|
|
|
+ &::after {
|
|
|
+ bottom: 0;
|
|
|
+ transform: translate(0, 100%);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .step-state {
|
|
|
+ top: unset;
|
|
|
+ bottom: 20px;
|
|
|
+ transform: translate(-50%, 50%);
|
|
|
+ }
|
|
|
+ .step-info {
|
|
|
+ top: 0;
|
|
|
+ bottom: unset;
|
|
|
+ transform: translate(-50%, 12px);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &.sub-step-down {
|
|
|
+ .step-line {
|
|
|
+ top: 20px;
|
|
|
+ border-radius: 0 0 6px 6px;
|
|
|
+ &::after {
|
|
|
+ top: 0;
|
|
|
+ transform: translate(0, -100%);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .step-state {
|
|
|
+ top: 20px;
|
|
|
+ }
|
|
|
+ .step-info {
|
|
|
+ transform: translate(-50%, -12px);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- .node-status-default {
|
|
|
- width: 24px;
|
|
|
- height: 24px;
|
|
|
- background: #ffffff;
|
|
|
- border: 2px solid #b7b1b4;
|
|
|
- border-radius: 50%;
|
|
|
- z-index: 2;
|
|
|
- position: relative;
|
|
|
- }
|
|
|
- .node-status-success-line {
|
|
|
- height: 6px;
|
|
|
- background: #d5327b;
|
|
|
- position: absolute;
|
|
|
- width: 100%;
|
|
|
- top: 9px;
|
|
|
- left: 0;
|
|
|
- z-index: 1;
|
|
|
- }
|
|
|
- .node-status-default-line {
|
|
|
- height: 6px;
|
|
|
- background: #b7b1b4;
|
|
|
- position: absolute;
|
|
|
- width: 100%;
|
|
|
- top: 9px;
|
|
|
- left: 0;
|
|
|
- z-index: 1;
|
|
|
}
|
|
|
}
|
|
|
</style>
|