记录---vue3如何封装一个基础甘特图?保姆级教程

🧑‍💻 写在开头
点赞 + 收藏 === 学会🤣🤣🤣

vue3封装一个基础甘特图

只支持简单展示功能
代码通俗易懂

效果图:

在这里插入图片描述
主要计算

计算出整体的日期范围
根据每项开始时间和结束时间计算出每一项所占的长度

在这里插入图片描述


// 基础甘特图封装
import { ref } from 'vue'
import dayjs from 'dayjs'
const data = [
    { order: 'AWM69963320-1', start: '2023-01-01', end: '2023-01-15', progress: 50 },
    { order: 'AWM69963320-1', start: '2023-01-18', end: '2023-02-15', progress: 30 },
    { order: 'AWM69963320-1', start: '2023-01-01', end: '2023-01-20', progress: 20 },
    { order: 'AWM69963320-1', start: '2023-02-18', end: '2023-02-25', progress: 10 },
]
// 日期数组
const dates = ref<string[]>([])
const itemWidth = 30
const init = () => {
    const earliestStart = data.reduce((min, item) => dayjs(item.start).isBefore(dayjs(min)) ? item.start : min, data[0].start)
    const latestEnd = data.reduce((max, item) => dayjs(item.end).isAfter(dayjs(max)) ? item.end : max, data[0].end)
    let currentDate = dayjs(earliestStart)
    let endDate = dayjs(latestEnd).add(1, 'day')
    while (currentDate.isBefore(endDate)) {
        dates.value.push(currentDate.format('YYYY-MM-DD'));
        currentDate = currentDate.add(1, 'day');
    }
}
// 计算每个任务的起始位置和宽度
const calculatePositionAndWidth = (item: any) => {
    const startIndex = dates.value.findIndex((date) => date === item.start)
    const endIndex = dates.value.findIndex((date) => date === item.end)
    const position = startIndex * itemWidth
    const width = (endIndex - startIndex + 1) * itemWidth
    console.log(width, position);
    return { position, width }
}
init()

代码还是非常少的,计算出日期存储到数组中用于渲染头部和每项长度的计算。

itemWidth: 头部每个日期的宽度(可以根据实际需求自行调整,我这只需要展示日,所以设定的比较短。)
渲染每条数据时都调用函数 calculatePositionAndWidth 计算出元素的偏移量和宽度。
偏移量:起始日期在数组中的位置 x 每个日期的宽度(itemWidth)
实际宽度:(结束日期在数组中的位置 - 起始日期在数组中的位置 + 1)x 每个日期的宽度(itemWidth),宽度需要加一个单位,这很好理解吧,假设起始日期在0位置,结束日期在1位置,1 - 0 = 1,而实际占了两个位置,所以需要 + 1。
有了偏移量和宽度,剩下的就是布局了,完整代码如下。

完整代码

css中: --labelWidth: 150px;
–labelWidth: 侧边栏的宽度,相当于 5 个日期长度,这个长度布局会用到。
“{ width: ${(dates.length + 5) * itemWidth}px }”,给每一项都赋予宽度,这个 + 5 就是侧边栏的宽度。数据太多太长的,滚动的时候需要固定左侧栏目和顶部日期,利于阅读。如果不指定每一项宽度,可能会导致,底部的border只有一部分。
至此,一个简单的甘特图封装完成。=。

<template>
    <div class="ganttChart">
        <ul class="itemList scrollBar">
            <li class="item header" :style="{ width: `${(dates.length + 5) * itemWidth}px` }">
                <div class="label"></div>
                <div class="dates">
                    <div class="date" :style="{ width: `${itemWidth}px` }" v-for="date in dates" :key="date">{{ date.split('-')[2] }}</div>
                </div>
            </li>
            <li class="item itemData" v-for="item in data" :style="{ width: `${(dates.length + 5) * itemWidth}px` }">
                <div class="label">{{ item.order }}</div>
                <a-tooltip color='white'>
                    <template #title>
                        <div style="color: black;">
                            排程数量:1000
                        </div>
                        <div style="color: black;">
                            完成数量:500
                        </div>
                    </template>
                    <div class="speedOfProgress"
                        :style="{ left: `${calculatePositionAndWidth(item).position}px`, width: `${calculatePositionAndWidth(item).width}px` }">
                        <div class="progress" :style="{ width: `${item.progress}%` }"></div>
                        {{ item.progress }}
                    </div>
                </a-tooltip>
            </li>
        </ul>
    </div>
</template>

