canvas实现带箭头拐弯步骤条

请添加图片描述

<template>
    <div>
        <div class="canvas-container">
            <canvas ref="canvas" id="canvas"></canvas>
            <ul class="step-wrap">
                <li :ref="`step${index}`" v-for="(item,index) in  stepBarData" :key="index" :style="cssVars" :class="{'active': index == status}">
                    <span v-if="status < index" class="number">{{ index + 1 }}</span>
                    <img v-else src="@/assets/images/step.png" class="image" alt />
                    <p>{{ item.label }}</p>
                </li>
            </ul>
        </div>
    </div>
</template>

<script>
export default {
    name: '',
    props: {
        status: {
            type: Number,
            default: 5,
        },
    },
    components: {},
    data() {
        return {
            stepBarData: [
                {
                    label: '商户下单',
                },
                {
                    label: '客户预约',
                },
                {
                    label: '人员调配',
                },
                {
                    label: '客户确认',
                },
                {
                    label: '上门服务',
                },
                {
                    label: '结单确认',
                },
                {
                    label: '服务完成',
                },
            ],
        }
    },
    computed: {
        cssVars() {
            return {
                '--normal-color': '#afb0b2',
                '--active-color': '#0000ff',
            }
        },
    },
    watch: {},
    methods: {
        drawCanvas(status) {
            const canvas = document.getElementById('canvas')
            const container = document.getElementsByClassName('canvas-container')[0]
            canvas.width = container.clientWidth
            const ctx = canvas.getContext('2d')
            const fontWidth = 3 * 16
            const color = status == 5 || status == 6 ? '#0000ff' : '#afb0b2'
            ctx.strokeStyle = color
            ctx.lineWidth = 1

            const canvasWindowLeft = this.$refs[`canvas`].getBoundingClientRect().left
            const canvasWindowTop = this.$refs[`canvas`].getBoundingClientRect().top
            const startX = this.$refs[`step${5}`][0].getBoundingClientRect().left - canvasWindowLeft + 10
            const startY = this.$refs[`step${5}`][0].getBoundingClientRect().top - canvasWindowTop - 10
            const middleX = this.$refs[`step${3}`][0].getBoundingClientRect().left - canvasWindowLeft
            const middleY = this.$refs[`step${3}`][0].getBoundingClientRect().top - canvasWindowTop
            const endX = this.$refs[`step${1}`][0].getBoundingClientRect().left - canvasWindowLeft + 10
            const endY = this.$refs[`step${1}`][0].getBoundingClientRect().top - canvasWindowTop - 10

            ctx.beginPath()
            ctx.moveTo(startX, startY)
            const movingUpStartY = startY - 30
            const movingUpEndY = endY - 30
            ctx.lineTo(startX, movingUpStartY)
            ctx.lineTo(middleX + fontWidth, movingUpStartY)
            ctx.moveTo(middleX - 32, movingUpStartY)
            ctx.lineTo(endX, movingUpEndY)
            ctx.lineTo(endX, endY)
            ctx.stroke()
            ctx.closePath()

            ctx.beginPath()
            let arrowX, arrowY
            const headlen = 12
            const theta = 25
            const angle = (Math.atan2(endY - movingUpEndY, endX - endX) * 180) / Math.PI
            const angleTop = ((angle + theta) * Math.PI) / 180
            const angleBot = ((angle - theta) * Math.PI) / 180
            const topX = headlen * Math.cos(angleTop)
            const topY = headlen * Math.sin(angleTop)
            const botX = headlen * Math.cos(angleBot)
            const botY = headlen * Math.sin(angleBot)
            arrowX = endX - topX
            arrowY = endY - topY
            ctx.moveTo(arrowX, arrowY)
            const endArrowX = endX - botX
            const endArrowY = endY - botY
            ctx.quadraticCurveTo(endX, endY - headlen / 2, endArrowX, endArrowY)
            ctx.lineTo(endX, endY)
            ctx.lineTo(arrowX, arrowY)
            ctx.fillStyle = color
            ctx.fill()
            ctx.strokeStyle = color
            ctx.font = '16px Arial'
            ctx.fillText('未结单', middleX - 14, movingUpStartY + 6)
            ctx.lineJoin = 'round'
            ctx.stroke()
            ctx.closePath()
        },
        addResizeListener() {
            console.log('sss')
            window.addEventListener('resize', this.drawCanvas(this.status))
        },
        removeResizeListener() {
            window.removeEventListener('resize', this.drawCanvas)
        },
    },
    created() {},
    mounted() {
        this.drawCanvas(this.status)
        window.addEventListener('resize', this.addResizeListener)
    },
    beforeCreate() {},
    beforeDestroy() {},
    destroyed() {
        this.removeResizeListener()
    },
    activated() {},
}
</script>

<style scoped lang='scss'>
.canvas-container {
    position: relative;
    height: 158px;
    width: 100%;
    #canvas {
        position: absolute;
        width: 100%;
        height: 158px;
    }
}

.step-wrap {
    display: flex;
    justify-content: space-between;
    align-items: center;
    height: 158px;
    width: 100%;

    li {
        position: relative;
        flex: auto;
        display: inline-flex;
        align-items: center;
        color: var(--active-color);
        p {
            position: absolute;
            top: 30px;
            left: -22px;
        }
        .number {
            width: 24px;
            height: 24px;
            border-radius: 50%;
            display: flex;
            justify-content: center;
            align-items: center;
            border: 2px solid;
        }
        img {
            width: 24px;
            height: 24px;
        }
    }
    li:first-child {
        margin-left: 24px;
    }
    li:last-child {
        flex: none;
        padding-right: 24px;
    }
    li:not(:first-child)::before {
        content: '➤';
        position: absolute;
        top: 0px;
        left: -20px;
        display: block;
        height: 2px;
    }

    li:not(:last-child)::after {
        content: '';
        border-bottom: 1px solid var(--active-color);
        position: absolute;
        top: 9.5px;
        left: 30px;
        display: block;
        width: calc(100% - 41px);
        height: 2px;
    }
}

.step-wrap .active {
    color: var(--active-color);
}

/* 高亮的尾箭头 */
.step-wrap .active::after {
    border-color: var(--normal-color) !important;
}

/* 未完成 箭头和字体样式置灰 */
.step-wrap > .active ~ li,
.step-wrap > .active ~ li::before {
    color: var(--normal-color) !important;
}
/* 未完成 引导线样式置灰 */
.step-wrap > .active ~ li::after {
    border-color: var(--normal-color) !important;
}
</style>
  • 10
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值