前连前/后连后触点时提示明确错误,链接成功发射事件

This commit is contained in:
qiuchengw
2025-12-03 09:32:41 +08:00
parent 124ef6e60e
commit 9db16b4e16

View File

@@ -67,6 +67,8 @@ const emit = defineEmits<{
'stop-timer': [task: Task]
'add-predecessor': [task: Task] // 新增:添加前置任务事件
'add-successor': [task: Task] // 新增:添加后置任务事件
'predecessor-added': [{ targetTask: Task; newTask: Task }] // 前置任务已添加
'successor-added': [{ targetTask: Task; newTask: Task }] // 后置任务已添加
delete: [task: Task, deleteChildren?: boolean]
}>()
@@ -693,9 +695,6 @@ const handleLinkDragStart = (event: { task: Task; type: 'predecessor' | 'success
document.addEventListener('mouseup', handleGlobalMouseUp)
}
// 目标检测的 RAF 节流(保留用于兼容性)
let targetDetectionRafId: number | null = null
// 🚀 优化:统一 RAF 调度(坐标更新 + 目标检测在同一帧处理)
let linkDragRafId: number | null = null
let pendingMouseX = 0
@@ -778,10 +777,6 @@ const handleGlobalMouseUp = () => {
cancelAnimationFrame(linkDragRafId)
linkDragRafId = null
}
if (targetDetectionRafId !== null) {
cancelAnimationFrame(targetDetectionRafId)
targetDetectionRafId = null
}
// 触发拖拽结束
if (linkDragSourceTask.value) {
@@ -870,7 +865,8 @@ const ANCHOR_SIZE = 8 // 触点视觉大小px
const ANCHOR_TOLERANCE = 4 // 碰撞容差px扩大点击区域
// 🚀 目标检测辅助函数(提取重复逻辑)
const detectTargetTaskId = (mouseX: number, mouseY: number): number | null => {
// 返回 { taskId: number, anchorType: 'left' | 'right' } 或 null
const detectTargetTaskId = (mouseX: number, mouseY: number): { taskId: number; anchorType: 'left' | 'right' } | null => {
// 使用缓存的 rect
if (!cachedBodyRect) {
cachedBodyRect = bodyContentRef.value!.getBoundingClientRect()
@@ -880,7 +876,6 @@ const detectTargetTaskId = (mouseX: number, mouseY: number): number | null => {
const relativeX = mouseX - cachedBodyRect.left
const relativeY = mouseY - cachedBodyRect.top
const isPredecessorMode = dragLinkMode.value === 'predecessor'
const halfSize = (ANCHOR_SIZE + ANCHOR_TOLERANCE) / 2
const expandedHalfSize = halfSize + 10
@@ -895,19 +890,28 @@ const detectTargetTaskId = (mouseX: number, mouseY: number): number | null => {
continue
}
const anchorX = isPredecessorMode ? pos.left + pos.width : pos.left
if (relativeX < anchorX - expandedHalfSize || relativeX > anchorX + expandedHalfSize) {
continue
}
const anchorY = pos.top + pos.height / 2
// 检查左侧触点predecessor
const leftAnchorX = pos.left
if (
relativeX >= anchorX - halfSize &&
relativeX <= anchorX + halfSize &&
relativeX >= leftAnchorX - halfSize &&
relativeX <= leftAnchorX + halfSize &&
relativeY >= anchorY - halfSize &&
relativeY <= anchorY + halfSize
) {
return taskId
return { taskId, anchorType: 'left' }
}
// 检查右侧触点successor
const rightAnchorX = pos.left + pos.width
if (
relativeX >= rightAnchorX - halfSize &&
relativeX <= rightAnchorX + halfSize &&
relativeY >= anchorY - halfSize &&
relativeY <= anchorY + halfSize
) {
return { taskId, anchorType: 'right' }
}
}
@@ -915,7 +919,7 @@ const detectTargetTaskId = (mouseX: number, mouseY: number): number | null => {
}
// 🚀 更新目标任务的状态
const updateTargetTaskState = (foundTarget: Task | null): void => {
const updateTargetTaskState = (foundTarget: Task | null, anchorType?: 'left' | 'right'): void => {
const currentTargetId = nonReactiveTargetTask?.id ?? null
const newTargetId = foundTarget?.id ?? null
@@ -923,7 +927,12 @@ const updateTargetTaskState = (foundTarget: Task | null): void => {
nonReactiveTargetTask = foundTarget
if (foundTarget && linkDragSourceTask.value) {
const validation = validateLink(linkDragSourceTask.value, foundTarget, dragLinkMode.value!)
const validation = validateLink(
linkDragSourceTask.value,
foundTarget,
dragLinkMode.value!,
anchorType,
)
nonReactiveIsValidTarget = validation.valid
nonReactiveErrorMessage = validation.error || ''
isValidLinkTarget.value = validation.valid
@@ -944,10 +953,11 @@ 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
const result = detectTargetTaskId(mouseX, mouseY)
const foundTarget = result !== null ? taskIdMap.get(result.taskId) || null : null
const anchorType = result?.anchorType
updateTargetTaskState(foundTarget)
updateTargetTaskState(foundTarget, anchorType)
}
// 验证连接是否有效(返回 { valid: boolean, error?: string }
@@ -955,7 +965,18 @@ const validateLink = (
sourceTask: Task,
targetTask: Task,
mode: 'predecessor' | 'successor',
targetAnchorType?: 'left' | 'right',
): { valid: boolean; error?: string } => {
// 0. 检查触点方向是否正确
if (targetAnchorType) {
if (mode === 'predecessor' && targetAnchorType !== 'right') {
return { valid: false, error: '前置任务应连接到右侧触点' }
}
if (mode === 'successor' && targetAnchorType !== 'left') {
return { valid: false, error: '后置任务应连接到左侧触点' }
}
}
// 1. 不能连接到自己
if (sourceTask.id === targetTask.id) {
return { valid: false, error: '不能连接到自己' }
@@ -1048,6 +1069,8 @@ const createLink = (sourceTask: Task, targetTask: Task, mode: 'predecessor' | 's
}
// 触发任务更新事件
updateTask(targetTask)
// 发射 predecessor-added 事件
emit('predecessor-added', { targetTask, newTask: sourceTask })
}
} else {
// 后置模式:将目标任务的 ID 添加到源任务的前置任务列表
@@ -1064,6 +1087,8 @@ const createLink = (sourceTask: Task, targetTask: Task, mode: 'predecessor' | 's
}
// 触发任务更新事件
updateTask(targetTask)
// 发射 successor-added 事件
emit('successor-added', { targetTask: sourceTask, newTask: targetTask })
}
}
}
@@ -1154,10 +1179,10 @@ const cleanupLinkDrag = () => {
// 清除缓存
cachedBodyRect = null
// 取消待处理的 mousemove
if (mouseMoveRafId !== null) {
cancelAnimationFrame(mouseMoveRafId)
mouseMoveRafId = null
// 取消待处理的 RAF
if (linkDragRafId !== null) {
cancelAnimationFrame(linkDragRafId)
linkDragRafId = null
}
// 🚀 清除 LinkDragGuide 画布
@@ -2854,7 +2879,7 @@ const startScrollLeft = ref(0)
const startScrollTop = ref(0)
const timelineContainer = ref<HTMLElement | null>(null)
const timelineBodyElement = ref<HTMLElement | null>(null) // 缓存timeline-body元素引用
let rafId: number | null = null // requestAnimationFrame ID
let scrollRafId: number | null = null // 时间轴拖拽滚动的 RAF ID
// 边界滚动相关状态
const isAutoScrolling = ref(false)
@@ -2980,12 +3005,12 @@ const handleMouseMove = (event: MouseEvent) => {
event.preventDefault()
// 取消之前的 RAF
if (rafId !== null) {
cancelAnimationFrame(rafId)
if (scrollRafId !== null) {
cancelAnimationFrame(scrollRafId)
}
// 使用 requestAnimationFrame 批处理滚动更新
rafId = requestAnimationFrame(() => {
scrollRafId = requestAnimationFrame(() => {
if (!timelineContainer.value) return
// 计算水平和垂直移动距离
@@ -3009,7 +3034,7 @@ const handleMouseMove = (event: MouseEvent) => {
}
}
rafId = null
scrollRafId = null
})
}
@@ -3018,9 +3043,9 @@ const handleMouseUp = () => {
isDragging.value = false
// 取消任何待处理的 RAF
if (rafId !== null) {
cancelAnimationFrame(rafId)
rafId = null
if (scrollRafId !== null) {
cancelAnimationFrame(scrollRafId)
scrollRafId = null
}
if (timelineContainer.value) {