Vue3 TS dhtmlx-gantt实现甘特图

13,500次阅读
没有评论

共计 7311 个字符,预计需要花费 19 分钟才能阅读完成。

实现样式

Vue3 TS dhtmlx-gantt 实现甘特图

因为只做展示,所以实现很简单

实现功能

  1. 自定义列头
  2. 增加斑马线,实际结束时间(自定义实现)
  3. 自定义进度展示,根据层级让进度背景颜色变浅
  4. marker 标记今天
  5. 自定义提示框内容

实现

import { gantt } from "dhtmlx-gantt"; 
import { ref } from "vue";
import dayjs from "dayjs";
import { WorkGantt } from "@/api/information-overview/types";

export const useGantt = () => {
  const ganttRef = ref();
  gantt.config.date_format = "%Y/%m/%d"; 
  gantt.config.duration_unit = "month"; 
  gantt.config.scale_unit = "month"; 
  gantt.config.date_scale = "%Y/%m/%d"; 
  gantt.config.step = 1; 
  gantt.i18n.setLocale("cn"); 
  gantt.config.autosize = true; 
  gantt.config.autofit = true; 
  gantt.config.open_tree_initially = true; 
  
  gantt.config.readonly = true;
  
  gantt.config.show_grid = true;
  
  gantt.templates.grid_open = (item: any) => {
    return (
      "
"
); }; gantt.templates.grid_folder = (item: any) => { return ""; }; gantt.templates.grid_file = (item: any) => { return ""; }; gantt.templates.task_text = function (start, end, task) { if (task.real_end_date) { const sizes = gantt.getTaskPosition( task, task.start_date, new Date(dayjs(task.real_end_date).format("YYYY-MM-DD")) ); return `
${sizes.width}px;height:100%">
`
; } return ""; }; gantt.templates.progress_text = function (start, end, task) { const level = task.$level as number; if (task.progress) { return `
${adjustColor( "#04aac1", level * 20, 0.7 )}">${Math.round(task.progress * 100)}%
`
; } return ""; }; gantt.config.columns = [ { name: "keyNode", resize: true, label: "关键节点", width: 200, align: "center", tree: true, }, { name: "receiver", resize: true, label: "签收人", width: 80, align: "center", }, ]; gantt.plugins({ marker: true, tooltip: true }); const today = new Date(dayjs(new Date()).format("YYYY-MM-DD")); const dateToStr = gantt.date.date_to_str(gantt.config.task_date); gantt.addMarker({ start_date: today, css: "today", text: "今日:" + dayjs(new Date()).format("YYYY-MM-DD"), title: "Today:" + dateToStr(today), }); gantt.templates.tooltip_text = function (start, end, task) { return `

关键节点详情

关键节点 ${ task.keyNode ? task.keyNode : "暂无" }
签收人 ${ task.receiver ? task.receiver : "暂无" }
节点数量 ${ task.quantity }
完成数量 ${ task.progressValue }
复盘认识 ${ task.reflectionOnKnowledge ? task.reflectionOnKnowledge : "暂无" }
复盘问题 ${ task.reflectionOnProblems ? task.reflectionOnProblems : "暂无" }
复盘总结 ${ task.reflectionOnCountermeasures ? task.reflectionOnCountermeasures : "暂无" }
`
; }; const init = (data: WorkGantt, startDate: string, endDate: string) => { gantt.config.start_date = new Date(startDate); gantt.config.end_date = new Date(endDate); gantt.init(ganttRef.value); gantt.parse(data); }; const refresh = (data: WorkGantt, startDate: string, endDate: string) => { gantt.clearAll(); gantt.config.start_date = new Date(startDate); gantt.config.end_date = new Date(endDate); gantt.parse(data); gantt.refreshData(); }; const destroyed = () => { gantt.clearAll(); }; return { init, refresh, ganttRef, destroyed, }; }; function adjustColor(color: string, depth: number, alpha: number) { const isRgb = color.length === 3 || color.length === 4; const isHex = /^#[0-9a-fA-F]{6}$/.test(color); if (!isRgb && !isHex) { throw new Error( "Invalid color format. Accepted formats: RGB (e.g., [255, 0, 0]) or Hex (e.g., #ff0000)" ); } let rgbaColor: any; if (isRgb) { rgbaColor = [...color, alpha]; } else if (isHex) { const rgbColor = hexToRgb(color) as number[]; rgbaColor = [...rgbColor, alpha]; } rgbaColor = adjustColorValue(rgbaColor, depth); return `rgba(${rgbaColor[0]},${rgbaColor[1]},${rgbaColor[2]},${rgbaColor[3]})`; } function hexToRgb(hex: string) { const result = /^#?([a-fd]{2})([a-fd]{2})([a-fd]{2})$/i.exec(hex); return result ? [ parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16), ] : null; } function adjustColorValue(rgba: number[], depth: number) { return [ Math.round(rgba[0] + depth) > 255 ? 255 : Math.round(rgba[0] + depth), Math.round(rgba[1] + depth) > 255 ? 255 : Math.round(rgba[1] + depth), Math.round(rgba[2] + depth) > 255 ? 255 : Math.round(rgba[2] + depth), rgba[3], ]; }

使用

template>
  div class="bg-white">
    div class="flex justify-between p-2">
      div class="flex">
        el-radio-group v-model="state.type">
          el-radio-button label="self"> 个人任务 /el-radio-button>
          el-radio-button label="team"> 全局任务 /el-radio-button>
        /el-radio-group>
        div class="ml-8 flex items-center">
          span class="font-size-4 mr-4"> 日期范围 /span>
          el-date-picker
            v-model="state.time"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            @change="changeDate"
          />
        /div>
      /div>
      el-button type="primary" @click="exportImg" :icon="Download"
        > 导出图片 /el-button
      >
    /div>
    div
      v-loading="state.loading"
      id="gantt"
      ref="ganttRef"
      class="h-full w-full"
    >/div>
  /div>
/template>

script lang="ts">
export default { name: "ObjectProgress" };
/script>
script lang="ts" setup>
import "dhtmlx-gantt/codebase/dhtmlxgantt.css"; 
import { onMounted, reactive } from "vue";
import html2canvas from "html2canvas";
import { useGantt } from ".";
import { Download } from "@element-plus/icons-vue";
import { gantt } from "dhtmlx-gantt";
import { getWorkGantt } from "@/api/january-post";
import { useUserStoreHook } from "@/store/modules/user";
import { WorkGantt } from "@/api/information-overview/types";
import dayjs from "dayjs";

const state = reactive({
  tasks: {
    data: [],
  } as WorkGantt,
  type: "self",
  timelist: "",
  time: "",
  loading: false,
});
const { account } = useUserStoreHook().user;
const { init, ganttRef, refresh } = useGantt();

watch(
  () => state.type,
  () => {
    getWorkGanttList((data, startDate, endDate) => {
      refresh(data, startDate, endDate);
    });
  }
);


const getWorkGanttList = (
  callback: (data: any, startDate: string, endDate: string) => void
) => {
  state.loading = true;
  const parmas = {
    type: state.type,
    user: account,
    timelist: state.timelist,
  };
  
  getWorkGantt(parmas)
    .then((response) => {
      const data = response.data;
      const handleData = data.map((item, index) => {
        const id = index + 1;
        const start_date = dayjs(item.releaseTime).format("YYYY-MM-DD");
        const end_date = dayjs(item.signingTime).format("YYYY-MM-DD");
        const real_end_date = item.completionTime
          ? dayjs(item.completionTime).format("YYYY-MM-DD")
          : "";
        return {
          id,
          start_date,
          end_date,
          real_end_date,
          progress: item.progressBar,
          keyNode: item.keyNode,
          receiver: item.receiver,
          name: item.name,
          reflectionOnKnowledge: item.reflectionOnKnowledge,
          reflectionOnProblems: item.reflectionOnProblems,
          reflectionOnCountermeasures: item.reflectionOnCountermeasures,
          quantity: item.quantity,
          progressValue: item.progressValue,
        };
      });
      const endDate = dayjs(
        Math.max(
          ...data
            .map((item) => [item.completionTime, item.signingTime])
            .flat()
            .map((item) => new Date(item).getTime())
        )
      ).format("YYYY-MM-DD");
      const startDate = dayjs(
        Math.min(
          ...data
            .map((item) => item.releaseTime)
            .map((item) => new Date(item).getTime())
        )
      ).format("YYYY-MM-DD");
      state.tasks.data = handleData;
      callback(state.tasks, startDate, endDate);
    })
    .finally(() => {
      state.loading = false;
    });
};


const exportImg = () => {
  html2canvas(document.querySelector("#gantt")!).then(function (canvas) {
    downloadPng(canvas);
  });
};

const downloadPng = (el: HTMLCanvasElement) => {
  
  var link = document.createElement("a");
  link.href = el.toDataURL("image/png");
  link.download = `${state.type === "personal" ? "个人任务" : "全局任务"}.png`;
  
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
};

const changeDate = (date: Date[]) => {
  if (date) {
    state.timelist = date
      .map((item) => dayjs(item).format("YYYY/MM/DD"))
      .join(";");
  } else {
    state.timelist = "";
  }
  getWorkGanttList((data, startDate, endDate) => {
    refresh(data, startDate, endDate);
  });
};

onMounted(() => {
  getWorkGanttList((data, startDate, endDate) => {
    init(data, startDate, endDate);
  });
});
/script>

style lang="scss" scoped>
:deep(.gantt_task_line) {
  background-color: #fff;
  border-color: rgb(220 223 230 / 100%);
  border-radius: 4px;

  .gantt_task_content {
    z-index: 1;
    overflow: initial;
    color: #000;
  }

  .gantt_task_progress_wrapper {
    z-index: 2;
    border-radius: 4px;
  }
}

:deep(.gantt_task_progress) {
  background-color: transparent;
}

:deep(.real-task) {
  z-index: 3;
  background: url("../../../../../assets/icons/diagonal-line.svg") repeat;
  border: 1px solid rgb(220 223 230 / 100%);
  border-radius: 4px;
  opacity: 0.5;
}

:deep(.gantt_marker) {
  z-index: 99;
}
/style>

原文地址: Vue3 TS dhtmlx-gantt 实现甘特图

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于2024-10-07发表,共计7311字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)