vue-gantt-3
Version:
A gantt component for Vue 3
272 lines (271 loc) • 11.4 kB
JavaScript
import { defineComponent, shallowRef, ref, inject, computed, watch, createElementBlock, openBlock, withDirectives, createElementVNode, normalizeStyle, Fragment, renderList, normalizeClass, toDisplayString, vShow, unref } from "vue";
import dayjs from "dayjs";
import quarterOfYear from "dayjs/plugin/quarterOfYear";
import { getRound } from "../../../utils/common.mjs";
import lang from "../../../locale/index.mjs";
const _hoisted_1 = ["title"];
const _hoisted_2 = { class: "vg-header-block-text" };
const _hoisted_3 = ["title"];
const _hoisted_4 = { class: "vg-header-block-text" };
const bufferWidth = 200;
const _sfc_main = /* @__PURE__ */ defineComponent({
__name: "GanttHeader",
props: {
headerHeight: {},
edgeSpacing: {},
perHourSpacing: {},
ganttMinDate: {},
ganttViewWidth: {},
locale: {},
headerTextRender: { type: Function },
headerTipRender: { type: Function }
},
setup(__props, { expose: __expose }) {
dayjs.extend(quarterOfYear);
const props = __props;
const firstLevelBlocks = shallowRef([]);
const secondLevelBlocks = shallowRef([]);
const units = ["hour", "day", "week", "month", "quarter", "year"];
const scrollViewScrollLeft = ref(0);
const ganttHeaderRef = ref();
const scrollLeftOffset = ref(0);
const showSecondLevel = inject("showSecondLevel");
const showFirstLevel = inject("showFirstLevel");
const localeRef = computed(() => {
if (props.locale) {
return props.locale;
} else {
const language = navigator.language;
if (language.startsWith("zh")) {
return "zh-cn";
} else {
return language;
}
}
});
const ganttHeaderLevelWidth = computed(() => {
if (!ganttHeaderRef.value) return "100%";
const minWidth = ganttHeaderRef.value.offsetWidth;
return Math.max(minWidth, props.ganttViewWidth + scrollLeftOffset.value) + "px";
});
const getUnit = computed(() => {
const { perHourSpacing } = props;
let firstLevelUnitIndex = 0;
const perMonthSpacing = perHourSpacing * 24 * 30;
let canShowSecondLevel = true;
if (perMonthSpacing >= 3e4) {
firstLevelUnitIndex = units.indexOf("day");
} else if (perMonthSpacing >= 800) {
firstLevelUnitIndex = units.indexOf("week");
} else if (perMonthSpacing >= 400) {
firstLevelUnitIndex = units.indexOf("month");
} else if (perMonthSpacing >= 40) {
firstLevelUnitIndex = units.indexOf("quarter");
} else if (perMonthSpacing >= 24) {
firstLevelUnitIndex = units.indexOf("year");
} else if (perMonthSpacing >= 5) {
firstLevelUnitIndex = units.indexOf("year");
canShowSecondLevel = false;
}
return {
firstLevelUnit: units[firstLevelUnitIndex],
secondLevelUnit: units[firstLevelUnitIndex - 1],
canShowSecondLevel
};
});
const startInfo = computed(() => {
const { perHourSpacing, edgeSpacing, ganttMinDate } = props;
const { firstLevelUnit, secondLevelUnit } = getUnit.value;
const diffSpacing = edgeSpacing / perHourSpacing;
const startDate = ganttMinDate.subtract(diffSpacing, "hour");
const firstLevelStartUnitDate = startDate.endOf(firstLevelUnit);
const secondLevelStartUnitDate = startDate.endOf(secondLevelUnit);
const firstLevelStartLeft = firstLevelStartUnitDate.diff(startDate, "hour", true) * perHourSpacing;
const secondLevelStartLeft = secondLevelStartUnitDate.diff(startDate, "hour", true) * perHourSpacing;
return {
firstLevelStartUnitDate,
secondLevelStartUnitDate,
firstLevelStartLeft,
secondLevelStartLeft
};
});
const freshBlocks = () => {
if (!ganttHeaderRef.value) return;
const { firstLevelStartUnitDate, secondLevelStartUnitDate, firstLevelStartLeft, secondLevelStartLeft } = startInfo.value;
const { firstLevelUnit, secondLevelUnit, canShowSecondLevel } = getUnit.value;
firstLevelBlocks.value = getNewBlocks(firstLevelStartLeft, firstLevelStartUnitDate, firstLevelUnit);
showSecondLevel.value = canShowSecondLevel;
if (canShowSecondLevel) {
secondLevelBlocks.value = getNewBlocks(secondLevelStartLeft, secondLevelStartUnitDate, secondLevelUnit);
}
};
const getNewBlocks = (startLeft, startDate, currentUnit) => {
const ganttHeaderWidth = ganttHeaderRef.value.offsetWidth;
const startLeftInView = scrollViewScrollLeft.value - bufferWidth;
const endLeftInView = scrollViewScrollLeft.value + ganttHeaderWidth + bufferWidth;
const { perHourSpacing } = props;
const newLevelBlocks = [];
let levelStart = startLeft;
let levelStartDateInView = startDate;
if (startLeftInView > startLeft) {
const diffHour = (startLeftInView - startLeft) / perHourSpacing;
const startDateInView = startDate.add(diffHour, "hour");
const endDateInView = startDateInView.endOf(currentUnit);
const diff = endDateInView.diff(startDate, "hour", true);
levelStart = startLeft + diff * perHourSpacing;
levelStartDateInView = endDateInView;
}
let currentLevelStartDateInView = levelStartDateInView;
const limitMinLeft = scrollViewScrollLeft.value;
const limintMaxLeft = scrollViewScrollLeft.value + ganttHeaderWidth;
let blockSpacing = getBlockSpacingByUnit(currentLevelStartDateInView, currentUnit);
while (levelStart - blockSpacing <= endLeftInView) {
let width = blockSpacing;
let offset = width;
let hideRightBorder = false;
if (levelStart > limitMinLeft && levelStart - blockSpacing < limitMinLeft) {
offset = width = levelStart - limitMinLeft;
if (width > ganttHeaderWidth) {
width = ganttHeaderWidth;
hideRightBorder = true;
}
} else if (levelStart > limintMaxLeft && levelStart - blockSpacing < limintMaxLeft) {
width = blockSpacing - (levelStart - limintMaxLeft);
hideRightBorder = true;
}
newLevelBlocks.push({
left: getRound(levelStart - offset),
width: getRound(width),
text: getBlockText(currentLevelStartDateInView, currentUnit),
key: getRound(levelStart),
tip: getBlockTip(currentLevelStartDateInView, currentUnit),
hideRightBorder
});
currentLevelStartDateInView = currentLevelStartDateInView.add(1, currentUnit).endOf(currentUnit);
blockSpacing = getBlockSpacingByUnit(currentLevelStartDateInView, currentUnit);
levelStart += blockSpacing;
}
return newLevelBlocks;
};
const getBlockText = (date, unit) => {
if (props.headerTextRender) {
return props.headerTextRender(date, unit);
}
const currentLang = lang[localeRef.value];
switch (unit) {
case "hour":
return date.hour();
case "day":
return date.date();
case "month":
return currentLang.month[date.month() + 1];
case "year":
return date.year();
case "week":
return `${date.startOf("week").format("YYYY.MM.DD")}-${date.endOf("week").format("YYYY.MM.DD")}`;
case "quarter":
return `${currentLang.month[date.startOf("quarter").month() + 1]}-${currentLang.month[date.endOf("quarter").month() + 1]}`;
}
};
const getBlockTip = (date, unit) => {
if (props.headerTipRender) {
return props.headerTipRender(date, unit);
}
const dateFormat = lang[localeRef.value].dateFormat;
switch (unit) {
case "hour":
return date.format(dateFormat["hour"]);
case "day":
return date.format(dateFormat["day"]);
case "month":
return date.format(dateFormat["month"]);
case "year":
return date.format(dateFormat["year"]);
case "week":
return `${date.startOf("week").format("YYYY.MM.DD")}-${date.endOf("week").format("YYYY.MM.DD")}`;
case "quarter":
return `${date.startOf("quarter").format(dateFormat["month"])}-${date.endOf("quarter").format(dateFormat["month"])}`;
}
};
const getBlockSpacingByUnit = (date, unit) => {
const { perHourSpacing } = props;
switch (unit) {
case "hour":
return perHourSpacing;
case "day":
return perHourSpacing * 24;
case "week":
return perHourSpacing * 24 * 7;
}
const startDate = date.startOf(unit);
const diffHour = date.diff(startDate, "hour", true);
return diffHour * perHourSpacing;
};
watch([localeRef, startInfo, scrollViewScrollLeft], freshBlocks);
const onScroll = ({ scrollLeft }) => {
scrollViewScrollLeft.value = scrollLeft;
ganttHeaderRef.value && (ganttHeaderRef.value.scrollLeft = scrollLeft);
};
const updateGanttHeaderWidth = (show, scrollbarWidth) => {
if (show) {
scrollLeftOffset.value = scrollbarWidth;
} else {
scrollLeftOffset.value = 0;
}
};
const onResize = () => {
freshBlocks();
};
__expose({
onScroll,
updateGanttHeaderWidth,
onResize
});
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", {
ref_key: "ganttHeaderRef",
ref: ganttHeaderRef,
class: "vg-header"
}, [
withDirectives(createElementVNode("div", {
class: "vg-header-first-level",
style: normalizeStyle({ width: ganttHeaderLevelWidth.value, height: `${_ctx.headerHeight + 1}px` })
}, [
(openBlock(true), createElementBlock(Fragment, null, renderList(firstLevelBlocks.value, (item) => {
return openBlock(), createElementBlock("div", {
key: item.key,
class: normalizeClass(["vg-header-block", { "vg-header-block-hide-right-border": item.hideRightBorder }]),
title: item.tip,
style: normalizeStyle({ width: item.width + "px", left: item.left + "px" })
}, [
createElementVNode("span", _hoisted_2, toDisplayString(item.text), 1)
], 14, _hoisted_1);
}), 128))
], 4), [
[vShow, unref(showFirstLevel) || !unref(showSecondLevel)]
]),
withDirectives(createElementVNode("div", {
class: "vg-header-second-level",
style: normalizeStyle({ width: ganttHeaderLevelWidth.value, height: `${_ctx.headerHeight + 1}px` })
}, [
(openBlock(true), createElementBlock(Fragment, null, renderList(secondLevelBlocks.value, (item) => {
return openBlock(), createElementBlock("div", {
key: item.key,
class: normalizeClass(["vg-header-block", { "vg-header-block-hide-right-border": item.hideRightBorder }]),
title: item.tip,
style: normalizeStyle({ width: item.width + "px", left: item.left + "px" })
}, [
createElementVNode("span", _hoisted_4, toDisplayString(item.text), 1)
], 14, _hoisted_3);
}), 128))
], 4), [
[vShow, unref(showSecondLevel)]
])
], 512);
};
}
});
export {
_sfc_main as default
};
//# sourceMappingURL=GanttHeader.vue.mjs.map