<script setup lang="ts">
// 基础甘特图封装
import { ref } from 'vue'
import dayjs from 'dayjs'
const data = [
    { order: 'AWM69963320-1', start: '2023-01-01', end: '2023-01-15', progress: 50 },
    { order: 'AWM69963320-1', start: '2023-01-18', end: '2023-02-15', progress: 30 },
    { order: 'AWM69963320-1', start: '2023-01-01', end: '2023-01-20', progress: 20 },
    { order: 'AWM69963320-1', start: '2023-02-18', end: '2023-02-25', progress: 10 },
    { order: 'AWM69963320-1', start: '2023-01-01', end: '2023-01-15', progress: 30 },
    { order: 'AWM69963320-1', start: '2023-01-18', end: '2023-02-15', progress: 70 },
    { order: 'AWM69963320-1', start: '2023-01-01', end: '2023-01-15', progress: 50 },
    { order: 'AWM69963320-1', start: '2023-01-18', end: '2023-02-15', progress: 30 },
    { order: 'AWM69963320-1', start: '2023-01-01', end: '2023-01-20', progress: 20 },
    { order: 'AWM69963320-1', start: '2023-02-18', end: '2023-02-25', progress: 10 },
    { order: 'AWM69963320-1', start: '2023-01-01', end: '2023-01-15', progress: 30 },
    { order: 'AWM69963320-1', start: '2023-01-18', end: '2023-02-15', progress: 70 },
    { order: 'AWM69963320-1', start: '2023-01-01', end: '2023-01-15', progress: 50 },
    { order: 'AWM69963320-1', start: '2023-01-18', end: '2023-02-15', progress: 30 },
    { order: 'AWM69963320-1', start: '2023-01-01', end: '2023-01-20', progress: 20 },
    { order: 'AWM69963320-1', start: '2023-02-18', end: '2023-02-25', progress: 10 },
    { order: 'AWM69963320-1', start: '2023-01-01', end: '2023-01-15', progress: 30 },
    { order: 'AWM69963320-1', start: '2023-01-18', end: '2023-02-15', progress: 70 },
]
// 日期数组
const dates = ref<string[]>([])
const itemWidth = 30
const init = () => {
    const earliestStart = data.reduce((min, item) => dayjs(item.start).isBefore(dayjs(min)) ? item.start : min, data[0].start)
    const latestEnd = data.reduce((max, item) => dayjs(item.end).isAfter(dayjs(max)) ? item.end : max, data[0].end)
    let currentDate = dayjs(earliestStart)
    let endDate = dayjs(latestEnd).add(1, 'day')
    while (currentDate.isBefore(endDate)) {
        dates.value.push(currentDate.format('YYYY-MM-DD'));
        currentDate = currentDate.add(1, 'day');
    }
}
// 计算每个任务的起始位置和宽度
const calculatePositionAndWidth = (item: any) => {
    const startIndex = dates.value.findIndex((date) => date === item.start)
    const endIndex = dates.value.findIndex((date) => date === item.end)
    const position = startIndex * itemWidth
    const width = (endIndex - startIndex + 1) * itemWidth
    console.log(width, position);
    return { position, width }
}
init()
</script>

<style scoped lang="scss">
.ganttChart {
    --labelWidth: 150px;
    height: calc(100% - 35px);

    .itemList {
        height: 100%;
        position: relative;

        .header {
            position: sticky;
            top: 0;
            background-color: white;
            z-index: 10;
        }


        .item {
            display: grid;
            box-sizing: border-box;
            border-bottom: 1px solid #ccc;
            grid-template-columns: var(--labelWidth) 1fr;

            &:first-child {
                border: none;
            }

            .label {
                position: sticky;
                left: 0;
                background-color: white;
                z-index: 5;
                text-align: center;
            }

            .dates {
                display: flex;
                position: relative;
                background-color: white;

                .date {
                    box-sizing: border-box;
                    border: 1px solid #ccc;
                    text-align: center;
                    border-left: none;

                    &:first-child {
                        border-left: 1px solid #ccc;
                    }
                }
            }

            .speedOfProgress {
                margin-top: 4px;
                height: 10px;
                position: relative;
                background-color: #b9d8fb;
                border-radius: 5px;
                overflow: hidden;
                cursor: pointer;

                .progress {
                    position: absolute;
                    height: 100%;
                    background-color: #27f;
                }
            }
        }

        .itemData {
            padding: 5px 0;
        }
    }
}
</style>


补充滚动条样式

scrollBar(滚动条样式) 是单独封装在sass文件中的,所以没在原文中显示,补充一下滚动条样式


//滚动条
.scrollBar{
  overflow-y: overlay;

  &::-webkit-scrollbar {
    width: 8px;
    height: 8px;
  }

  &::-webkit-scrollbar-track {
    background: transparent;
  }

  &::-webkit-scrollbar-thumb {
    background-color: #bfbfbf;
    border-radius: 5px;
    border: 1px solid #F1F1F1;
    box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
  }

  &::-webkit-scrollbar-thumb:hover {
    background-color: #A8A8A8;
  }

  &::-webkit-scrollbar-thumb:active {
    background-color: #787878;
  }

  /*边角,即两个滚动条的交汇处*/
  &::-webkit-scrollbar-corner {
    background-color: #FFFFFF;
  }

}

本文转载于:https://juejin.cn/post/7458107546391117887
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值