v1.9.3 - fix taskbar cal issue

This commit is contained in:
LINING-PC\lining
2026-03-05 13:01:31 +08:00
parent 58edf95f5a
commit fb21be2cf9
12 changed files with 270 additions and 80 deletions
+26 -19
View File
@@ -77,6 +77,7 @@ const props = withDefaults(defineProps<Props>(), {
theme: undefined, // 不设置默认值,允许自动检测系统主题
enableTaskListCollapsible: true,
taskListVisible: true,
enableTaskDrawerAutoClose: true,
})
const emit = defineEmits([
@@ -559,6 +560,12 @@ interface Props {
* 可通过 prop 传入或通过 defineExpose 暴露的方法控制
*/
taskListVisible?: boolean
/**
* 是否允许点击遮罩层关闭 TaskDrawer,默认为 true(保持原有行为)
* 设置为 false 可防止 TaskDrawer 在失去焦点(点击遮罩区域)时自动关闭
* 仅在 useDefaultDrawer=true 时有效
*/
enableTaskDrawerAutoClose?: boolean
}
// TaskList的固定总长度(所有列的最小宽度之和 + 边框等额外空间)
@@ -1601,31 +1608,30 @@ const tasksForTimeline = computed(() => {
}
// 递归更新任务树中所有父任务的时间范围(从叶子节点开始向上)
// 优化:叶子任务直接返回原引用,避免无效 { ...task } 对象创建
// (后续 smartFlattenTasks 会在 spread 时附加 level,不会污染原对象)
const updateParentDateRanges = (tasks: Task[]): Task[] => {
return tasks.map(task => {
let updatedTask = { ...task }
// 先递归更新子任务
if (task.children && task.children.length > 0) {
updatedTask.children = updateParentDateRanges(task.children)
// 叶子任务:无需创建新对象,直接返回原引用
if (!task.children || task.children.length === 0) {
return task
}
// 基于任务类型判断是否为父任务
const isParent =
task.type === 'story' || (updatedTask.children && updatedTask.children.length > 0)
updatedTask.isParent = isParent
// 父任务:先递归处理子任务
const updatedChildren = updateParentDateRanges(task.children)
const isParent = task.type === 'story' || true
// 如果是父任务且有子任务,重新计算时间范围
if (isParent && updatedTask.children && updatedTask.children.length > 0) {
const { startDate, endDate } = calculateParentDateRange(updatedTask)
updatedTask = {
...updatedTask,
startDate,
endDate,
}
// 重新计算父任务时间范围
const taskWithChildren = { ...task, children: updatedChildren }
const { startDate, endDate } = calculateParentDateRange(taskWithChildren)
return {
...task,
children: updatedChildren,
isParent,
startDate,
endDate,
}
return updatedTask
})
}
@@ -3530,6 +3536,7 @@ defineExpose({
:delay-task-background-color="props.delayTaskBackgroundColor"
:complete-task-background-color="props.completeTaskBackgroundColor"
:ongoing-task-background-color="props.ongoingTaskBackgroundColor"
:enable-close-on-overlay-click="props.enableTaskDrawerAutoClose"
@submit="handleTaskDrawerSubmit"
@close="taskDrawerVisible = false"
@start-timer="handleStartTimer"
+8 -2
View File
@@ -1569,8 +1569,14 @@ const handleMouseMove = (e: MouseEvent) => {
} else {
// 其他情况:使用原有的简单计算
const newStartDate = addDaysToLocalDate(props.startDate, newLeft / props.dayWidth)
const duration = dragStartWidth.value / props.dayWidth
const newEndDate = addDaysToLocalDate(newStartDate, duration - 1)
// 从原始任务日期计算持续天数,避免从像素宽度反推(dragStartWidth / dayWidth
// 导致的精度损失(WEEK视图 dayWidth=60/7 非整数,parseInt截断后往返计算误差会缩短任务)
const originalStartDate = createLocalDate(props.task.startDate) || props.startDate
const originalEndDate = createLocalDate(props.task.endDate) || props.startDate
const durationMs = originalEndDate.getTime() - originalStartDate.getTime()
const duration = Math.round(durationMs / (1000 * 60 * 60 * 24))
const newEndDate = addDaysToLocalDate(newStartDate, duration)
// 只更新临时数据,不触发事件
tempTaskData.value = {
+6 -1
View File
@@ -27,6 +27,8 @@ interface Props {
delayTaskBackgroundColor?: string
completeTaskBackgroundColor?: string
ongoingTaskBackgroundColor?: string
/** 是否允许点击遮罩层关闭抽屉,默认为 true(保持原有行为)。设置为 false 可防止失去焦点时自动关闭 */
enableCloseOnOverlayClick?: boolean
}
const props = withDefaults(defineProps<Props>(), {
@@ -45,6 +47,7 @@ const props = withDefaults(defineProps<Props>(), {
delayTaskBackgroundColor: '#f56c6c',
completeTaskBackgroundColor: '#67c23a',
ongoingTaskBackgroundColor: '#409eff',
enableCloseOnOverlayClick: true,
})
const emit = defineEmits<{
@@ -484,7 +487,9 @@ const handleClose = () => {
//
const handleOverlayClick = () => {
handleClose()
if (props.enableCloseOnOverlayClick) {
handleClose()
}
}
//
@@ -65,41 +65,31 @@ export function useTaskListLayout(tasks: Ref<Task[]>) {
const flattenedTasks = computed(() => getFlattenedVisibleTasks(tasks.value))
/**
* v1.9.0 /
* +
* O(n)
*/
const cumulativeHeights = computed<number[]>(() => {
if (viewMode.value === 'resource') {
// 资源视图:使用每个资源的实际高度
const resources = dataSource.value as Resource[]
const heights: number[] = [0] // 第一个位置是0
let cumulative = 0
if (viewMode.value !== 'resource') return []
resources.forEach(resource => {
const layout = resourceTaskLayouts.value.get(resource.id)
const height = layout?.totalHeight || ROW_HEIGHT
cumulative += height
heights.push(cumulative)
})
return heights
} else {
// 任务视图:使用固定行高
const heights: number[] = [0]
for (let i = 1; i <= flattenedTasks.value.length; i++) {
heights.push(i * ROW_HEIGHT)
}
return heights
}
const resources = dataSource.value as Resource[]
const heights: number[] = [0]
let cumulative = 0
resources.forEach(resource => {
const layout = resourceTaskLayouts.value.get(resource.id)
const height = layout?.totalHeight || ROW_HEIGHT
cumulative += height
heights.push(cumulative)
})
return heights
})
/**
* /
*
*/
const findIndexByScrollTop = (scrollTop: number): number => {
const heights = cumulativeHeights.value
let left = 0
let right = heights.length - 1
while (left < right) {
const mid = Math.floor((left + right) / 2)
if (heights[mid] <= scrollTop) {
@@ -108,38 +98,37 @@ export function useTaskListLayout(tasks: Ref<Task[]>) {
right = mid
}
}
return Math.max(0, left - 1)
}
/**
*
*
* - O(1) cumulativeHeights
* - 使 +
*/
const visibleTaskRange = computed<VisibleTaskRange>(() => {
const scrollTop = taskListScrollTop.value
const containerHeight = taskListBodyHeight.value || 600
const heights = cumulativeHeights.value
if (heights.length <= 1) {
return { startIndex: 0, endIndex: 0 }
}
if (viewMode.value === 'resource') {
const heights = cumulativeHeights.value
if (heights.length <= 1) return { startIndex: 0, endIndex: 0 }
// 找到起始索引(考虑缓冲区)
let startIndex = findIndexByScrollTop(scrollTop)
startIndex = Math.max(0, startIndex - VERTICAL_BUFFER)
// 找到结束索引(考虑缓冲区)
const scrollBottom = scrollTop + containerHeight
let endIndex = findIndexByScrollTop(scrollBottom)
endIndex = Math.min(heights.length - 1, endIndex + VERTICAL_BUFFER + 1)
const total = viewMode.value === 'resource'
? (dataSource.value as Resource[]).length
: flattenedTasks.value.length
return {
startIndex: Math.min(startIndex, total),
endIndex: Math.min(endIndex, total),
const total = (dataSource.value as Resource[]).length
let startIndex = Math.max(0, findIndexByScrollTop(scrollTop) - VERTICAL_BUFFER)
const scrollBottom = scrollTop + containerHeight
let endIndex = Math.min(heights.length - 1, findIndexByScrollTop(scrollBottom) + VERTICAL_BUFFER + 1)
return {
startIndex: Math.min(startIndex, total),
endIndex: Math.min(endIndex, total),
}
} else {
// 任务视图:固定行高 ROW_HEIGHTO(1) 直接计算,不访问 cumulativeHeights
const total = flattenedTasks.value.length
if (total === 0) return { startIndex: 0, endIndex: 0 }
const startIndex = Math.max(0, Math.floor(scrollTop / ROW_HEIGHT) - VERTICAL_BUFFER)
const endIndex = Math.min(total, Math.ceil((scrollTop + containerHeight) / ROW_HEIGHT) + VERTICAL_BUFFER)
return { startIndex, endIndex }
}
})
@@ -149,7 +138,6 @@ export function useTaskListLayout(tasks: Ref<Task[]>) {
const visibleTasks = computed<TaskWithLevelAndIndex[]>(() => {
const { startIndex, endIndex } = visibleTaskRange.value
const slicedTasks = flattenedTasks.value.slice(startIndex, endIndex)
// 添加 rowIndex(基于在扁平化列表中的实际索引)
return slicedTasks.map((item, index) => ({
...item,
rowIndex: startIndex + index,
@@ -157,22 +145,31 @@ export function useTaskListLayout(tasks: Ref<Task[]>) {
})
/**
* Spacer
* Spacer
* O(1)
*/
const totalContentHeight = computed(() => {
const heights = cumulativeHeights.value
return heights.length > 0 ? heights[heights.length - 1] : 0
if (viewMode.value === 'resource') {
const heights = cumulativeHeights.value
return heights.length > 0 ? heights[heights.length - 1] : 0
}
return flattenedTasks.value.length * ROW_HEIGHT
})
const startSpacerHeight = computed(() => {
const startIdx = visibleTaskRange.value.startIndex
return cumulativeHeights.value[startIdx] || 0
if (viewMode.value === 'resource') {
return cumulativeHeights.value[visibleTaskRange.value.startIndex] || 0
}
return visibleTaskRange.value.startIndex * ROW_HEIGHT
})
const endSpacerHeight = computed(() => {
const endIdx = visibleTaskRange.value.endIndex
const endHeight = cumulativeHeights.value[endIdx] || 0
return Math.max(0, totalContentHeight.value - endHeight)
if (viewMode.value === 'resource') {
const endIdx = visibleTaskRange.value.endIndex
const endHeight = cumulativeHeights.value[endIdx] || 0
return Math.max(0, totalContentHeight.value - endHeight)
}
return Math.max(0, (flattenedTasks.value.length - visibleTaskRange.value.endIndex) * ROW_HEIGHT)
})
return {