vxe-gantt
Version:
A vue based gantt component
2,342 lines • 93.1 kB
text/typescript
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, getTaskBarLeft, getTaskBarWidth, hasMilestoneTask, getTaskType, hasSubviewTask } from './util'
import { VxeGanttDependencyType } from './static'
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, VxeGanttPropTypes, VxeGanttViewPrivateMethods, GanttViewInternalData, VxeGanttViewMethods, GanttViewPrivateComputed, VxeGanttConstructor, VxeGanttPrivateMethods } from '../../../types'
interface CpmLink extends VxeGanttPropTypes.Link {
raw: VxeGanttPropTypes.Link
}
interface CpmItem {
row: any
rowid: string
duration: number
incoming: CpmLink[]
outgoing: CpmLink[]
indeg: number
ES: number
EF: number
LS: number
LF: number
}
const { globalEvents } = VxeUI
const { FinishToStart, StartToFinish, StartToStart, FinishToFinish } = VxeGanttDependencyType
const sourceType = 'gantt'
const secondMs = 1000
const minuteMs = 1000 * 60
const hourMs = 1000 * 60 * 60
const dayMs = hourMs * 24
function getTaskLinkKey (from: string | number, to: string | number, type?: VxeGanttDependencyType) {
return from + '_' + to + '_' + (type || 0)
}
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 ganttProps = $xeGantt.props
const ganttReactData = $xeGantt.reactData
const ganttInternalData = $xeGantt.internalData
const { computeDateFormat, computeTaskBarOpts, 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
let leftSize = -(ganttReactData.currLeftSpacing + XEUtils.toNumber(gridding ? gridding.leftSpacing || 0 : 0))
let rightSize = ganttReactData.currRightSpacing + XEUtils.toNumber(gridding ? gridding.rightSpacing || 0 : 0)
if (scaleStep > 0) {
leftSize *= scaleStep
rightSize *= scaleStep
}
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': {
let currDate = XEUtils.getWhatHours(minViewDate, leftSize, 'first')
const endDate = XEUtils.getWhatHours(maxViewDate, rightSize, 'first')
while (currDate <= endDate) {
const itemDate = currDate
dateList.push(itemDate)
currDate = XEUtils.getWhatHours(currDate, scaleStep)
}
break
}
case 'minute': {
let currDate = XEUtils.getWhatMinutes(minViewDate, leftSize, 'first')
const endDate = XEUtils.getWhatMinutes(maxViewDate, rightSize, 'first')
while (currDate <= endDate) {
const itemDate = currDate
dateList.push(itemDate)
currDate = XEUtils.getWhatMinutes(currDate, scaleStep)
}
break
}
case 'second': {
let currDate = XEUtils.getWhatSeconds(minViewDate, leftSize, 'first')
const endDate = XEUtils.getWhatSeconds(maxViewDate, rightSize, 'first')
while (currDate <= endDate) {
const itemDate = currDate
dateList.push(itemDate)
currDate = XEUtils.getWhatSeconds(currDate, scaleStep)
}
break
}
}
return dateList
})
const computeNowLineLeft = computed(() => {
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]
const todayDate = XEUtils.toStringDate(todayValue)
let currCol: VxeGanttDefines.ViewColumn | null = null
let nextCol: VxeGanttDefines.ViewColumn | null = null
for (let i = 0; i < visibleColumn.length; i++) {
const column = visibleColumn[i]
const { startDateObj, endDateObj } = column
if (startDateObj.date <= todayDate && endDateObj.date >= todayDate) {
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 { 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 { taskScaleList } = ganttReactData
const scaleUnit = computeScaleUnit.value
const minScale = computeMinScale.value
const weekScale = computeWeekScale.value
const scaleDateList = computeScaleDateList.value
const scaleStep = computeScaleStep.value
const weekStartDay = weekScale ? weekScale.startDay : undefined
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)
}
}
const createDateObj = (date: Date) => {
let [yy, yyyy, M, MM, d, dd, H, HH, m, mm, s, ss] = XEUtils.toDateString(date, 'yy-yyyy-M-MM-d-dd-H-HH-m-mm-s-ss').split('-')
const e = date.getDay()
const E = e + 1
const q = Math.ceil((date.getMonth() + 1) / 3)
const W = `${XEUtils.getYearWeek(date, 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
}
return { date, yy, yyyy, M, MM, d, dd, H, HH, m, mm, s, ss, q, W, WW, E, e }
}
for (let i = 0; i < scaleDateList.length; i++) {
const itemDate = scaleDateList[i]
const dateObj: VxeGanttDefines.ScaleDateObj = createDateObj(itemDate)
const { yyyy, MM, dd, HH, mm, ss, q, W, E } = dateObj
const startDateObj = dateObj
const colMaps: Record<VxeGanttDefines.ColumnScaleType, VxeGanttDefines.ViewColumn> = {
year: {
field: yyyy,
title: yyyy,
dateObj,
startDateObj,
endDateObj: scaleStep > 1 ? createDateObj(XEUtils.getWhatYear(itemDate, scaleStep - 1)) : startDateObj
},
quarter: {
field: `${yyyy}_q${q}`,
title: `${q}`,
dateObj,
startDateObj,
endDateObj: scaleStep > 1 ? createDateObj(XEUtils.getWhatQuarter(itemDate, scaleStep - 1)) : startDateObj
},
month: {
field: `${yyyy}_${MM}`,
title: MM,
dateObj,
startDateObj,
endDateObj: scaleStep > 1 ? createDateObj(XEUtils.getWhatMonth(itemDate, scaleStep - 1)) : startDateObj
},
week: {
field: `${yyyy}_W${W}`,
title: `${W}`,
dateObj,
startDateObj,
endDateObj: scaleStep > 1 ? createDateObj(XEUtils.getWhatWeek(itemDate, scaleStep - 1, weekStartDay, weekStartDay)) : startDateObj
},
day: {
field: `${yyyy}_${MM}_${dd}_E${E}`,
title: `${E}`,
dateObj,
startDateObj,
endDateObj: scaleStep > 1 ? createDateObj(XEUtils.getWhatDay(itemDate, scaleStep - 1)) : startDateObj
},
date: {
field: `${yyyy}_${MM}_${dd}`,
title: dd,
dateObj,
startDateObj,
endDateObj: scaleStep > 1 ? createDateObj(XEUtils.getWhatDay(itemDate, scaleStep - 1)) : startDateObj
},
hour: {
field: `${yyyy}_${MM}_${dd}_${HH}`,
title: HH,
dateObj,
startDateObj,
endDateObj: scaleStep > 1 ? createDateObj(XEUtils.getWhatHours(itemDate, scaleStep - 1)) : startDateObj
},
minute: {
field: `${yyyy}_${MM}_${dd}_${HH}_${mm}`,
title: mm,
dateObj,
startDateObj,
endDateObj: scaleStep > 1 ? createDateObj(XEUtils.getWhatMinutes(itemDate, scaleStep - 1)) : startDateObj
},
second: {
field: `${yyyy}_${MM}_${dd}_${HH}_${mm}_${ss}`,
title: ss,
dateObj,
startDateObj,
endDateObj: scaleStep > 1 ? createDateObj(XEUtils.getWhatSeconds(itemDate, scaleStep - 1)) : startDateObj
}
}
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 + ((scaleStep - currStep) * 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 + ((scaleStep - currStep) * 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 + ((scaleStep - currStep) * 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 + ((scaleStep - currStep) * 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 + ((scaleStep - currStep) * 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 + ((scaleStep - currStep) * 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 + ((scaleStep - currStep) * 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 + ((scaleStep - currStep) * 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 { linkList } = ganttReactData
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
const allList: any[] = []
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
}
}
allList.push(row)
}
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()
handleCriticalPath(allList, linkList)
}
/**
* 正向:后置 ES 的下连接线
*/
function forwardCriticalPathLowerBound (linkType: VxeGanttDependencyType, predES: number, predEF: number, duration: number): number {
switch (linkType) {
case FinishToStart:
return predEF
case StartToFinish:
return predES - duration
case StartToStart:
return predES
case FinishToFinish:
return predEF - duration
default:
return 0
}
}
/**
* 反向:前置 LF 的上连接线
*/
function backwardCriticalPathUpperBound (linkType: VxeGanttDependencyType, succLS: number, succLF: number, duration: number): number {
switch (linkType) {
case FinishToStart:
return succLS
case StartToFinish:
return succLF + duration
case StartToStart:
return succLS + duration
case FinishToFinish:
return succLF
default:
return -1
}
}
/**
* 关键边:两端都关键时,约束是否连接
*/
function isCriticalPathLinkTight (linkType: VxeGanttDependencyType, fromES: number, fromEF: number, toES: number, toEF: number, tolerance: number) {
switch (linkType) {
case FinishToStart:
return Math.abs(fromEF - toES) < tolerance
case StartToFinish:
return Math.abs(fromES - toEF) < tolerance
case StartToStart:
return Math.abs(fromES - toES) < tolerance
case FinishToFinish:
return Math.abs(fromEF - toEF) < tolerance
default:
return false
}
}
function handleCriticalPath (data: any[], links: VxeGanttPropTypes.Links) {
const taskBarOpts = computeTaskBarOpts.value
const $xeTable = internalData.xeTable
const criticalRowList: any[] = []
const criticalRowMaps = new Map<string, any>()
const criticalLinkKeyMaps = new Map<string, VxeGanttPropTypes.Link>()
ganttInternalData.criticalRowList = criticalRowList
ganttInternalData.criticalRowMaps = criticalRowMaps
ganttInternalData.criticalLinkKeyMaps = criticalLinkKeyMaps
if (!taskBarOpts.showCriticalPath || !links.length || !data.length || !$xeTable) {
if (ganttReactData.criticalRowIds.length || ganttReactData.criticalLinks.length) {
ganttReactData.criticalPathFlag++
}
ganttReactData.criticalLinks = []
ganttReactData.criticalRowIds = []
return
}
const { chartMaps } = internalData
const tolerance = 1e-6
const nodeMap = new Map<string, CpmItem>()
for (const row of data) {
const rowid = $xeTable.getRowid(row)
const chartRest = chartMaps[rowid]
nodeMap.set(rowid, {
row,
rowid: rowid,
duration: chartRest ? chartRest.oWidthSize : 0,
incoming: [],
outgoing: [],
indeg: 0,
ES: 0,
EF: 0,
LS: 0,
LF: 0
})
}
// 边界
const linkList: CpmLink[] = []
for (const link of links) {
const fromKey = '' + link.from
const toKey = '' + link.to
if (!nodeMap.has(fromKey) || !nodeMap.has(toKey) || fromKey === toKey) {
continue
}
const lkItemitem: CpmLink = {
from: link.from,
to: link.to,
type: XEUtils.toNumber(link.type) || 0,
raw: link
}
linkList.push(lkItemitem)
const toObjItem = nodeMap.get('' + link.to)
const fromObjItem = nodeMap.get('' + link.from)
if (fromObjItem) {
fromObjItem.outgoing.push(lkItemitem)
}
if (toObjItem) {
toObjItem.incoming.push(lkItemitem)
toObjItem.indeg++
}
}
// 拓扑排序 + 环检测
const queueList: CpmItem[] = []
nodeMap.forEach(nodeitem => {
if (nodeitem.indeg === 0) {
queueList.push(nodeitem)
}
})
const orderList: CpmItem[] = []
for (let head = 0; head < queueList.length; head++) {
const node = queueList[head]
orderList.push(node)
for (const lkItemitem of node.outgoing) {
const toObjItem = nodeMap.get('' + lkItemitem.to)
if (toObjItem) {
const nextDeg = toObjItem.indeg - 1
toObjItem.indeg = nextDeg
if (nextDeg === 0) {
queueList.push(nodeMap.get('' + lkItemitem.to)!)
}
}
}
}
if (orderList.length !== nodeMap.size) {
// 异常数据,依赖图中存在环
if (ganttReactData.criticalRowIds.length || ganttReactData.criticalLinks.length) {
ganttReactData.criticalPathFlag++
}
ganttReactData.criticalLinks = []
ganttReactData.criticalRowIds = []
return
}
// 正向:ES / EF
let criticalDuration = 0
for (const node of orderList) {
let earliestStart = 0
for (const lkItem of node.incoming) {
const fromObjItem = nodeMap.get('' + lkItem.from)
if (fromObjItem) {
const need = forwardCriticalPathLowerBound(lkItem.type, fromObjItem.ES, fromObjItem.EF, node.duration)
if (need > earliestStart) {
earliestStart = need
}
}
}
node.ES = earliestStart
node.EF = earliestStart + node.duration
if (node.EF > criticalDuration) {
criticalDuration = node.EF
}
}
// 反向:LS / LF
for (let i = orderList.length - 1; i >= 0; i--) {
const node = orderList[i]
let latestFinish = criticalDuration
for (const lkItem of node.outgoing) {
const toObjItem = nodeMap.get('' + lkItem.to)
if (toObjItem) {
const need = backwardCriticalPathUpperBound(lkItem.type, toObjItem.LS, toObjItem.LF, node.duration)
if (need < latestFinish) {
latestFinish = need
}
}
}
node.LF = latestFinish
node.LS = latestFinish - node.duration
}
// 关键任务
const criticalSet = new Set<string>()
const criticalRowIds: string[] = []
for (const nodeItem of orderList) {
if (Math.abs(nodeItem.LS - nodeItem.ES) < tolerance) {
criticalSet.add(nodeItem.rowid)
criticalRowIds.push(nodeItem.rowid)
criticalRowList.push(nodeItem.row)
criticalRowMaps.set(nodeItem.rowid, nodeItem.row)
}
}
// 关键连接线
const criticalLinks: VxeGanttPropTypes.Links = []
for (const lkItem of linkList) {
const toObjItem = nodeMap.get('' + lkItem.to)
const fromObjItem = nodeMap.get('' + lkItem.from)
if (!toObjItem || !fromObjItem) {
continue
}
// 两端必须都是关键任务
if (!criticalSet.has(fromObjItem.rowid) || !criticalSet.has(toObjItem.rowid)) {
continue
}
// 约束必须是连接的
if (isCriticalPathLinkTight(lkItem.type, fromObjItem.ES, fromObjItem.EF, toObjItem.ES, toObjItem.EF, tolerance)) {
criticalLinks.push(lkItem.raw)
criticalLinkKeyMaps.set(getTaskLinkKey(lkItem.from, lkItem.to, lkItem.type), lkItem.raw)
}
}
ganttReactData.criticalLinks = criticalLinks
ganttReactData.criticalRowIds = criticalRowIds
ganttReactData.criticalPathFlag++
}
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) {
const scrollbarWidth = scrollbarOpts.width || (yHandleEl.offsetWidth - yHandleEl.clientWidth) || 14
reactData.scrollbarWidth = scrollbarWidth
ganttReactData.scrollbarWidth = scrollbarWidth
}
reactData.overflowY = overflowY
ganttReactData.overflowY = overflowY
overflowX = scrollXWidth > bodyWrapperElem.clientWidth
if (xHandleEl) {
const scrollbarHeight = scrollbarOpts.height || (xHandleEl.offsetHeight - xHandleEl.clientHeight) || 14
reactData.scrollbarHeight = scrollbarHeight
ganttReactData.scrollbarHeight = scrollbarHeight
}
reactData.overflowX = overflowX
ganttReactData.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 { showLeftView } = ganttReactData
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.value
const xRightCornerEl = refScrollXRightCornerElem.value
const scrollXVirtualEl = refScrollXVirtualElem.value
let osbWidth = scrollbarWidth
const osbHeight = scrollbarHeight
let tbHeight = 0
let tHeaderHeight = 0
let tFooterHeight = 0
if ($xeTable) {
const tableInternalData = $xeTable.internalData
tbHeight = tableInternalData.tBodyHeight
tHeaderHeight = tableInternalData.tHeaderHeight
tFooterHeight = tableInternalData.tFooterHeight
}
let yScrollbarVisible = 'visible'
if ((scrollbarYToLeft && showLeftView) || (scrollbarOpts.y && scrollbarOpts.y.visible === false)) {
osbWidth = 0
yScrollbarVisible = 'hidden'
}
const headerScrollElem = getRefElem(elemStore['main-header-scroll'])
if (headerScrollElem) {
headerScrollElem.style.height = `${tHeaderHeight}px`
headerScrollElem.style.setProperty('--vxe-ui-gantt-view-cell-height', `${tHeaderHeight / headerGroups.length}px`)
}
const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
if (bodyScrollElem) {
bodyScrollElem.style.height = `${tbHeight}px`
}
const footerScrollElem = getRefElem(elemStore['main-footer-scroll'])
if (footerScrollElem) {
footerScrollElem.style.height = `${tFooterHeight}px`
}
if (scrollXVirtualEl) {
scrollXVirtualEl.style.height = `${osbHeight}px`
scrollXVirtualEl.style.visibility = 'visible'
}
const xWrapperEl = refScrollXWrapperElem.value
if (xWrapperEl) {
xWrapperEl.style.left = scrollbarXToTop ? `${osbWidth}px` : ''
xWrapperEl.style.width = `${el.clientWidth - osbWidth}px`
}
if (xLeftCornerEl) {
xLeftCornerEl.style.width = scrollbarXToTop ? `${osbWidth}px` : ''
xLeftCornerEl.style.display = scrollbarXToTop ? (osbHeight ? 'block' : '') : ''
}
if (xRightCornerEl) {
xRightCornerEl.style.width = scrollbarXToTop ? '' : `${osbWidth}px`
xRightCornerEl.style.display = scrollbarXToTop ? '' : (osbHeight ? 'block' : '')
}
const scrollYVirtualEl = refScrollYVirtualElem.value
if (scrollYVirtualEl) {
scrollYVirtualEl.style.width = `${osbWidth}px`
scrollYVirtualEl.style.height = `${tbHeight + tHeaderHeight + tFooterHeight}px`
scrollYVirtualEl.style.visibility = yScrollbarVisible
}
const yTopCornerEl = refScrollYTopCornerElem.value
if (yTopCornerEl) {
yTopCornerEl.style.height = `${tHeaderHeight}px`
yTopCornerEl.style.display = tHeaderHeight ? 'block' : ''
}
const yWrapperEl = refScrollYWrapperElem.value
if (yWrapperEl) {
yWrapperEl.style.height = `${tbHeight}px`
yWrapperEl.style.top = `${tHeaderHeight}px`
}
const yBottomCornerEl = refScrollYBottomCornerElem.value
if (yBottomCornerEl) {
yBottomCornerEl.style.height = `${tFooterHeight}px`
yBottomCornerEl.style.top = `${tHeaderHeight + tbHeight}px`
yBottomCornerEl.style.display = tFooterHeight ? 'block' : ''
}
const colInfoElem = refColInfoElem.value
let viewCellWidth = 40
if (colInfoElem) {
viewCellWidth = colInfoElem.clientWidth || 40
}
let viewTableWidth = viewCellWidth
if (visibleColumn.length) {
viewTableWidth = Math.max(0, viewCellWidth * visibleColumn.length)
if (bodyScrollElem) {
const viewWidth = bodyScrollElem.clientWidth
const remainWidth = viewWidth - viewTableWidth
if (remainWidth > 0) {
viewCellWidth += Math.max(0, remainWidth / visibleColumn.length)
viewTableWidth = viewWidth
}
}
}
reactData.viewCellWidth = viewCellWidth
const headerTableElem = getRefElem(elemStore['main-header-table'])
const bodyTableElem = getRefElem(elemStore['main-body-table'])
const vmTableWidth = viewCellWidth * tableColumn.length
if (headerTableElem) {
headerTableElem.style.width = `${viewTableWidth}px`
}
if (bodyTableElem) {
bodyTableElem.style.width = `${vmTableWidth}px`
}
reactData.scrollXWidth = viewTableWidth
return Promise.all([
updateTaskChartStyle(),
$xeGantt.handleUpTaskLinkStyle ? $xeGantt.handleUpTaskLinkStyle($xeGanttView) : null
])
}
const handleRecalculateStyle = () => {
const el = refElem.value
internalData.rceRunTime = Date.now()
if (!el || !el.clientWidth) {
return nextTick()
}
if (!$xeGantt) {
return nextTick()
}
calcScrollbar()
updateStyle()
return computeScrollLoad()
}
const handleLazyRecalculate = () => {
return new Promise<void>(resolve => {
const { rceTimeout, rceRunTime } = internalData
const $xeTable = internalData.xeTable
let refreshDelay = 30
if ($xeTable) {
const { computeResizeOpts } = $xeTable.getComputeMaps()
const resizeOpts = computeResizeOpts.value
refreshDelay = resizeOpts.refreshDelay || refreshDelay
}
if (rceTimeout) {
clearTimeout(rceTimeout)
if (rceRunTime && rceRunTime + (refreshDelay - 5) < Date.now()) {
resolve(
handleRecalculateStyle()
)
} else {
nextTick(() => {
resolve()
})
}
} else {
resolve(
handleRecalculateStyle()
)
}
internalData.rceTimeout = setTimeout(() => {
internalData.rceTimeout = undefined
handleRecalculateStyle()
}, refreshDelay)
})
}
const computeScrollLoad = () => {
return nextTick().then(() => {
const { scrollXLoad } = reactData
const { scrollXStore } = internalData
// 计算 X 逻辑
if (scrollXLoad) {
const { toVisibleIndex: toXVisibleIndex, visibleSize: visibleXSize } = handleVirtualXVisible()
const offsetXSize = 2
scrollXStore.preloadSize = 1
scrollXStore.offsetSize = offsetXSize
scrollXStore.visibleSize = visibleXSize
scrollXStore.endIndex = Math.max(scrollXStore.startIndex + scrollXStore.visibleSize + offsetXSize, scrollXStore.endIndex)
scrollXStore.visibleStartIndex = Math.max(scrollXStore.startIndex, toXVisibleIndex)
scrollXStore.visibleEndIndex = Math.min(scrollXStore.endIndex, toXVisibleIndex + visibleXSize)
updateScrollXData().then(() => {
loadScrollXData()
})
} else {
updateScrollXSpace()
}
})
}
const handleVirtualXVisible = () => {
const { viewCellWidth } = reactData
const { elemStore } = internalData
const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
if (bodyScrollElem) {
const clientWidth = bodyScrollElem.clientWidth
const scrollLeft = bodyScrollElem.scrollLeft
const toVisibleIndex = Math.floor(scrollLeft / viewCellWidth) - 1
const visibleSize = Math.ceil(clientWidth / viewCellWidth) + 1
return { toVisibleIndex: Math.max(0, toVisibleIndex), visibleSize: Math.max(1, visibleSize) }
}
return { toVisibleIndex: 0, visibleSize: 6 }
}
const loadScrollXData = () => {
const { isScrollXBig } = reactData
const { scrollXStore } = internalData
const { preloadSize, startIndex, endIndex, offsetSize } = scrollXStore
const { toVisibleIndex, visibleSize } = handleVirtualXVisible()
const offsetItem = {
startIndex: Math.max(0, isScrollXBig ? toVisibleIndex - 1 : toVisibleIndex - 1 - offsetSize - preloadSize),
endIndex: isScrollXBig ? toVisibleIndex + visibleSize : toVisibleIndex + visibleSize + offsetSize + preloadSize
}
scrollXStore.visibleStartIndex = toVisibleIndex - 1
scrollXStore.visibleEndIndex = toVisibleIndex + visibleSize + 1
const { startIndex: offsetStartIndex, endIndex: offsetEndIndex } = offsetItem
if (toVisibleIndex <= startIndex || toVisibleIndex >= endIndex - visibleSize - 1) {
if (startIndex !== offsetStartIndex || endIndex !== offsetEndIndex) {
scrollXStore.startIndex = offsetStartIndex
scrollXStore.endIndex = offsetEndIndex
updateScrollXData()
}
}
}
const updateScrollXData = () => {
handleTableColumn()
updateScrollXSpace()
return nextTick()
}
const updateScrollXStatus = () => {
const scrollXLoad = true
reactData.scrollXLoad = scrollXLoad
return scrollXLoad
}
const handleTableColumn = () => {
const { scrollXLoad } = reactData
const { visibleColumn, scrollXStore } = internalData
const tableColumn = scrollXLoad ? visibleColumn.slice(scrollXStore.startIndex, scrollXStore.endIndex) : visibleColumn.slice(0)
reactData.tableColumn = tableColumn
}
const updateScrollXSpace = () => {
const { scrollXLoad, scrollXWidth, viewCellWidth } = reactData
const { elemStore, scrollXStore } = internalData
const bodyTableElem = getRefElem(elemStore['main-body-table'])
// const headerTableElem = getRefElem(elemStore['main-header-table'])
// const footerTableElem = getRefElem(elemStore['main-footer-table'])
const { startIndex } = scrollXStore
let xSpaceLeft = 0
if (scrollXLoad) {
xSpaceLeft = Math.max(0, startIndex * viewCellWidth)
}
// if (headerTableElem) {
// headerTableElem.style.transform = `translate(${xSpaceLeft}px, 0px)`
// }
if (bodyTableElem) {
bodyTableElem.style.transform = `translate(${xSpaceLeft}px, ${reactData.scrollYTop || 0}px)`
}
// if (footerTableElem) {
// footerTableElem.style.transform = `translate(${xSpaceLeft}px, 0px)`
// }
const ySpaceWidth = scrollXWidth
const layoutList = ['header', 'body', 'footer']
layoutList.forEach(layout => {
const xSpaceElem = getRefElem(elemStore[`main-${layout}-xSpace`])
if (xSpaceElem) {
xSpaceElem.style.width = scrollXLoad ? `${ySpaceWidth}px` : ''
}
})
const scrollXSpaceEl = refScrollXSpaceElem.value
if (scrollXSpaceEl) {
scrollXSpaceEl.style.width = `${ySpaceWidth}px`
}
const beforeWrapper = getRefElem(elemStore['main-chart-before-wrapper'])
const beforeSvgElem = beforeWrapper ? beforeWrapper.firstElementChild as HTMLDivElement : null
if (beforeSvgElem) {
beforeSvgElem.style.width = `${ySpaceWidth}px`
}
const afterWrapper = getRefElem(elemStore['main-chart-after-wrapper'])
const afterSvgElem = afterWrapper ? afterWrapper.firstElementChild as HTMLDivElement : null
if (afterSvgElem) {
afterSvgElem.style.width = `${ySpaceWidth}px`
}
reactData.scrollXLeft = xSpaceLeft
reactData.scrollXWidth = ySpaceWidth
calcScrollbar()
return nextTick()
}
const triggerScrollXEvent = () => {
loadScrollXData()
}
const updateScrollYSpace = () => {
const { scrollYLoad, overflowY } = reactData
const { elemStore } = internalData
const $xeTable = internalData.xeTable
const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
const bodyTableElem = getRefElem(elemStore['main-body-table'])
let ySpaceTop = 0
let scrollYHeight = 0
let isScrollYBig = false
if ($xeTable) {
const tableReactData = $xeTable.reactData
ySpaceTop = tableReactData.scrollYTop
scrollYHeight = tableReactData.scrollYHeight
isScrollYBig = tableReactData.isScrollYBig
}
let ySpaceHeight = scrollYHeight
let scrollYTop = ySpaceTop
let clientHeight = 0
if (bodyScrollElem) {
clientHeight = bodyScrollElem.clientHeight
}
if (isScrollYBig) {
// 触底
if (bodyScrollElem && bodyTableElem && bodyScrollElem.scrollTop + clientHeight >= maxYHeight) {
scrollYTop = maxYHeight - bodyTableElem.clientHeight
} else {
scrollYTop = (maxYHeight - clientHeight) * (ySpaceTop / (scrollYHeight - clientHeight))
}
ySpaceHeight = maxYHeight
}
if (!(scrollYLoad && overflowY)) {
scrollYTop = 0
}
const bodyChartWrapperElem = getRefElem(elemStore['main-chart-task-wrapper'])
if (bodyTableElem) {
bodyTableElem.style.transform = `translate(${reactData.scrollXLeft || 0}px, ${scrollYTop}px)`
}
if (bodyChartWrapperElem) {
bodyChartWrapperElem.style.transform = `translateY(${scrollYTop}px)`
}
const bodyYSpaceElem = getRefElem(elemStore['main-body-ySpace'])
if (bodyYSpaceElem) {
bodyYSpaceElem.style.height = ySpaceHeight ? `${ySpaceHeight}px` : ''
}
const scrollYSpaceEl = refScrollYSpaceElem.value
if (scrollYSpaceEl) {
scrollYSpaceEl.style.height = ySpaceHeight ? `${ySpaceHeight}px` : ''
}
const beforeWrapper = getRefElem(elemStore['main-chart-before-wrapper'])
const beforeSvgElem = beforeWrapper ? beforeWrapper.firstElementChild as HTMLDivElement : null
if (beforeSvgElem) {
beforeSvgElem.style.height = ySpaceHeight ? `${ySpaceHeight}px` : ''
}
const afterWrapper = getRefElem(elemStore['main-chart-after-wrapper'])
const afterSvgElem = afterWrapper ? afterWrapper.firstElementChild as HTMLDivElement : null
if (afterSvgElem) {
afterSvgElem.style.height = ySpaceHeight ? `${ySpaceHeight}px` : ''
}
const nowLineElem = getRefElem(elemStore['main-chart-now-line'])
if (nowLineElem) {
nowLineElem.style.height = ySpaceHeight ? `${ySpaceHeight}px` : ''
}
reactData.scrollYTop = scrollYTop
reactData.scrollYHeight = scrollYHeight
reactData.isScrollYBig = isScrollYBig
calcScrollbar()
return nextTick().then(() => {
updateStyle()
})
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const checkLastSyncScroll = (isRollX: boolean, isRollY: boolean) => {
const { lcsTimeout } = internalData
reactData.lazScrollLoading = true
if (lcsTimeout) {
clearTimeout(lcsTimeout)
}
internalData.lcsTimeout = setTimeout(() => {
internalData.lcsRunTime = Date.now()
internalData.lcsTimeout = undefined
internalData.intoRunScroll = false
internalData.inVirtualScroll = false
internalData.inWheelScroll = false
internalData.inHeaderScroll = false
internalData.inBodyScroll = false
internalData.inFooterScroll = false
reactData.lazScrollLoading = false
}, 200)
}
const handleScrollData = (isRollY: boolean, isRollX: boolean, scrollTop: number, scrollLeft: number) => {
if (isRollX) {
internalData.lastScrollLeft = scrollLeft
}
if (isRollY) {
internalData.lastScrollTop = scrollTop
}
reactData.lastScrollTime = Date.now()
checkLastSyncScroll(isRollX, isRollY)
}
const handleScrollEvent = (evnt: Event, isRollY: boolean, isRollX: boolean, scrollTop: number, scrollLeft: number) => {
const $xeTable = internalData.xeTable
const { lastScrollLeft, lastScrollTop } = internalData
const xHandleEl = refScrollXHandleElem.value
const yHandleEl = refScrollYHandleElem.value
if (!xHandleEl || !yHandleEl) {
return
}
if (!$xeTable) {
return
}
const { computeScrollXThreshold, computeScrollYThreshold } = $xeTable.getComputeMaps()
const bodyHeight = yHandleEl.clientHeight
const bodyWidth = xHandleEl.clientWidth
const scrollHeight = yHandleEl.scrollHeight
const scrollWidth = xHandleEl.scrollWidth
let isTop = false
let isBottom = false
let isLeft = false
let isRight = false
let direction = ''
let isTopBoundary = false
let isBottomBoundary = false
let isLeftBoundary = false
let isRightBoundary = false
if (isRollX) {
const xThreshold = computeScrollXThreshold.value
isLeft = scrollLeft <= 0
if (!isLeft) {
isRight = scrollLeft + bodyWidth >= scrollWidth - 1
}
if (scrollLeft > lastScrollLeft) {
direction = 'right'
if (scrollLeft + bodyWidth >= scrollWidth - xThreshold) {
isRightBoundary = true
}
} else {
direction = 'left'
if (scrollLeft <= xThreshold) {
isLeftBoundary = true
}
}
}
if (isRollY) {
const yThreshold = computeScrollYThreshold.value
isTop = scrollTop <= 0
if (!isTop) {
isBottom = scrollTop + bodyHeight >= scrollHeight - 1
}
if (scrollTop > lastScrollTop) {
direction = 'bottom'
if (scrollTop + bodyHeight >= scrollHeight - yThreshold) {
isBottomBoundary = true
}
} else {
direction = 'top'
if (scrollTop <= yThreshold) {
isTopBoundary = true
}
}
}
handleScrollData(isRollY, isRollX, scrollTop, scrollLeft)
const evntParams = {
source: sourceType,
scrollTop,
scrollLeft,
bodyHeight,
bodyWidth,
scrollHeight,
scrollWidth,
isX: isRollX,
isY: isRollY,
isTop,
isBottom,
isLeft,
isRight,
direction
}
if (isBottomBoundary || isTopBoundary || isRightBoundary || isLeftBoundary) {
$xeGantt.dispatchEvent('scroll-boundary', evntParams, evnt)
}
$xeGantt.dispatchEvent('scroll', evntParams, evnt)
}
const ganttViewMethods: VxeGanttViewMethods = {
refreshData () {
handleUpdateData()
handleRecalculateStyle()
return nextTick().then(() => {
const $xeTable = internalData.xeTable
handleRecalculateStyle()
if ($xeTable) {
return $xeTable.recalculate()
}
})
},
updateViewData (force?: boolean) {
const $xeTable = internalData.xeTable
if ($xeTable) {
const tableReactData = $xeTable.reactData
const { tableData } = tableReactData
reactData.tableData = tableData
if (force) {
ganttReactData.currLeftSpacing = 0
ganttReactData.currRightSpacing = 0
handleUpdateData()
}
handleRecalculateStyle()
}
return nextTick()
},
connectUpdate ({ $table }) {
if ($table) {
internalData.xeTable = $table
}
return nextTick()
}
}
/**
* 同步表格滚动
*/
const syncTableScrollTop = (scrollTop: number) => {
const $xeTable = internalData.xeTable
if ($xeTable) {
const tableInternalData = $xeTable.internalData
const { elemStore: tableElemStore } = tableInternalData
const tableBodyScrollElem = getRefElem(tableElemStore['main-body-scroll'])
if (tableBodyScrollElem) {
tableBodyScrollElem.scrollTop = scrollTop
}
}
}
const ganttViewPrivateMethods: VxeGanttViewPrivateMethods = {
handleUpdateStyle: updateStyle,
handleLazyRecalculate,
handleUpdateCurrentRow (row) {
const $xeTable = internalData.xeTable
const el = refElem.value
if ($xeTable && el) {
if (row) {
const tableProps = $xeTable.props
const { computeRowOpts } = $xeTable.getComputeMaps()
const rowOpts = computeRowOpts.value
if (rowOpts.isCurrent || tableProps.highlightCurrentRow) {
XEUtils.arrayEach(el.querySelectorAll(`.vxe-gantt-view--body-row[rowid="${$xeTable.getRowid(row)}"]`), elem => addClass(elem, 'row--current'))
}
} else {
XEUtils.arrayEach(el.querySelectorAll('.vxe-gantt-view--body-row.row--current'), elem => removeClass(elem, 'row--current'))
}
}
},
handleUpdateHoverRow (row) {
const $xeTable = internalData.xeTable
const el = refElem.value
if ($xeTable && el) {
if (row) {
XEUtils.arrayEach(el.querySelectorAll(`.vxe-gantt-view--body-row[rowid="${$xeTable.getRowid(row)}"]`), elem => addClass(elem, 'row--hover'))
} else {
XEUtils.arrayEach(el.querySelectorAll('.vxe-gantt-view--body-row.row--hover'), elem => removeClass(elem, 'row--hover'))
}
}
},
triggerHeaderScrollEvent (evnt) {
const { elemStore, inVirtualScroll, inBodyScroll, inFooterScroll } = internalData
if (inVirtualScroll) {
return
}
if (inBodyScroll || inFooterScroll) {
return
}
const wrapperEl = evnt.currentTarget as HTMLDivElement
const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
const xHandleEl = refScrollXHandleElem.value
if (bodyScrollElem && wrapperEl) {
const isRollX = true
const isRollY = false
const currLeftNum = wrapperEl.scrollLeft
internalData.inHeaderScroll = true
setScrollLeft(xHandleEl, currLeftNum)
setScrollLeft(bodyScrollElem, currLeftNum)
handleScrollEvent(evnt, isRollY, isRollX, wrapperEl.scrollTop, currLeftNum)
}
},
triggerBodyScrollEvent (evnt) {
const { scrollXLoad } = reactData
const { elemStore, inVirtualScroll, inHeaderScroll, inFooterScroll, lastScrollLeft, lastScrollTop } = internalData
if (inVirtualScroll) {
return
}
if (inHeaderScroll || inFooterScroll) {
return
}
const wrapperEl = evnt.currentTarget as HTMLDivElement
const headerScrollElem = getRefElem(elemStore['main-header-scroll'])
const xHandleEl = refScrollXHandleElem.value
const yHandleEl = refScrollYHandleElem.value
const scrollLeft = wrapperEl.scrollLeft
const scrollTop = wrapperEl.scrollTop
const isRollX = scrollLeft !== lastScrollLeft
const isRollY = scrollTop !== lastScrollTop
internalData.inBodyScroll = true
internalData.scrollRenderType = ''
if (isRollY) {
setScrollTop(yHandleEl, scrollTop)
syncTableScrollTop(scrollTop)
}
if (isRollX) {
internalData.inBodyScroll = true
setScrollLeft(xHandleEl, scrollLeft)
setScrollLeft(headerScrollElem, scrollLeft)
if (scrollXLoad) {
triggerScrollXEvent()
}
}
if (isRollY) {
handleScrollData(isRollY, isRollX, wrapperEl.scrollTop, scrollLeft)
}
if (isRollX) {
handleScrollEvent(evnt, isRollY, isRollX, wrapperEl.scrollTop, scrollLeft)
}
},
// triggerFooterScrollEvent (evnt) {
// const { inVirtualScroll, inHeaderScroll, inBodyScroll } = internalData
// if (inVirtualScroll) {
// return
// }
// if (inHeaderScroll || inBodyScroll) {
// return
// }
// const wrapperEl = evnt.currentTarget as HTMLDivElement
// if (wrapperEl) {
// const isRollX = true
// const isRollY = false
// const currLeftNum = wrapperEl.scrollLeft
// handleScrollEvent(evnt, isRollY, isRollX, wrapperEl.scrollTop, currLeftNum)
// }
// },
triggerVirtualScrollXEvent (evnt) {
const { scrollXLoad } = reactData
const { elemStore, inHeaderScroll, inBodyScroll } = internalData
if (inHeaderScroll || inBodyScroll) {
return
}
const wrapperEl = evnt.currentTarget as HTMLDivElement
const headerScrollElem = getRefElem(elemStore['main-header-scroll'])
const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
if (wrapperEl) {
const isRollY = false
const isRollX = true
const currLeftNum = wrapperEl.scrollLeft
internalData.inVirtualScroll = true
setScrollLeft(headerScrollElem, currLeftNum)
setScrollLeft(bodyScrollElem, currLeftNum)
if (scrollXLoad) {
triggerScrollXEvent()
}
handleScrollEvent(evnt, isRollY, isRollX, wrapperEl.scrollTop, currLeftNum)
}
},
triggerVirtualScrollYEvent (evnt) {
const { elemStore, inHeaderScroll, inBodyScroll } = internalData
if (inHeaderScroll || inBodyScroll) {
return
}
const wrapperEl = evnt.currentTarget as HTMLDivElement
const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
if (wrapperEl) {
const isRollY = true
const isRollX = false
const currTopNum = wrapperEl.scrollTop
internalData.inVirtualScroll = true
setScrollTop(bodyScrollElem, currTopNum)
syncTableScrollTop(currTopNum)
handleScrollData(isRollY, isRollX, currTopNum, wrapperEl.scrollLeft)
}
},
handleUpdateSXSpace () {
return updateScrollXSpace()
},
handleUpdateSYSpace () {
return updateScrollYSpace()
},
handleUpdateSYStatus (sYLoad) {
reactData.scrollYLoad = sYLoad
}
}
const handleGlobalResizeEvent = () => {
handleLazyRecalculate()
}
Object.assign($xeGanttView, ganttViewMethods, ganttViewPrivateMethods)
const renderScrollX = () => {
return h('div', {
key: 'vsx',
ref: refScrollXVirtualElem,
class: 'vxe-gantt-view--scroll-x-virtual'
}, [
h('div', {
ref: refScrollXLeftCornerElem,
class: 'vxe-gantt-view--scroll-x-left-corner'
}),
h('div', {
ref: refScrollXWrapperElem,
class: 'vxe-gantt-view--scroll-x-wrapper'
}, [
h('div', {
ref: refScrollXHandleElem,
class: 'vxe-gantt-view--scroll-x-handle',
onScroll: $xeGanttView.triggerVirtualScrollXEvent
}, [
h('div', {
ref: refScrollXSpaceElem,
class: 'vxe-gantt-view--scroll-x-space'
})
]),
h('div', {
class: 'vxe-gantt-view--scroll-x-handle-appearance'
})
]),
h('div', {
ref: refScrollXRightCornerElem,
class: 'vxe-gantt-view--scroll-x-right-corner'
})
])
}
const renderScrollY = () => {
return h('div', {
ref: refScrollYVirtualElem,
class: 'vxe-gantt-view--scroll-y-virtual'
}, [
h('div', {
ref: refScrollYTopCornerElem,
class: 'vxe-gantt-view--scroll-y-top-corner'
}),
h('div', {
ref: refScrollYWrapperElem,
class: 'vxe-gantt-view--scroll-y-wrapper'
}, [
h('div', {
ref: refScrollYHandleElem,
class: 'vxe-gantt-view--scroll-y-handle',
onScroll: $xeGanttView.triggerVirtualScrollYEvent
}, [
h('div', {
ref: refScrollYSpaceElem,
class: 'vxe-gantt-view--scroll-y-space'
})
]),
h('div', {
class: 'vxe-gantt-view--scroll-y-handle-appearance'
})
]),
h('div', {
ref: refScrollYBottomCornerElem,
class: 'vxe-gantt-view--scroll-y-bottom-corner'
})
])
}
const renderViewport = () => {
return h('div', {
class: 'vxe-gantt-view--viewport-wrapper'
}, [
h(GanttViewHeaderComponent),
h(GanttViewBodyComponent),
h(GanttViewFooterComponent)
])
}
const renderBody = () => {
const scrollbarYToLeft = computeScrollbarYToLeft.value
return h('div', {
class: 'vxe-gantt-view--layout-wrapper'
}, scrollbarYToLeft
? [
renderScrollY(),
renderViewport()
]
: [
renderViewport(),
renderScrollY()
])
}
const renderVN = () => {
const { overflowX, overflowY, scrollXLoad, scrollYLoad } = reactData
const scrollbarXToTop = computeScrollbarXToTop.value
return h('div', {
ref: refElem,
class: ['vxe-gantt-view', {
'is--scroll-y': overflowY,
'is--scroll-x': overflowX,
'is--virtual-x': scrollXLoad,
'is--virtual-y': scrollYLoad
}]
}, [
h('div', {
class: 'vxe-gantt-view--render-wrapper'
}, scrollbarXToTop
? [
renderScrollX(),
renderBody()
]
: [
renderBody(),
renderScrollX()
]),
h('div', {
class: 'vxe-gantt-view--render-vars'
}, [
h('div', {
ref: refColInfoElem,
class: 'vxe-gantt-view--column-info'
})
])
])
}
onMounted(() => {
globalEvents.on($xeGanttView, 'resize', handleGlobalResizeEvent)
})
onBeforeUnmount(() => {
XEUtils.assign(reactData, createReactData())
XEUtils.assign(internalData, createInternalData())
})
onUnmounted(() => {
globalEvents.off($xeGanttView, 'keydown')
})
$xeGanttView.renderVN = renderVN
provide('$xeGanttView', $xeGanttView)
return $xeGanttView
},
render () {
return this.renderVN()
}
})