From 961f8fefde8025c29243eb58c77d8fc1158fc95a Mon Sep 17 00:00:00 2001 From: "LINING-PC\\lining" Date: Fri, 30 Jan 2026 22:43:47 +0800 Subject: [PATCH] v1.9.0-rc.1 - updates --- src/components/TaskBar.vue | 202 +++++++++++++++++--- src/components/TaskList/taskRow/TaskRow.vue | 4 +- src/components/Timeline.vue | 6 +- src/utils/taskLayoutUtils.ts | 25 +-- 4 files changed, 185 insertions(+), 52 deletions(-) diff --git a/src/components/TaskBar.vue b/src/components/TaskBar.vue index 122ff85..58da7bb 100644 --- a/src/components/TaskBar.vue +++ b/src/components/TaskBar.vue @@ -22,14 +22,24 @@ const enableLinkAnchor = inject>('enable-link-anchor', comp const resourcePercent = computed(() => { // 如果直接传递了占比,使用传递的值 if (props.resourceAllocationPercent !== undefined) { - return Math.max(20, Math.min(100, props.resourceAllocationPercent)) + const val = Number(props.resourceAllocationPercent) + if (Number.isFinite(val) && val > 0) { + return Math.max(0, Math.min(100, val)) + } } // 在资源视图中,尝试从task.resources中查找当前资源的占比 if (viewMode.value === 'resource' && props.currentResourceId && props.task.resources) { - const allocation = props.task.resources.find((r: any) => r.id === props.currentResourceId) + const allocation = props.task.resources.find((r: any) => { + // 确保ID类型匹配(可能是字符串或数字) + return String(r.id) === String(props.currentResourceId) + }) + if (allocation && allocation.percent !== undefined) { - return Math.max(20, Math.min(100, allocation.percent)) + const val = Number(allocation.percent) + if (Number.isFinite(val) && val >= 0) { + return Math.max(0, Math.min(100, val)) + } } } @@ -576,17 +586,15 @@ const taskBarStyle = computed(() => { } } - // v1.9.0 计算TaskBar高度:保持基础高度一致,资源视图中应用占比缩放 + // v1.9.1 计算TaskBar高度:资源视图固定41px全高度,不再按占比缩放 const baseTaskBarHeight = props.rowHeight - 10 // 基础高度(与任务视图一致) let taskBarHeight = baseTaskBarHeight - if (viewMode.value === 'resource') { - // 资源视图:在基础高度上应用占比缩放 - const percentValue = resourcePercent.value / 100 - taskBarHeight = baseTaskBarHeight * percentValue - } + // v1.9.1 资源视图:固定全高度,不缩放(占比通过CSS伪元素实现) + // 任务视图:保持原有高度 + // (资源视图的占比视觉效果通过CSS的::before和::after伪元素实现) - // v1.9.0 计算垂直位置:资源视图中支持换行布局 + // v1.9.1 计算垂直位置:资源视图中支持换行布局 let topOffset = (props.rowHeight - taskBarHeight) / 2 // 默认:居中对齐 if (viewMode.value === 'resource' && props.taskSubRow !== undefined && props.rowHeights && props.rowHeights.length > 0) { @@ -601,15 +609,15 @@ const taskBarStyle = computed(() => { cumulativeOffset += rowHeights[i] || 51 } - // 在当前子行内底边对齐 - // 第一行:padding-top(5px) + 可用空间 - // 后续行:可用空间(无padding-top) + // v1.9.1 在当前子行内居中对齐(因为TaskBar固定41px高度) + // 第一行:padding-top(5px) + 居中 + // 后续行:居中(无padding-top) if (subRow === 0) { - // 第一行:顶部5px padding,底部对齐 - topOffset = cumulativeOffset + (currentRowHeight - 5 - taskBarHeight) + // 第一行:顶部5px padding,居中对齐 + topOffset = cumulativeOffset + 5 + (currentRowHeight - 5 - taskBarHeight) / 2 } else { - // 后续行:底部对齐(底部5px padding) - topOffset = cumulativeOffset + (currentRowHeight - 5 - taskBarHeight) + // 后续行:居中对齐 + topOffset = cumulativeOffset + (currentRowHeight - taskBarHeight) / 2 } } @@ -2415,6 +2423,14 @@ const handleBubbleMouseEnter = (event: MouseEvent) => { // 阻止事件冒泡 event.stopPropagation() + // v1.9.1 隐藏TaskBar的hover tooltip,只显示bubble tooltip + showHoverTooltip.value = false + // 清除可能正在等待的tooltip定时器 + if (hoverTooltipTimer) { + window.clearTimeout(hoverTooltipTimer) + hoverTooltipTimer = null + } + showTooltip.value = true // 智能定位:右侧气泡在左侧显示tooltip,左侧气泡在右侧显示 @@ -3093,6 +3109,9 @@ const handleAnchorDragEnd = (anchorEvent: { taskId: number; type: 'predecessor' '--row-height': `${rowHeight}px` /* 传递行高给CSS变量 */, '--handle-width': `${actualHandleWidth}px` /* 传递手柄宽度给CSS变量 */, '--parent-color': taskStatus.color, /* 传递父级TaskBar颜色给伪元素箭头使用 */ + '--allocation-percent': (Number.isFinite(resourcePercent) ? resourcePercent / 100 : 1), /* v1.9.1 传递占比给CSS变量 */ + '--task-bar-bg-color': taskStatus.bgColor, /* v1.9.1 传递背景色给伪元素 */ + '--task-bar-border-color': taskStatus.borderColor, /* v1.9.1 传递边框色给伪元素 */ boxShadow: isParent ? `0 4px 16px ${taskStatus.color}40, 0 2px 8px ${taskStatus.color}26` /* 父级任务也使用动态颜色阴影 */ : `0 4px 16px ${taskStatus.color}40, 0 2px 8px ${taskStatus.color}26`, /* 使用TaskBar颜色的阴影 - 加强版 */ @@ -3110,6 +3129,8 @@ const handleAnchorDragEnd = (anchorEvent: { taskId: number; type: 'predecessor' dimmed: isDimmed, 'has-actual': showActualTaskbar && hasActualProgress, /* 只有在showActualTaskbar=true时才标记有实际进度 */ 'resource-conflict': props.hasResourceConflict, /* v1.9.0 资源冲突样式 */ + 'resource-view': viewMode === 'resource', /* v1.9.1 资源视图专属样式 */ + 'has-bubble': bubbleIndicator.show, /* v1.9.1 有气泡时提升层级,确保气泡不被其他TaskBar遮挡 */ }" @click="handleTaskBarClick" @contextmenu="handleContextMenu" @@ -3210,8 +3231,7 @@ const handleAnchorDragEnd = (anchorEvent: { taskId: number; type: 'predecessor'
@@ -3227,7 +3247,7 @@ const handleAnchorDragEnd = (anchorEvent: { taskId: number; type: 'predecessor'
{{ task.progress || 0 }}%
@@ -3496,6 +3516,11 @@ const handleAnchorDragEnd = (anchorEvent: { taskId: number; type: 'predecessor' z-index: 160 !important; } +/* v1.9.1 有气泡指示器时提升z-index,解决DOM渲染顺序导致的遮挡问题 */ +.task-bar.has-bubble { + z-index: 200 !important; +} + /* 有实际进度时,计划条使用虚线边框样式 */ .task-bar.has-actual { /* 不再强制设置半透明背景,由内联样式的isTaskBarHovered控制 */ @@ -3538,28 +3563,141 @@ const handleAnchorDragEnd = (anchorEvent: { taskId: number; type: 'predecessor' cursor: pointer; } -/* v1.9.0 资源冲突样式(根据UI规范)*/ -.task-bar.resource-conflict { +/* v1.9.1 资源视图TaskBar全高度占比设计 */ +.task-bar.resource-view { + display: flex; + align-items: center; + justify-content: center; + background: transparent !important; + border: none !important; + box-shadow: none !important; +} + +/* 上半部分:镂空区域(仅上、左、右三边虚线) */ +.task-bar.resource-view::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: calc((1 - var(--allocation-percent, 1)) * 100%); + border-top: 1.5px dashed currentColor; + border-left: 1.5px dashed currentColor; + border-right: 1.5px dashed currentColor; + border-bottom: none; + background: transparent; + box-sizing: border-box; + pointer-events: none; + border-radius: 4px 4px 0 0; + opacity: 0.6; + z-index: -1; /* v1.9.1 设置为负值,确保不遮挡 bubble-indicator */ +} + +/* 占比100%时,隐藏上半部分镂空区域 */ +.task-bar.resource-view[style*="--allocation-percent: 1"]::before { + display: none; +} + +/* 下半部分:实心填充区域(四边实线边框 + 背景色) */ +.task-bar.resource-view::after { + content: ''; + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: calc(var(--allocation-percent, 1) * 100%); + background: var(--task-bar-bg-color, #e3f2fd); + border: 1px solid var(--task-bar-border-color, #90caf9); + border-radius: 0 0 4px 4px; + box-sizing: border-box; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05); + pointer-events: none; + z-index: -1; /* v1.9.1 设置为负值,确保不遮挡 bubble-indicator */ +} + +/* 占比100%时,整个TaskBar都是实心,四个角圆角 */ +.task-bar.resource-view[style*="--allocation-percent: 1"]::after { + border-radius: 4px; +} + +/* 进度条限制在填充区内 */ +.task-bar.resource-view .progress-bar { + position: absolute; + bottom: 0; + left: 0; + top: auto; + height: calc(var(--allocation-percent, 1) * 100%); + z-index: 1; + border-radius: 0 0 4px 4px; + pointer-events: none; + box-shadow: 2px 0 6px rgba(0, 0, 0, 0.8); /* 右侧黑色阴影,增强层次感 */ +} + +/* 占比100%时,进度条四个角圆角 */ +.task-bar.resource-view[style*="--allocation-percent: 1"] .progress-bar { + border-radius: 4px; +} + +/* 已完成任务的进度条不显示阴影 */ +.task-bar.completed.resource-view .progress-bar { + box-shadow: none; +} + +/* 文字层在最上层,覆盖整个41px高度 */ +.task-bar.resource-view .task-bar-content { + position: relative; + /*z-index: 2;*/ + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +/* 资源视图悬停效果 */ +.task-bar.resource-view:hover::after { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); +} + +/* v1.9.0 资源冲突样式(根据UI规范)- 调整到最顶层 */ +.task-bar.resource-conflict::before { + /* 覆盖伪元素样式,显示警告效果 */ + border-color: var(--gantt-error-color, #f56c6c) !important; + opacity: 1 !important; + z-index: 10 !important; +} + +.task-bar.resource-conflict::after { /* 左侧3px红色色带 */ border-left: 3px solid var(--gantt-error-color, #f56c6c) !important; /* 浅红底色 */ - background-color: rgba(245, 108, 108, 0.08) !important; + background-color: rgba(245, 108, 108, 0.12) !important; /* 斑马纹背景(叠加在底色上)*/ background-image: repeating-linear-gradient( 45deg, transparent, transparent 10px, - rgba(245, 108, 108, 0.12) 10px, - rgba(245, 108, 108, 0.12) 20px + rgba(245, 108, 108, 0.1) 10px, + rgba(245, 108, 108, 0.1) 20px ) !important; + z-index: 1 !important; +} + +/* 已完成的任务冲突:不显示斜杠蒙版,仅保留左侧红色警示边 */ +.task-bar.completed.resource-conflict::after { + background-color: transparent !important; + background-image: none !important; + /* 保留左侧红色边框 */ + border-left: 3px solid var(--gantt-error-color, #f56c6c) !important; } /* 冲突状态的文字保持清晰可读 */ .task-bar.resource-conflict .task-name, .task-bar.resource-conflict .task-progress { - color: var(--gantt-text-primary); + /*color: var(--gantt-text-primary);*/ font-weight: 600; + z-index: 11; } .task-bar.dragging { @@ -3961,10 +4099,14 @@ const handleAnchorDragEnd = (anchorEvent: { taskId: number; type: 'predecessor' padding-left: 8px; } -/* 资源视图中的标题包裹器,禁用绝对定位 */ +/* v1.9.1 资源视图中的标题包裹器,确保在41px全高度内居中 */ .task-name-wrapper { position: relative; width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; text-align: center; } @@ -4063,7 +4205,7 @@ const handleAnchorDragEnd = (anchorEvent: { taskId: number; type: 'predecessor' top: 50%; width: 8px; /* 半圆宽度 */ height: 16px; /* 半圆高度 */ - z-index: 15; + z-index: 9999 !important; /* v1.9.1 使用超高层级和 !important,确保不被任何元素遮挡 */ cursor: pointer; border: 2px solid; transform: translateY(-50%); @@ -4598,14 +4740,14 @@ const handleAnchorDragEnd = (anchorEvent: { taskId: number; type: 'predecessor' z-index: 16; pointer-events: none; padding: 1px 3px; - border-radius: 2px; + /*border-radius: 2px;*/ } /* 周视图下的TaskBar基础样式调整 */ .task-bar.week-view { min-width: 4px; /* 确保即使很短的任务也有最小可见宽度 */ border-width: 1px; - border-radius: 2px; + /*border-radius: 2px;*/ } /* 暗色主题下的短TaskBar溢出效果 */ diff --git a/src/components/TaskList/taskRow/TaskRow.vue b/src/components/TaskList/taskRow/TaskRow.vue index a83513a..7fe1457 100644 --- a/src/components/TaskList/taskRow/TaskRow.vue +++ b/src/components/TaskList/taskRow/TaskRow.vue @@ -113,7 +113,7 @@ const rowHeight = computed(() => { if (isResourceRow.value) { const resourceId = String(props.task.id) // 转换为string const layout = resourceTaskLayouts.value.get(resourceId) - return layout?.totalHeight || 51 + return layout?.totalHeight || 56 // v1.9.1 默认56px(51 + 5px底部padding) } return 51 // task视图下使用固定高度 }) @@ -614,7 +614,7 @@ const assigneeDisplayData = computed(() => { .task-row { display: flex; border-bottom: 1px solid var(--gantt-border-light); - height: 51px; /* 修改为51px,与Timeline中的task-row高度保持一致,包含border-bottom 1px */ + height: 51px; /* v1.9.1 任务视图51px(包含border-bottom 1px),资源视图动态高度(第一行56px,后续行46px) */ box-sizing: border-box; /* 确保border包含在高度计算中 */ background: var(--gantt-bg-primary); align-items: center; diff --git a/src/components/Timeline.vue b/src/components/Timeline.vue index 160f23d..9491150 100644 --- a/src/components/Timeline.vue +++ b/src/components/Timeline.vue @@ -4895,9 +4895,6 @@ const handleAddSuccessor = (task: Task) => { :all-tasks="tasks" :has-resource-conflict="resourceConflicts.get(String(resource.id))?.has(task.id) || false" :current-resource-id="resource.id" - :style="{ - zIndex: taskIndex, - }" @update:task="updateTask" @bar-mounted="handleBarMounted" @click="handleTaskBarClick(task, $event)" @@ -5273,10 +5270,11 @@ const handleAddSuccessor = (task: Task) => { position: absolute; left: 0; width: 100%; - height: 51px; /** 为了对齐左侧的Task List Row高度,同时需要包含List Row的Bottom Border 1px */ + min-height: 51px; /** 为了对齐左侧的Task List Row高度,同时需要包含List Row的Bottom Border 1px */ pointer-events: auto; z-index: 11; transition: background-color 0.2s ease; + padding-bottom: 2px; } /* 资源视图行样式 */ diff --git a/src/utils/taskLayoutUtils.ts b/src/utils/taskLayoutUtils.ts index c44d1e7..ad85af9 100644 --- a/src/utils/taskLayoutUtils.ts +++ b/src/utils/taskLayoutUtils.ts @@ -106,24 +106,17 @@ export function assignTaskRows( } // 计算每行的高度 - const baseTaskBarHeight = baseRowHeight - 10 // 基础TaskBar高度 + // v1.9.1: TaskBar固定41px全高度,用伪元素实现镂空,所以行高不再按占比缩放 + // v1.9.1: 每行底部都有5px padding-bottom,第一行顶部也有5px padding-top + const baseTaskBarHeight = baseRowHeight - 10 // 基础TaskBar高度 (51 - 10 = 41px) + for (let i = 0; i < rowTasks.length; i++) { - const tasksInRow = rowTasks[i] - - // 找到该行中最大的TaskBar高度 - let maxTaskBarHeight = 0 - for (const task of tasksInRow) { - const percent = getTaskResourcePercent(task, resourceId) - const taskBarHeight = baseTaskBarHeight * (percent / 100) - maxTaskBarHeight = Math.max(maxTaskBarHeight, taskBarHeight) - } - - // 计算该行的容器高度 - // 第一行:padding-top(5px) + maxTaskBarHeight + padding-bottom(5px) - // 后续行:maxTaskBarHeight + padding-bottom(5px) + // v1.9.1: 所有TaskBar都是固定41px高度,每行都有底部padding + // 第一行:padding-top(5px) + TaskBar高度(41px) + padding-bottom(5px) = 51px + // 后续行:TaskBar高度(41px) + padding-bottom(5px) = 46px const rowHeight = i === 0 - ? 5 + maxTaskBarHeight + 5 - : maxTaskBarHeight + 5 + ? 5 + baseTaskBarHeight + 5 // 第一行:5 + 41 + 5 = 51px + : baseTaskBarHeight + 5 // 后续行:41 + 5 = 46px rowHeights.push(rowHeight) }