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"; //设置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 ("<div data-icon='" +(item.$open ? "close" : "open") +"' class='gantt_tree_icon gantt_" +(item.$open ? "close" : "open") +"'></div>");};//更改父项图标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 `<div class="real-task" style="position:absolute;left:0px;top:0px;width:${sizes.width}px;height:100%"></div>`;}return "";};// 指定工单栏已完成部分的文本gantt.templates.progress_text = function (start, end, task) {const level = task.$level as number; //层级if (task.progress) {return `<div style="text-align:right;color:#000;background-color:${adjustColor("#04aac1",level * 20,0.7)}">${Math.round(task.progress * 100)}%</div>`;}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 `<h3>关键节点详情</h3><div class="pop-message"><span>关键节点</span><span>${task.keyNode ? task.keyNode : "暂无"}</span></div><div class="pop-message"><span>签收人</span><span>${task.receiver ? task.receiver : "暂无"}</span></div><div class="pop-message"><span>节点数量</span><span>${task.quantity}</span></div><div class="pop-message"><span>完成数量</span><span>${task.progressValue}</span></div><div class="pop-message"><span>复盘认识</span><span>${task.reflectionOnKnowledge ? task.reflectionOnKnowledge : "暂无"}</span></div><div class="pop-message"><span>复盘问题</span><span>${task.reflectionOnProblems ? task.reflectionOnProblems : "暂无"}</span></div><div class="pop-message"><span>复盘总结</span><span>${task.reflectionOnCountermeasures? task.reflectionOnCountermeasures: "暂无"}</span></div>`;};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-pickerv-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><divv-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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/645378.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32F407移植OpenHarmony笔记1

参考文档&#xff1a; OpenAtom OpenHarmonywidthdevice-width,initial-scale1.0https://docs.openharmony.cn/pages/v3.2/zh-cn/device-dev/get-code/gettools-acquire.md/ 搭建环境 安装linux系统: Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-91-generic x86_64) 下载源代码&a…

如何在转接的NVME 固态盘上安装WIN 系统并引导启动

问题&#xff1a; 家里的台式机一直挂着一块128G的SSD固态盘&#xff0c;由于家里自己建了NAS存储&#xff0c;所以基本数据都在NAS里&#xff0c;台式机就没有挂机械盘了&#xff0c;但是最近台式机空间被系统侵蚀&#xff0c;显然就不够用了&#xff0c;除了清理系统&#xf…

bash 5.2中文修订4

Compound Commands 复合命令 复合命令是 shell 编程语言的结构。每个构造都以保留字或控制运算符开始&#xff0c;并以相应的保留字或运算符终止。与复合命令关联的任何重定向&#xff08;请参阅 Redirections &#xff09;都适用于该复合命令中的所有命令&#xff0c;除非显式…

vue3使用AntV G6 (图可视化引擎)历程[二]

上期回顾&#xff1a;历程[一]描述了基本的树状图的绘制&#xff0c;默认节点类型defaultNode中的type是circle,下面这篇描述的是节点抽离自定义节点并做数据静态渲染。 官网地址&#xff1a;https://g6-next.antv.antgroup.com/manual/introduction 一、案例效果 二、自定义节…

使用CSS 或 SASS 实现主题背景切换效果

目录 &#x1f389;应用背景 &#x1f389;分析实现思路 &#x1f389;CSS实现主题切换 &#x1f389;SCSS实现主题切换 &#x1f389;结语 &#x1f389;应用背景 现在的主流网站中&#xff0c;无论是一些技术文档获取官网&#xff0c;都存在着使用一个switch按钮实现主题…

Nginx与keepalived实现集群

提醒一下&#xff1a;下面实例讲解是在mac虚拟机里的Ubuntu系统演示的&#xff1b; Nginx与keepalived实现集群实现的效果 两台服务器都安装Nginx与keepalived&#xff1a; mater服务器的ip(192.168.200.2) backup服务器的ip(192.168.200.4) 将 mater服务器Nginx与keepalived都…

【DG 特长生2019】模拟赛赛后总结(2024.1.24)

打了330pt,订正后350pt T1 签到 T2 dfs剪枝&#xff08;虽然我写挂了&#xff09; T3 NOIP原题 T4 floyd 主要是想分享一下T4。 写了一种基于floyd的做法。 感觉好像和大部分人的写法不太一样。 因为看到大小关系&#xff0c;我就想到了传递性。 floyd是可以维护传递…

Unity中URP下获取每一个额外灯数据

文章目录 前言一、我们先来看一下 SimpleLit 中的调用二、获取额外灯索引1、非移动平台2、非GLES平台3、大多数平台 三、获取额外灯数据 前言 在上一篇文章中&#xff0c;我们知道了URP下是怎么获取额外灯数量的。 Unity中URP下获取额外灯数量 在这篇文章中&#xff0c;我们…

虹科分享丨AR与AI融合加速,医疗护理更便捷!

