去除/整理性能调试相关代码

This commit is contained in:
qiuchengw
2025-12-03 09:04:23 +08:00
parent 50a4df70f0
commit 6cd243e1fd
3 changed files with 230 additions and 189 deletions
+6 -32
View File
@@ -17,12 +17,6 @@ const props = withDefaults(defineProps<Props>(), {
const canvasRef = ref<HTMLCanvasElement | null>(null)
// 性能监控
const ENABLE_PERF_MONITOR = false // 生产环境关闭
let drawCount = 0
let drawTotalTime = 0
let lastReportTime = 0
// 缓存 canvas 上下文和尺寸信息,避免重复初始化
let cachedCtx: CanvasRenderingContext2D | null = null
let cachedWidth = 0
@@ -75,8 +69,6 @@ const initCanvas = () => {
* @param isValidTarget 是否是合法的连接目标
* @param errorMessage 错误提示消息
*/
// 🔧 调试开关:跳过实际绘制
const DEBUG_SKIP_ACTUAL_DRAW = false
const USE_SIMPLE_LINE = true // true = 使用简单直线替代贝塞尔曲线(性能更好)
const draw = (
@@ -87,14 +79,10 @@ const draw = (
isValidTarget = true,
errorMessage = '',
) => {
// 🔧 调试:跳过实际绘制
if (DEBUG_SKIP_ACTUAL_DRAW) return
const startTime = ENABLE_PERF_MONITOR ? performance.now() : 0
const ctx = initCanvas()
if (!ctx) {
console.warn('[LinkDragGuide] initCanvas returned null')
// eslint-disable-next-line no-console
console.error('[LinkDragGuide] initCanvas returned null')
return
}
@@ -132,8 +120,10 @@ const draw = (
const color = isValidTarget ? '#67c23a' : '#f56c6c'
ctx.strokeStyle = color
ctx.lineWidth = 3
// 🔧 测试:移除虚线,看是否是虚线+贝塞尔曲线导致的性能问题
// ctx.setLineDash([8, 4])
// 虚线绘制也会导致巨大的性能开销,慎用
if (!USE_SIMPLE_LINE) {
ctx.setLineDash([8, 4])
}
ctx.globalAlpha = 0.8
// 绘制直线
@@ -190,22 +180,6 @@ const draw = (
}
ctx.restore()
if (ENABLE_PERF_MONITOR) {
drawCount++
drawTotalTime += performance.now() - startTime
const now = Date.now()
if (now - lastReportTime > 1000) {
const avgTime = drawCount > 0 ? (drawTotalTime / drawCount).toFixed(3) : 0
// eslint-disable-next-line no-console
console.log(`[LinkDragGuide Perf] 绘制次数: ${drawCount}/秒, 平均耗时: ${avgTime}ms`)
drawCount = 0
drawTotalTime = 0
lastReportTime = now
}
}
}
/**
+60 -157
View File
@@ -8,6 +8,7 @@ import LinkDragGuide from './LinkDragGuide.vue'
import { useI18n } from '../composables/useI18n'
import type { TaskBarConfig } from '../models/configs/TaskBarConfig'
import { getPredecessorIds } from '../utils/predecessorUtils'
import { perfMonitor } from '../utils/perfMonitor'
import type { Task } from '../models/classes/Task'
import type { Milestone } from '../models/classes/Milestone'
import type { TimelineConfig } from '../models/configs/TimelineConfig'
@@ -648,9 +649,6 @@ const setHighlightTask = (taskId: number) => {
}
// ==================== 连接线拖拽状态管理 ====================
// 🔧 调试开关:禁用传递给 TaskBar 的响应式 props 以测试 Vue 渲染性能影响
const DISABLE_REACTIVE_PROPS_TO_TASKBAR = false // 设为 false 以启用正常的响应式 propslink anchor 需要此功能)
const dragLinkMode = ref<'predecessor' | 'successor' | null>(null) // 当前拖拽模式
const linkDragSourceTask = shallowRef<Task | null>(null) // 拖拽起始任务(使用 shallowRef 优化性能)
const linkDragTargetTask = shallowRef<Task | null>(null) // 当前悬停的目标任务(使用 shallowRef 优化性能)
@@ -662,71 +660,11 @@ const linkAutoScrollInterval = ref<number | null>(null) // 自动滚动定时器
let nonReactiveIsValidTarget = false
let nonReactiveErrorMessage = ''
// 🔧 调试用:用于传递给 TaskBar 的静态替代值(避免触发 Vue 更新)
const staticDragLinkMode = computed(() =>
DISABLE_REACTIVE_PROPS_TO_TASKBAR ? null : dragLinkMode.value,
)
const staticLinkDragSourceTaskId = computed(() =>
DISABLE_REACTIVE_PROPS_TO_TASKBAR ? null : linkDragSourceTask.value?.id,
)
const staticLinkDragTargetTaskId = computed(() =>
DISABLE_REACTIVE_PROPS_TO_TASKBAR ? null : linkDragTargetTask.value?.id,
)
const staticIsValidLinkTarget = computed(() =>
DISABLE_REACTIVE_PROPS_TO_TASKBAR ? false : isValidLinkTarget.value,
)
// 任务查找缓存 Map(优化性能,避免每次都遍历数组)
const taskIdMap = new Map<number, Task>()
// 性能监控开关(开发调试用)
const ENABLE_PERF_MONITOR = true
const perfStats = {
coordUpdateCount: 0,
coordUpdateTotalTime: 0,
targetDetectCount: 0,
targetDetectTotalTime: 0,
lastReportTime: 0,
}
// 帧时间监控(诊断主线程阻塞)
let frameMonitorId: number | null = null
let lastFrameTime = 0
let longFrameCount = 0
let frameCount = 0
const startFrameMonitor = () => {
lastFrameTime = performance.now()
longFrameCount = 0
frameCount = 0
const checkFrame = () => {
const now = performance.now()
const frameTime = now - lastFrameTime
frameCount++
// 超过 30ms 的帧被认为是"长帧"(正常应该是 ~16ms
if (frameTime > 30) {
longFrameCount++
// eslint-disable-next-line no-console
console.warn(`[Frame Monitor] 长帧检测: ${frameTime.toFixed(1)}ms`)
}
lastFrameTime = now
frameMonitorId = requestAnimationFrame(checkFrame)
}
frameMonitorId = requestAnimationFrame(checkFrame)
}
const stopFrameMonitor = () => {
if (frameMonitorId !== null) {
cancelAnimationFrame(frameMonitorId)
frameMonitorId = null
// eslint-disable-next-line no-console
console.log(`[Frame Monitor] 统计: ${frameCount}帧, 长帧数: ${longFrameCount} (${(longFrameCount / frameCount * 100).toFixed(1)}%)`)
}
}
// 开始连接线拖拽
const handleLinkDragStart = (event: { task: Task; type: 'predecessor' | 'successor'; mouseEvent: MouseEvent }) => {
@@ -734,7 +672,7 @@ const handleLinkDragStart = (event: { task: Task; type: 'predecessor' | 'success
linkDragSourceTask.value = event.task
// 启动帧监控
startFrameMonitor()
perfMonitor.startFrameMonitor()
// 初始化鼠标坐标(使用非响应式版本)
updateLinkDragCoordinatesNonReactive(event.mouseEvent.clientX, event.mouseEvent.clientY)
@@ -781,11 +719,23 @@ const processLinkDragFrame = () => {
// 🔧 调试:逐步启用各操作
if (DEBUG_ENABLE_COORD_UPDATE) {
updateLinkDragCoordinatesNonReactive(pendingMouseX, pendingMouseY)
if (ENABLE_PERF_MONITOR) {
const startTime = performance.now()
updateLinkDragCoordinatesNonReactive(pendingMouseX, pendingMouseY)
perfMonitor.recordLinkDragCoordUpdate(performance.now() - startTime)
} else {
updateLinkDragCoordinatesNonReactive(pendingMouseX, pendingMouseY)
}
}
if (DEBUG_ENABLE_TARGET_DETECT) {
detectLinkTargetNonReactive(pendingMouseX, pendingMouseY)
if (ENABLE_PERF_MONITOR) {
const startTime = performance.now()
detectLinkTargetNonReactive(pendingMouseX, pendingMouseY)
perfMonitor.recordLinkDragTargetDetect(performance.now() - startTime)
} else {
detectLinkTargetNonReactive(pendingMouseX, pendingMouseY)
}
}
if (DEBUG_ENABLE_CANVAS_DRAW) {
@@ -824,7 +774,7 @@ const handleGlobalMouseUp = () => {
if (!dragLinkMode.value) return
// 停止帧监控
stopFrameMonitor()
perfMonitor.stopFrameMonitor()
// 🚀 取消待处理的 RAF
if (linkDragRafId !== null) {
@@ -859,22 +809,14 @@ let bodyRectCacheTime = 0
const BODY_RECT_CACHE_DURATION = 200 // 200ms 缓存(优化:增加缓存时间)
let bodyRectInvalidated = false // 缓存失效标记(滚动时失效)
// 🚀 非响应式坐标更新(完全绕过 Vue 响应式系统
const updateLinkDragCoordinatesNonReactive = (mouseX: number, mouseY: number) => {
const startTime = ENABLE_PERF_MONITOR ? performance.now() : 0
// 🚀 坐标更新辅助函数(提取重复逻辑
const updateCoordinates = (mouseX: number, mouseY: number): void => {
if (!bodyContentRef.value) {
currentDragX = mouseX
currentDragY = mouseY
if (ENABLE_PERF_MONITOR) {
perfStats.coordUpdateCount++
perfStats.coordUpdateTotalTime += performance.now() - startTime
}
return
}
// 使用缓存的 rect 或获取新的
const now = Date.now()
const shouldRefreshRect =
!cachedBodyRect || bodyRectInvalidated || now - bodyRectCacheTime > BODY_RECT_CACHE_DURATION
@@ -885,36 +827,11 @@ const updateLinkDragCoordinatesNonReactive = (mouseX: number, mouseY: number) =>
}
currentDragX = mouseX - cachedBodyRect.left
currentDragY = mouseY - cachedBodyRect.top
}
if (ENABLE_PERF_MONITOR) {
perfStats.coordUpdateCount++
perfStats.coordUpdateTotalTime += performance.now() - startTime
// 每秒输出一次性能统计
if (now - perfStats.lastReportTime > 1000) {
const avgCoordTime =
perfStats.coordUpdateCount > 0
? (perfStats.coordUpdateTotalTime / perfStats.coordUpdateCount).toFixed(3)
: 0
const avgTargetTime =
perfStats.targetDetectCount > 0
? (perfStats.targetDetectTotalTime / perfStats.targetDetectCount).toFixed(3)
: 0
// eslint-disable-next-line no-console
console.log(
`[LinkDrag Perf] 坐标更新: ${perfStats.coordUpdateCount}次, 平均${avgCoordTime}ms | ` +
`目标检测: ${perfStats.targetDetectCount}次, 平均${avgTargetTime}ms`,
)
// 重置统计
perfStats.coordUpdateCount = 0
perfStats.coordUpdateTotalTime = 0
perfStats.targetDetectCount = 0
perfStats.targetDetectTotalTime = 0
perfStats.lastReportTime = now
}
}
// 🚀 非响应式坐标更新(完全绕过 Vue 响应式系统)
const updateLinkDragCoordinatesNonReactive = (mouseX: number, mouseY: number) => {
updateCoordinates(mouseX, mouseY)
}
// 拖拽过程中更新鼠标位置(🚀 优化:使用统一 RAF 调度)
@@ -964,25 +881,17 @@ const handleLinkDragEnd = (event: { task: Task; type: 'predecessor' | 'successor
const ANCHOR_SIZE = 8 // 触点视觉大小(px
const ANCHOR_TOLERANCE = 4 // 碰撞容差(px),扩大点击区域
// 🚀 非响应式目标检测(完全绕过 Vue 响应式系统
// 缓存当前检测到的目标任务(用于 handleLinkDragEnd
let nonReactiveTargetTask: Task | null = null
const detectLinkTargetNonReactive = (mouseX: number, mouseY: number) => {
const startTime = ENABLE_PERF_MONITOR ? performance.now() : 0
if (!linkDragSourceTask.value || !bodyContentRef.value) return
// 🚀 目标检测辅助函数(提取重复逻辑
const detectTargetTaskId = (mouseX: number, mouseY: number): number | null => {
// 使用缓存的 rect
if (!cachedBodyRect) {
cachedBodyRect = bodyContentRef.value.getBoundingClientRect()
cachedBodyRect = bodyContentRef.value!.getBoundingClientRect()
bodyRectCacheTime = Date.now()
}
const relativeX = mouseX - cachedBodyRect.left
const relativeY = mouseY - cachedBodyRect.top
let foundTaskId: number | null = null
const isPredecessorMode = dragLinkMode.value === 'predecessor'
const halfSize = (ANCHOR_SIZE + ANCHOR_TOLERANCE) / 2
const expandedHalfSize = halfSize + 10
@@ -998,40 +907,27 @@ const detectLinkTargetNonReactive = (mouseX: number, mouseY: number) => {
continue
}
if (isPredecessorMode) {
const anchorX = pos.left + pos.width
if (relativeX < anchorX - expandedHalfSize || relativeX > anchorX + expandedHalfSize) {
continue
}
const anchorY = pos.top + pos.height / 2
if (
relativeX >= anchorX - halfSize &&
relativeX <= anchorX + halfSize &&
relativeY >= anchorY - halfSize &&
relativeY <= anchorY + halfSize
) {
foundTaskId = taskId
break
}
} else {
const anchorX = pos.left
if (relativeX < anchorX - expandedHalfSize || relativeX > anchorX + expandedHalfSize) {
continue
}
const anchorY = pos.top + pos.height / 2
if (
relativeX >= anchorX - halfSize &&
relativeX <= anchorX + halfSize &&
relativeY >= anchorY - halfSize &&
relativeY <= anchorY + halfSize
) {
foundTaskId = taskId
break
}
const anchorX = isPredecessorMode ? pos.left + pos.width : pos.left
if (relativeX < anchorX - expandedHalfSize || relativeX > anchorX + expandedHalfSize) {
continue
}
const anchorY = pos.top + pos.height / 2
if (
relativeX >= anchorX - halfSize &&
relativeX <= anchorX + halfSize &&
relativeY >= anchorY - halfSize &&
relativeY <= anchorY + halfSize
) {
return taskId
}
}
const foundTarget = foundTaskId !== null ? taskIdMap.get(foundTaskId) || null : null
return null
}
// 🚀 更新目标任务的状态
const updateTargetTaskState = (foundTarget: Task | null): void => {
const currentTargetId = nonReactiveTargetTask?.id ?? null
const newTargetId = foundTarget?.id ?? null
@@ -1042,7 +938,6 @@ const detectLinkTargetNonReactive = (mouseX: number, mouseY: number) => {
const validation = validateLink(linkDragSourceTask.value, foundTarget, dragLinkMode.value!)
nonReactiveIsValidTarget = validation.valid
nonReactiveErrorMessage = validation.error || ''
// 🚀 同步更新响应式变量(仅在目标变化时,用于 handleLinkDragEnd
isValidLinkTarget.value = validation.valid
linkDragTargetTask.value = foundTarget
} else {
@@ -1052,11 +947,19 @@ const detectLinkTargetNonReactive = (mouseX: number, mouseY: number) => {
linkDragTargetTask.value = null
}
}
}
if (ENABLE_PERF_MONITOR) {
perfStats.targetDetectCount++
perfStats.targetDetectTotalTime += performance.now() - startTime
}
// 🚀 非响应式目标检测(完全绕过 Vue 响应式系统)
// 缓存当前检测到的目标任务(用于 handleLinkDragEnd
let nonReactiveTargetTask: Task | null = null
const detectLinkTargetNonReactive = (mouseX: number, mouseY: number) => {
if (!linkDragSourceTask.value || !bodyContentRef.value) return
const foundTaskId = detectTargetTaskId(mouseX, mouseY)
const foundTarget = foundTaskId !== null ? taskIdMap.get(foundTaskId) || null : null
updateTargetTaskState(foundTarget)
}
// 验证连接是否有效(返回 { valid: boolean, error?: string }
@@ -4395,13 +4298,13 @@ const handleAddSuccessor = (task: Task) => {
:is-highlighted="highlightedTaskIds.has(task.id)"
:is-primary-highlight="highlightedTaskId === task.id"
:is-in-highlight-mode="isInHighlightMode"
:drag-link-mode="staticDragLinkMode"
:is-link-drag-source="staticLinkDragSourceTaskId === task.id"
:drag-link-mode="dragLinkMode"
:is-link-drag-source="linkDragSourceTask?.id === task.id"
:is-valid-link-target="
staticLinkDragTargetTaskId === task.id && staticIsValidLinkTarget === true
linkDragTargetTask?.id === task.id && isValidLinkTarget === true
"
:is-invalid-link-target="
staticLinkDragTargetTaskId === task.id && staticIsValidLinkTarget === false
linkDragTargetTask?.id === task.id && isValidLinkTarget === false
"
@update:task="updateTask"
@bar-mounted="handleBarMounted"
+164
View File
@@ -33,6 +33,38 @@ const drawStats: DrawStats = {
const MAX_RECENT_DRAWS = 100 // 保留最近 100 次绘制记录
// ==================== 连接线拖拽性能监控 ====================
interface LinkDragStats {
coordUpdateCount: number
coordUpdateTotalTime: number
targetDetectCount: number
targetDetectTotalTime: number
lastReportTime: number
}
const linkDragStats: LinkDragStats = {
coordUpdateCount: 0,
coordUpdateTotalTime: 0,
targetDetectCount: 0,
targetDetectTotalTime: 0,
lastReportTime: 0,
}
// ==================== 帧时间监控 ====================
interface FrameMonitorStats {
frameMonitorId: number | null
lastFrameTime: number
longFrameCount: number
frameCount: number
}
const frameMonitorStats: FrameMonitorStats = {
frameMonitorId: null,
lastFrameTime: 0,
longFrameCount: 0,
frameCount: 0,
}
export const perfMonitor = {
/**
*
@@ -260,4 +292,136 @@ export const perfMonitor = {
)
}, durationMs)
},
// ==================== 连接线拖拽性能监控 API ====================
/**
*
* @param duration
* @returns
*/
recordLinkDragCoordUpdate(duration: number): boolean {
linkDragStats.coordUpdateCount++
linkDragStats.coordUpdateTotalTime += duration
const now = Date.now()
if (now - linkDragStats.lastReportTime > 1000) {
const avgCoordTime =
linkDragStats.coordUpdateCount > 0
? (linkDragStats.coordUpdateTotalTime / linkDragStats.coordUpdateCount).toFixed(3)
: 0
const avgTargetTime =
linkDragStats.targetDetectCount > 0
? (linkDragStats.targetDetectTotalTime / linkDragStats.targetDetectCount).toFixed(3)
: 0
// eslint-disable-next-line no-console
console.log(
`[LinkDrag Perf] 坐标更新: ${linkDragStats.coordUpdateCount}次, 平均${avgCoordTime}ms | ` +
`目标检测: ${linkDragStats.targetDetectCount}次, 平均${avgTargetTime}ms`,
)
this.resetLinkDragStats()
linkDragStats.lastReportTime = now
return true
}
return false
},
/**
*
* @param duration
*/
recordLinkDragTargetDetect(duration: number): void {
linkDragStats.targetDetectCount++
linkDragStats.targetDetectTotalTime += duration
},
/**
* 线
*/
resetLinkDragStats(): void {
linkDragStats.coordUpdateCount = 0
linkDragStats.coordUpdateTotalTime = 0
linkDragStats.targetDetectCount = 0
linkDragStats.targetDetectTotalTime = 0
},
/**
* 线
*/
getLinkDragStats() {
return {
coordUpdateCount: linkDragStats.coordUpdateCount,
coordUpdateTotalTime: linkDragStats.coordUpdateTotalTime,
targetDetectCount: linkDragStats.targetDetectCount,
targetDetectTotalTime: linkDragStats.targetDetectTotalTime,
}
},
// ==================== 帧时间监控 API ====================
/**
* 线
* @param longFrameThreshold 30ms
*/
startFrameMonitor(longFrameThreshold = 30): void {
frameMonitorStats.lastFrameTime = performance.now()
frameMonitorStats.longFrameCount = 0
frameMonitorStats.frameCount = 0
const checkFrame = () => {
const now = performance.now()
const frameTime = now - frameMonitorStats.lastFrameTime
frameMonitorStats.frameCount++
// 超过阈值的帧被认为是"长帧"
if (frameTime > longFrameThreshold) {
frameMonitorStats.longFrameCount++
// eslint-disable-next-line no-console
console.warn(`[Frame Monitor] 长帧检测: ${frameTime.toFixed(1)}ms`)
}
frameMonitorStats.lastFrameTime = now
frameMonitorStats.frameMonitorId = requestAnimationFrame(checkFrame)
}
frameMonitorStats.frameMonitorId = requestAnimationFrame(checkFrame)
},
/**
*
*/
stopFrameMonitor(): void {
if (frameMonitorStats.frameMonitorId !== null) {
cancelAnimationFrame(frameMonitorStats.frameMonitorId)
frameMonitorStats.frameMonitorId = null
const percentage =
frameMonitorStats.frameCount > 0
? (frameMonitorStats.longFrameCount / frameMonitorStats.frameCount * 100).toFixed(1)
: 0
// eslint-disable-next-line no-console
console.log(
`[Frame Monitor] 统计: ${frameMonitorStats.frameCount}帧, ` +
`长帧数: ${frameMonitorStats.longFrameCount} (${percentage}%)`,
)
}
},
/**
*
*/
getFrameMonitorStats() {
return {
frameCount: frameMonitorStats.frameCount,
longFrameCount: frameMonitorStats.longFrameCount,
percentage:
frameMonitorStats.frameCount > 0
? (frameMonitorStats.longFrameCount / frameMonitorStats.frameCount * 100).toFixed(1)
: 0,
}
},
}