antdVue + Vue3实现table + 节点的甘特图

实现效果:
在这里插入图片描述

项目背景:由于我们是vue2升级vue3项目,之前老项目Vue2使用的是gantt-elastic ,但是引入到Vue3版本中报错

Uncaught SyntaxError: The requested module ‘/node_modules/.vite/deps/vue.js?v=80f42b69’ does not provide an export named ‘default’

感觉是Vue3的导出方式不兼容这种vue2的默认导出引用方式导致的,又在网上扒了几个甘特图几乎都是只有图表部分,没有表格内容,遂自己实现。

实现思路:获取表格中所有开始时间与结束时间,筛选出最小与最大的时间节点进行计算得出总天数用来遍历生成多少个dom节点,然后根据dayjs().isBetween判断当前节点是否在所选范围之内,如果在就展示进度条,如果大于1天就把表格中的名称信息展示在进度条后一天。

具体代码:

 <a-table
      :columns="data.ganntColumns"
      :data-source="data.dataSource"
      size="small"
      :pagination="false"
      bordered
      ref="tableRef"
      defaultExpandAllRows
      :scroll="{ x: 2000 }"
    >
      <template #bodyCell="{ column, record }">
        <template v-if="column.dataIndex === 'dom'">
          <div class="block" v-if="dayjs(column.text).isBetween(dayjs(record.start).add(-1, 'days'), dayjs(record.endVal))"> </div>
          <div v-if="dayjs(column.text).isSame(dayjs(record.endVal))"> {{ record.label }} </div>
        </template>
      </template>
    </a-table>

这里需要把开始时间与结束时间换成你自己的真实key节点
setColunmPush 方法只需要在获取完表格数据后调用一次,再把获取到的数组数据传入进来就好了,其他的都不用改动

  import { onMounted, reactive, inject, watch, ref, nextTick } from 'vue';
  import dayjs from 'dayjs';
  import isBetween from 'dayjs/plugin/isBetween';
  dayjs.extend(isBetween);

 //类型注解
  interface DataProps {
    dataSource: Array<object>;
    ganntColumns: object[];
  }

  const data: DataProps = reactive({
    dataSource: [],
    ganntColumns: [
      {
        title: '序号',
        dataIndex: 'planCode',
        key: 'planCode',
        resizable: true,
        fixed: 'left',
        width: 100,
      },
      {
        title: '计划名称',
        dataIndex: 'label',
        key: 'label',
        resizable: true,
        fixed: 'left',
        width: 100,
      },
      {
        title: '阶段',
        dataIndex: 'stageText',
        key: 'stageText',
        resizable: true,
        fixed: 'left',
        width: 80,
      },
      {
        title: '项目节点',
        dataIndex: 'projectMilestoneText',
        key: 'projectMilestoneText',
        resizable: true,
        fixed: 'left',
        width: 100,
      },
      {
        title: '里程碑计划',
        dataIndex: 'isMilestoneText',
        key: 'isMilestoneText',
        resizable: true,
        fixed: 'left',
        width: 120,
      },
      {
        title: '计划开始时间',
        dataIndex: 'start',
        key: 'start',
        resizable: true,
        fixed: 'left',
        width: 125,
      },
      {
        title: '计划完成时间',
        dataIndex: 'endVal',
        key: 'endVal',
        resizable: true,
        fixed: 'left',
        width: 125,
      },
      {
        title: '前置任务',
        dataIndex: 'dependentOnText',
        key: 'dependentOnText',
        resizable: true,
        fixed: 'left',
        width: 100,
      },
      {
        title: '负责人',
        dataIndex: 'userText',
        key: 'userText',
        resizable: true,
        fixed: 'left',
        width: 80,
      },
      {
        title: '完成度',
        dataIndex: 'planSchedule',
        key: 'planSchedule',
        resizable: true,
        fixed: 'left',
        width: 80,
        customRender: ({ text }) => {
          return text + '%';
        },
      },
    ],
  });



 const getDateMax = (dataArr) => {
    let max = dataArr[0];
    for (let i = 1; i < dataArr.length; i++) {
      if (Date.parse(dataArr[i]) > Date.parse(max)) {
        max = dataArr[i];
      }
    }
    return max;
  };

  // 求最小值
  const getDateMin = (dataArr) => {
    let min = dataArr[0];
    for (let i = 1; i < dataArr.length; i++) {
      if (Date.parse(dataArr[i]) < Date.parse(min)) {
        min = dataArr[i];
      }
    }
    return min;
  };

  // newData为你的表格数据
  const setColunmPush = (newData) => {
    if (newData && newData.length) {
      // start改成你自己的真实数据中的开始时间字段
      const startAr = newData.map((item) => item.start);
      // endVal改成你自己的真实数据中的结束时间字段
      const endAr = newData.map((item) => item.endVal);
      const minTime = getDateMin(startAr);
      const maxTime = getDateMax(endAr);
      const intervalDay = Math.abs(dayjs(minTime).diff(maxTime, 'day')) + 3;

      const months = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'];
      const days = ['日', '一', '二', '三', '四', '五', '六'];
      const now = dayjs(minTime);
      const month: number = dayjs(minTime).add(-1, 'day').format('MM') * 1;
      const arr = [
        {
          title: months[month] + '月 ' + dayjs(minTime).add(-1, 'day').format('YYYY'),
          value: month + dayjs(minTime).add(-1, 'day').format('YYYY'),
          children: [],
        },
      ];

      for (var i = -1; i < intervalDay; i++) {
        const values = arr.map((item) => item.value);
        const newMonth: any = dayjs(minTime).add(i, 'day').format('MM') * 1;
        if (!values.includes(newMonth)) {
          arr.push({
            title: months[newMonth] + '月 ' + now.add(i, 'day').format('YYYY'),
            value: newMonth + now.add(i, 'day').format('YYYY'),
            children: [],
          });
        }

        const index = arr.findIndex((item) => item.value == newMonth + now.add(i, 'day').format('YYYY'));
        arr[index].children.push({
          title: now.add(i, 'day').format('DD') + ' 周' + days[now.add(i, 'day').day()],
          dataIndex: i < 0 ? 'root' : i >= 0 ? 'dom' : '',
          key: now.add(i, 'day').format('MM'),
          width: 90,
          text: now.add(i, 'day').format('YYYY-MM-DD'),
        });
      }

      data.ganntColumns = [...data.ganntColumns, ...arr.filter((item) => item.children.length)];
    }
  };

