diff --git a/src/views/mes/tasksummary/components/TaskSchedulePreviewDialog.vue b/src/views/mes/tasksummary/components/TaskSchedulePreviewDialog.vue index 6f61afc7..bd265092 100644 --- a/src/views/mes/tasksummary/components/TaskSchedulePreviewDialog.vue +++ b/src/views/mes/tasksummary/components/TaskSchedulePreviewDialog.vue @@ -16,8 +16,12 @@
+
+ {{ plan.productCode ?? '-' }} / {{ plan.productName ?? '-' }} + 进行中 +
任务明细ID:{{ plan.taskDetailId ?? '-' }}
计划数量:{{ plan.planNumber ?? '-' }}
交货日期:{{ plan.deliveryDateStr ?? '-' }}
@@ -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 ` +
任务明细
+
任务单:${plan.taskCode ?? '-'}
+
产品:${plan.productCode ?? '-'} / ${plan.productName ?? '-'}
+
明细ID:${plan.taskDetailId ?? '-'}
+
计划数量:${plan.planNumber ?? '-'}
+
开始:${formatTooltipDateTime(start ?? task?.start_date)}
+
结束:${formatTooltipDateTime(end ?? task?.end_date)}
+
最晚开工:${formatTooltipDateTime(plan.latestStartTimeStr)}
+ ` + } + const device = task?._deviceData + const summary = getDeviceTaskSummary(task) + return ` +
汇总
+
设备:${device?.deviceName ?? '-'}
+
任务明细条数:${summary.planCount}
+
计划总数:${summary.totalPlanNumber}
+
最早计划开始:${summary.earliestStart}
+
最晚计划结束:${summary.latestEnd}
+ ` +} +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 ` -
产品名称:${plan.productName ?? '-'}
-
计划数量:${plan.planNumber ?? '-'}
-
开始:${dayjs(start).format('YYYY-MM-DD HH:mm:ss')}
-
结束:${dayjs(end).format('YYYY-MM-DD HH:mm:ss')}
- ` - } - const device = task._deviceData - const plans = device?.plans ?? [] - return ` -
设备:${device?.deviceName ?? '-'}
-
计划条数:${plans.length}
- ` - } + 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; +}