来源&#xff1a;虹科数字化与AR 虹科分享丨AR与AI融合加速&#xff0c;医疗护理更便捷&#xff01; 原文链接&#xff1a;https://mp.weixin.qq.com/s/Fi0wNfk_TDXRo_1-6cSRNQ 欢迎关注虹科&#xff0c;为您提供最新资讯&#xff01; #AR眼镜 #医疗护理 根据Reports and Da…

MySQL函数—流程函数

MySQL函数—流程函数&#xff1a;用于实现条件筛选&#xff0c;从而题搞语句的效率。 MySQL函数—流程函数 函数功能IF(value,t,f)如果value为true&#xff0c;则返回t&#xff0c;否则返回fIFNULL(value1,value2)如果value1不为空&#xff0c;返回value1&#xff0c;否则返回v…

5G安卓手机定制_基于天玑900的安卓主板方案

5G安卓手机方案是一款采用联发科MT6877(天玑900)平台的高性能、可运行安卓操作系统的5G智能模块。该手机采用台积电6纳米低功耗工艺&#xff0c;主频高达2.4GHz&#xff0c;内存支持LPDDR5&#xff0c;并支持5G Sub-6GHz全频段和5G双载波聚合技术等多种制式。同时&#xff0c;该…

docker相关

下载Ubuntu18.04文件64位&#xff08;32位安装不了MySQL&#xff09; https://old-releases.ubuntu.com/releases/18.04.4/?_ga2.44113060.1243545826.1617173008-2055924693.1608557140 Linux ubuntu16.04打开控制台&#xff1a;到桌面&#xff0c;可以按快捷键ctrlaltt 查…

国民技术N32G430C8开发笔记一-新建IAR工程

一、创建IAR工程 1、新建工程&#xff0c;保存到project文件夹。 2、添加SDK到工程。 根据原厂SDK的文件结构在IAR新建相应分组&#xff0c;把各个文件夹的文件加载进去&#xff0c;其中startup文件选择IAR平台的startup_n32g430_EWARM.s。 3、添加头文件路径&#xff0…

2024-01-24(ElasticSearch)

1.mysql和elasticsearch的架构&#xff1a; 2.IK分词器利于分中文词汇。 底层是有一个中文字典&#xff0c;这个字典中的中文词汇也是可以拓展的和禁用某些词。 3.mapping常见属性&#xff1a; type&#xff1a;数据类型 index&#xff1a;是否索引 analyzer&#xff1a;分…

重设红帽Linux root密码:简单实用指南

在使用红帽Linux系统时&#xff0c;我们可能会遇到忘记root密码的情况红帽linux系统root密码&#xff0c;这时候该怎么办呢&#xff1f;本文将详细介绍如何重置红帽Linux系统的root密码。 1.进入单用户模式 首先&#xff0c;我们需要进入单用户模式。开机时红帽linux系统root…

一个使用pyqt的word文档查重工具

一个使用pyqt的word文档查重工具 使用场景代码使用截图打包好的软件下载链接结尾 使用场景 有时我们在借鉴一篇文档之后还不想有太多重复&#xff0c;这个时候可以使用这个工具对两个word文档进行对比 代码 import sys from PyQt5.QtWidgets import QApplication, QMainWind…

Redisson 分布式锁解决主从一致性问题的原理

目录 一、主从不一致产生原因 二、Redisson 解决主从一致性的原理 一、主从不一致产生原因 1. Redis 主从集群&#xff1a;主从读写分离&#xff0c;主节点将数据同步给从节点 主节点&#xff1a;增删改从节点&#xff1a;读 2. 主从同步存在延迟&#xff0c;若主节点宕机…

前端开发提高效率的两大工具

一、浏览器中的开发者工具 怎么启动开发者工具&#xff1f; 在浏览器中按下F12或者鼠标右键点击检查 怎么利用&#xff08;常用的几点&#xff09;&#xff1f; 1、元素 点击标红的图标可以用于在页面选择元素&#xff0c;同时右侧会找到元素在前端代码中的位置 点击下方红…

Hudi学习笔记(一)

大数据发展背景 Hudi用于管理分布式文件系统上大型分析数据集存储&#xff0c;支持Spark和Flink整合。它能够是DFS数据集在分钟级时延内支持变更&#xff0c;也支持下游系统对这个数据集的增量处理。 学习目标 什么是数据湖为什么使用数据湖Hudi基本功能如何编译Hudi源码Hud…

vue 解决:Module not found: Error: Can‘t resolve ‘vue-router‘ 的问题

1、问题描述&#xff1a; 其一、报错为&#xff1a; Module not found: Error: Cant resolve vue-router 中文为&#xff1a; 找不到模块&#xff1a;错误&#xff1a;无法解析“vue-router” 其二、问题描述为&#xff1a; 根据报错的中文信息可知&#xff1a;应该是无法…