在实际应用中,步骤条往往并不是一条直线,在某些节点需要分叉显示,但是现成的UI组件都不支持此功能,比如ElementUI和antV,这两个UI组件提供的steps只支持一条直线显示的step
antv
element UI
实际需要的效果
于是花了一天时间写了一个支持分叉显示的步骤条,代码如下:
参数steps
steps为传入的步骤数据:
const steps = [{
id: 0,
title: '步骤一',
icon: 'icon-1'
},
{
id: 1,
title: '步骤二',
icon: 'icon-2'
},
{
id: 2,
title: '步骤三',
icon: 'icon-3'
},
{
id: 3,
title: '步骤四',
icon: 'icon-4'
},
{
id: 4,
title: '步骤五',
icon: 'icon-5'
}]
参数currentStep表示步骤的实际进度
currentStep: 3
参数current表示步骤的显示进度
current: 1
分叉显示需要手动配置
const steps = [{
id: 0,
title: '步骤一',
icon: 'icon-1',
childSteps: [{
id: 10,
parentId: 0,
title: '步骤六',
icon: 'icon-1',
}]
}]
<template>
<div>
<div class="wrap">
<div class="svg-wrap"><svg></svg></div>
</div>
</div>
</template>
<script>
import * as d3 from 'd3'
export default {
name: 'Progress',
data () {
return {
height: 150,
width: 0,
svg: {},
innerWrap: {},
outterWrap: {},
innerSvg: {},
outterSvg: {}
}
},
mounted () {
this.steps.forEach((item, index) => {
item.index = index
})
this.width = Number(d3.select('.svg-wrap').style('width').replace(/px/g, ''))
this.svg = d3.select('.svg-wrap svg')
.attr('width', this.width)
.attr('height', this.height)
.append('g')
.attr('transform', `translate(${-(this.width + 300)}, 0)`)
this.innerWrap = this.svg.append('g')
this.outterWrap = this.svg.append('g')
this.render()
this.addResize()
},
methods: {
addResize () {
window.onresize = () => {
this.$nextTick(() => {
this.width = Number(d3.select('.svg-wrap').style('width').replace(/px/g, ''))
this.svg.attr('width', this.width).attr('height', this.height)
this.updateSvg()
})
}
},
render () {
this.outterSvg = this.setData({ svg: this.outterWrap, data: this.outterSteps })
this.innerSvg = this.setData({ svg: this.innerWrap, data: this.innerSteps })
this.updateSvg()
},
setData (params = {}) {
return params.svg
.selectAll('g')
.data(params.data)
.enter()
.append('g')
.attr('transform', '')
.on('click', (e, d) => {
if (d.selected) {
this.$emit('change', d.id)
}
if (d.index <= this.currentStep && !d.parentId) {
this.$emit('change', d.index)
}
})
},
updateSvg (params = {}) {
this.renderStep({
svg: this.innerSvg,
data: this.innerSteps
})
this.renderStep({
svg: this.outterSvg,
data: this.outterSteps
})
this.svg.transition()
.duration(1000)
.delay(30)
.attr('transform', `translate(0, 0)`)
},
renderStep (params = {}) {
params.svg.html('')
params.svg.append('text')
.text(function (d) { return d.title })
.attr('y', 55)
.attr('x', 15)
.attr('class', (d, i) => i === this.current ? 'selected' : '')
params.svg.append('path')
.attr('class', (d, i) => {
let className = ''
if (typeof d.parentId !== 'undefined') {
className = this.current === d.id ? 'path finish' : 'path'
} else {
className = i < this.current ? 'path finish' : 'path'
}
return className
})
.attr('d', (d, i) => {
let pathStr = `M35 17 L${this.itemWidth - 3} 17 Z`
if (typeof d.parentIndex !== 'undefined') {
// 分叉的路径显示
const his = -75 * i + 57
pathStr = `M${-this.itemWidth + 32} ${his} L35 17 Z`
} else {
if (i === params.data.length - 1) pathStr = ''
}
return pathStr
})
params.svg.attr('class', (d, i) => {
let className = 'progress-item '
if (typeof d.parentIndex !== 'undefined') {
className += d.selected ? 'finish ' : ''
className += this.current === d.id ? 'selected' : ''
} else {
className += i <= this.currentStep ? 'finish ' : ''
className += d.selected ? 'finish ' : ''
className += i === this.current ? 'selected' : ''
}
return className
})
params.svg.append('circle')
.attr('r', 19)
.attr('cx', 16)
.attr('cy', 15)
params.svg.append('g').html((d) => `${d.svg}`).attr('class', 'icon')
params.svg.transition()
.attr('transform', (d, i) => {
let num = i
let his = 50
if (typeof d.parentId !== 'undefined') {
num = d.parentIndex
his = 75 * i + 10
}
return `translate(${num * (this.itemWidth) + 15}, ${his})`
})
}
},
destroyed () {
window.onresize = null
},
watch: {
current () {
this.updateSvg()
},
currentStep () {
this.updateSvg()
}
},
computed: {
plus () {
return (this.itemWidth - 35) / this.steps.length
},
itemWidth () {
return (this.width - 58) / (this.steps.length - 1)
},
outterSteps () {
const arr = this.steps.filter(item => !item.child)
return arr
},
innerSteps () {
let rst = []
this.steps.forEach((item, index) => {
if (item.child) {
rst = item.child.map(step => {
step.parentIndex = index
return step
})
}
})
return rst
}
},
props: {
current: {
type: Number,
default: 0
},
currentStep: {
type: Number,
default: 0
},
steps: {
type: Array,
default: () => {
return []
}
}
}
}
</script>
<style lang="less" scoped>
.wrap {
.svg-wrap {
svg {
overflow: visible;
.progress-item {
circle {
fill: #fff;
stroke: #aaa;
stroke-width: 1px;
}
.path {
fill: #aaa;
stroke: #aaa;
stroke-width: 0.5px;
}
.icon {
svg path {
fill: #aaa;
}
}
text {
fill: #aaa;
text-anchor: middle;
}
&.finish {
cursor: pointer;
circle {
fill: #fff;
stroke: #08c;
}
.path.finish {
fill: #08c;
stroke: #08c;
}
.icon {
svg path {
fill: #08c;
}
}
text {
fill: #666;
}
}
&.selected {
circle {
fill: #08c;
stroke: #08cr;
}
.path.finish {
fill: #08c;
stroke: #08c;
}
.icon {
svg path {
fill: #fff;
}
}
text {
fill: #333;
&.selected {
font-weight: bold;
}
}
}
}
}
}
}
</style>
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!