2018-03-26 16:04:04 +08:00
|
|
|
<template>
|
2018-08-31 16:37:15 +08:00
|
|
|
<div
|
|
|
|
|
class="md-steps"
|
|
|
|
|
:class="{
|
|
|
|
|
'md-steps-vertical': direction == 'vertical',
|
2018-11-27 20:27:45 +08:00
|
|
|
'md-steps-horizontal': direction == 'horizontal',
|
2018-11-30 20:44:54 +08:00
|
|
|
'vertical-adaptive': direction == 'vertical' && verticalAdaptive,
|
|
|
|
|
'no-current': currentLength % 1 !== 0
|
2018-08-31 16:37:15 +08:00
|
|
|
}"
|
|
|
|
|
>
|
2021-08-06 14:34:26 +08:00
|
|
|
<slot v-if="custom" :steps="steps"></slot>
|
|
|
|
|
<template v-else v-for="(step, index) of steps">
|
2018-11-30 20:44:54 +08:00
|
|
|
<div class="step-wrapper"
|
|
|
|
|
:class="[$_getStepStatusClass(index)]"
|
|
|
|
|
:key="`steps-${index}`"
|
|
|
|
|
>
|
2019-04-22 23:16:34 +08:00
|
|
|
<!-- Customize uniformly -->
|
|
|
|
|
<div v-if="$scopedSlots.icon" class="icon-wrapper" >
|
|
|
|
|
<slot name="icon" :index="index" :current-index="currentLength"></slot>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- Customize by status-->
|
|
|
|
|
<div v-else class="icon-wrapper">
|
|
|
|
|
<template v-if="index < currentLength">
|
|
|
|
|
<slot
|
|
|
|
|
v-if="$scopedSlots.reached || $slots.reached"
|
|
|
|
|
name="reached"
|
|
|
|
|
:index="index"
|
|
|
|
|
></slot>
|
|
|
|
|
<div v-else class="step-node-default">
|
|
|
|
|
<div class="step-node-default-icon" style="width: 6px;height: 6px;border-radius: 50%;"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else-if="index === currentLength">
|
|
|
|
|
<slot
|
|
|
|
|
v-if="$scopedSlots.current || $slots.current"
|
|
|
|
|
name="current"
|
|
|
|
|
:index="index"
|
|
|
|
|
></slot>
|
|
|
|
|
<md-icon v-else name="success"></md-icon>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else>
|
|
|
|
|
<slot
|
|
|
|
|
v-if="$scopedSlots.unreached || $slots.unreached"
|
|
|
|
|
name="unreached"
|
|
|
|
|
:index="index"
|
|
|
|
|
></slot>
|
|
|
|
|
<div v-else class="step-node-default">
|
|
|
|
|
<div class="step-node-default-icon" style="width: 6px;height: 6px;border-radius: 50%;"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
2018-03-26 16:04:04 +08:00
|
|
|
</div>
|
|
|
|
|
<div class="text-wrapper">
|
2018-11-27 16:56:06 +08:00
|
|
|
<slot
|
|
|
|
|
v-if="$scopedSlots.content"
|
|
|
|
|
name="content"
|
|
|
|
|
:index="index"
|
|
|
|
|
:step="step"
|
|
|
|
|
></slot>
|
|
|
|
|
<template v-else>
|
|
|
|
|
<div class="name">
|
|
|
|
|
{{step.name}}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="desc" v-if="step.text">
|
|
|
|
|
{{step.text}}
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
2018-03-26 16:04:04 +08:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2018-10-17 16:35:47 +08:00
|
|
|
<div class="bar"
|
|
|
|
|
:class="[direction === 'horizontal' ? 'horizontal-bar' : 'vertical-bar']"
|
2018-11-27 20:27:45 +08:00
|
|
|
:style="$_getStepSizeForStyle(index)"
|
2018-10-17 16:35:47 +08:00
|
|
|
:key="`bar-${index}`"
|
2018-08-31 16:37:15 +08:00
|
|
|
>
|
2018-10-17 16:35:47 +08:00
|
|
|
<i
|
|
|
|
|
class="bar-inner"
|
|
|
|
|
v-if="progress[index]"
|
2018-12-17 16:52:28 +08:00
|
|
|
:style="$_barInnerStyle(index)"
|
2018-10-17 16:35:47 +08:00
|
|
|
></i>
|
2018-08-31 16:37:15 +08:00
|
|
|
</div>
|
2018-10-17 16:35:47 +08:00
|
|
|
</template>
|
2018-03-26 16:04:04 +08:00
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
import Icon from '../icon'
|
2018-11-27 20:27:45 +08:00
|
|
|
import {toArray} from '../_util'
|
2018-03-26 16:04:04 +08:00
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
name: 'md-steps',
|
|
|
|
|
|
|
|
|
|
components: {
|
|
|
|
|
[Icon.name]: Icon,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
props: {
|
|
|
|
|
steps: {
|
|
|
|
|
type: Array,
|
|
|
|
|
default() {
|
2019-01-26 22:28:11 +08:00
|
|
|
/* istanbul ignore next */
|
2018-03-26 16:04:04 +08:00
|
|
|
return []
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
current: {
|
|
|
|
|
type: Number,
|
|
|
|
|
default: 0,
|
2018-10-17 16:35:47 +08:00
|
|
|
validator(val) {
|
|
|
|
|
return val >= 0
|
2018-03-26 16:04:04 +08:00
|
|
|
},
|
|
|
|
|
},
|
2018-08-31 16:37:15 +08:00
|
|
|
direction: {
|
|
|
|
|
type: String,
|
|
|
|
|
default: 'horizontal',
|
|
|
|
|
},
|
2018-10-17 16:35:47 +08:00
|
|
|
transition: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false,
|
|
|
|
|
},
|
2018-11-27 20:27:45 +08:00
|
|
|
verticalAdaptive: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false,
|
|
|
|
|
},
|
2021-08-06 14:34:26 +08:00
|
|
|
custom: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false,
|
|
|
|
|
},
|
2018-10-17 16:35:47 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
initialed: false,
|
|
|
|
|
progress: [],
|
2018-11-27 20:27:45 +08:00
|
|
|
stepsSize: [],
|
2018-10-17 16:35:47 +08:00
|
|
|
currentLength: 0,
|
|
|
|
|
duration: 0.3,
|
|
|
|
|
timer: null,
|
|
|
|
|
}
|
2018-08-31 16:37:15 +08:00
|
|
|
},
|
|
|
|
|
|
2018-12-17 16:52:28 +08:00
|
|
|
computed: {
|
|
|
|
|
$_barInnerStyle() {
|
|
|
|
|
return index => {
|
|
|
|
|
const {progress} = this
|
|
|
|
|
const transform =
|
|
|
|
|
this.direction === 'horizontal'
|
|
|
|
|
? `(${(progress[index]['len'] - 1) * 100}%, 0, 0)`
|
|
|
|
|
: `(0, ${(progress[index]['len'] - 1) * 100}%, 0)`
|
|
|
|
|
return {
|
|
|
|
|
transform: `translate3d${transform}`,
|
|
|
|
|
transition: `all ${progress[index]['time']}s linear`,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
2018-08-31 16:37:15 +08:00
|
|
|
watch: {
|
|
|
|
|
current(val, oldVal) {
|
2018-10-17 16:35:47 +08:00
|
|
|
const currentStep = this.$_formatValue(val)
|
|
|
|
|
const newProgress = this.$_sliceProgress(currentStep)
|
|
|
|
|
if (this.transition) {
|
|
|
|
|
const isAdd = currentStep >= oldVal
|
|
|
|
|
this.timer && clearTimeout(this.timer)
|
|
|
|
|
this.timer = setTimeout(() => {
|
|
|
|
|
this.$_doTransition(newProgress, isAdd, len => {
|
|
|
|
|
if ((isAdd && len > this.currentLength) || (!isAdd && len < this.currentLength)) {
|
|
|
|
|
this.currentLength = len
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}, 100)
|
|
|
|
|
} else {
|
|
|
|
|
this.progress = newProgress
|
|
|
|
|
this.currentLength = currentStep
|
|
|
|
|
}
|
2018-08-31 16:37:15 +08:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
2018-10-17 16:35:47 +08:00
|
|
|
created() {
|
|
|
|
|
const currentStep = this.$_formatValue(this.current)
|
|
|
|
|
this.currentLength = currentStep
|
|
|
|
|
this.progress = this.$_sliceProgress(currentStep)
|
|
|
|
|
},
|
2018-11-27 20:27:45 +08:00
|
|
|
mounted() {
|
|
|
|
|
this.$_initStepSize()
|
|
|
|
|
},
|
|
|
|
|
updated() {
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
this.$_initStepSize()
|
|
|
|
|
})
|
|
|
|
|
},
|
2018-08-31 16:37:15 +08:00
|
|
|
|
2018-10-17 16:35:47 +08:00
|
|
|
methods: {
|
|
|
|
|
// MARK: private methods
|
2018-11-27 20:27:45 +08:00
|
|
|
$_initStepSize() {
|
|
|
|
|
if (this.direction !== 'vertical' || this.verticalAdaptive) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
const iconWrappers = this.$el.querySelectorAll('.icon-wrapper')
|
|
|
|
|
const textWrappers = this.$el.querySelectorAll('.text-wrapper')
|
|
|
|
|
const stepsSize = toArray(textWrappers).map((wrapper, index) => {
|
|
|
|
|
let stepHeight = wrapper.clientHeight
|
|
|
|
|
const iconHeight = iconWrappers[index].clientHeight
|
|
|
|
|
if (index === textWrappers.length - 1) {
|
|
|
|
|
// The last step needs to subtract floated height
|
|
|
|
|
stepHeight -= iconHeight
|
|
|
|
|
} else {
|
|
|
|
|
// Add spacing between steps to prevent distance too close
|
|
|
|
|
stepHeight += 40
|
|
|
|
|
}
|
|
|
|
|
return stepHeight > 0 ? stepHeight : 0
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (stepsSize.toString() !== this.stepsSize.toString()) {
|
|
|
|
|
this.stepsSize = stepsSize
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
$_getStepSizeForStyle(index) {
|
|
|
|
|
const size = this.direction === 'vertical' && !this.verticalAdaptive ? this.stepsSize[index] : 0
|
|
|
|
|
return size
|
|
|
|
|
? {
|
|
|
|
|
height: `${size}px`,
|
|
|
|
|
}
|
|
|
|
|
: null
|
|
|
|
|
},
|
2018-11-30 20:44:54 +08:00
|
|
|
$_getStepStatusClass(index) {
|
|
|
|
|
const currentLength = this.currentLength
|
|
|
|
|
|
|
|
|
|
let status = []
|
|
|
|
|
|
|
|
|
|
if (index < currentLength) {
|
|
|
|
|
status.push('reached')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (index === Math.floor(currentLength)) {
|
|
|
|
|
status.push('current')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return status.join(' ')
|
|
|
|
|
},
|
2018-10-17 16:35:47 +08:00
|
|
|
$_formatValue(val) {
|
|
|
|
|
if (val < 0) {
|
|
|
|
|
return 0
|
|
|
|
|
} else if (val > this.steps.length - 1) {
|
|
|
|
|
return this.steps.length - 1
|
|
|
|
|
} else {
|
|
|
|
|
return val
|
|
|
|
|
}
|
2018-08-31 16:37:15 +08:00
|
|
|
},
|
2018-10-17 16:35:47 +08:00
|
|
|
$_sliceProgress(current) {
|
|
|
|
|
return this.steps.slice(0, this.steps.length - 1).map((step, index) => {
|
|
|
|
|
const offset = current - index
|
|
|
|
|
const progress = this.progress[index]
|
|
|
|
|
const isNewProgress = progress === undefined
|
|
|
|
|
let len, time
|
|
|
|
|
if (offset <= 0) {
|
|
|
|
|
len = 0
|
|
|
|
|
} else if (offset >= 1) {
|
|
|
|
|
len = 1
|
|
|
|
|
} else {
|
|
|
|
|
len = offset
|
|
|
|
|
}
|
|
|
|
|
time = (isNewProgress ? len : Math.abs(progress.len - len)) * this.duration
|
|
|
|
|
return {
|
|
|
|
|
len,
|
|
|
|
|
time,
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
$_doTransition(progress, isAdd, step) {
|
|
|
|
|
let currentLength = isAdd ? 0 : this.currentLength
|
|
|
|
|
const walk = index => {
|
|
|
|
|
if ((index < progress.length) & (index > -1) && progress[index]) {
|
|
|
|
|
if (isAdd) {
|
|
|
|
|
currentLength += progress[index].len
|
|
|
|
|
} else {
|
|
|
|
|
currentLength -= this.progress[index].len - progress[index].len
|
|
|
|
|
}
|
2018-08-31 16:37:15 +08:00
|
|
|
|
2018-10-17 16:35:47 +08:00
|
|
|
setTimeout(() => {
|
|
|
|
|
index += isAdd ? 1 : -1
|
|
|
|
|
step(currentLength)
|
|
|
|
|
walk(index)
|
|
|
|
|
}, progress[index].time * 1000)
|
|
|
|
|
}
|
|
|
|
|
this.$set(this.progress, index, progress[index])
|
|
|
|
|
}
|
|
|
|
|
walk(isAdd ? 0 : progress.length - 1)
|
|
|
|
|
},
|
2018-03-26 16:04:04 +08:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="stylus">
|
|
|
|
|
.md-steps
|
|
|
|
|
display flex
|
|
|
|
|
justify-content space-around
|
|
|
|
|
font-size 28px
|
2018-10-17 16:35:47 +08:00
|
|
|
|
2018-08-31 16:37:15 +08:00
|
|
|
&.md-steps-horizontal
|
2018-10-17 16:35:47 +08:00
|
|
|
align-items center
|
|
|
|
|
padding 40px 100px 100px
|
2018-11-27 16:56:06 +08:00
|
|
|
.step-wrapper
|
2018-10-17 16:35:47 +08:00
|
|
|
margin 0 4px
|
2018-08-31 16:37:15 +08:00
|
|
|
justify-content center
|
|
|
|
|
align-items center
|
|
|
|
|
flex-direction column
|
2018-11-30 20:44:54 +08:00
|
|
|
&.reached
|
|
|
|
|
.text-wrapper .name
|
|
|
|
|
color steps-text-color
|
|
|
|
|
&.current
|
2019-04-22 23:49:13 +08:00
|
|
|
.text-wrapper .name
|
2018-11-30 20:44:54 +08:00
|
|
|
color steps-color-active
|
2018-08-31 16:37:15 +08:00
|
|
|
.text-wrapper
|
2018-10-17 16:35:47 +08:00
|
|
|
top 100%
|
|
|
|
|
padding-top steps-text-gap-horizontal
|
|
|
|
|
text-align center
|
2018-11-30 20:44:54 +08:00
|
|
|
.name
|
|
|
|
|
color steps-desc-color
|
2018-10-17 16:35:47 +08:00
|
|
|
.desc
|
|
|
|
|
margin-top 10px
|
2018-11-30 20:44:54 +08:00
|
|
|
color steps-desc-color
|
|
|
|
|
&.no-current
|
|
|
|
|
.reached:last-of-type
|
|
|
|
|
display none !important
|
2018-12-17 16:52:28 +08:00
|
|
|
|
2018-08-31 16:37:15 +08:00
|
|
|
&.md-steps-vertical
|
|
|
|
|
align-items flex-start
|
2018-11-27 20:27:45 +08:00
|
|
|
padding 40px
|
2018-10-17 16:35:47 +08:00
|
|
|
flex-direction column
|
2018-11-27 20:27:45 +08:00
|
|
|
&.vertical-adaptive
|
|
|
|
|
justify-content normal
|
|
|
|
|
padding 40px 40px 8px
|
|
|
|
|
.bar.vertical-bar
|
|
|
|
|
flex 1
|
2018-11-27 16:56:06 +08:00
|
|
|
.step-wrapper
|
|
|
|
|
width 100%
|
2018-10-17 16:35:47 +08:00
|
|
|
margin 4px 0
|
2018-08-31 16:37:15 +08:00
|
|
|
align-items stretch
|
2018-11-27 16:56:06 +08:00
|
|
|
.icon-wrapper
|
2018-08-31 16:37:15 +08:00
|
|
|
position relative
|
2018-10-17 16:35:47 +08:00
|
|
|
.step-node-default
|
|
|
|
|
min-width steps-icon-size
|
|
|
|
|
min-height steps-icon-size
|
2018-08-31 16:37:15 +08:00
|
|
|
.text-wrapper
|
2018-11-27 16:56:06 +08:00
|
|
|
left steps-icon-size
|
2018-10-17 16:35:47 +08:00
|
|
|
padding-left steps-text-gap-vertical
|
2018-11-27 16:56:06 +08:00
|
|
|
.name, .desc
|
|
|
|
|
white-space normal
|
2018-11-30 20:44:54 +08:00
|
|
|
.name
|
|
|
|
|
color steps-text-color
|
2018-10-17 16:35:47 +08:00
|
|
|
.desc
|
|
|
|
|
margin-top 18px
|
2018-11-30 20:44:54 +08:00
|
|
|
color steps-desc-color
|
2018-03-26 16:04:04 +08:00
|
|
|
|
2018-11-27 16:56:06 +08:00
|
|
|
.icon-wrapper
|
2018-03-26 16:04:04 +08:00
|
|
|
display flex
|
|
|
|
|
justify-content center
|
|
|
|
|
align-items center
|
2019-04-22 23:16:34 +08:00
|
|
|
color steps-color
|
2018-03-26 16:04:04 +08:00
|
|
|
|
|
|
|
|
>div
|
|
|
|
|
display flex
|
|
|
|
|
justify-content center
|
|
|
|
|
align-items center
|
2018-10-17 16:35:47 +08:00
|
|
|
&:nth-child(2)
|
|
|
|
|
display none
|
2018-03-26 16:04:04 +08:00
|
|
|
|
2018-12-17 16:52:28 +08:00
|
|
|
.step-node-default-icon
|
2018-03-26 16:04:04 +08:00
|
|
|
background steps-color
|
2018-06-09 20:44:42 +08:00
|
|
|
|
2018-11-27 16:56:06 +08:00
|
|
|
.step-wrapper
|
2018-08-31 16:37:15 +08:00
|
|
|
display flex
|
|
|
|
|
position relative
|
2018-10-17 16:35:47 +08:00
|
|
|
min-width steps-icon-size
|
|
|
|
|
min-height steps-icon-size
|
2018-11-27 16:56:06 +08:00
|
|
|
.icon-wrapper
|
2018-10-17 16:35:47 +08:00
|
|
|
min-width steps-icon-size
|
|
|
|
|
min-height steps-icon-size
|
|
|
|
|
.md-icon
|
|
|
|
|
width steps-icon-size
|
|
|
|
|
height steps-icon-size
|
|
|
|
|
font-size steps-icon-size
|
|
|
|
|
line-height steps-icon-size
|
2018-08-31 16:37:15 +08:00
|
|
|
.text-wrapper
|
2018-10-17 16:35:47 +08:00
|
|
|
position absolute
|
|
|
|
|
.name, .desc
|
|
|
|
|
white-space nowrap
|
|
|
|
|
.name
|
2018-08-31 16:37:15 +08:00
|
|
|
line-height steps-text-font-size
|
|
|
|
|
font-size steps-text-font-size
|
|
|
|
|
.desc
|
|
|
|
|
line-height steps-text-font-size
|
|
|
|
|
font-size steps-desc-font-size
|
2019-04-22 23:49:13 +08:00
|
|
|
&.reached, &.current
|
|
|
|
|
.icon-wrapper
|
|
|
|
|
color steps-color-active
|
|
|
|
|
.step-node-default-icon
|
|
|
|
|
background steps-color-active
|
2018-03-26 16:04:04 +08:00
|
|
|
|
|
|
|
|
.bar
|
2018-10-17 16:35:47 +08:00
|
|
|
position relative
|
2018-08-31 16:37:15 +08:00
|
|
|
background-color steps-color
|
2018-10-17 16:35:47 +08:00
|
|
|
overflow hidden
|
|
|
|
|
.bar-inner
|
|
|
|
|
z-index 10
|
|
|
|
|
position absolute
|
|
|
|
|
top 0
|
|
|
|
|
left 0
|
|
|
|
|
display block
|
|
|
|
|
content ''
|
|
|
|
|
transition all linear 1s
|
2018-08-31 16:37:15 +08:00
|
|
|
&.horizontal-bar
|
2018-11-27 20:27:45 +08:00
|
|
|
flex 1
|
2018-10-17 16:35:47 +08:00
|
|
|
height steps-border-size
|
|
|
|
|
.bar-inner
|
2018-08-31 16:37:15 +08:00
|
|
|
width 100%
|
2018-10-17 16:35:47 +08:00
|
|
|
height steps-border-size
|
|
|
|
|
background-color steps-color-active
|
2018-08-31 16:37:15 +08:00
|
|
|
&.vertical-bar
|
2018-10-17 16:35:47 +08:00
|
|
|
left 16px
|
|
|
|
|
width steps-border-size
|
|
|
|
|
transform translateX(-50%)
|
|
|
|
|
.bar-inner
|
|
|
|
|
width steps-border-size
|
2018-08-31 16:37:15 +08:00
|
|
|
height 100%
|
2018-10-17 16:35:47 +08:00
|
|
|
background-color steps-color-active
|
2018-11-27 20:27:45 +08:00
|
|
|
&:last-of-type
|
|
|
|
|
&.horizontal-bar
|
|
|
|
|
display none
|
|
|
|
|
&.vertical-bar
|
|
|
|
|
visibility hidden
|
2018-03-26 16:04:04 +08:00
|
|
|
</style>
|