实现样式
因为只做展示,所以实现很简单
实现功能
- 自定义列头
- 增加斑马线,实际结束时间(自定义实现)
- 自定义进度展示,根据层级让进度背景颜色变浅
- marker标记今天
- 自定义提示框内容
实现
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-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>