最后把dom节点的样式引入进来就ok了

<style lang="less" scoped>
  .block {
    padding: 0 !important;
    left: -8px;
    top: 0;
    right: 0;
    bottom: 0;
    margin: auto;
    position: absolute;
    cursor: pointer;
    width: calc(100% + 1px);
    height: 15px;
    margin-left: 8px;
    background-color: rgb(30, 188, 97);
    background-image: -webkit-gradient(
      linear,
      0 0,
      100% 100%,
      color-stop(0.25, rgb(33, 201, 103)),
      color-stop(0.25, transparent),
      color-stop(0.5, transparent),
      color-stop(0.5, rgb(33, 201, 103)),
      color-stop(0.75, rgb(33, 201, 103)),
      color-stop(0.75, transparent),
      to(transparent)
    );
    background-image: -moz-linear-gradient(
      -45deg,
      rgb(33, 201, 103) 25%,
      transparent 25%,
      transparent 50%,
      rgb(33, 201, 103) 50%,
      rgb(33, 201, 103) 75%,
      transparent 75%,
      transparent
    );
    background-image: -o-linear-gradient(
      -45deg,
      rgb(33, 201, 103) 25%,
      transparent 25%,
      transparent 50%,
      rgb(33, 201, 103) 50%,
      rgb(33, 201, 103) 75%,
      transparent 75%,
      transparent
    );
    background-image: linear-gradient(
      -45deg,
      rgb(33, 201, 103) 25%,
      transparent 25%,
      transparent 50%,
      rgb(33, 201, 103) 50%,
      rgb(33, 201, 103) 75%,
      transparent 75%,
      transparent
    );
  }
</style>

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黑豆1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值