v1.7.0 - add TaskListContextMenu and TaskBarContextMenu components & slots
This commit is contained in:
+68
-50
@@ -17,6 +17,7 @@ interface Props {
|
||||
startDate: Date
|
||||
isParent?: boolean
|
||||
onClick?: (task: Task, event: MouseEvent) => void
|
||||
rowIndex?: number
|
||||
// 新增:用于粘性文字显示的滚动位置信息
|
||||
scrollLeft?: number
|
||||
containerWidth?: number
|
||||
@@ -92,47 +93,50 @@ const emit = defineEmits([
|
||||
|
||||
defineSlots<{
|
||||
'custom-task-content'(props: TaskBarSlotProps): unknown
|
||||
'task-bar-context-menu'(props: {
|
||||
task: Task
|
||||
position: { x: number; y: number }
|
||||
visible: boolean
|
||||
allTasks?: Task[]
|
||||
onClose: () => void
|
||||
onStartTimer: () => void
|
||||
onStopTimer: () => void
|
||||
onAddPredecessor: () => void
|
||||
onAddSuccessor: () => void
|
||||
onDelete: (task: Task, deleteChildren?: boolean) => void
|
||||
onDeleteLink: (event: { sourceTaskId: number; targetTaskId: number }) => void
|
||||
}): unknown
|
||||
}>()
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
// 注入右键菜单配置
|
||||
const useDefaultContextMenu = inject<ComputedRef<boolean>>('use-default-context-menu', computed(() => true))
|
||||
const enableTaskBarContextMenu = inject<ComputedRef<boolean>>('enable-task-bar-context-menu', computed(() => true))
|
||||
const hasTaskBarContextMenuSlot = inject<ComputedRef<boolean>>('task-bar-context-menu-slot', computed(() => false))
|
||||
const declarativeTaskBarContextMenu = inject<ComputedRef<any>>('declarative-task-bar-context-menu', computed(() => null))
|
||||
|
||||
// 判断是否应该显示任何右键菜单
|
||||
const shouldShowAnyContextMenu = computed(() => {
|
||||
// 如果 useDefaultContextMenu 为 false 且没有自定义 slot,则不显示任何菜单
|
||||
if (!useDefaultContextMenu.value && !hasTaskBarContextMenuSlot.value) {
|
||||
// 如果 enableTaskBarContextMenu 为 false,则不显示任何菜单
|
||||
if (!enableTaskBarContextMenu.value) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// 判断是否显示默认右键菜单(没有自定义 slot 时显示)
|
||||
// 判断是否显示默认右键菜单(enableTaskBarContextMenu=true 且没有自定义 slot 时显示)
|
||||
const shouldShowDefaultContextMenu = computed(() => {
|
||||
if (!useDefaultContextMenu.value) {
|
||||
if (!enableTaskBarContextMenu.value) {
|
||||
return false
|
||||
}
|
||||
return !hasTaskBarContextMenuSlot.value
|
||||
})
|
||||
|
||||
// 判断是否显示自定义右键菜单(有自定义 slot 时显示)
|
||||
// 判断是否显示自定义右键菜单(enableTaskBarContextMenu=true 且有自定义 slot 时显示)
|
||||
const shouldShowCustomContextMenu = computed(() => {
|
||||
return hasTaskBarContextMenuSlot.value
|
||||
if (!enableTaskBarContextMenu.value) {
|
||||
return false
|
||||
}
|
||||
if (!hasTaskBarContextMenuSlot.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查 taskType 过滤
|
||||
const config = declarativeTaskBarContextMenu.value
|
||||
if (config?.taskType !== undefined) {
|
||||
const taskType = props.task.type || 'task'
|
||||
const allowedTypes = Array.isArray(config.taskType) ? config.taskType : [config.taskType]
|
||||
return allowedTypes.includes(taskType)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
const { getTranslation } = useI18n()
|
||||
@@ -1330,6 +1334,7 @@ onMounted(() => {
|
||||
|
||||
// 监听全局关闭菜单事件
|
||||
window.addEventListener('close-all-taskbar-menus', closeContextMenu)
|
||||
document.addEventListener('click', handleDocumentClick)
|
||||
|
||||
// 清理函数
|
||||
onUnmounted(() => {
|
||||
@@ -1342,12 +1347,19 @@ onMounted(() => {
|
||||
window.removeEventListener('timeline-scale-updated', handleTimelineScaleUpdate)
|
||||
window.removeEventListener('timeline-force-recalculate', handleForceRecalculate)
|
||||
window.removeEventListener('close-all-taskbar-menus', closeContextMenu)
|
||||
document.removeEventListener('click', handleDocumentClick)
|
||||
|
||||
// 清除长按定时器
|
||||
if (longPressTimer.value !== null) {
|
||||
clearTimeout(longPressTimer.value)
|
||||
longPressTimer.value = null
|
||||
}
|
||||
|
||||
// 清理单击定时器
|
||||
if (clickTimer !== null) {
|
||||
clearTimeout(clickTimer)
|
||||
clickTimer = null
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2303,7 +2315,8 @@ function handleContextMenu(event: MouseEvent) {
|
||||
|
||||
// 先广播关闭所有TaskBar菜单
|
||||
window.dispatchEvent(new CustomEvent('close-all-taskbar-menus'))
|
||||
if (props.task.type !== 'task' && props.task.type !== 'story') {
|
||||
const taskType = props.task.type || 'task'
|
||||
if (taskType !== 'task' && taskType !== 'story') {
|
||||
// 为了排除里程碑类型
|
||||
event.preventDefault()
|
||||
contextMenuVisible.value = false
|
||||
@@ -2317,6 +2330,20 @@ function closeContextMenu() {
|
||||
contextMenuVisible.value = false
|
||||
}
|
||||
|
||||
// 处理文档点击事件,点击菜单外部时关闭
|
||||
function handleDocumentClick(event: MouseEvent) {
|
||||
if (!contextMenuVisible.value) return
|
||||
|
||||
const target = event.target as HTMLElement
|
||||
// 检查点击是否在右键菜单内部
|
||||
const contextMenuElement = document.querySelector('.task-context-menu')
|
||||
if (contextMenuElement && contextMenuElement.contains(target)) {
|
||||
return
|
||||
}
|
||||
|
||||
closeContextMenu()
|
||||
}
|
||||
|
||||
const handleTaskDelete = (task: Task, deleteChildren?: boolean) => {
|
||||
// 触发删除事件
|
||||
emit('delete', task, deleteChildren)
|
||||
@@ -2370,19 +2397,7 @@ const handleAnchorDragEnd = (anchorEvent: { taskId: number; type: 'predecessor'
|
||||
task: props.task,
|
||||
type: anchorEvent.type,
|
||||
})
|
||||
}// 监听全局关闭菜单事件
|
||||
onMounted(() => {
|
||||
window.addEventListener('close-all-taskbar-menus', closeContextMenu)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('close-all-taskbar-menus', closeContextMenu)
|
||||
|
||||
// 清理单击定时器
|
||||
if (clickTimer !== null) {
|
||||
clearTimeout(clickTimer)
|
||||
clickTimer = null
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -2594,22 +2609,25 @@ onUnmounted(() => {
|
||||
@delete-link="handleDeleteLink"
|
||||
/>
|
||||
|
||||
<!-- 自定义右键菜单 Slot -->
|
||||
<slot
|
||||
v-if="shouldShowCustomContextMenu"
|
||||
name="task-bar-context-menu"
|
||||
:task="contextMenuTask"
|
||||
:position="contextMenuPosition"
|
||||
:visible="contextMenuVisible"
|
||||
:all-tasks="allTasks"
|
||||
:on-close="closeContextMenu"
|
||||
:on-start-timer="() => $emit('start-timer', props.task)"
|
||||
:on-stop-timer="() => $emit('stop-timer', props.task)"
|
||||
:on-add-predecessor="() => $emit('add-predecessor', props.task)"
|
||||
:on-add-successor="() => $emit('add-successor', props.task)"
|
||||
:on-delete="handleTaskDelete"
|
||||
:on-delete-link="handleDeleteLink"
|
||||
/>
|
||||
<!-- 声明式右键菜单 -->
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-if="shouldShowCustomContextMenu && contextMenuVisible && declarativeTaskBarContextMenu?.defaultSlot"
|
||||
class="gantt-context-menu-wrapper"
|
||||
:style="{
|
||||
position: 'fixed',
|
||||
left: `${contextMenuPosition.x}px`,
|
||||
top: `${contextMenuPosition.y}px`,
|
||||
zIndex: 9999,
|
||||
}"
|
||||
>
|
||||
<component
|
||||
:is="declarativeTaskBarContextMenu.defaultSlot"
|
||||
:row="contextMenuTask"
|
||||
:$index="props.rowIndex ?? -1"
|
||||
/>
|
||||
</div>
|
||||
</Teleport>
|
||||
</div>
|
||||
|
||||
<!-- Tooltip 弹窗 -->
|
||||
|
||||
Reference in New Issue
Block a user