|
|
|
|
@ -16,8 +16,12 @@
|
|
|
|
|
<div
|
|
|
|
|
v-for="(plan, index) in activePreviewDevice?.plans ?? []"
|
|
|
|
|
:key="`${activePreviewDevice?.deviceId}-${plan.taskDetailId}-${index}`"
|
|
|
|
|
class="schedule-plan-item"
|
|
|
|
|
:class="['schedule-plan-item', { 'schedule-plan-item-active': isPlanInProgress(plan) }]"
|
|
|
|
|
>
|
|
|
|
|
<div class="schedule-plan-item-head">
|
|
|
|
|
<span class="schedule-plan-item-title">{{ plan.productCode ?? '-' }} / {{ plan.productName ?? '-' }}</span>
|
|
|
|
|
<el-tag v-if="isPlanInProgress(plan)" type="success" size="small">进行中</el-tag>
|
|
|
|
|
</div>
|
|
|
|
|
<div>任务明细ID:{{ plan.taskDetailId ?? '-' }}</div>
|
|
|
|
|
<div>计划数量:{{ plan.planNumber ?? '-' }}</div>
|
|
|
|
|
<div>交货日期:{{ plan.deliveryDateStr ?? '-' }}</div>
|
|
|
|
|
@ -227,18 +231,134 @@ const formatGridDateText = (value: unknown) => {
|
|
|
|
|
if (!date.isValid()) return '-'
|
|
|
|
|
return date.format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
}
|
|
|
|
|
const formatTooltipDateTime = (value: unknown) => {
|
|
|
|
|
const date = dayjs(value)
|
|
|
|
|
if (!date.isValid()) return '-'
|
|
|
|
|
return date.format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
}
|
|
|
|
|
const getDeviceTaskSummary = (task: any) => {
|
|
|
|
|
if (!task) {
|
|
|
|
|
return {
|
|
|
|
|
planCount: 0,
|
|
|
|
|
totalPlanNumber: 0,
|
|
|
|
|
earliestStart: '-',
|
|
|
|
|
latestEnd: '-'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const childTaskIds = gantt.getChildren(task.id)
|
|
|
|
|
const childTasks = (Array.isArray(childTaskIds) ? childTaskIds : [])
|
|
|
|
|
.map((childId: string | number) => gantt.getTask(childId))
|
|
|
|
|
.filter((child: any) => child?._planData)
|
|
|
|
|
if (!childTasks.length) {
|
|
|
|
|
return {
|
|
|
|
|
planCount: 0,
|
|
|
|
|
totalPlanNumber: 0,
|
|
|
|
|
earliestStart: '-',
|
|
|
|
|
latestEnd: '-'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const starts = childTasks.map((child: any) => dayjs(child.start_date).valueOf()).filter((val: number) => Number.isFinite(val))
|
|
|
|
|
const ends = childTasks.map((child: any) => dayjs(child.end_date).valueOf()).filter((val: number) => Number.isFinite(val))
|
|
|
|
|
const totalPlanNumber = childTasks.reduce((sum: number, child: any) => sum + Number(child?._planData?.planNumber ?? 0), 0)
|
|
|
|
|
return {
|
|
|
|
|
planCount: childTasks.length,
|
|
|
|
|
totalPlanNumber,
|
|
|
|
|
earliestStart: starts.length ? formatTooltipDateTime(Math.min(...starts)) : '-',
|
|
|
|
|
latestEnd: ends.length ? formatTooltipDateTime(Math.max(...ends)) : '-'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const buildTaskTooltipHtml = (task: any, start?: Date, end?: Date) => {
|
|
|
|
|
const plan = task?._planData
|
|
|
|
|
if (plan) {
|
|
|
|
|
return `
|
|
|
|
|
<div><b>任务明细</b></div>
|
|
|
|
|
<div>任务单:${plan.taskCode ?? '-'}</div>
|
|
|
|
|
<div>产品:${plan.productCode ?? '-'} / ${plan.productName ?? '-'}</div>
|
|
|
|
|
<div>明细ID:${plan.taskDetailId ?? '-'}</div>
|
|
|
|
|
<div>计划数量:${plan.planNumber ?? '-'}</div>
|
|
|
|
|
<div>开始:${formatTooltipDateTime(start ?? task?.start_date)}</div>
|
|
|
|
|
<div>结束:${formatTooltipDateTime(end ?? task?.end_date)}</div>
|
|
|
|
|
<div>最晚开工:${formatTooltipDateTime(plan.latestStartTimeStr)}</div>
|
|
|
|
|
`
|
|
|
|
|
}
|
|
|
|
|
const device = task?._deviceData
|
|
|
|
|
const summary = getDeviceTaskSummary(task)
|
|
|
|
|
return `
|
|
|
|
|
<div><b>汇总</b></div>
|
|
|
|
|
<div>设备:${device?.deviceName ?? '-'}</div>
|
|
|
|
|
<div>任务明细条数:${summary.planCount}</div>
|
|
|
|
|
<div>计划总数:${summary.totalPlanNumber}</div>
|
|
|
|
|
<div>最早计划开始:${summary.earliestStart}</div>
|
|
|
|
|
<div>最晚计划结束:${summary.latestEnd}</div>
|
|
|
|
|
`
|
|
|
|
|
}
|
|
|
|
|
const getTaskByTooltipNode = (node: HTMLElement) => {
|
|
|
|
|
const holder =
|
|
|
|
|
node.closest('[task_id]') ||
|
|
|
|
|
node.closest('[data-task-id]') ||
|
|
|
|
|
node.closest('.gantt_task_line') ||
|
|
|
|
|
node
|
|
|
|
|
const taskId = holder?.getAttribute('task_id') || holder?.getAttribute('data-task-id')
|
|
|
|
|
if (!taskId) return undefined
|
|
|
|
|
try {
|
|
|
|
|
return gantt.getTask(taskId)
|
|
|
|
|
} catch {
|
|
|
|
|
return undefined
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const isPlanInProgress = (plan: any) => {
|
|
|
|
|
const start = dayjs(plan?.planStartTimeStr)
|
|
|
|
|
const end = dayjs(plan?.planEndTimeStr)
|
|
|
|
|
if (!start.isValid() || !end.isValid()) return false
|
|
|
|
|
const now = dayjs()
|
|
|
|
|
return (now.isAfter(start) || now.isSame(start)) && (now.isBefore(end) || now.isSame(end))
|
|
|
|
|
}
|
|
|
|
|
const initTaskTooltips = () => {
|
|
|
|
|
const tooltipsExt = (gantt.ext as any)?.tooltips
|
|
|
|
|
const tooltip = tooltipsExt?.tooltip
|
|
|
|
|
if (!tooltipsExt || !tooltip || !ganttContainerRef.value) return
|
|
|
|
|
const showTooltip = (event: MouseEvent, html: string) => {
|
|
|
|
|
tooltip.setContent(html)
|
|
|
|
|
tooltip.show(event)
|
|
|
|
|
const node = tooltip.getNode?.()
|
|
|
|
|
if (node) {
|
|
|
|
|
node.style.display = 'block'
|
|
|
|
|
node.style.visibility = 'visible'
|
|
|
|
|
node.style.opacity = '1'
|
|
|
|
|
node.style.zIndex = '10000'
|
|
|
|
|
node.style.pointerEvents = 'none'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
tooltipsExt.detach('.gantt_task_line')
|
|
|
|
|
tooltipsExt.detach('.gantt_task_content')
|
|
|
|
|
tooltipsExt.attach({
|
|
|
|
|
selector: '.gantt_task_line,.gantt_task_content',
|
|
|
|
|
onmouseenter: (event: MouseEvent, node: HTMLElement) => {
|
|
|
|
|
const task = getTaskByTooltipNode(node)
|
|
|
|
|
if (!task) return
|
|
|
|
|
showTooltip(event, buildTaskTooltipHtml(task))
|
|
|
|
|
},
|
|
|
|
|
onmousemove: (event: MouseEvent, node: HTMLElement) => {
|
|
|
|
|
const task = getTaskByTooltipNode(node)
|
|
|
|
|
if (!task) return
|
|
|
|
|
showTooltip(event, buildTaskTooltipHtml(task))
|
|
|
|
|
},
|
|
|
|
|
onmouseleave: () => {
|
|
|
|
|
tooltip.hide()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const syncPlanTimeFromTask = (task: any) => {
|
|
|
|
|
if (!task?._planData) return
|
|
|
|
|
if (String(task?._planData?.sourceType ?? '').toUpperCase() !== 'CURRENT') return
|
|
|
|
|
const start = dayjs(task.start_date)
|
|
|
|
|
if (!start.isValid()) return
|
|
|
|
|
const duration = Number(task.duration) > 0 ? Number(task.duration) : 1
|
|
|
|
|
const nextEnd = start.startOf('day').add(duration - 1, 'day').endOf('day')
|
|
|
|
|
task.end_date = nextEnd.toDate()
|
|
|
|
|
const end = dayjs(task.end_date)
|
|
|
|
|
if (!start.isValid() || !end.isValid()) return
|
|
|
|
|
const duration = Math.max(Number(task.duration) || end.diff(start, 'day') + 1, 1)
|
|
|
|
|
task.duration = duration
|
|
|
|
|
task._planData.planStartTimeStr = start.format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
task._planData.planEndTimeStr = nextEnd.format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
task._planData.planEndTimeStr = end.format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
task._planData.scheduleDays = duration
|
|
|
|
|
activePreviewDevice.value = task._deviceData
|
|
|
|
|
}
|
|
|
|
|
@ -302,19 +422,9 @@ const normalizeDeviceChildren = (deviceTaskId: string | number, priorityTaskId?:
|
|
|
|
|
if (priorityTaskId !== undefined && String(b.id) === String(priorityTaskId)) return 1
|
|
|
|
|
return 0
|
|
|
|
|
})
|
|
|
|
|
let previousEnd: dayjs.Dayjs | null = null
|
|
|
|
|
childTasks.forEach((item: any) => {
|
|
|
|
|
const start = dayjs(item.start_date).startOf('day')
|
|
|
|
|
const end = dayjs(item.end_date).endOf('day')
|
|
|
|
|
const duration = Math.max(end.diff(start, 'day') + 1, 1)
|
|
|
|
|
const nextStart = previousEnd && !start.isAfter(previousEnd, 'day') ? previousEnd.add(1, 'day').startOf('day') : start
|
|
|
|
|
const nextEnd = nextStart.add(duration - 1, 'day').endOf('day')
|
|
|
|
|
item.start_date = nextStart.toDate()
|
|
|
|
|
item.end_date = nextEnd.toDate()
|
|
|
|
|
item.duration = duration
|
|
|
|
|
syncPlanTimeFromTask(item)
|
|
|
|
|
gantt.updateTask(item.id)
|
|
|
|
|
previousEnd = nextEnd
|
|
|
|
|
})
|
|
|
|
|
const fakePlanTask = { _planData: true, parent: deviceTaskId }
|
|
|
|
|
syncDeviceTaskRangeFromChildren(fakePlanTask)
|
|
|
|
|
@ -420,8 +530,8 @@ const refreshTimelineRangeByTasks = () => {
|
|
|
|
|
})
|
|
|
|
|
if (!Number.isFinite(minStart) || !Number.isFinite(maxEnd)) return
|
|
|
|
|
|
|
|
|
|
const nextStartDate = dayjs(minStart).startOf('day').subtract(1, 'day').toDate()
|
|
|
|
|
const nextEndDate = dayjs(maxEnd).endOf('day').add(1, 'day').toDate()
|
|
|
|
|
const nextStartDate = dayjs(minStart).startOf('day').toDate()
|
|
|
|
|
const nextEndDate = dayjs(maxEnd).endOf('day').toDate()
|
|
|
|
|
const currentStart = dayjs(gantt.config.start_date).valueOf()
|
|
|
|
|
const currentEnd = dayjs(gantt.config.end_date).valueOf()
|
|
|
|
|
const nextStart = dayjs(nextStartDate).valueOf()
|
|
|
|
|
@ -496,23 +606,7 @@ const initGanttPreview = () => {
|
|
|
|
|
{ unit: 'day', step: 1, format: (date) => dayjs(date).format('MM-DD') }
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
gantt.templates.tooltip_text = (start, end, task: any) => {
|
|
|
|
|
const plan = task._planData
|
|
|
|
|
if (plan) {
|
|
|
|
|
return `
|
|
|
|
|
<div>产品名称:${plan.productName ?? '-'}</div>
|
|
|
|
|
<div>计划数量:${plan.planNumber ?? '-'}</div>
|
|
|
|
|
<div>开始:${dayjs(start).format('YYYY-MM-DD HH:mm:ss')}</div>
|
|
|
|
|
<div>结束:${dayjs(end).format('YYYY-MM-DD HH:mm:ss')}</div>
|
|
|
|
|
`
|
|
|
|
|
}
|
|
|
|
|
const device = task._deviceData
|
|
|
|
|
const plans = device?.plans ?? []
|
|
|
|
|
return `
|
|
|
|
|
<div>设备:${device?.deviceName ?? '-'}</div>
|
|
|
|
|
<div>计划条数:${plans.length}</div>
|
|
|
|
|
`
|
|
|
|
|
}
|
|
|
|
|
gantt.templates.tooltip_text = (start, end, task: any) => buildTaskTooltipHtml(task, start, end)
|
|
|
|
|
|
|
|
|
|
gantt.templates.task_class = (_start, _end, task: any) => {
|
|
|
|
|
if (!task?._planData) return ''
|
|
|
|
|
@ -526,6 +620,7 @@ const initGanttPreview = () => {
|
|
|
|
|
|
|
|
|
|
const ganttData = buildPreviewGanttData(previewScheduleList.value)
|
|
|
|
|
gantt.parse(ganttData)
|
|
|
|
|
initTaskTooltips()
|
|
|
|
|
|
|
|
|
|
if (ganttData.data.length) {
|
|
|
|
|
activePreviewDevice.value = ganttData.data[0]._deviceData
|
|
|
|
|
@ -763,6 +858,24 @@ onBeforeUnmount(() => {
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
background: var(--el-fill-color-light);
|
|
|
|
|
}
|
|
|
|
|
.schedule-plan-item-head {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
}
|
|
|
|
|
.schedule-plan-item-title {
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: var(--el-text-color-primary);
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
.schedule-plan-item-active {
|
|
|
|
|
border: 1px solid var(--el-color-success-light-5);
|
|
|
|
|
background: var(--el-color-success-light-9);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.schedule-plan-item + .schedule-plan-item {
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
@ -800,4 +913,10 @@ onBeforeUnmount(() => {
|
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task-history .gantt_task_content) {
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
}
|
|
|
|
|
:deep(.gantt_tooltip) {
|
|
|
|
|
z-index: 5000 !important;
|
|
|
|
|
}
|
|
|
|
|
.schedule-custom-tooltip {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|