v1.9.2-rc.1 - tooltip slots / taskList collapse enable etc.

This commit is contained in:
LINING-PC\lining
2026-02-26 22:43:54 +08:00
parent f3b0c55bba
commit 544f6ec2d5
13 changed files with 2494 additions and 160 deletions
+63 -5
View File
@@ -75,6 +75,8 @@ const props = withDefaults(defineProps<Props>(), {
locale: 'zh-CN',
timeScale: 'week',
theme: undefined, // 不设置默认值,允许自动检测系统主题
enableTaskListCollapsible: true,
taskListVisible: true,
})
const emit = defineEmits([
@@ -545,6 +547,18 @@ interface Props {
timeScale?: TimelineScale
// 主题模式(响应式)
theme?: 'light' | 'dark'
/**
* 是否允许 TaskList 展开/收起功能(默认为 true)
* 当设置为 false 时,TaskList 面板强制隐藏,SplitterBar 及折叠按钮同步隐藏,仅展示 Timeline 区域
* taskListVisible 在此情况下失效
*/
enableTaskListCollapsible?: boolean
/**
* TaskList 可见状态(响应式),默认为 true
* 仅当 enableTaskListCollapsible=true 时有效
* 可通过 prop 传入或通过 defineExpose 暴露的方法控制
*/
taskListVisible?: boolean
}
// TaskList的固定总长度(所有列的最小宽度之和 + 边框等额外空间)
@@ -733,7 +747,6 @@ watch(
// props变化时清空增量追踪,执行全量更新
triggerFullUpdate()
},
{ deep: true },
)
// v1.9.9 监听props.resources变化,自动触发Timeline更新
@@ -744,7 +757,6 @@ watch(
// props变化时清空增量追踪,执行全量更新
triggerFullUpdate()
},
{ deep: true },
)
// 时间刻度状态
@@ -934,7 +946,32 @@ function onMouseDown(e: MouseEvent) {
}
// TaskList显示/隐藏状态管理
const isTaskListVisible = ref(true)
// 初始值:enableTaskListCollapsible=false 时强制隐藏;否则使用 taskListVisible prop 的值
const isTaskListVisible = ref(props.enableTaskListCollapsible ? (props.taskListVisible ?? true) : false)
// 响应 taskListVisible prop 变化(仅在 enableTaskListCollapsible=true 时生效)
watch(
() => props.taskListVisible,
(newVal) => {
if (props.enableTaskListCollapsible) {
isTaskListVisible.value = newVal ?? true
}
},
)
// 响应 enableTaskListCollapsible prop 变化
watch(
() => props.enableTaskListCollapsible,
(newVal) => {
if (!newVal) {
// 禁用折叠功能时,强制隐藏 TaskList
isTaskListVisible.value = false
} else {
// 恢复折叠功能时,还原为 taskListVisible prop 的值
isTaskListVisible.value = props.taskListVisible ?? true
}
},
)
// 动画状态管理
const isAnimating = ref(false)
@@ -942,6 +979,8 @@ const animationClass = ref('')
// 切换TaskList显示状态
const toggleTaskList = () => {
// enableTaskListCollapsible=false 时,禁止切换
if (!props.enableTaskListCollapsible) return
// 如果正在动画中,忽略点击
if (isAnimating.value) return
@@ -1299,7 +1338,7 @@ watch(
notifyTaskListUpdated()
})
},
{ deep: true, immediate: true },
{ immediate: true },
)
onMounted(() => {
@@ -3303,6 +3342,21 @@ defineExpose({
// 主题相关
setTheme,
currentTheme,
// TaskList 显隐相关
/** 获取 TaskList 当前可见状态 */
getTaskListVisible: () => isTaskListVisible.value,
/**
* 设置 TaskList 可见状态(仅在 enableTaskListCollapsible=true 时生效)
* @param visible true=展开,false=收起
*/
setTaskListVisible: (visible: boolean) => {
if (props.enableTaskListCollapsible) {
isTaskListVisible.value = visible
}
},
/** 切换 TaskList 展开/收起(仅在 enableTaskListCollapsible=true 时生效,带动画) */
toggleTaskList,
})
</script>
@@ -3371,7 +3425,7 @@ defineExpose({
</template>
</TaskList>
</div>
<div class="gantt-splitter" @mousedown="onMouseDown">
<div v-if="props.enableTaskListCollapsible" class="gantt-splitter" @mousedown="onMouseDown">
<!-- TaskList切换按钮 - 贴合splitter右侧 -->
<div
class="task-list-toggle"
@@ -3434,6 +3488,10 @@ defineExpose({
<template v-if="$slots['custom-task-content']" #custom-task-content="barScope">
<slot name="custom-task-content" v-bind="barScope" />
</template>
<!-- Timeline 转发 #taskbar-tooltip scoped slot仅此一层不穿透至 TaskBar -->
<template v-if="$slots['taskbar-tooltip']" #taskbar-tooltip="tooltipScope">
<slot name="taskbar-tooltip" v-bind="tooltipScope" />
</template>
</Timeline>
<!-- 关闭聚焦按钮 - 固定在gantt-panel-right底部居中 -->
+1 -1
View File
@@ -551,7 +551,7 @@ onUnmounted(() => {
</div>
<!-- v1.9.0 视图模式切换按钮组 - 使用 Segmented Control 样式 -->
<div class="gantt-view-mode-control">
<div v-if="config.showViewMode !== false" class="gantt-view-mode-control">
<div class="view-mode-track">
<div
class="view-mode-thumb"
+20 -115
View File
@@ -38,6 +38,9 @@ const emit = defineEmits([
'link-drag-start',
'link-drag-move',
'link-drag-end',
// Singleton TooltipTaskBar 只负责触发,Timeline 统一渲染
'tooltip-show',
'tooltip-hide',
])
defineSlots<{
@@ -197,9 +200,6 @@ const resourceTaskLayouts = inject<ComputedRef<Map<string, { taskRowMap: Map<str
// v1.9.6 Phase1 - 注入位置计算缓存实例(由Timeline提供)
const positionCache = inject<PositionCache | null>('positionCache', null)
// 注入 TaskList 宽度(用于 tooltip 定位边界检测)
const taskListWidth = inject<Ref<number>>('gantt-task-list-width', ref(0))
// 注入右键菜单配置
const enableTaskBarContextMenu = inject<ComputedRef<boolean>>('enable-task-bar-context-menu', computed(() => true))
const hasTaskBarContextMenuSlot = inject<ComputedRef<boolean>>('task-bar-context-menu-slot', computed(() => false))
@@ -2396,10 +2396,7 @@ const bubbleIndicator = computed(() => {
const showTooltip = ref(false)
const tooltipPosition = ref({ x: 0, y: 0 })
// TaskBar 悬停 tooltip 状态
const showHoverTooltip = ref(false)
const hoverTooltipPosition = ref({ x: 0, y: 0 })
const isTooltipBelow = ref(false) // v1.9.0 标记tooltip是否显示在TaskBar下方
// TaskBar 悬停 tooltip 延迟定时器(状态已上移至 Timeline)
let hoverTooltipTimer: number | null = null
// 跟踪滚动状态,避免非滚动时的动画
@@ -2568,7 +2565,7 @@ const handleBubbleMouseEnter = (event: MouseEvent) => {
event.stopPropagation()
// v1.9.1 隐藏TaskBar的hover tooltip,只显示bubble tooltip
showHoverTooltip.value = false
emit('tooltip-hide')
// 清除可能正在等待的tooltip定时器
if (hoverTooltipTimer) {
window.clearTimeout(hoverTooltipTimer)
@@ -2668,67 +2665,16 @@ const handleTaskBarMouseEnter = (event: MouseEvent) => {
const mouseY = event.clientY
// 延迟显示tooltip,避免快速滑过时显示
// Singleton TooltipTimer后 emit tooltip-show,由 Timeline 统一计算位置和渲染
hoverTooltipTimer = window.setTimeout(() => {
showHoverTooltip.value = true
const rect = targetElement.getBoundingClientRect()
// 计算tooltip的预估宽高(根据实际CSS设置)
const tooltipWidth = 250 // 预估宽度
const margin = 10 // 边距
// 视口尺寸
const viewportWidth = window.innerWidth
const viewportHeight = window.innerHeight
// v1.9.0 改为基于TaskBar边界定位
// 水平位置:TaskBar中心对齐
let x = rect.left + rect.width / 2
// 默认显示在TaskBar上方边缘外(CSS transform: translateY(-100%)会向上偏移tooltip高度)
let y = rect.top - 10
// 获取TaskList的右边界(用于防止tooltip进入TaskList区域)
const taskListRightBoundary = taskListWidth.value + margin
// 水平边界检测:左侧超出或进入TaskList区域
if (x - tooltipWidth / 2 < taskListRightBoundary) {
x = taskListRightBoundary + tooltipWidth / 2
}
// 水平边界检测:右侧超出
if (x + tooltipWidth / 2 > viewportWidth - margin) {
x = viewportWidth - margin - tooltipWidth / 2
}
// 垂直边界检测:计算上下方可用空间,选择空间更大的方向
const spaceAbove = rect.top - margin
const spaceBelow = viewportHeight - rect.bottom - margin
// 动态计算tooltip高度(基础高度 + 每行内容)
const baseHeight = 80 // 基础高度(标题 + padding
const rowHeight = 24 // 每行内容高度
let contentRows = 4 // 默认4行(开始日期、结束日期、预估工时、实际工时、进度)
// 资源视图且利用率<100%时,多1行
if (viewMode.value === 'resource' && resourcePercent.value < 100) {
contentRows += 1
}
// 有资源冲突警告时,多1行
if (props.hasResourceConflict) {
contentRows += 1
}
const estimatedTooltipHeight = baseHeight + (contentRows * rowHeight)
// 如果上方空间不足,显示在下方
if (spaceAbove < estimatedTooltipHeight && spaceBelow > spaceAbove) {
// 显示在TaskBar下方
y = rect.bottom + 10
isTooltipBelow.value = true
} else {
// 显示在TaskBar上方
isTooltipBelow.value = false
}
hoverTooltipPosition.value = { x, y }
emit('tooltip-show', {
task: props.task,
taskStatus: taskStatus.value,
resourcePercent: resourcePercent.value,
hasResourceConflict: props.hasResourceConflict ?? false,
targetRect: rect,
})
}, 300) // 300ms延迟
}
}
@@ -2736,19 +2682,19 @@ const handleTaskBarMouseEnter = (event: MouseEvent) => {
const handleTaskBarMouseLeave = () => {
isTaskBarHovered.value = false
// 清除定时器并隐藏tooltip
// 清除定时器并通知 Timeline 隐藏 tooltip
if (hoverTooltipTimer) {
clearTimeout(hoverTooltipTimer)
hoverTooltipTimer = null
}
showHoverTooltip.value = false
emit('tooltip-hide')
}
// 监听拖拽/拉伸状态,如果开始拖拽/拉伸,立即隐藏tooltip
// v1.9.7 使用 flush: 'sync' 确保状态变化时立即同步执行,避免在资源视图拖拽时因组件更新导致watch延迟执行
watch([isDragging, isResizingLeft, isResizingRight], ([dragging, resizingL, resizingR]) => {
if (dragging || resizingL || resizingR) {
showHoverTooltip.value = false
emit('tooltip-hide')
if (hoverTooltipTimer) {
clearTimeout(hoverTooltipTimer)
hoverTooltipTimer = null
@@ -2767,18 +2713,14 @@ watch([isDragging, isResizingLeft, isResizingRight], ([dragging, resizingL, resi
// v1.9.2 监听 Tab 悬停状态,当 Tab 悬停时立即隐藏 TaskBar 的 tooltip
watch(isTabHovered, (tabHovered) => {
if (tabHovered) {
// Tab 悬停:隐藏 TaskBar 的 tooltip
showHoverTooltip.value = false
// Tab 悬停:隐藏 TaskBar 的 tooltip(通知 Timeline
emit('tooltip-hide')
if (hoverTooltipTimer) {
clearTimeout(hoverTooltipTimer)
hoverTooltipTimer = null
}
} else {
// Tab 离开:如果鼠标还在 TaskBar 上,重新显示 tooltip
if (isTaskBarHovered.value && props.enableTaskBarTooltip !== false) {
showHoverTooltip.value = true
}
}
// Tab 离开后不主动重显 tooltip,等待用户下次 mouseenter 触发
})
// 格式化日期显示
@@ -3726,44 +3668,7 @@ const handleAnchorDragEnd = (anchorEvent: { taskId: number; type: 'predecessor'
</div>
</Teleport>
<!-- TaskBar悬停提示框 -->
<Teleport to="body">
<div
v-if="showHoverTooltip"
class="task-hover-tooltip"
:class="{ 'tooltip-below': isTooltipBelow }"
:style="{
left: `${hoverTooltipPosition.x}px`,
top: `${hoverTooltipPosition.y}px`,
backgroundColor: taskStatus.color,
}"
>
<div
class="hover-tooltip-arrow" :style="{
borderTopColor: isTooltipBelow ? 'transparent' : taskStatus.color,
borderBottomColor: isTooltipBelow ? taskStatus.color : 'transparent'
}"></div>
<div class="hover-tooltip-content">
<div class="hover-tooltip-title">{{ task.name }}</div>
<div class="hover-tooltip-row">
<span class="hover-tooltip-label">{{ t('plannedStartDate') }}:</span>
<span class="hover-tooltip-value">{{ formatDisplayDate(task.startDate) }}</span>
</div>
<div class="hover-tooltip-row">
<span class="hover-tooltip-label">{{ t('plannedEndDate') }}:</span>
<span class="hover-tooltip-value">{{ formatDisplayDate(task.endDate) }}</span>
</div>
<div class="hover-tooltip-row">
<span class="hover-tooltip-label">{{ t('actualStartDate') }}:</span>
<span class="hover-tooltip-value">{{ task.actualStartDate ? formatDisplayDate(task.actualStartDate) : '-' }}</span>
</div>
<div class="hover-tooltip-row">
<span class="hover-tooltip-label">{{ t('actualEndDate') }}:</span>
<span class="hover-tooltip-value">{{ task.actualEndDate ? formatDisplayDate(task.actualEndDate) : '-' }}</span>
</div>
</div>
</div>
</Teleport>
<!-- TaskBar悬停 Tooltip 已上移至 Timeline 单例渲染Singleton Tooltip-->
</template>
<style scoped>
+229 -30
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed, watch, nextTick, shallowRef, inject, provide } from 'vue'
import { ref, onMounted, onUnmounted, computed, watch, nextTick, shallowRef, inject, provide, reactive } from 'vue'
import type { Ref, ComputedRef } from 'vue'
import TaskBar from './TaskBar.vue'
import MilestonePoint from './MilestonePoint.vue'
@@ -18,6 +18,7 @@ import type { Resource } from '../models/classes/Resource'
import type { Milestone } from '../models/classes/Milestone'
import type { TimelineConfig } from '../models/configs/TimelineConfig'
import { TimelineScale } from '../models/types/TimelineScale'
import type { TooltipShowPayload } from '../models/types/TimelineDataTypes'
import { positionCache } from '../utils/positionCache' // v1.9.6 Phase1
// Props
@@ -181,6 +182,77 @@ const timelineBodyHeight = ref(0) // 容器高度状态管理
const resourceTaskLayouts = inject<ComputedRef<Map<string | number, any>>>('resourceTaskLayouts', computed(() => new Map()))
const resourceRowPositions = inject<ComputedRef<Map<string | number, number>>>('resourceRowPositions', computed(() => new Map()))
// Singleton Tooltip BTimeline
// TaskList Tooltip
const ganttTaskListWidth = inject<Ref<number>>('gantt-task-list-width', ref(0))
const tooltipState = reactive({
visible: false,
task: null as any,
taskStatus: { color: '#409eff', label: '' } as { color: string; label: string; type?: string },
resourcePercent: 100,
hasResourceConflict: false,
isBelow: false,
position: { x: 0, y: 0 },
})
/** 格式化日期显示 (YYYY-MM-DD) */
const formatTooltipDate = (dateStr: string | undefined): string => {
if (!dateStr) return t('dateNotSet')
return String(dateStr).substring(0, 10)
}
/** TaskBar 触发, Timeline 接受并计算定位 */
const handleTooltipShow = (payload: TooltipShowPayload) => {
if (props.enableTaskBarTooltip === false) return
const { task, taskStatus, resourcePercent, hasResourceConflict, targetRect } = payload
tooltipState.task = task
tooltipState.taskStatus = taskStatus
tooltipState.resourcePercent = resourcePercent
tooltipState.hasResourceConflict = hasResourceConflict
// TaskBar使 Timeline ref
const tooltipWidth = 250
const margin = 10
const viewportWidth = window.innerWidth
const viewportHeight = window.innerHeight
let x = targetRect.left + targetRect.width / 2
let y = targetRect.top - 10
const taskListRightBoundary = ganttTaskListWidth.value + margin
if (x - tooltipWidth / 2 < taskListRightBoundary) {
x = taskListRightBoundary + tooltipWidth / 2
}
if (x + tooltipWidth / 2 > viewportWidth - margin) {
x = viewportWidth - margin - tooltipWidth / 2
}
const spaceAbove = targetRect.top - margin
const spaceBelow = viewportHeight - targetRect.bottom - margin
const baseHeight = 80
const rowHeight = 24
let contentRows = 4
if (viewMode.value === 'resource' && resourcePercent < 100) contentRows += 1
if (hasResourceConflict) contentRows += 1
const estimatedTooltipHeight = baseHeight + contentRows * rowHeight
if (spaceAbove < estimatedTooltipHeight && spaceBelow > spaceAbove) {
y = targetRect.bottom + 10
tooltipState.isBelow = true
} else {
tooltipState.isBelow = false
}
tooltipState.position = { x, y }
tooltipState.visible = true
}
/** TaskBar 鼠标离开 / 拖拽 / Tab悬停时触发 */
const handleTooltipHide = () => {
tooltipState.visible = false
}
// 线
const cachedTodayCenteredRange = (() => {
const today = new Date()
@@ -3777,10 +3849,25 @@ const handleTimelineBodyScroll = (event: Event) => {
}
}
// SVG
// tasks.value.length watch invalidateTaskDateRangeCache
// / / / SVG
watch(
() => tasks.value.length,
() => {
() => tasks.value?.length,
(newLength, oldLength) => {
// ~4562 +
invalidateTaskDateRangeCache()
computeAllMilestonesPositions()
// ~4616
// bugfix: hasInitialAutoScroll
// updateTimelineRange()
// scrollToTodayCenter
if (oldLength === 0 && newLength > 0) {
hasInitialAutoScroll = false
debouncedUpdateTimelineRange(50)
}
// ~3854 SVG
nextTick(() => {
updateSvgSize()
})
@@ -4485,16 +4572,6 @@ const convertTaskToMilestone = (task: Task): Milestone => {
}
}
// tasks使 shallow watch
watch(
() => tasks.value.length,
() => {
invalidateTaskDateRangeCache()
computeAllMilestonesPositions()
},
{ immediate: true },
)
// setTimeout
let timelineUpdateTimer: number | null = null
@@ -4542,19 +4619,6 @@ const updateTimelineRange = () => {
}
}
watch(
() => tasks.value?.length,
(newLength, oldLength) => {
if (newLength !== oldLength) {
invalidateTaskDateRangeCache()
}
//
if (oldLength === 0 && newLength > 0) {
debouncedUpdateTimelineRange(50)
}
},
)
watch(
timelineContainerWidth,
(newWidth, oldWidth) => {
@@ -4597,11 +4661,16 @@ watch([timelineData, timelineContainerWidth], () => {
// viewModedataSource线
watch(
[viewMode, dataSource],
() => {
([newViewMode]) => {
invalidateTaskDateRangeCache()
// bugfix: hasInitialAutoScroll updateTimelineRange
// updateTimelineRange
// scrollToTodayCenter
if (newViewMode === 'task') {
hasInitialAutoScroll = false
}
debouncedUpdateTimelineRange()
},
{ deep: true },
)
// tasks
@@ -4626,7 +4695,6 @@ watch(
}
taskBarPositions.value = newPositions
},
{ deep: true },
)
//
@@ -5473,6 +5541,8 @@ const handleAddSuccessor = (task: Task) => {
@link-drag-start="handleLinkDragStart"
@link-drag-move="handleLinkDragMove"
@link-drag-end="handleLinkDragEnd"
@tooltip-show="handleTooltipShow"
@tooltip-hide="handleTooltipHide"
>
<template v-if="$slots['custom-task-content']" #custom-task-content="barScope">
<slot name="custom-task-content" v-bind="barScope" />
@@ -5567,6 +5637,8 @@ const handleAddSuccessor = (task: Task) => {
@link-drag-start="handleLinkDragStart"
@link-drag-move="handleLinkDragMove"
@link-drag-end="handleLinkDragEnd"
@tooltip-show="handleTooltipShow"
@tooltip-hide="handleTooltipHide"
>
<template v-if="$slots['custom-task-content']" #custom-task-content="barScope">
<slot name="custom-task-content" v-bind="barScope" />
@@ -5616,11 +5688,138 @@ const handleAddSuccessor = (task: Task) => {
</div>
</div>
</div>
<!-- Singleton Tooltip单一 Teleport所有 TaskBar 共享应支持 #taskbar-tooltip slot -->
<Teleport v-if="props.enableTaskBarTooltip !== false" to="body">
<div
v-if="tooltipState.visible"
class="task-hover-tooltip"
:class="{ 'tooltip-below': tooltipState.isBelow }"
:style="{
left: `${tooltipState.position.x}px`,
top: `${tooltipState.position.y}px`,
backgroundColor: tooltipState.taskStatus.color,
}"
>
<!-- 有自定义 slot消费方内容 -->
<template v-if="$slots['taskbar-tooltip']">
<slot
name="taskbar-tooltip"
:task="tooltipState.task"
:task-status="tooltipState.taskStatus"
:resource-percent="tooltipState.resourcePercent"
/>
</template>
<!-- 默认内容向后兼容 -->
<template v-else>
<div
class="hover-tooltip-arrow"
:style="{
borderTopColor: tooltipState.isBelow ? 'transparent' : tooltipState.taskStatus.color,
borderBottomColor: tooltipState.isBelow ? tooltipState.taskStatus.color : 'transparent',
}"
/>
<div class="hover-tooltip-content">
<div class="hover-tooltip-title">{{ tooltipState.task?.name }}</div>
<div class="hover-tooltip-row">
<span class="hover-tooltip-label">{{ t('plannedStartDate') }}:</span>
<span class="hover-tooltip-value">{{ formatTooltipDate(tooltipState.task?.startDate) }}</span>
</div>
<div class="hover-tooltip-row">
<span class="hover-tooltip-label">{{ t('plannedEndDate') }}:</span>
<span class="hover-tooltip-value">{{ formatTooltipDate(tooltipState.task?.endDate) }}</span>
</div>
<div class="hover-tooltip-row">
<span class="hover-tooltip-label">{{ t('actualStartDate') }}:</span>
<span class="hover-tooltip-value">{{ tooltipState.task?.actualStartDate ? formatTooltipDate(tooltipState.task.actualStartDate) : '-' }}</span>
</div>
<div class="hover-tooltip-row">
<span class="hover-tooltip-label">{{ t('actualEndDate') }}:</span>
<span class="hover-tooltip-value">{{ tooltipState.task?.actualEndDate ? formatTooltipDate(tooltipState.task.actualEndDate) : '-' }}</span>
</div>
</div>
</template>
</div>
</Teleport>
</template>
<style scoped>
@import '../styles/theme-variables.css';
/* ─── Singleton Tooltip CSS(从 TaskBar.vue 迁移至此) ─────────────────────── */
.task-hover-tooltip {
position: fixed;
background-color: rgba(0, 0, 0, 0.85);
color: white;
padding: 10px 14px;
border-radius: 6px;
font-size: 12px;
z-index: 999999999;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
pointer-events: none;
transform: translate(-50%, -100%);
margin-top: -8px;
min-width: 150px;
}
.task-hover-tooltip.tooltip-below {
transform: translate(-50%, 0);
margin-top: 0;
}
.hover-tooltip-arrow {
position: absolute;
left: 50%;
bottom: -5px;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid rgba(0, 0, 0, 0.85);
border-bottom: 0;
}
.tooltip-below .hover-tooltip-arrow {
bottom: auto;
top: -5px;
border-top: 0;
border-bottom: 6px solid rgba(0, 0, 0, 0.85);
}
.hover-tooltip-content {
display: flex;
flex-direction: column;
gap: 6px;
}
.hover-tooltip-title {
font-weight: 600;
font-size: 13px;
margin-bottom: 4px;
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
padding-bottom: 4px;
}
.hover-tooltip-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
}
.hover-tooltip-label {
opacity: 0.9;
font-size: 11px;
white-space: nowrap;
}
.hover-tooltip-value {
font-weight: 500;
text-align: right;
font-size: 11px;
}
.timeline {
height: 100%;
display: flex;
+28 -4
View File
@@ -247,8 +247,15 @@ const messages = {
maxWidth: '最大宽度',
pixelsModel: '像素 (px)',
percentageModel: '百分比 (%)',
},
},
}, collapsible: {
title: '展开/收起控制',
enableCollapsible: '启用任务列可折叠',
enableCollapsibleHint: '启用后任务列可展开/收起',
visible: '任务列显示状态',
visibleExpanded: '展开',
visibleCollapsed: '收起',
visibleHint: '通过 prop 控制任务列初始/当前显示状态',
}, },
// TaskBar配置
taskBarConfig: {
title: 'TaskBar 配置',
@@ -289,6 +296,11 @@ const messages = {
description: 'data.json · 含完整前/后置依赖,适合功能演示',
badge: 'data.json',
},
medium: {
label: '中等数据源',
description: 'data-100.json · 含完整前/后置依赖,适合功能演示',
badge: 'data-100.json',
},
large: {
label: '超大数据源',
description: 'data-large-1m.json · 百万级任务,验证虚拟渲染性能',
@@ -551,8 +563,15 @@ const messages = {
maxWidth: 'Max Width',
pixelsModel: 'pixels (px)',
percentageModel: 'percentage (%)',
},
},
}, collapsible: {
title: 'Collapsible Control',
enableCollapsible: 'Enable collapsible task list',
enableCollapsibleHint: 'Allow task list to expand/collapse when enabled',
visible: 'Task list visibility',
visibleExpanded: 'Expanded',
visibleCollapsed: 'Collapsed',
visibleHint: 'Controls initial/current visibility state of the task list',
}, },
// TaskBar配置
taskBarConfig: {
title: 'TaskBar Configuration',
@@ -593,6 +612,11 @@ const messages = {
description: 'data.json · Full predecessor graph for feature demos',
badge: 'data.json',
},
medium: {
label: 'Medium Dataset',
description: 'data-100.json · Full predecessor graph for feature demos',
badge: 'data-100.json',
},
large: {
label: 'Massive Dataset',
description: 'data-large-1m.json · Million-level tasks to stress virtual rendering',
+1
View File
@@ -14,4 +14,5 @@ export interface ToolbarConfig {
timeScaleDimensions?: TimelineScale[] // 设置时间刻度按钮的展示维度
defaultTimeScale?: TimelineScale // 默认选中的时间刻度
showExpandCollapse?: boolean // 显示全部展开/折叠按钮
showViewMode?: boolean // 显示 Task/Resource 视图切换按钮组
}
+24
View File
@@ -2,6 +2,7 @@
*
*
*/
import type { Task } from '../classes/Task'
// 基础时间单位接口
export interface TimelineHour {
@@ -129,3 +130,26 @@ export interface TimelineCacheKey {
scale: string
workingHours: string
}
// ─── Tooltip 相关类型(Singleton Tooltip 方案) ───────────────────────────────
/**
* TaskBar Timeline emit tooltip-show
*/
export interface TooltipShowPayload {
task: Task
taskStatus: { color: string; label: string; type?: string }
resourcePercent: number
hasResourceConflict: boolean
/** TaskBar 元素的 DOMRect,用于 Timeline 计算定位 */
targetRect: DOMRect
}
/**
* #taskbar-tooltip scoped slot
*/
export interface TaskbarTooltipSlotScope {
task: Task
taskStatus: { color: string; label: string }
resourcePercent: number
}