UNPKG

vxe-gantt

Version:
1,174 lines (1,120 loc) 81.5 kB
import { h, ref, reactive, nextTick, inject, onBeforeUnmount, provide, computed, onMounted, onUnmounted } from 'vue' import { defineVxeComponent } from '../../ui/src/comp' import { setScrollTop, setScrollLeft, removeClass, addClass, hasClass } from '../../ui/src/dom' import { VxeUI } from '@vxe-ui/core' import { getRefElem, getStandardGapTime, getTaskBarLeft, getTaskBarWidth, hasMilestoneTask, getTaskType, hasSubviewTask } from './util' import XEUtils from 'xe-utils' import GanttViewHeaderComponent from './gantt-header' import GanttViewBodyComponent from './gantt-body' import GanttViewFooterComponent from './gantt-footer' import type { VxeTableConstructor } from 'vxe-table' import type { VxeGanttViewConstructor, GanttViewReactData, GanttViewPrivateRef, VxeGanttDefines, VxeGanttViewPrivateMethods, GanttViewInternalData, VxeGanttViewMethods, GanttViewPrivateComputed, VxeGanttConstructor, VxeGanttPrivateMethods } from '../../../types' const { globalEvents } = VxeUI const sourceType = 'gantt' const secondMs = 1000 const minuteMs = 1000 * 60 const hourMs = 1000 * 60 * 60 const dayMs = hourMs * 24 function createInternalData (): GanttViewInternalData { return { xeTable: null, visibleColumn: [], startMaps: {}, endMaps: {}, chartMaps: {}, todayDateMaps: {}, elemStore: {}, // 存放横向 X 虚拟滚动相关的信息 scrollXStore: { preloadSize: 0, offsetSize: 0, visibleSize: 0, visibleStartIndex: 0, visibleEndIndex: 0, startIndex: 0, endIndex: 0 }, // 最后滚动位置 lastScrollTop: 0, lastScrollLeft: 0 } } function createReactData (): GanttViewReactData { return { // 是否启用了横向 X 可视渲染方式加载 scrollXLoad: false, // 是否启用了纵向 Y 可视渲染方式加载 scrollYLoad: false, // 是否存在纵向滚动条 overflowY: true, // 是否存在横向滚动条 overflowX: true, // 纵向滚动条的宽度 scrollbarWidth: 0, // 横向滚动条的高度 scrollbarHeight: 0, // 最后滚动时间戳 lastScrollTime: 0, lazScrollLoading: false, scrollVMLoading: false, scrollYHeight: 0, scrollYTop: 0, isScrollYBig: false, scrollXLeft: 0, scrollXWidth: 0, isScrollXBig: false, minViewDate: null, maxViewDate: null, tableData: [], tableColumn: [], headerGroups: [], viewCellWidth: 40 } } const maxYHeight = 5e6 // const maxXWidth = 5e6 export default defineVxeComponent({ name: 'VxeGanttView', setup (props, context) { const xID = XEUtils.uniqueId() const $xeGantt = inject('$xeGantt', {} as (VxeGanttConstructor & VxeGanttPrivateMethods)) const { reactData: ganttReactData, internalData: ganttInternalData } = $xeGantt const { computeDateFormat, computeTaskViewOpts, computeStartField, computeEndField, computeTypeField, computeScrollbarOpts, computeScrollbarXToTop, computeScrollbarYToLeft, computeScaleUnit, computeWeekScale, computeMinScale, computeTaskNowLineOpts, computeScaleStep } = $xeGantt.getComputeMaps() const refElem = ref<HTMLDivElement>() const refScrollXVirtualElem = ref<HTMLDivElement>() const refScrollYVirtualElem = ref<HTMLDivElement>() const refScrollXHandleElem = ref<HTMLDivElement>() const refScrollXLeftCornerElem = ref<HTMLDivElement>() const refScrollXRightCornerElem = ref<HTMLDivElement>() const refScrollYHandleElem = ref<HTMLDivElement>() const refScrollYTopCornerElem = ref<HTMLDivElement>() const refScrollXWrapperElem = ref<HTMLDivElement>() const refScrollYWrapperElem = ref<HTMLDivElement>() const refScrollYBottomCornerElem = ref<HTMLDivElement>() const refScrollXSpaceElem = ref<HTMLDivElement>() const refScrollYSpaceElem = ref<HTMLDivElement>() const refColInfoElem = ref<HTMLDivElement>() const reactData = reactive(createReactData()) const internalData = createInternalData() const refMaps: GanttViewPrivateRef = { refElem, refScrollXHandleElem, refScrollYHandleElem } const computeScaleDateList = computed(() => { const { minViewDate, maxViewDate } = reactData const taskViewOpts = computeTaskViewOpts.value const minScale = computeMinScale.value const scaleStep = computeScaleStep.value const { gridding } = taskViewOpts const dateList: Date[] = [] if (!minScale || !minViewDate || !maxViewDate) { return dateList } const { type, startDay } = minScale const leftSize = -(ganttReactData.currLeftSpacing + XEUtils.toNumber(gridding ? gridding.leftSpacing || 0 : 0)) const rightSize = ganttReactData.currRightSpacing + XEUtils.toNumber(gridding ? gridding.rightSpacing || 0 : 0) switch (type) { case 'year': { let currDate = XEUtils.getWhatYear(minViewDate, leftSize, 'first') const endDate = XEUtils.getWhatYear(maxViewDate, rightSize, 'first') while (currDate <= endDate) { const itemDate = currDate dateList.push(itemDate) currDate = XEUtils.getWhatYear(currDate, scaleStep) } break } case 'quarter': { let currDate = XEUtils.getWhatQuarter(minViewDate, leftSize, 'first') const endDate = XEUtils.getWhatQuarter(maxViewDate, rightSize, 'first') while (currDate <= endDate) { const itemDate = currDate dateList.push(itemDate) currDate = XEUtils.getWhatQuarter(currDate, scaleStep) } break } case 'month': { let currDate = XEUtils.getWhatMonth(minViewDate, leftSize, 'first') const endDate = XEUtils.getWhatMonth(maxViewDate, rightSize, 'first') while (currDate <= endDate) { const itemDate = currDate dateList.push(itemDate) currDate = XEUtils.getWhatMonth(currDate, scaleStep) } break } case 'week': { let currDate = XEUtils.getWhatWeek(minViewDate, leftSize, startDay, startDay) const endDate = XEUtils.getWhatWeek(maxViewDate, rightSize, startDay, startDay) while (currDate <= endDate) { const itemDate = currDate dateList.push(itemDate) currDate = XEUtils.getWhatWeek(currDate, scaleStep) } break } case 'day': case 'date': { let currDate = XEUtils.getWhatDay(minViewDate, leftSize, 'first') const endDate = XEUtils.getWhatDay(maxViewDate, rightSize, 'first') while (currDate <= endDate) { const itemDate = currDate dateList.push(itemDate) currDate = XEUtils.getWhatDay(currDate, scaleStep) } break } case 'hour': case 'minute': case 'second': { const gapTime = getStandardGapTime(minScale.type) * scaleStep let currTime = minViewDate.getTime() + (leftSize * gapTime) const endTime = maxViewDate.getTime() + (rightSize * gapTime) while (currTime <= endTime) { const itemDate = new Date(currTime) dateList.push(itemDate) currTime += gapTime } break } } return dateList }) const computeNowLineLeft = computed(() => { const ganttReactData = $xeGantt.reactData const { minViewDate, maxViewDate, viewCellWidth } = reactData const { visibleColumn, todayDateMaps } = internalData const minScale = computeMinScale.value const taskViewOpts = computeTaskViewOpts.value const taskNowLineOpts = computeTaskNowLineOpts.value const { showNowLine } = taskViewOpts const { mode } = taskNowLineOpts const { nowTime } = ganttReactData // 此刻线 let nlLeft = 0 if (showNowLine && minScale && minViewDate && maxViewDate && nowTime >= minViewDate.getTime() && nowTime <= maxViewDate.getTime()) { const todayValue = todayDateMaps[minScale.type] let currCol: VxeGanttDefines.ViewColumn | null = null let nextCol: VxeGanttDefines.ViewColumn | null = null for (let i = 0; i < visibleColumn.length; i++) { const column = visibleColumn[i] if (column.field === todayValue) { currCol = column nlLeft = i * viewCellWidth nextCol = visibleColumn[i + 1] break } } if (mode === 'progress') { if (currCol && nextCol) { const currTime = currCol.dateObj.date.getTime() const offsetTime = nowTime - currTime const nowProgress = Math.max(0, Math.min(1, offsetTime / (nextCol.dateObj.date.getTime() - currTime))) nlLeft += nowProgress * viewCellWidth } } else if (mode === 'center') { nlLeft += viewCellWidth / 2 } else if (mode === 'end') { nlLeft += viewCellWidth - 1 } } return nlLeft }) const computeMaps: GanttViewPrivateComputed = { computeScaleDateList, computeNowLineLeft } const $xeGanttView = { xID, props, context, reactData, internalData, getRefMaps: () => refMaps, getComputeMaps: () => computeMaps } as unknown as VxeGanttViewConstructor & VxeGanttViewPrivateMethods const parseStringDate = (dateValue: any) => { const dateFormat = computeDateFormat.value return XEUtils.toStringDate(dateValue, dateFormat) } const updateTodayData = () => { const ganttReactData = $xeGantt.reactData const { taskScaleList } = ganttReactData const minScale = computeMinScale.value if (minScale) { const weekScale = taskScaleList.find(item => item.type === 'week') const isMinWeek = minScale.type === 'week' const itemDate = new Date() let [yyyy, M, MM, dd, HH, mm, ss] = XEUtils.toDateString(itemDate, 'yyyy-M-MM-dd-HH-mm-ss').split('-') const e = itemDate.getDay() const E = e + 1 const q = Math.ceil((itemDate.getMonth() + 1) / 3) const W = `${XEUtils.getYearWeek(itemDate, weekScale ? weekScale.startDay : undefined)}` if (isMinWeek && checkWeekOfsetYear(W, M)) { yyyy = `${Number(yyyy) + 1}` M = '1' MM = '0' + M } ganttReactData.nowTime = itemDate.getTime() internalData.todayDateMaps = { year: yyyy, quarter: `${yyyy}_q${q}`, month: `${yyyy}_${MM}`, week: `${yyyy}_W${W}`, day: `${yyyy}_${MM}_${dd}_E${E}`, date: `${yyyy}_${MM}_${dd}`, hour: `${yyyy}_${MM}_${dd}_${HH}`, minute: `${yyyy}_${MM}_${dd}_${HH}_${mm}`, second: `${yyyy}_${MM}_${dd}_${HH}_${mm}_${ss}` } } } const handleColumnHeader = () => { const ganttReactData = $xeGantt.reactData const { taskScaleList } = ganttReactData const scaleUnit = computeScaleUnit.value const minScale = computeMinScale.value const weekScale = computeWeekScale.value const scaleDateList = computeScaleDateList.value const fullCols: VxeGanttDefines.ViewColumn[] = [] const groupCols: VxeGanttDefines.GroupColumn[] = [] if (minScale && scaleUnit && scaleDateList.length) { const renderListMaps: Record<VxeGanttDefines.ColumnScaleType, VxeGanttDefines.ViewColumn[]> = { year: [], quarter: [], month: [], week: [], day: [], date: [], hour: [], minute: [], second: [] } const tempTypeMaps: Record<VxeGanttDefines.ColumnScaleType, Record<string, VxeGanttDefines.ViewColumn>> = { year: {}, quarter: {}, month: {}, week: {}, day: {}, date: {}, hour: {}, minute: {}, second: {} } const isMinWeek = minScale.type === 'week' const handleData = (type: VxeGanttDefines.ColumnScaleType, colMaps: Record<VxeGanttDefines.ColumnScaleType, VxeGanttDefines.ViewColumn>, minCol: VxeGanttDefines.ViewColumn) => { if (minScale.type === type) { return } const currCol = colMaps[type] const currKey = `${currCol.field}` let currGpCol = tempTypeMaps[type][currKey] if (!currGpCol) { currGpCol = currCol tempTypeMaps[type][currKey] = currGpCol renderListMaps[type].push(currGpCol) } if (currGpCol) { if (!currGpCol.children) { currGpCol.children = [] } currGpCol.children.push(minCol) } } for (let i = 0; i < scaleDateList.length; i++) { const itemDate = scaleDateList[i] let [yy, yyyy, M, MM, d, dd, H, HH, m, mm, s, ss] = XEUtils.toDateString(itemDate, 'yy-yyyy-M-MM-d-dd-H-HH-m-mm-s-ss').split('-') const e = itemDate.getDay() const E = e + 1 const q = Math.ceil((itemDate.getMonth() + 1) / 3) const W = `${XEUtils.getYearWeek(itemDate, weekScale ? weekScale.startDay : undefined)}` const WW = XEUtils.padStart(W, 2, '0') if (isMinWeek && checkWeekOfsetYear(W, M)) { yyyy = `${Number(yyyy) + 1}` M = '1' MM = '0' + M } const dateObj: VxeGanttDefines.ScaleDateObj = { date: itemDate, yy, yyyy, M, MM, d, dd, H, HH, m, mm, s, ss, q, W, WW, E, e } const colMaps: Record<VxeGanttDefines.ColumnScaleType, VxeGanttDefines.ViewColumn> = { year: { field: yyyy, title: yyyy, dateObj }, quarter: { field: `${yyyy}_q${q}`, title: `${q}`, dateObj }, month: { field: `${yyyy}_${MM}`, title: MM, dateObj }, week: { field: `${yyyy}_W${W}`, title: `${W}`, dateObj }, day: { field: `${yyyy}_${MM}_${dd}_E${E}`, title: `${E}`, dateObj }, date: { field: `${yyyy}_${MM}_${dd}`, title: dd, dateObj }, hour: { field: `${yyyy}_${MM}_${dd}_${HH}`, title: HH, dateObj }, minute: { field: `${yyyy}_${MM}_${dd}_${HH}_${mm}`, title: mm, dateObj }, second: { field: `${yyyy}_${MM}_${dd}_${HH}_${mm}_${ss}`, title: ss, dateObj } } const minCol = colMaps[minScale.type] if (minScale.level < 19) { handleData('year', colMaps, minCol) } if (minScale.level < 17) { handleData('quarter', colMaps, minCol) } if (minScale.level < 15) { handleData('month', colMaps, minCol) } if (minScale.level < 13) { handleData('week', colMaps, minCol) } if (minScale.level < 11) { handleData('day', colMaps, minCol) } if (minScale.level < 9) { handleData('date', colMaps, minCol) } if (minScale.level < 7) { handleData('hour', colMaps, minCol) } if (minScale.level < 5) { handleData('minute', colMaps, minCol) } if (minScale.level < 3) { handleData('second', colMaps, minCol) } fullCols.push(minCol) } taskScaleList.forEach(scaleItem => { if (scaleItem.type === minScale.type) { groupCols.push({ scaleItem, columns: fullCols }) return } const list = renderListMaps[scaleItem.type] || [] if (list) { list.forEach(item => { item.childCount = item.children ? item.children.length : 0 item.children = undefined }) } groupCols.push({ scaleItem, columns: list }) }) } return { fullCols, groupCols } } /** * 判断周的年份是否跨年 */ const checkWeekOfsetYear = (W: number | string, M: number | string) => { return `${W}` === '1' && `${M}` === '12' } /** * 周维度,由于年份和第几周是冲突的行为,所以需要特殊处理,判断是否跨年,例如 * '2024-12-31' 'yyyy-MM-dd W' >> '2024-12-31 1' * '2025-01-01' 'yyyy-MM-dd W' >> '2025-01-01 1' */ const parseWeekObj = (date: any, firstDay?: 0 | 5 | 1 | 2 | 3 | 4 | 6) => { const currDate = XEUtils.toStringDate(date) let yyyy = currDate.getFullYear() const month = currDate.getMonth() const weekNum = XEUtils.getYearWeek(currDate, firstDay) if (checkWeekOfsetYear(weekNum, month + 1)) { yyyy++ } return { yyyy, W: weekNum } } const createChartRender = (fullCols: VxeGanttDefines.ViewColumn[]) => { const minScale = computeMinScale.value const scaleStep = computeScaleStep.value const scaleUnit = computeScaleUnit.value const weekScale = computeWeekScale.value const dateFormat = computeDateFormat.value if (minScale) { const currRatio = scaleStep > 1 ? 1 / scaleStep : 0 switch (scaleUnit) { case 'year': { const showActualProgress = /M|d|H|m|s|S/.test(dateFormat) const renderFormat = 'yyyy' const indexMaps: Record<string, number> = {} fullCols.forEach(({ dateObj }, i) => { const currStr = XEUtils.toDateString(dateObj.date, 'yyyy') indexMaps[currStr] = i if (currRatio) { let currStep = scaleStep - 1 while (currStep) { const currStr = XEUtils.toDateString(XEUtils.getWhatYear(dateObj.date, scaleStep - currStep), renderFormat) indexMaps[currStr] = i + currRatio currStep-- } } }) return (startValue: any, endValue: any) => { const startValDate = parseStringDate(startValue) const endValDate = parseStringDate(endValue) const startStr = XEUtils.toDateString(startValDate, renderFormat) const startDate = showActualProgress ? startValDate : XEUtils.getWhatYear(startValDate, 0, 'first') const startFirstDate = XEUtils.getWhatYear(startValDate, 0, 'first') const endStr = XEUtils.toDateString(endValDate, renderFormat) const endDate = showActualProgress ? endValDate : XEUtils.getWhatYear(endValDate, 0, 'last') const endFirstDate = XEUtils.getWhatYear(endDate, 0, 'first') // 当前是年维度,当指定解析格式精确到天时,显示实际进度 let startSubtractSize = 0 let endSubtractSize = 1 if (showActualProgress) { // 按年的天数比计算 const syDaySize = XEUtils.getDayOfYear(startDate, 0) startSubtractSize = (startDate.getTime() - startFirstDate.getTime()) / dayMs / syDaySize const eyDaySize = XEUtils.getDayOfYear(endDate, 0) endSubtractSize = (endDate.getTime() - endFirstDate.getTime()) / dayMs / eyDaySize if (currRatio) { startSubtractSize *= currRatio endSubtractSize *= currRatio } } const offsetLeftSize = (indexMaps[startStr] || 0) + startSubtractSize return { offsetLeftSize, offsetWidthSize: (indexMaps[endStr] || 0) - offsetLeftSize + endSubtractSize } } } case 'quarter': { const showActualProgress = /M|d|H|m|ss|S/.test(dateFormat) const renderFormat = 'yyyy-q' const indexMaps: Record<string, number> = {} fullCols.forEach(({ dateObj }, i) => { const currStr = XEUtils.toDateString(dateObj.date, renderFormat) indexMaps[currStr] = i if (currRatio) { let currStep = scaleStep - 1 while (currStep) { const currStr = XEUtils.toDateString(XEUtils.getWhatQuarter(dateObj.date, scaleStep - currStep), renderFormat) indexMaps[currStr] = i + currRatio currStep-- } } }) return (startValue: any, endValue: any) => { const startValDate = parseStringDate(startValue) const endValDate = parseStringDate(endValue) const startStr = XEUtils.toDateString(startValDate, renderFormat) const startDate = showActualProgress ? startValDate : XEUtils.getWhatQuarter(startValDate, 0, 'first') const startFirstDate = XEUtils.getWhatQuarter(startValDate, 0, 'first') const endStr = XEUtils.toDateString(endValDate, renderFormat) const endDate = showActualProgress ? endValDate : XEUtils.getWhatQuarter(endValDate, 0, 'last') const endFirstDate = XEUtils.getWhatQuarter(endDate, 0, 'first') // 当前是季度维度,当指定解析格式精确到天时,显示实际进度 let startSubtractSize = 0 let endSubtractSize = 1 if (showActualProgress) { // 按季度天数比计算 const sqDaySize = XEUtils.getDayOfQuarter(startDate, 0) startSubtractSize = (startDate.getTime() - startFirstDate.getTime()) / dayMs / sqDaySize const eqDaySize = XEUtils.getDayOfQuarter(endDate, 0) endSubtractSize = (endDate.getTime() - endFirstDate.getTime()) / dayMs / eqDaySize if (currRatio) { startSubtractSize *= currRatio endSubtractSize *= currRatio } } const offsetLeftSize = (indexMaps[startStr] || 0) + startSubtractSize return { offsetLeftSize, offsetWidthSize: (indexMaps[endStr] || 0) - offsetLeftSize + endSubtractSize } } } case 'month': { const renderFormat = 'yyyy-MM' const showActualProgress = /d|H|m|ss|S/.test(dateFormat) const indexMaps: Record<string, number> = {} fullCols.forEach(({ dateObj }, i) => { const currStr = XEUtils.toDateString(dateObj.date, renderFormat) indexMaps[currStr] = i if (currRatio) { let currStep = scaleStep - 1 while (currStep) { const currStr = XEUtils.toDateString(XEUtils.getWhatMonth(dateObj.date, scaleStep - currStep), renderFormat) indexMaps[currStr] = i + currRatio currStep-- } } }) return (startValue: any, endValue: any) => { const startValDate = parseStringDate(startValue) const endValDate = parseStringDate(endValue) const startStr = XEUtils.toDateString(startValDate, renderFormat) const startDate = showActualProgress ? startValDate : XEUtils.getWhatMonth(startValDate, 0, 'first') const startFirstDate = XEUtils.getWhatMonth(startValDate, 0, 'first') const endStr = XEUtils.toDateString(endValDate, renderFormat) const endDate = showActualProgress ? endValDate : XEUtils.getWhatMonth(endValDate, 0, 'last') const endFirstDate = XEUtils.getWhatMonth(endDate, 0, 'first') // 当前是月维度,当指定解析格式精确到天时,显示实际进度 let startSubtractSize = 0 let endSubtractSize = 1 if (showActualProgress) { // 按月天数比计算 const smDaySize = XEUtils.getDayOfMonth(startDate, 0) startSubtractSize = (startDate.getTime() - startFirstDate.getTime()) / dayMs / smDaySize const emDaySize = XEUtils.getDayOfMonth(endDate, 0) endSubtractSize = (endDate.getTime() - endFirstDate.getTime()) / dayMs / emDaySize if (currRatio) { startSubtractSize *= currRatio endSubtractSize *= currRatio } } const offsetLeftSize = (indexMaps[startStr] || 0) + startSubtractSize return { offsetLeftSize, offsetWidthSize: (indexMaps[endStr] || 0) - offsetLeftSize + endSubtractSize } } } case 'week': { const weekStartDay = weekScale ? weekScale.startDay : undefined const showActualProgress = /d|H|m|s|S/.test(dateFormat) const indexMaps: Record<string, number> = {} fullCols.forEach(({ dateObj }, i) => { const currStr = `${dateObj.yyyy}-${dateObj.W}` indexMaps[currStr] = i if (currRatio) { let currStep = scaleStep - 1 while (currStep) { const offsetDate = XEUtils.getWhatWeek(dateObj.date, scaleStep - currStep, weekStartDay, weekStartDay) let [yyyy, M] = XEUtils.toDateString(offsetDate, 'yyyy-M-MM-dd-HH-mm-ss').split('-') const W = `${XEUtils.getYearWeek(offsetDate, weekScale ? weekScale.startDay : undefined)}` if (checkWeekOfsetYear(W, M)) { yyyy = `${Number(yyyy) + 1}` } const currStr = `${yyyy}-${W}` indexMaps[currStr] = i + currRatio currStep-- } } }) return (startValue: any, endValue: any) => { const startValDate = parseStringDate(startValue) const endValDate = parseStringDate(endValue) const startDate = showActualProgress ? startValDate : XEUtils.getWhatWeek(startValDate, 0, 'first', weekStartDay) const startWeekObj = parseWeekObj(startDate, weekStartDay) const startStr = `${startWeekObj.yyyy}-${startWeekObj.W}` const startFirstDate = XEUtils.getWhatWeek(startDate, 0, 'first', weekStartDay) const endDate = showActualProgress ? endValDate : XEUtils.getWhatWeek(endValDate, 0, 'first', weekStartDay) const endWeekObj = parseWeekObj(endDate, weekStartDay) const endStr = `${endWeekObj.yyyy}-${endWeekObj.W}` const endFirstDate = XEUtils.getWhatWeek(endDate, 0, 'first', weekStartDay) // 当前是周维度,当指定解析格式精确到天时,显示实际进度 let startSubtractSize = 0 let endSubtractSize = 1 if (showActualProgress) { // 按周天数比计算 startSubtractSize = (startDate.getTime() - startFirstDate.getTime()) / dayMs / 7 endSubtractSize = (endDate.getTime() - endFirstDate.getTime()) / dayMs / 7 if (currRatio) { startSubtractSize *= currRatio endSubtractSize *= currRatio } } const offsetLeftSize = (indexMaps[startStr] || 0) + startSubtractSize return { offsetLeftSize, offsetWidthSize: (indexMaps[endStr] || 0) - offsetLeftSize + endSubtractSize } } } case 'day': case 'date': { const renderFormat = 'yyyy-MM-dd' const showActualProgress = /H|m|s|S/.test(dateFormat) const indexMaps: Record<string, number> = {} fullCols.forEach(({ dateObj }, i) => { const currStr = XEUtils.toDateString(dateObj.date, renderFormat) indexMaps[currStr] = i if (currRatio) { let currStep = scaleStep - 1 while (currStep) { const currStr = XEUtils.toDateString(XEUtils.getWhatDay(dateObj.date, scaleStep - currStep), renderFormat) indexMaps[currStr] = i + currRatio currStep-- } } }) return (startValue: any, endValue: any) => { const startValDate = parseStringDate(startValue) const endValDate = parseStringDate(endValue) const startStr = XEUtils.toDateString(startValDate, renderFormat) const startDate = showActualProgress ? startValDate : XEUtils.getWhatDay(startValDate, 0, 'first') const startFirstDate = XEUtils.getWhatDay(startDate, 0, 'first') const endStr = XEUtils.toDateString(endValDate, renderFormat) const endDate = showActualProgress ? endValDate : XEUtils.getWhatDay(endValDate, 0, 'last') const endFirstDate = XEUtils.getWhatDay(endDate, 0, 'first') // 当前是天维度,当指定解析格式精确到时分秒豪秒时,显示实际进度 let startSubtractSize = 0 let endSubtractSize = 1 if (showActualProgress) { startSubtractSize = (startDate.getTime() - startFirstDate.getTime()) / dayMs endSubtractSize = (endDate.getTime() - endFirstDate.getTime()) / dayMs if (currRatio) { startSubtractSize *= currRatio endSubtractSize *= currRatio } } const offsetLeftSize = (indexMaps[startStr] || 0) + startSubtractSize return { offsetLeftSize, offsetWidthSize: (indexMaps[endStr] || 0) - offsetLeftSize + endSubtractSize } } } case 'hour': { const renderFormat = 'yyyy-MM-dd HH' const showActualProgress = /m|s|S/.test(dateFormat) const indexMaps: Record<string, number> = {} fullCols.forEach(({ dateObj }, i) => { const currStr = XEUtils.toDateString(dateObj.date, renderFormat) indexMaps[currStr] = i if (currRatio) { let currStep = scaleStep - 1 while (currStep) { const currStr = XEUtils.toDateString(XEUtils.getWhatHours(dateObj.date, scaleStep - currStep), renderFormat) indexMaps[currStr] = i + currRatio currStep-- } } }) return (startValue: any, endValue: any) => { const startValDate = parseStringDate(startValue) const endValDate = parseStringDate(endValue) const startStr = XEUtils.toDateString(startValDate, renderFormat) const startDate = showActualProgress ? startValDate : XEUtils.getWhatHours(startValDate, 0, 'first') const startFirstDate = XEUtils.getWhatHours(startDate, 0, 'first') const endStr = XEUtils.toDateString(endValDate, renderFormat) const endDate = showActualProgress ? endValDate : XEUtils.getWhatHours(endValDate, 0, 'last') const endFirstDate = XEUtils.getWhatHours(endDate, 0, 'first') // 当前是小时维度,当指定解析格式精确到分秒豪秒时,显示实际进度 let startSubtractSize = 0 let endSubtractSize = 1 if (showActualProgress) { startSubtractSize = (startDate.getTime() - startFirstDate.getTime()) / hourMs endSubtractSize = (endDate.getTime() - endFirstDate.getTime()) / hourMs if (currRatio) { startSubtractSize *= currRatio endSubtractSize *= currRatio } } const offsetLeftSize = (indexMaps[startStr] || 0) + startSubtractSize return { offsetLeftSize, offsetWidthSize: (indexMaps[endStr] || 0) - offsetLeftSize + endSubtractSize } } } case 'minute': { const renderFormat = 'yyyy-MM-dd HH:mm' const showActualProgress = /s|S/.test(dateFormat) const indexMaps: Record<string, number> = {} fullCols.forEach(({ dateObj }, i) => { const currStr = XEUtils.toDateString(dateObj.date, renderFormat) indexMaps[currStr] = i if (currRatio) { let currStep = scaleStep - 1 while (currStep) { const currStr = XEUtils.toDateString(XEUtils.getWhatMinutes(dateObj.date, scaleStep - currStep), renderFormat) indexMaps[currStr] = i + currRatio currStep-- } } }) return (startValue: any, endValue: any) => { const startValDate = parseStringDate(startValue) const endValDate = parseStringDate(endValue) const startStr = XEUtils.toDateString(startValDate, renderFormat) const startDate = showActualProgress ? startValDate : XEUtils.getWhatMinutes(startValDate, 0, 'first') const startFirstDate = XEUtils.getWhatMinutes(startDate, 0, 'first') const endStr = XEUtils.toDateString(endValDate, renderFormat) const endDate = showActualProgress ? endValDate : XEUtils.getWhatMinutes(endValDate, 0, 'last') const endFirstDate = XEUtils.getWhatMinutes(endDate, 0, 'first') // 当前是分钟维度,当指定解析格式精确到秒豪秒时,显示实际进度 let startSubtractSize = 0 let endSubtractSize = 1 if (showActualProgress) { startSubtractSize = (startDate.getTime() - startFirstDate.getTime()) / minuteMs endSubtractSize = (endDate.getTime() - endFirstDate.getTime()) / minuteMs if (currRatio) { startSubtractSize *= currRatio endSubtractSize *= currRatio } } const offsetLeftSize = (indexMaps[startStr] || 0) + startSubtractSize return { offsetLeftSize, offsetWidthSize: (indexMaps[endStr] || 0) - offsetLeftSize + endSubtractSize } } } case 'second': { const renderFormat = 'yyyy-MM-dd HH:mm:ss' const showActualProgress = /S/.test(dateFormat) const indexMaps: Record<string, number> = {} fullCols.forEach(({ dateObj }, i) => { const currStr = XEUtils.toDateString(dateObj.date, renderFormat) indexMaps[currStr] = i if (currRatio) { let currStep = scaleStep - 1 while (currStep) { const currStr = XEUtils.toDateString(XEUtils.getWhatSeconds(dateObj.date, scaleStep - currStep), renderFormat) indexMaps[currStr] = i + currRatio currStep-- } } }) return (startValue: any, endValue: any) => { const startValDate = parseStringDate(startValue) const endValDate = parseStringDate(endValue) const startStr = XEUtils.toDateString(startValDate, renderFormat) const startDate = showActualProgress ? startValDate : XEUtils.getWhatSeconds(startValDate, 0, 'first') const startFirstDate = XEUtils.getWhatSeconds(startDate, 0, 'first') const endStr = XEUtils.toDateString(endValDate, renderFormat) const endDate = showActualProgress ? endValDate : XEUtils.getWhatSeconds(endValDate, 0, 'last') const endFirstDate = XEUtils.getWhatSeconds(endDate, 0, 'first') // 当前是秒维度,当指定解析格式精确到豪秒时,显示实际进度 let startSubtractSize = 0 let endSubtractSize = 1 if (showActualProgress) { startSubtractSize = (startDate.getTime() - startFirstDate.getTime()) / secondMs endSubtractSize = (endDate.getTime() - endFirstDate.getTime()) / secondMs if (currRatio) { startSubtractSize *= currRatio endSubtractSize *= currRatio } } const offsetLeftSize = (indexMaps[startStr] || 0) + startSubtractSize return { offsetLeftSize, offsetWidthSize: (indexMaps[endStr] || 0) - offsetLeftSize + endSubtractSize } } } } } return () => { return { offsetLeftSize: 0, offsetWidthSize: 0 } } } const handleParseColumn = () => { const ganttProps = $xeGantt.props const { treeConfig } = ganttProps const { minViewDate, maxViewDate } = reactData const { fullCols, groupCols } = handleColumnHeader() if (minViewDate && maxViewDate && fullCols.length) { const $xeTable = internalData.xeTable if ($xeTable) { const startField = computeStartField.value const endField = computeEndField.value const typeField = computeTypeField.value const { computeAggregateOpts, computeTreeOpts } = $xeTable.getComputeMaps() const tableReactData = $xeTable.reactData const { isRowGroupStatus } = tableReactData const tableInternalData = $xeTable.internalData const { afterFullData, afterTreeFullData, afterGroupFullData } = tableInternalData const aggregateOpts = computeAggregateOpts.value const treeOpts = computeTreeOpts.value const { transform } = treeOpts const childrenField = treeOpts.children || treeOpts.childrenField const ctMaps: Record<string, VxeGanttDefines.RowCacheItem> = {} const renderFn = createChartRender(fullCols) const handleParseRender = (row: any) => { const rowid = $xeTable.getRowid(row) let startValue = XEUtils.get(row, startField) let endValue = XEUtils.get(row, endField) const renderTaskType = getTaskType(XEUtils.get(row, typeField)) const isMilestone = hasMilestoneTask(renderTaskType) const isSubview = hasSubviewTask(renderTaskType) if (isMilestone) { if (!startValue) { startValue = endValue } endValue = startValue } if (isSubview) { ctMaps[rowid] = { row, rowid, oLeftSize: 0, oWidthSize: 0 } } else if (startValue && endValue) { const { offsetLeftSize, offsetWidthSize } = renderFn(startValue, endValue) ctMaps[rowid] = { row, rowid, oLeftSize: offsetLeftSize, oWidthSize: offsetWidthSize } } } if (isRowGroupStatus) { // 行分组 const mapChildrenField = aggregateOpts.mapChildrenField if (mapChildrenField) { XEUtils.eachTree(afterGroupFullData, handleParseRender, { children: mapChildrenField }) } } else if (treeConfig) { // 树结构 XEUtils.eachTree(afterTreeFullData, handleParseRender, { children: transform ? treeOpts.mapChildrenField : childrenField }) } else { afterFullData.forEach(handleParseRender) } internalData.chartMaps = ctMaps } } internalData.visibleColumn = fullCols reactData.headerGroups = groupCols updateTodayData() updateScrollXStatus() handleTableColumn() } const handleUpdateData = () => { const ganttProps = $xeGantt.props const { treeConfig } = ganttProps const { scrollXStore } = internalData const $xeTable = internalData.xeTable const sdMaps: Record<string, any> = {} const edMaps: Record<string, any> = {} let minDate: Date | null = null let maxDate: Date | null = null if ($xeTable) { const startField = computeStartField.value const endField = computeEndField.value const typeField = computeTypeField.value const { computeAggregateOpts, computeTreeOpts } = $xeTable.getComputeMaps() const tableReactData = $xeTable.reactData const { isRowGroupStatus } = tableReactData const tableInternalData = $xeTable.internalData const { afterFullData, afterTreeFullData, afterGroupFullData } = tableInternalData const aggregateOpts = computeAggregateOpts.value const treeOpts = computeTreeOpts.value const { transform } = treeOpts const childrenField = treeOpts.children || treeOpts.childrenField const handleMinMaxData = (row: any) => { let startValue = XEUtils.get(row, startField) let endValue = XEUtils.get(row, endField) const typeValue = XEUtils.get(row, typeField) const isMilestone = hasMilestoneTask(typeValue) if (!startValue) { startValue = endValue } if (isMilestone || !endValue) { endValue = startValue } if (startValue) { const startDate = parseStringDate(startValue) if (!minDate || minDate.getTime() > startDate.getTime()) { minDate = startDate } } if (endValue) { const endDate = parseStringDate(endValue) if (!maxDate || maxDate.getTime() < endDate.getTime()) { maxDate = endDate } } } if (isRowGroupStatus) { // 行分组 const mapChildrenField = aggregateOpts.mapChildrenField if (mapChildrenField) { XEUtils.eachTree(afterGroupFullData, handleMinMaxData, { children: mapChildrenField }) } } else if (treeConfig) { // 树结构 XEUtils.eachTree(afterTreeFullData, handleMinMaxData, { children: transform ? treeOpts.mapChildrenField : childrenField }) } else { afterFullData.forEach(handleMinMaxData) } } scrollXStore.startIndex = 0 scrollXStore.endIndex = Math.max(1, scrollXStore.visibleSize) reactData.minViewDate = minDate reactData.maxViewDate = maxDate internalData.startMaps = sdMaps internalData.endMaps = edMaps handleParseColumn() } const calcScrollbar = () => { const { scrollXWidth, scrollYHeight } = reactData const { elemStore } = internalData const scrollbarOpts = computeScrollbarOpts.value const bodyWrapperElem = getRefElem(elemStore['main-body-wrapper']) const xHandleEl = refScrollXHandleElem.value const yHandleEl = refScrollYHandleElem.value let overflowY = false let overflowX = false if (bodyWrapperElem) { overflowY = scrollYHeight > bodyWrapperElem.clientHeight if (yHandleEl) { reactData.scrollbarWidth = scrollbarOpts.width || (yHandleEl.offsetWidth - yHandleEl.clientWidth) || 14 } reactData.overflowY = overflowY overflowX = scrollXWidth > bodyWrapperElem.clientWidth if (xHandleEl) { reactData.scrollbarHeight = scrollbarOpts.height || (xHandleEl.offsetHeight - xHandleEl.clientHeight) || 14 } reactData.overflowX = overflowX } } const handleSubTaskMinMaxSize = ($xeTable: VxeTableConstructor, list: any[]) => { const { chartMaps } = internalData const { computeTreeOpts } = $xeTable.getComputeMaps() const treeOpts = computeTreeOpts.value const childrenField = treeOpts.children || treeOpts.childrenField const typeField = computeTypeField.value let minChildLeftSize = 0 let maxChildLeftSize = 0 XEUtils.eachTree(list, childRow => { const childRowid = $xeTable.getRowid(childRow) const renderTaskType = XEUtils.get(childRow, typeField) if (hasSubviewTask(renderTaskType)) { return } const childChartRest = childRowid ? chartMaps[childRowid] : null if (childChartRest) { maxChildLeftSize = Math.max(maxChildLeftSize, childChartRest.oLeftSize + childChartRest.oWidthSize) minChildLeftSize = minChildLeftSize ? Math.min(minChildLeftSize, childChartRest.oLeftSize) : childChartRest.oLeftSize } }, { children: childrenField }) return { minSize: minChildLeftSize, maxSize: maxChildLeftSize } } const updateTaskChartStyle = () => { const { dragBarRow } = ganttInternalData const { viewCellWidth } = reactData const { elemStore, chartMaps } = internalData const $xeTable = internalData.xeTable const chartWrapper = getRefElem(elemStore['main-chart-task-wrapper']) if (chartWrapper && $xeTable) { const { computeTreeOpts } = $xeTable.getComputeMaps() const treeOpts = computeTreeOpts.value const childrenField = treeOpts.children || treeOpts.childrenField XEUtils.arrayEach(chartWrapper.children, (rowEl) => { const barEl = rowEl.children[0] as HTMLDivElement if (!barEl) { return } const rowid = rowEl.getAttribute('rowid') if (dragBarRow && $xeTable.getRowid(dragBarRow) === rowid) { return } const chartRest = rowid ? chartMaps[rowid] : null const row = chartRest ? chartRest.row : null // 子任务视图 if (hasClass(barEl, 'is--subview')) { const childWrapperEl = barEl.firstElementChild as HTMLDivElement if (childWrapperEl) { // 行内展示 if (hasClass(childWrapperEl, 'is--inline')) { XEUtils.arrayEach(childWrapperEl.children, (childRowEl) => { const childBarEl = childRowEl.children[0] as HTMLDivElement const childRowid = childBarEl.getAttribute('rowid') || '' const childChartRest = childRowid ? chartMaps[childRowid] : null if (childChartRest) { const childRow = childChartRest.row // 如果是子视图 if (hasClass(childBarEl, 'is--subview')) { const subChildren: any[] = childRow[childrenField] const { minSize: minChildLeftSize, maxSize: maxChildLeftSize } = handleSubTaskMinMaxSize($xeTable, subChildren) childBarEl.style.left = `${viewCellWidth * minChildLeftSize}px` childBarEl.style.width = `${viewCellWidth * (maxChildLeftSize - minChildLeftSize)}px` } else { childBarEl.style.left = `${getTaskBarLeft(childChartRest, viewCellWidth)}px` if (!hasClass(childBarEl, 'is--milestone')) { // 里程碑不需要宽度 childBarEl.style.width = `${getTaskBarWidth(childChartRest, viewCellWidth)}px` } } } }) } else { // 如果展开子任务 const childRowEl = childWrapperEl.children[0] as HTMLDivElement const childBarEl = childRowEl ? childRowEl.children[0] as HTMLDivElement : null if (childBarEl) { const rowChildren: any[] = row ? row[childrenField] : [] const { minSize: minChildLeftSize, maxSize: maxChildLeftSize } = handleSubTaskMinMaxSize($xeTable, rowChildren) childBarEl.style.left = `${viewCellWidth * minChildLeftSize}px` childBarEl.style.width = `${viewCellWidth * (maxChildLeftSize - minChildLeftSize)}px` } } } } else { barEl.style.left = `${getTaskBarLeft(chartRest, viewCellWidth)}px` // 里程碑不需要宽度 if (!hasClass(barEl, 'is--milestone')) { barEl.style.width = `${getTaskBarWidth(chartRest, viewCellWidth)}px` } } }) } return nextTick() } const updateStyle = () => { const { scrollbarWidth, scrollbarHeight, headerGroups, tableColumn } = reactData const { elemStore, visibleColumn } = internalData const $xeTable = internalData.xeTable const el = refElem.value if (!el) { return } if (!$xeGantt) { return } const scrollbarOpts = computeScrollbarOpts.value const scrollbarXToTop = computeScrollbarXToTop.value const scrollbarYToLeft = computeScrollbarYToLeft.value const xLeftCornerEl = refScrollXLeftCornerElem.v