vxe-gantt
Version:
A vue based gantt component
500 lines (461 loc) • 21.5 kB
text/typescript
import { h, inject, VNode, ref, Ref, onMounted, onUnmounted } from 'vue'
import { defineVxeComponent } from '../../ui/src/comp'
import { VxeUI } from '@vxe-ui/core'
import XEUtils from 'xe-utils'
import { getCellRestHeight, hasMilestoneTask, getTaskType, hasSubviewTask } from './util'
import { getStringValue, isEnableConf, hasEnableConf } from '../../ui/src/utils'
import type { VxeComponentStyleType } from 'vxe-pc-ui'
import type { VxeTableConstructor, VxeTableMethods, VxeTablePrivateMethods } from 'vxe-table'
import type { VxeGanttViewConstructor, VxeGanttViewPrivateMethods, VxeGanttConstructor, VxeGanttPrivateMethods } from '../../../types'
const { getIcon, renderEmptyElement } = VxeUI
const sourceType = 'gantt'
const viewType = 'chart'
export default defineVxeComponent({
name: 'VxeGanttViewChart',
setup () {
const $xeGantt = inject('$xeGantt', {} as (VxeGanttConstructor & VxeGanttPrivateMethods))
const $xeGanttView = inject('$xeGanttView', {} as VxeGanttViewConstructor & VxeGanttViewPrivateMethods)
const { props: ganttProps, context: ganttContext, reactData: ganttReactData, internalData: ganttInternalData } = $xeGantt
const { reactData: ganttViewReactData, internalData: ganttViewInternalData } = $xeGanttView
const { computeProgressField, computeTitleField, computeTypeField, computeTaskBarOpts, computeScaleUnit, computeTaskLinkOpts, computeTaskBarMilestoneOpts, computeTaskBarSubviewOpts } = $xeGantt.getComputeMaps()
const { computeNowLineLeft } = $xeGanttView.getComputeMaps()
const refElem = ref<HTMLDivElement>()
const refTaskWrapperElem = ref() as Ref<HTMLDivElement>
const refChartBeforeWrapperElem = ref() as Ref<HTMLDivElement>
const refChartAfterWrapperElem = ref() as Ref<HTMLDivElement>
const refNowLineElem = ref() as Ref<HTMLDivElement>
const renderTaskBar = ($xeTable: VxeTableConstructor & VxeTableMethods & VxeTablePrivateMethods, row: any, rowid: string, rowIndex: number, $rowIndex: number, _rowIndex: number, rowChildren: any[], isExpandTree: boolean) => {
const tableReactData = $xeTable.reactData
const { resizeHeightFlag, pendingRowFlag } = tableReactData
const tableInternalData = $xeTable.internalData
const { fullAllDataRowIdData, pendingRowMaps } = tableInternalData
const { computeCellOpts, computeRowOpts, computeDefaultRowHeight } = $xeTable.getComputeMaps()
const cellOpts = computeCellOpts.value
const rowOpts = computeRowOpts.value
const defaultRowHeight = computeDefaultRowHeight.value
const { computeTreeOpts } = $xeTable.getComputeMaps()
const treeOpts = computeTreeOpts.value
const childrenField = treeOpts.children || treeOpts.childrenField
const ganttSlots = $xeGantt.context.slots
const taskBarSlot = ganttSlots.taskBar || ganttSlots['task-bar']
const taskBarOverviewSlot = ganttSlots.taskBarOverview || ganttSlots['task-bar-overview']
const { treeConfig, taskBarMilestoneConfig, taskBarSubviewConfig } = ganttProps
const { activeLink, activeBarRowid } = ganttReactData
const titleField = computeTitleField.value
const progressField = computeProgressField.value
const typeField = computeTypeField.value
const taskBarOpts = computeTaskBarOpts.value
const taskBarMilestoneOpts = computeTaskBarMilestoneOpts.value
const taskBarSubviewOpts = computeTaskBarSubviewOpts.value
const { showOverview, barStyle: subBarStyle } = taskBarSubviewOpts
const scaleUnit = computeScaleUnit.value
const barParams = { $gantt: $xeGantt, row, scaleType: scaleUnit }
const { showProgress, showContent, contentMethod, barStyle, moveable, showTooltip } = taskBarOpts
const isBarRowStyle = XEUtils.isFunction(barStyle)
const barStyObj = (barStyle ? (isBarRowStyle ? barStyle(barParams) || undefined : barStyle) : {}) || {}
const { round } = barStyObj
const rowRest = fullAllDataRowIdData[rowid] || {}
const cellHeight = resizeHeightFlag ? getCellRestHeight(rowRest, cellOpts, rowOpts, defaultRowHeight) : 0
let title = getStringValue(XEUtils.get(row, titleField))
const progressValue = showProgress ? Math.min(100, Math.max(0, XEUtils.toNumber(XEUtils.get(row, progressField)))) : 0
const renderTaskType = getTaskType(XEUtils.get(row, typeField))
const vbStyle: VxeComponentStyleType = {}
const vpStyle: VxeComponentStyleType = {
width: `${progressValue || 0}%`
}
if (isBarRowStyle) {
const { bgColor, completedBgColor } = barStyObj
if (bgColor) {
vbStyle.backgroundColor = bgColor
}
if (completedBgColor) {
vpStyle.backgroundColor = completedBgColor
}
}
const ctParams = {
$gantt: $xeGantt,
source: sourceType,
type: viewType,
scaleType: scaleUnit,
row,
$rowIndex,
rowIndex,
_rowIndex
}
let cbVNs: VNode[] = []
if ($xeGantt.renderGanttTaskBarContent) {
cbVNs = $xeGantt.renderGanttTaskBarContent(ctParams, {
$gantt: $xeGantt,
$table: $xeTable,
rowid
})
} else {
const isMilestone = !!(hasEnableConf(taskBarMilestoneConfig, taskBarMilestoneOpts) && hasMilestoneTask(renderTaskType))
const isSubview = !!(hasEnableConf(taskBarSubviewConfig, taskBarSubviewOpts) && hasSubviewTask(renderTaskType))
if (contentMethod) {
title = getStringValue(contentMethod({ row, title, scaleType: scaleUnit }))
}
const ctOns: {
onMouseover?: any
onMouseleave?: any
} = {}
if (showTooltip) {
ctOns.onMouseover = (evnt: MouseEvent) => {
const { dragBarRow } = ganttInternalData
const ttParams = Object.assign({ $event: evnt }, ctParams)
if (!dragBarRow) {
$xeGantt.triggerTaskBarTooltipEvent(evnt, ttParams)
}
$xeGantt.dispatchEvent('task-bar-mouseenter', ttParams, evnt)
}
ctOns.onMouseleave = (evnt: MouseEvent) => {
const { dragBarRow } = ganttInternalData
const ttParams = Object.assign({ $event: evnt }, ctParams)
if (!dragBarRow) {
$xeGantt.handleTaskBarTooltipLeaveEvent(evnt, ttParams)
}
$xeGantt.dispatchEvent('task-bar-mouseleave', ttParams, evnt)
}
}
if (isSubview && treeConfig && rowChildren && rowChildren.length) {
if (isExpandTree) {
if (showOverview) {
cbVNs.push(
h('div', {
key: 'vcso',
class: 'vxe-gantt-view--chart-subview-wrapper is--overview'
}, [
h('div', {
key: rowid,
rowid: rowid,
class: ['vxe-gantt-view--chart-subview-row', {
'is--progress': showProgress,
'is--round': round,
'is--move': moveable
}]
}, [
h('div', {
rowid: rowid,
class: [taskBarOverviewSlot ? 'vxe-gantt-view--chart-subview-custom-bar' : 'vxe-gantt-view--chart-subview-bar', `is--${renderTaskType}`]
}, [
taskBarOverviewSlot
? h('div', {
key: 'cbc',
class: 'vxe-gantt-view--chart-subview-custom-bar-content-wrapper'
}, $xeGantt.callSlot(taskBarOverviewSlot, barParams))
: h('div', {
class: 'vxe-gantt-view--chart-subview-bar-content-wrapper'
}, [
showContent
? h('div', {
class: 'vxe-gantt-view--chart-content'
}, title)
: renderEmptyElement($xeGantt)
])
])
])
])
)
}
} else {
const cbcVNs: VNode[] = []
XEUtils.eachTree(rowChildren, childRow => {
const childBarParams = { $gantt: $xeGantt, row: childRow, scaleType: scaleUnit }
const childBarStyObj = (barStyle ? (isBarRowStyle ? barStyle(childBarParams) : barStyle) : {}) || {}
const { round } = childBarStyObj
const childRowid = $xeTable.getRowid(childRow)
let childTitle = getStringValue(XEUtils.get(childRow, titleField))
const childProgressValue = showProgress ? Math.min(100, Math.max(0, XEUtils.toNumber(XEUtils.get(childRow, progressField)))) : 0
const childRenderTaskType = getTaskType(XEUtils.get(childRow, typeField))
const isChildSubview = !!(hasEnableConf(taskBarSubviewConfig, taskBarSubviewOpts) && hasSubviewTask(childRenderTaskType))
if (isChildSubview) {
return
}
const childVbStyle: VxeComponentStyleType = {}
const childVpStyle: VxeComponentStyleType = {
width: `${childProgressValue || 0}%`
}
if (isBarRowStyle) {
const { bgColor, completedBgColor } = childBarStyObj
if (bgColor) {
childVbStyle.backgroundColor = bgColor
}
if (completedBgColor) {
childVpStyle.backgroundColor = completedBgColor
}
}
const childCtParams = XEUtils.assign({}, ctParams, {
row: childRow,
rowIndex: $xeTable.getRowIndex(childRow),
$rowIndex: $xeTable.getVMRowIndex(childRow),
_rowIndex: $xeTable.getVTRowIndex(childRow)
})
if (contentMethod) {
childTitle = getStringValue(contentMethod({ row: childRow, title: childTitle, scaleType: scaleUnit }))
}
cbcVNs.push(
h('div', {
key: childRowid,
rowid: childRowid,
class: ['vxe-gantt-view--chart-subview-row', `is--${childRenderTaskType}`, {
'is--progress': showProgress,
'is--round': round,
'is--move': moveable,
'row--pending': !!pendingRowFlag && !!pendingRowMaps[childRowid]
}]
}, [
h('div', {
rowid: childRowid,
class: [taskBarSlot ? 'vxe-gantt-view--chart-subview-custom-bar' : 'vxe-gantt-view--chart-subview-bar', `is--${childRenderTaskType}`],
style: subBarStyle ? (XEUtils.isFunction(subBarStyle) ? subBarStyle(childCtParams) : subBarStyle) : undefined,
onClick (evnt: MouseEvent) {
evnt.stopPropagation()
$xeGantt.handleTaskBarClickEvent(evnt, childCtParams)
},
onDblclick (evnt: MouseEvent) {
evnt.stopPropagation()
$xeGantt.handleTaskBarDblclickEvent(evnt, childCtParams)
},
onMousedown (evnt: MouseEvent) {
evnt.stopPropagation()
if ($xeGantt.handleTaskBarMousedownEvent) {
$xeGantt.handleTaskBarMousedownEvent(evnt, childCtParams)
}
}
}, [
taskBarSlot
? h('div', {
key: 'cbc',
class: 'vxe-gantt-view--chart-subview-custom-bar-content-wrapper',
...ctOns
}, $xeGantt.callSlot(taskBarSlot, childCtParams))
: h('div', {
class: 'vxe-gantt-view--chart-subview-bar-content-wrapper',
...ctOns
}, [
showProgress
? h('div', {
class: 'vxe-gantt-view--chart-progress',
style: childVpStyle
})
: renderEmptyElement($xeGantt),
showContent
? h('div', {
class: 'vxe-gantt-view--chart-content'
}, childTitle)
: renderEmptyElement($xeGantt)
])
])
])
)
}, { children: childrenField })
cbVNs.push(
h('div', {
key: 'vcsc',
class: 'vxe-gantt-view--chart-subview-wrapper is--inline'
}, cbcVNs)
)
}
} else {
if (taskBarSlot) {
cbVNs.push(
h('div', {
key: 'cbc',
class: 'vxe-gantt-view--chart-custom-bar-content-wrapper',
...ctOns
}, $xeGantt.callSlot(taskBarSlot, barParams))
)
} else if (isMilestone) {
const { icon, iconStatus, iconStyle } = taskBarMilestoneOpts
const tbmParams = { $gantt: $xeGantt, row }
cbVNs.push(
h('div', {
key: 'vcm',
class: 'vxe-gantt-view--chart-milestone-wrapper',
...ctOns
}, [
h('div', {
class: ['vxe-gantt-view--chart-milestone-icon', iconStatus ? `theme--${XEUtils.isFunction(iconStatus) ? iconStatus(tbmParams) : iconStatus}` : ''],
style: iconStyle ? Object.assign({}, XEUtils.isFunction(iconStyle) ? iconStyle(tbmParams) : iconStyle) : undefined
}, [
h('i', {
class: (icon ? (XEUtils.isFunction(icon) ? icon(tbmParams) : icon) : '') || getIcon().GANTT_VIEW_TASK_MILESTONE
})
]),
showContent
? h('div', {
class: 'vxe-gantt-view--chart-milestone-content'
}, title)
: renderEmptyElement($xeGantt)
])
)
} else {
cbVNs.push(
h('div', {
key: 'vbc',
class: 'vxe-gantt-view--chart-bar-content-wrapper',
...ctOns
}, [
showProgress
? h('div', {
class: 'vxe-gantt-view--chart-progress',
style: vpStyle
})
: renderEmptyElement($xeGantt),
showContent
? h('div', {
class: 'vxe-gantt-view--chart-content'
}, title)
: renderEmptyElement($xeGantt)
])
)
}
}
}
return h('div', {
key: rowid,
rowid,
class: ['vxe-gantt-view--chart-row', `is--${renderTaskType}`, {
'is--progress': showProgress,
'row--pending': !!pendingRowFlag && !!pendingRowMaps[rowid],
'is--round': round,
'is--move': moveable
}],
style: {
height: `${cellHeight}px`
},
onDragstart (evnt) {
if (ganttInternalData.dragBarRow) {
evnt.preventDefault()
}
},
onContextmenu (evnt) {
$xeGantt.handleTaskBarContextmenuEvent(evnt, ctParams)
}
}, [
h('div', {
class: [taskBarSlot ? 'vxe-gantt-view--chart-custom-bar' : 'vxe-gantt-view--chart-bar', `is--${renderTaskType}`, {
'is--active': activeBarRowid === rowid,
'active--link': activeLink && (rowid === `${activeLink.from}` || rowid === `${activeLink.to}`)
}],
style: vbStyle,
rowid,
onClick (evnt: MouseEvent) {
$xeGantt.handleTaskBarClickEvent(evnt, barParams)
},
onDblclick (evnt: MouseEvent) {
$xeGantt.handleTaskBarDblclickEvent(evnt, barParams)
},
onMousedown (evnt: MouseEvent) {
if ($xeGantt.handleTaskBarMousedownEvent) {
$xeGantt.handleTaskBarMousedownEvent(evnt, barParams)
}
}
}, cbVNs)
])
}
const renderTaskRows = ($xeTable: VxeTableConstructor & VxeTableMethods & VxeTablePrivateMethods, tableData: any[]) => {
const tableProps = $xeTable.props
const { treeConfig } = tableProps
const tableReactData = $xeTable.reactData
const { treeExpandedFlag } = tableReactData
const tableInternalData = $xeTable.internalData
const { fullAllDataRowIdData, treeExpandedMaps } = tableInternalData
const { computeTreeOpts } = $xeTable.getComputeMaps()
const treeOpts = computeTreeOpts.value
const { transform } = treeOpts
const childrenField = treeOpts.children || treeOpts.childrenField
const { scrollYLoad } = ganttViewReactData
const trVNs: VNode[] = []
tableData.forEach((row, $rowIndex) => {
const rowid = $xeTable.getRowid(row)
const rowRest = fullAllDataRowIdData[rowid] || {}
let rowIndex = $rowIndex
let _rowIndex = -1
if (rowRest) {
rowIndex = rowRest.index
_rowIndex = rowRest._index
}
let isExpandTree = false
let rowChildren: any[] = []
if (treeConfig) {
rowChildren = row[childrenField]
isExpandTree = !!treeExpandedFlag && rowChildren && rowChildren.length > 0 && !!treeExpandedMaps[rowid]
}
trVNs.push(renderTaskBar($xeTable, row, rowid, rowIndex, $rowIndex, _rowIndex, rowChildren, isExpandTree))
// 如果是树形表格
if (treeConfig && isExpandTree && !scrollYLoad && !transform) {
trVNs.push(...renderTaskRows($xeTable, rowChildren))
}
})
return trVNs
}
const renderVN = () => {
const $xeTable = ganttViewInternalData.xeTable
const { slots: ganttSlots } = ganttContext
const { dragLinkFromStore } = ganttReactData
const { tableData } = ganttViewReactData
const taskLinkOpts = computeTaskLinkOpts.value
const taskBarOpts = computeTaskBarOpts.value
const nowLineLeft = computeNowLineLeft.value
const { isCurrent, isHover } = taskLinkOpts
const { linkCreatable } = taskBarOpts
const taskNowLineSlot = ganttSlots.taskNowLine || ganttSlots['task-now-line']
return h('div', {
ref: refElem,
class: ['vxe-gantt-view--chart-wrapper', {
'is--cl-drag': dragLinkFromStore.rowid
}]
}, [
nowLineLeft > 0
? h('div', {
ref: refNowLineElem,
class: 'vxe-gantt-view--chart-now-line',
style: {
left: nowLineLeft + 'px'
}
}, taskNowLineSlot ? taskNowLineSlot({}) : [])
: renderEmptyElement($xeGantt),
$xeGantt.renderGanttTaskChartBefores
? h('div', {
ref: refChartBeforeWrapperElem,
class: ['vxe-gantt-view--chart-before-wrapper', {
'link--current': isCurrent,
'link--hover': isHover
}]
}, $xeTable && isEnableConf(taskLinkOpts) ? $xeGantt.renderGanttTaskChartBefores() : [])
: renderEmptyElement($xeGantt),
h('div', {
ref: refTaskWrapperElem,
class: ['vxe-gantt-view--chart-task-wrapper', {
'link--current': isCurrent,
'link--create': linkCreatable
}]
}, $xeTable ? renderTaskRows($xeTable, tableData) : []),
$xeGantt.renderGanttTaskChartAfters
? h('div', {
ref: refChartAfterWrapperElem,
class: 'vxe-gantt-view--chart-after-wrapper'
}, $xeTable && isEnableConf(taskLinkOpts) ? $xeGantt.renderGanttTaskChartAfters() : [])
: renderEmptyElement($xeGantt)
])
}
onMounted(() => {
const { elemStore } = ganttViewInternalData
const prefix = 'main-chart-'
elemStore[`${prefix}now-line`] = refNowLineElem
elemStore[`${prefix}task-wrapper`] = refTaskWrapperElem
elemStore[`${prefix}before-wrapper`] = refChartBeforeWrapperElem
elemStore[`${prefix}after-wrapper`] = refChartAfterWrapperElem
})
onUnmounted(() => {
const { elemStore } = ganttViewInternalData
const prefix = 'main-chart-'
elemStore[`${prefix}now-line`] = null
elemStore[`${prefix}task-wrapper`] = null
elemStore[`${prefix}before-wrapper`] = null
elemStore[`${prefix}after-wrapper`] = null
})
return renderVN
}
})