v1.9.0 - 事件交叉Taskbar展示优化
This commit is contained in:
@@ -4,6 +4,7 @@ import type { StyleValue } from 'vue'
|
||||
import { useI18n } from '../../../composables/useI18n'
|
||||
import { formatPredecessorDisplay } from '../../../utils/predecessorUtils'
|
||||
import type { Task } from '../../../models/classes/Task'
|
||||
// @ts-expect-error - Resource is used in type definitions
|
||||
import type { Resource } from '../../../models/classes/Resource'
|
||||
import type { TaskListColumnConfig } from '../../../models/configs/TaskListConfig'
|
||||
import type { DeclarativeColumnConfig } from '../composables/taskList/useTaskListColumns'
|
||||
@@ -86,11 +87,37 @@ const hasContentSlot = computed(() => Boolean(slots['custom-task-content']))
|
||||
// v1.9.0 Inject view mode to detect if we're rendering a resource
|
||||
const viewMode = inject<Ref<'task' | 'resource'>>('gantt-view-mode', ref('task'))
|
||||
|
||||
// 从 GanttChart 注入资源布局信息
|
||||
const resourceTaskLayouts = inject<ComputedRef<Map<string, { taskRowMap: Map<string | number, number>, rowHeights: number[], totalHeight: number }>>>('resourceTaskLayouts', computed(() => new Map()))
|
||||
|
||||
// v1.9.0 Detect if current row is a resource
|
||||
const isResourceRow = computed(() => {
|
||||
return viewMode.value === 'resource' && 'tasks' in props.task
|
||||
})
|
||||
|
||||
// v1.9.0 检测资源是否超载(任务重叠)
|
||||
const isResourceOverloaded = computed(() => {
|
||||
if (!isResourceRow.value) return false
|
||||
|
||||
// 类型断言为Resource
|
||||
const resource = props.task as any
|
||||
if (typeof resource.isOverloaded === 'function') {
|
||||
return resource.isOverloaded()
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
// 计算行高度 - resource视图下使用动态高度
|
||||
const rowHeight = computed(() => {
|
||||
if (isResourceRow.value) {
|
||||
const resourceId = String(props.task.id) // 转换为string
|
||||
const layout = resourceTaskLayouts.value.get(resourceId)
|
||||
return layout?.totalHeight || 51
|
||||
}
|
||||
return 51 // task视图下使用固定高度
|
||||
})
|
||||
|
||||
// 注入右键菜单配置
|
||||
const enableTaskListContextMenu = inject<ComputedRef<boolean>>('enable-task-list-context-menu', computed(() => true))
|
||||
const hasTaskListContextMenuSlot = inject<ComputedRef<boolean>>('task-list-context-menu-slot', computed(() => false))
|
||||
@@ -236,9 +263,16 @@ const slotPayload = computed(() => ({
|
||||
progressClass: progressClass.value,
|
||||
}))
|
||||
|
||||
// 计算左侧边框颜色 - 支持barColor自定义
|
||||
// 计算左侧边框颜色 - 支持barColor/color自定义
|
||||
const leftBorderColor = computed(() => {
|
||||
// 如果设置了barColor,优先使用
|
||||
// 资源视图:优先使用resource.color
|
||||
if (isResourceRow.value) {
|
||||
const resource = props.task as any
|
||||
if (resource.color) {
|
||||
return resource.color
|
||||
}
|
||||
}
|
||||
// 任务视图:使用barColor
|
||||
if (props.task.barColor) {
|
||||
return props.task.barColor
|
||||
}
|
||||
@@ -247,10 +281,12 @@ const leftBorderColor = computed(() => {
|
||||
})
|
||||
|
||||
// 计算自定义边框样式
|
||||
const customBorderStyle = computed(() => {
|
||||
const customBorderStyle = computed((): StyleValue => {
|
||||
if (leftBorderColor.value) {
|
||||
return {
|
||||
borderLeftColor: leftBorderColor.value
|
||||
borderLeftColor: `${leftBorderColor.value} !important` as any,
|
||||
borderLeftWidth: '3px',
|
||||
borderLeftStyle: 'solid' as const,
|
||||
}
|
||||
}
|
||||
return {}
|
||||
@@ -298,7 +334,7 @@ const assigneeDisplayData = computed(() => {
|
||||
:data-task-id="props.task.id"
|
||||
:class="{
|
||||
'task-row-hovered': isHovered,
|
||||
'task-type-story': viewMode === 'resource', /* v1.9.0 资源视图始终显示左边框 */
|
||||
'task-type-resource': isResourceRow, /* v1.9.0 资源视图始终显示左边框 */
|
||||
'parent-task': isParentTask,
|
||||
'milestone-group-row': isMilestoneGroup,
|
||||
'task-type-story': isStoryTask,
|
||||
@@ -306,7 +342,7 @@ const assigneeDisplayData = computed(() => {
|
||||
'task-type-milestone': isMilestoneTask,
|
||||
[customRowClass]: customRowClass,
|
||||
}"
|
||||
:style="[customRowStyle, customBorderStyle]"
|
||||
:style="[customRowStyle, customBorderStyle, { height: `${rowHeight}px` }]"
|
||||
@click="handleRowClick"
|
||||
@dblclick="handleTaskRowDoubleClick"
|
||||
@mousedown="handleMouseDown"
|
||||
@@ -374,6 +410,25 @@ const assigneeDisplayData = computed(() => {
|
||||
|
||||
<!-- v1.9.0 资源视图:直接显示资源名称 -->
|
||||
<div v-if="isResourceRow" class="resource-row-name">
|
||||
<!-- v1.9.0 资源超载警示图标 -->
|
||||
<svg
|
||||
v-if="isResourceOverloaded"
|
||||
class="resource-warning-icon"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 2L2 20h20L12 2z"
|
||||
fill="var(--gantt-danger, #f56c6c)"
|
||||
/>
|
||||
<path
|
||||
d="M12 8v6M12 16h.01"
|
||||
stroke="#fff"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
<div v-if="(props.task as any).avatar" class="resource-avatar">
|
||||
<img :src="(props.task as any).avatar" :alt="(props.task as any).name" />
|
||||
</div>
|
||||
@@ -626,6 +681,11 @@ const assigneeDisplayData = computed(() => {
|
||||
border-left: 3px solid var(--gantt-danger, #f56c6c);
|
||||
}
|
||||
|
||||
/* v1.9.0 资源类型左边框颜色 */
|
||||
.task-type-resource {
|
||||
border-left: 3px solid var(--gantt-success, #67c23a);
|
||||
}
|
||||
|
||||
/* 任务类型悬停时保持左边框,无需加粗 */
|
||||
.task-type-story:hover {
|
||||
border-left: 3px solid var(--gantt-primary, #409eff);
|
||||
@@ -639,6 +699,10 @@ const assigneeDisplayData = computed(() => {
|
||||
border-left: 3px solid var(--gantt-danger, #f56c6c);
|
||||
}
|
||||
|
||||
.task-type-resource:hover {
|
||||
border-left: 3px solid var(--gantt-success, #67c23a);
|
||||
}
|
||||
|
||||
/* 悬停状态下的左边框保持 */
|
||||
.task-row-hovered.task-type-story {
|
||||
border-left: 3px solid var(--gantt-primary, #409eff) !important;
|
||||
@@ -652,6 +716,10 @@ const assigneeDisplayData = computed(() => {
|
||||
border-left: 3px solid var(--gantt-danger, #f56c6c) !important;
|
||||
}
|
||||
|
||||
.task-row-hovered.task-type-resource {
|
||||
border-left: 3px solid var(--gantt-success, #67c23a) !important;
|
||||
}
|
||||
|
||||
:global(html[data-theme='dark']) .milestone-group-row {
|
||||
border-left-color: var(--gantt-danger, #f67c7c);
|
||||
}
|
||||
@@ -669,6 +737,10 @@ const assigneeDisplayData = computed(() => {
|
||||
border-left-color: var(--gantt-danger, #f67c7c);
|
||||
}
|
||||
|
||||
:global(html[data-theme='dark']) .task-type-resource {
|
||||
border-left-color: var(--gantt-success, #85ce61);
|
||||
}
|
||||
|
||||
.collapse-btn:hover {
|
||||
background-color: var(--gantt-primary-light);
|
||||
}
|
||||
@@ -850,6 +922,23 @@ const assigneeDisplayData = computed(() => {
|
||||
color: var(--gantt-text-primary);
|
||||
}
|
||||
|
||||
/* v1.9.0 资源超载警示图标 */
|
||||
.resource-warning-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex-shrink: 0;
|
||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.resource-avatar {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
|
||||
Reference in New Issue
Block a user