339 lines
8.7 KiB
Vue
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>
|