v1.9.3 - fix taskbar cal issue
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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_HEIGHT,O(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 {
|
||||
|
||||
Reference in New Issue
Block a user