Files
2023-03-24 13:28:06 +08:00

339 lines
8.7 KiB
Vue

<template>
<ChartContainer title="项目甘特图">
<template #suffix>
<!-- <n-config-provider :locale="zhCN" :theme="darkTheme">
<NRadioGroup v-model:value="mode" @update:value="modeChanged">
<NRadioButton label="实际" :value="0"></NRadioButton>
<NRadioButton label="预期" :value="1"></NRadioButton>
</NRadioGroup>
</n-config-provider> -->
<div class="flex w-32 h-8 items-center justify-between text-sm">
<div
class="rounded-full w-4 h-4"
style="background-color: springgreen"
></div>
<div>计划</div>
<div
class="rounded-full w-4 h-4"
style="background-color: indianred"
></div>
<div>实际</div>
</div>
</template>
<div id="gantt-chart"></div>
</ChartContainer>
</template>
<script setup name="Gantt">
import ChartContainer from "../../components/ChartContainer.vue";
import { gantt } from "dhtmlx-gantt";
import dayjs from "dayjs";
import duration from "dayjs/plugin/duration";
import { throttle } from "lodash-es";
// import {
// NRadioGroup,
// NRadioButton,
// NConfigProvider,
// darkTheme,
// zhCN,
// } from "naive-ui";
import { onMounted, ref } from "vue";
import axios from "axios";
dayjs.extend(duration);
// const projectList = ref([]);
// const dataList = ref([]);
// const mode = ref(0);
const loadGanttData = async (proid) => {
const resp = await axios.get(
"./jd?cmd=com.awspaas.user.apps.cmp_screen_getProjectProgress&sid=" +
sid +
"&projectId=" +
proid
);
// dataList.value = resp.data;
// console.log(resp.data);
parseData(resp.data);
};
const parseData = (list) => {
const ganttData = list.map((el) => {
let startDate;
let endDate;
if (!el.RBDATE || !el.REDATE) {
startDate = dayjs(el.BDATE).valueOf();
endDate = dayjs(el.EDATE).valueOf();
} else {
const starts = [dayjs(el.RBDATE), dayjs(el.BDATE)];
const ends = [dayjs(el.REDATE), dayjs(el.BDATE)];
startDate = Math.min(...starts);
endDate = Math.max(...ends);
}
return {
id: el.ID,
text: el.TASKTITLE,
start_date: dayjs(startDate).format("DD-MM-YYYY"),
end_date: dayjs(endDate).format("DD-MM-YYYY"),
continue: parseInt(
dayjs
.duration(new Date(el.EDATE).getTime() - new Date(el.BDATE).getTime())
.asDays()
),
isMilestone: el.MILESTONEFLAG == 1,
// progress: parseInt(el.PROGRESS) / 100,
percent: el.PROGRESS,
status: el.STATUS,
days: parseInt(el.DAYS),
};
});
gantt.clearAll();
gantt.parse({
data: ganttData,
});
const setMultiBar = () => {
for (const item of list) {
if (!item.RBDATE || !item.REDATE) {
console.log("empty");
continue;
}
const starts = [dayjs(item.RBDATE), dayjs(item.BDATE)];
// const ends = [dayjs(item.REDATE), dayjs(item.BDATE)];
const startDate = Math.min(...starts);
// const endDate = Math.max(...ends);
const left = dayjs
.duration(dayjs(item.BDATE).diff(dayjs(startDate)))
.days();
const rLeft = dayjs
.duration(dayjs(item.RBDATE).diff(dayjs(startDate)))
.days();
const width = dayjs
.duration(dayjs(item.EDATE).diff(dayjs(item.BDATE)))
.days();
const rWidth = dayjs
.duration(dayjs(item.REDATE).diff(dayjs(item.RBDATE)))
.days();
const bar = document.querySelector(
`div[task_id="${item.ID}"].gantt_bar_task .gantt_task_content`
);
if (!bar) continue;
const exitRbar = document.querySelector(
`div[task_id="${item.ID}"].gantt_bar_task .gantt_task_content.rdate`
);
if (exitRbar) {
exitRbar.remove();
}
const rBar = bar.cloneNode(true);
bar.style.left = `${left * 70}px`;
rBar.style.left = `${rLeft * 70}px`;
bar.style.width = `${width * 70}px`;
rBar.style.width = `${rWidth * 70}px`;
// rBar.style.height = `${80}%`;
rBar.style.textAlign = "right";
rBar.classList.add("rdate");
bar.innerHTML = "";
rBar.innerHTML = "";
document
.querySelector(`div[task_id="${item.ID}"].gantt_bar_task`)
.appendChild(rBar);
}
};
gantt.attachEvent("onDataRender", function () {
setMultiBar();
});
gantt.attachEvent("onTaskClick", function (id, e) {
nextTick(() => {
setMultiBar();
});
return true;
});
// gantt.attachEvent("onAfterTaskUpdate", function (id, item) {
// //any custom logic here
// nextTick(() => {
// setMultiBar();
// });
// });
gantt.attachEvent(
"onGanttScroll",
throttle(() => {
// nextTick(() => {
setMultiBar();
// });
}, 200)
);
};
// const modeChanged = (mode) => {
// // console.log(val);
// parseData(dataList.value, mode);
// };
onMounted(async () => {
gantt.i18n.setLocale("cn");
gantt.config.columns = [
{
name: "text",
label: "任务名称",
tree: false,
},
{
name: "start_date",
label: "计划开始时间",
align: "center",
min_width: 180,
},
{ name: "continue", label: "持续时间", align: "center" },
{
name: "percent",
label: "进度",
align: "center",
max_width: 80,
template: (task) => {
return `<div >${task.percent}%</div>`;
},
},
{
name: "status",
label: "状态",
align: "center",
// max_width: 100,
template: function (task) {
if (task.status == "0") {
return `<div style="color: springgreen">按时完成</div>`;
} else if (task.status == "1") {
return `<div style="color: springgreen">提前${task.days}天</div>`;
} else if (task.status == "2") {
return `<div style="color: tomato">逾期${task.days}天</div>`;
} else {
return ``;
}
},
},
// { name: "days", label: "预期/天", align: "center" },
// { name: "e", label: "预期", align: "center" },
];
gantt.config.readonly = true;
gantt.config.duration_unit = "day";
// gantt.templates.progress_text = function (start, end, task) {
// return "<span style='text-align:left;'>" + task.progress + "% </span>";
// };
gantt.templates.task_class = function (start, end, task) {
return task.isMilestone ? "milestone" : "";
};
gantt.config.scales = [
// { unit: "year", step: 1, format: "%Y" },
{ unit: "week", step: 1, format: "%Y年 %M" },
{ unit: "day", step: 1, format: "%d" },
];
gantt.init("gantt-chart");
// loadGanttData();
// const resp = await axios.get(
// "./jd?cmd=com.awspaas.user.apps.cmp_screen_getProjectList&sid=" + sid
// );
// projectList.value = resp.data;
//TODO:
// if (projectList.value.length) loadGanttData(projectList.value[0].PROID);
// loadGanttData();
// window.addEventListener("message", (e) => {
// const msg = e.data;
// if (msg.type === "marker-clicked") {
// loadGanttData(msg.proid);
// }
// });
});
// loadGanttData();
defineExpose({
loadGanttData,
});
</script>
<style lang="scss" scoped>
@import "dhtmlx-gantt/codebase/dhtmlxgantt.css";
// @import "dhtmlx-gantt/codebase/skins/dhtmlxgantt_contrast_black.css";
@import "dhtmlx-gantt/codebase/skins/dhtmlxgantt_material.css";
// @import "@/assets/gantt-material.css";
// @import "";
#gantt-chart {
width: 916px;
height: 300px;
}
.gantt_add,
.gantt_grid_head_add {
display: none;
}
// :deep(.milestone) {
// background-color: springgreen;
// border: 1px solid springgreen;
// }
:deep(.gantt_container) {
background-color: transparent;
border: 0px;
background-image: linear-gradient(
180deg,
rgba(4, 53, 99, 0.4) 0%,
rgba(4, 53, 99, 0.13) 100%
);
}
:deep(.gantt_scale_cell) {
background-color: transparent;
color: white !important;
}
:deep(.gantt_grid_scale) {
background-color: transparent;
}
:deep(.gantt_grid_head_cell) {
color: #fff !important;
}
:deep(.gantt_row) {
background-color: transparent;
border: 0;
}
:deep(.gantt_tree_content) {
color: #fff;
}
:deep(.gantt_task_scale) {
background-color: transparent;
}
:deep(.gantt_task_row) {
background-color: transparent;
border: 0 !important;
}
:deep(.gantt_task_cell) {
border: 0;
}
:deep(.gantt_bar_task) {
background-color: transparent;
border: none;
.gantt_task_content {
border-radius: 999px;
position: absolute;
// border: 1px solid tomato;
opacity: 0.3;
background-color: springgreen;
&.rdate {
height: 72%;
top: 50%;
transform: translateY(-50%);
background-color: indianred;
}
}
}
// 进度条
// :deep(.gantt_task_progress) {
// text-align: left;
// padding-left: 20px;
// color: #fff;
// }
// 任务内容
:deep(.gantt_task_content) {
text-align: left;
// padding-left: 60px;
}
</style>