Vue3+TS+dhtmlx-gantt实现甘特图

实现样式

Vue3+TS+dhtmlx-gantt实现甘特图_第1张图片

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

实现功能

  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"; //设置x轴的日期格式
  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 ""; }; // timeLine 文字 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", }, ]; // 开启marker插件 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)" ); } // 将RGB或十六进制颜色转为RGBA格式 let rgbaColor: any; if (isRgb) { rgbaColor = [...color, alpha]; } else if (isHex) { const rgbColor = hexToRgb(color) as number[]; rgbaColor = [...rgbColor, alpha]; } // 根据深浅值调整RGBA值 rgbaColor = adjustColorValue(rgbaColor, depth); return `rgba(${rgbaColor[0]},${rgbaColor[1]},${rgbaColor[2]},${rgbaColor[3]})`; } // 十六进制转RGB function hexToRgb(hex: string) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{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);
    });
  }
);

/**
 * @description 获取甘特图数据
 */
const getWorkGanttList = (
  callback: (data: any, startDate: string, endDate: string) => void
) => {
  state.loading = true;
  const parmas = {
    type: state.type,
    user: account,
    timelist: state.timelist,
  };
  // debugger;
  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;
    });
};

/**
 * @description 甘特图转canvas
 */
const exportImg = () => {
  html2canvas(document.querySelector("#gantt")!).then(function (canvas) {
    downloadPng(canvas);
  });
};
/**
 * @description 下载canvas
 */
const downloadPng = (el: HTMLCanvasElement) => {
  // 创建一个新的a元素,设置其href为canvas的toDataURL方法,并添加download属性
  var link = document.createElement("a");
  link.href = el.toDataURL("image/png");
  link.download = `${state.type === "personal" ? "个人任务" : "全局任务"}.png`;
  // 触发a元素的click事件以开始下载
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
};
/**
 * @description 选择日期
 */
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>

你可能感兴趣的:(甘特图,vue.js,javascript)