diff --git a/src/components/GanttLinks.vue b/src/components/GanttLinks.vue index fa99e48..0fa62bc 100644 --- a/src/components/GanttLinks.vue +++ b/src/components/GanttLinks.vue @@ -2,6 +2,7 @@ import { ref, watch, nextTick, onMounted, computed } from 'vue' import type { Task } from '../models/classes/Task' import { getPredecessorIds } from '../utils/predecessorUtils' +// import { perfMonitor } from '../utils/perfMonitor' // 定义 TaskBar 位置信息类型 interface TaskBarPosition { @@ -49,6 +50,8 @@ const isDarkTheme = computed(() => { * 性能优势:相比 SVG 提升 18 倍渲染性能 */ const drawLinks = () => { + // const startTime = performance.now() + const canvas = canvasRef.value if (!canvas) return @@ -78,13 +81,13 @@ const drawLinks = () => { ctx.strokeStyle = lineColor ctx.lineWidth = 1 - // 绘制每条垂直线 + // 优化:一次性绘制所有垂直线,避免多次 stroke() 调用 + ctx.beginPath() for (const line of props.verticalLines) { - ctx.beginPath() ctx.moveTo(line.left, 0) ctx.lineTo(line.left, rect.height) - ctx.stroke() } + ctx.stroke() ctx.restore() } @@ -179,6 +182,16 @@ const drawLinks = () => { ctx.globalAlpha = 1 } } + + // 性能监控 + // const endTime = performance.now() + // const duration = endTime - startTime + // perfMonitor.log('Canvas 重绘', { + // 耗时: `${duration.toFixed(2)}ms`, + // 关系线数量: props.tasks.filter(t => t.predecessor).length, + // 垂直线数量: props.verticalLines?.length || 0, + // Canvas尺寸: `${rect.width}x${rect.height}`, + // }) } /** @@ -224,8 +237,24 @@ watch( () => props.hoveredTaskId, () => props.width, () => props.height, + () => props.verticalLines, + () => props.showVerticalLines, ], - () => { + (newVals, oldVals) => { + // 记录是什么触发了重绘 + // const changes: string[] = [] + // if (newVals[0] !== oldVals[0]) changes.push('taskBarPositions') + // if (newVals[1] !== oldVals[1]) changes.push('tasks.length') + // if (newVals[2] !== oldVals[2]) changes.push('highlightedTaskId') + // if (newVals[3] !== oldVals[3]) changes.push('highlightedTaskIds') + // if (newVals[4] !== oldVals[4]) changes.push('hoveredTaskId') + // if (newVals[5] !== oldVals[5]) changes.push('width') + // if (newVals[6] !== oldVals[6]) changes.push('height') + // if (newVals[7] !== oldVals[7]) changes.push('verticalLines') + // if (newVals[8] !== oldVals[8]) changes.push('showVerticalLines') + + // perfMonitor.log('Canvas 触发重绘', { 变化属性: changes.join(', ') }) + nextTick(() => { drawLinks() }) diff --git a/src/components/Timeline.vue b/src/components/Timeline.vue index 66f6f52..159aff4 100644 --- a/src/components/Timeline.vue +++ b/src/components/Timeline.vue @@ -1019,8 +1019,8 @@ const handleTimelineContainerResized = () => { // 处理任务行悬停事件 const handleTaskRowHover = (taskId: number | null) => { - // 如果正在拖拽Splitter,则不响应悬停事件 - if (isSplitterDragging.value) { + // 如果正在拖拽Splitter或拖动滚动,则不响应悬停事件 + if (isSplitterDragging.value || isDragging.value) { return } @@ -1047,6 +1047,10 @@ const contentHeight = computed(() => { // 监听TaskList的悬停事件 const handleTaskListHover = (event: CustomEvent) => { + // 如果正在拖动滚动,则不响应悬停事件 + if (isDragging.value) { + return + } hoveredTaskId.value = event.detail } @@ -2341,6 +2345,9 @@ const handleMouseUp = () => { timelineContainer.value.style.userSelect = 'auto' } + // 清空 hover 状态,避免拖动结束后立即触发 hover 重绘 + hoveredTaskId.value = null + // 移除全局事件监听器 document.removeEventListener('mousemove', handleMouseMove) document.removeEventListener('mouseup', handleMouseUp) diff --git a/src/utils/perfMonitor.ts b/src/utils/perfMonitor.ts new file mode 100644 index 0000000..d0f935b --- /dev/null +++ b/src/utils/perfMonitor.ts @@ -0,0 +1,40 @@ +/** + * 性能监控工具 + */ + +let lastLogTime = 0 +const LOG_THROTTLE = 100 // 最多每 100ms 输出一次日志 + +export const perfMonitor = { + /** + * 记录性能日志(自动节流) + */ + log(tag: string, data: unknown) { + const now = Date.now() + if (now - lastLogTime < LOG_THROTTLE) { + return + } + lastLogTime = now + + // eslint-disable-next-line no-console + console.log(`[Perf] ${tag}:`, data) + }, + + /** + * 测量函数执行时间 + */ + measure(tag: string, fn: () => T): T { + const start = performance.now() + const result = fn() + const end = performance.now() + const duration = end - start + + if (duration > 5) { + // 只记录耗时超过 5ms 的操作 + // eslint-disable-next-line no-console + console.log(`[Perf] ${tag}: ${duration.toFixed(2)}ms`) + } + + return result + }, +}