v1.4.0 - new features
This commit is contained in:
@@ -126,7 +126,7 @@ const leftPanelWidth = ref(props.taskListConfig?.defaultWidth || 320)
|
||||
// 监听taskListConfig变化,更新相关配置
|
||||
watch(
|
||||
() => props.taskListConfig,
|
||||
(newConfig) => {
|
||||
newConfig => {
|
||||
if (newConfig) {
|
||||
// 更新默认宽度
|
||||
if (newConfig.defaultWidth !== undefined) {
|
||||
@@ -607,7 +607,8 @@ const tasksForTaskList = computed(() => {
|
||||
// 不排序:直接使用原始任务数据
|
||||
result.push(...props.tasks)
|
||||
}
|
||||
} return result
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
// 为Timeline提供正确的扁平化数据
|
||||
|
||||
+36
-31
@@ -42,6 +42,8 @@ interface TaskBarSlotProps {
|
||||
currentTimeScale?: TimelineScale
|
||||
rowHeight: number
|
||||
dayWidth: number
|
||||
// 新增:动态样式对象
|
||||
dynamicStyles: Record<string, string>
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -165,6 +167,9 @@ const quarterDragOverride = ref<{
|
||||
|
||||
const barRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const taskBarNameRef = ref<HTMLElement | null>(null)
|
||||
const nameTextWidth = ref(0)
|
||||
|
||||
// 计算任务条位置和宽度
|
||||
const taskBarStyle = computed(() => {
|
||||
// 季度视图拖拽时使用位置覆盖
|
||||
@@ -390,6 +395,7 @@ const slotPayload = computed(() => ({
|
||||
currentTimeScale: props.currentTimeScale,
|
||||
rowHeight: props.rowHeight,
|
||||
dayWidth: props.dayWidth,
|
||||
dynamicStyles: getNameStyles(),
|
||||
}))
|
||||
|
||||
// 判断是否已完成
|
||||
@@ -506,7 +512,7 @@ const handleMouseMove = (e: MouseEvent) => {
|
||||
dragTooltipVisible.value = true
|
||||
dragTooltipPosition.value = {
|
||||
x: e.clientX + 15, // 鼠标右侧偏移
|
||||
y: e.clientY - 60, // 鼠标上方偏移
|
||||
y: e.clientY - 60, // 鼠标上方偏移
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,9 +575,9 @@ const handleMouseMove = (e: MouseEvent) => {
|
||||
}
|
||||
|
||||
// 同时计算日期用于提示框(使用与Timeline相同的季度视图计算)
|
||||
const quarterWidth = 60 // 与Timeline.vue保持一致
|
||||
const daysInQuarter = 90 // 季度平均天数
|
||||
const pixelsPerDay = quarterWidth / daysInQuarter // 约0.67px/天
|
||||
const quarterWidth = 60 // 与Timeline.vue保持一致
|
||||
const daysInQuarter = 90 // 季度平均天数
|
||||
const pixelsPerDay = quarterWidth / daysInQuarter // 约0.67px/天
|
||||
const dayOffset = Math.round(deltaX / pixelsPerDay)
|
||||
|
||||
// 基于原始任务日期计算新日期
|
||||
@@ -659,9 +665,9 @@ const handleMouseMove = (e: MouseEvent) => {
|
||||
}
|
||||
|
||||
// 计算日期用于提示框(使用与Timeline相同的季度视图计算)
|
||||
const quarterWidth = 60 // 与Timeline.vue保持一致
|
||||
const daysInQuarter = 90 // 季度平均天数
|
||||
const pixelsPerDay = quarterWidth / daysInQuarter // 约0.67px/天
|
||||
const quarterWidth = 60 // 与Timeline.vue保持一致
|
||||
const daysInQuarter = 90 // 季度平均天数
|
||||
const pixelsPerDay = quarterWidth / daysInQuarter // 约0.67px/天
|
||||
const dayOffset = Math.round(deltaX / pixelsPerDay)
|
||||
|
||||
const originalStartDate = createLocalDate(props.task.startDate) || props.startDate
|
||||
@@ -737,9 +743,9 @@ const handleMouseMove = (e: MouseEvent) => {
|
||||
}
|
||||
|
||||
// 计算日期用于提示框(使用与Timeline相同的季度视图计算)
|
||||
const quarterWidth = 60 // 与Timeline.vue保持一致
|
||||
const daysInQuarter = 90 // 季度平均天数
|
||||
const pixelsPerDay = quarterWidth / daysInQuarter // 约0.67px/天
|
||||
const quarterWidth = 60 // 与Timeline.vue保持一致
|
||||
const daysInQuarter = 90 // 季度平均天数
|
||||
const pixelsPerDay = quarterWidth / daysInQuarter // 约0.67px/天
|
||||
const dayOffset = Math.round(deltaX / pixelsPerDay)
|
||||
|
||||
const originalEndDate = createLocalDate(props.task.endDate) || props.startDate
|
||||
@@ -835,6 +841,9 @@ const handleMouseUp = () => {
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
reportBarPosition()
|
||||
if (taskBarNameRef.value) {
|
||||
nameTextWidth.value = taskBarNameRef.value.getBoundingClientRect().width
|
||||
}
|
||||
})
|
||||
|
||||
// 监听时间刻度变化事件,重新计算位置
|
||||
@@ -936,8 +945,7 @@ const stickyStyles = computed(() => {
|
||||
let progressTop = ''
|
||||
|
||||
// 估算文字内容的实际位置
|
||||
const nameText = props.task.name || ''
|
||||
const nameWidth = Math.max(nameText.length * 7, 40)
|
||||
const nameWidth = Math.max(nameTextWidth.value, 40) // 最小40px
|
||||
const progressWidth = 35
|
||||
|
||||
// 计算名称和进度在默认居中状态下的位置
|
||||
@@ -959,12 +967,12 @@ const stickyStyles = computed(() => {
|
||||
const offset = leftBoundary - taskLeft
|
||||
nameLeft = `${offset + 8}px`
|
||||
namePosition = 'absolute'
|
||||
nameTop = '6px'
|
||||
nameTop = '2px'
|
||||
} else if (nameNeedsRightSticky) {
|
||||
const offset = rightBoundary - taskLeft - nameWidth
|
||||
nameLeft = `${offset - 8}px`
|
||||
namePosition = 'absolute'
|
||||
nameTop = '6px'
|
||||
nameTop = '2px'
|
||||
}
|
||||
|
||||
// 进度左侧边界粘性逻辑
|
||||
@@ -980,12 +988,12 @@ const stickyStyles = computed(() => {
|
||||
const offset = leftBoundary - taskLeft
|
||||
progressLeft = `${offset + 8}px`
|
||||
progressPosition = 'absolute'
|
||||
progressTop = '24px'
|
||||
progressTop = '18px'
|
||||
} else if (progressNeedsRightSticky) {
|
||||
const offset = rightBoundary - taskLeft - progressWidth
|
||||
progressLeft = `${offset - 8}px`
|
||||
progressPosition = 'absolute'
|
||||
progressTop = '24px'
|
||||
progressTop = '18px'
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -1529,14 +1537,8 @@ onUnmounted(() => {
|
||||
>
|
||||
<!-- 父级任务的标签 -->
|
||||
<div v-if="isParent" class="parent-label">
|
||||
<slot
|
||||
v-if="hasContentSlot"
|
||||
name="custom-task-content"
|
||||
v-bind="slotPayload"
|
||||
/>
|
||||
<template v-else>
|
||||
{{ task.name }} ({{ task.progress || 0 }}%)
|
||||
</template>
|
||||
<slot v-if="hasContentSlot" name="custom-task-content" v-bind="slotPayload" />
|
||||
<template v-else> {{ task.name }} ({{ task.progress || 0 }}%) </template>
|
||||
</div>
|
||||
|
||||
<!-- 完成进度条(非父级任务) -->
|
||||
@@ -1564,13 +1566,16 @@ onUnmounted(() => {
|
||||
@mousedown="e => (isInteractionDisabled ? null : handleMouseDown(e, 'drag'))"
|
||||
>
|
||||
<!-- 任务名称 -->
|
||||
<slot
|
||||
v-if="hasContentSlot"
|
||||
name="custom-task-content"
|
||||
v-bind="slotPayload"
|
||||
/>
|
||||
<div v-else class="task-name" :style="getNameStyles()">
|
||||
{{ task.name }}
|
||||
<div ref="taskBarNameRef">
|
||||
<slot
|
||||
v-if="hasContentSlot"
|
||||
name="custom-task-content"
|
||||
v-bind="slotPayload"
|
||||
:style="getNameStyles()"
|
||||
/>
|
||||
<div v-else class="task-name" :style="getNameStyles()">
|
||||
{{ task.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 进度百分比 -->
|
||||
|
||||
@@ -313,7 +313,7 @@ const handleTaskListScroll = (event: Event) => {
|
||||
detail: { scrollTop },
|
||||
}),
|
||||
)
|
||||
}// 处理Timeline垂直滚动同步
|
||||
} // 处理Timeline垂直滚动同步
|
||||
const handleTimelineVerticalScroll = (event: CustomEvent) => {
|
||||
const { scrollTop } = event.detail
|
||||
const taskListBodyElement = document.querySelector('.task-list-body') as HTMLElement
|
||||
@@ -406,7 +406,7 @@ onUnmounted(() => {
|
||||
:class="column.cssClass || `col-${column.key}`"
|
||||
:style="column.width ? { width: column.width + 'px' } : undefined"
|
||||
>
|
||||
{{ column.label || (t as any)[column.key] }}
|
||||
{{ (t as any)[column.key] || column.label }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-list-body" @scroll="handleTaskListScroll">
|
||||
|
||||
@@ -67,7 +67,7 @@ const isStoryTask = computed(() => props.task.type === 'story')
|
||||
const isMilestoneGroup = computed(() => props.task.type === 'milestone-group')
|
||||
const isMilestoneTask = computed(() => props.task.type === 'milestone')
|
||||
const isParentTask = computed(
|
||||
() => isStoryTask.value || hasChildren.value || isMilestoneGroup.value,
|
||||
() => isStoryTask.value || hasChildren.value || isMilestoneGroup.value
|
||||
)
|
||||
function handleToggle() {
|
||||
emit('toggle', props.task)
|
||||
@@ -75,10 +75,7 @@ function handleToggle() {
|
||||
|
||||
function handleRowClick() {
|
||||
// 如果是普通父级任务(story类型或有子任务的任务,非里程碑分组),点击行也可以展开/收起
|
||||
if (
|
||||
(isStoryTask.value || hasChildren.value) &&
|
||||
!isMilestoneGroup.value
|
||||
) {
|
||||
if ((isStoryTask.value || hasChildren.value) && !isMilestoneGroup.value) {
|
||||
emit('toggle', props.task)
|
||||
}
|
||||
}
|
||||
@@ -224,7 +221,7 @@ watch(
|
||||
updateTimer()
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 右键菜单相关状态
|
||||
@@ -308,7 +305,7 @@ onUnmounted(() => {
|
||||
'milestone-group-row': isMilestoneGroup,
|
||||
'task-type-story': isStoryTask,
|
||||
'task-type-task': props.task.type === 'task',
|
||||
'task-type-milestone': isMilestoneTask
|
||||
'task-type-milestone': isMilestoneTask,
|
||||
}"
|
||||
@click="handleRowClick"
|
||||
@dblclick="handleTaskRowDoubleClick"
|
||||
@@ -318,7 +315,7 @@ onUnmounted(() => {
|
||||
>
|
||||
<div class="col col-name" :style="{ paddingLeft: indent }">
|
||||
<span
|
||||
v-if="(isStoryTask || hasChildren) && !isMilestoneGroup"
|
||||
v-if="(isStoryTask || hasChildren) && !isMilestoneGroup"
|
||||
class="collapse-btn"
|
||||
@click.stop="handleToggle"
|
||||
>
|
||||
@@ -470,9 +467,7 @@ onUnmounted(() => {
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<template
|
||||
v-if="hasChildren && !props.task.collapsed && !isMilestoneGroup"
|
||||
>
|
||||
<template v-if="hasChildren && !props.task.collapsed && !isMilestoneGroup">
|
||||
<TaskRow
|
||||
v-for="child in props.task.children"
|
||||
:key="child.id"
|
||||
@@ -492,7 +487,7 @@ onUnmounted(() => {
|
||||
@delete="handleTaskDelete"
|
||||
>
|
||||
<template v-if="hasContentSlot" #custom-task-content="slotProps">
|
||||
<slot name="custom-task-content" v-bind="(slotProps as TaskRowSlotProps)" />
|
||||
<slot name="custom-task-content" v-bind="slotProps as TaskRowSlotProps" />
|
||||
</template>
|
||||
</TaskRow>
|
||||
</template>
|
||||
@@ -696,8 +691,8 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
min-width: 25px;
|
||||
min-height: 25px;
|
||||
border-radius: 50%;
|
||||
background: var(--gantt-primary);
|
||||
color: var(--gantt-text-white);
|
||||
|
||||
@@ -2419,8 +2419,10 @@ const handleAddSuccessor = (task: Task) => {
|
||||
v-if="subDay.date && subDay.date.getDate() === 1"
|
||||
class="month-first-flag"
|
||||
:style="{
|
||||
left: `${getGlobalWeekPosition(monthIndex, weekIndex) + dayIndex * (60/7)}px`,
|
||||
transform: 'translateX(-50%)' // 使旗帜中心(杆子)对齐日期位置
|
||||
left: `${
|
||||
getGlobalWeekPosition(monthIndex, weekIndex) + dayIndex * (60 / 7)
|
||||
}px`,
|
||||
transform: 'translateX(-50%)', // 使旗帜中心(杆子)对齐日期位置
|
||||
}"
|
||||
>
|
||||
<div class="flag-pole"></div>
|
||||
@@ -2551,8 +2553,10 @@ const handleAddSuccessor = (task: Task) => {
|
||||
v-if="subDay.date && subDay.date.getDate() === 1"
|
||||
class="month-first-vertical-line"
|
||||
:style="{
|
||||
left: `${getGlobalWeekPosition(monthIndex, weekIndex) + dayIndex * (60/7)}px`,
|
||||
height: `${contentHeight}px`
|
||||
left: `${
|
||||
getGlobalWeekPosition(monthIndex, weekIndex) + dayIndex * (60 / 7)
|
||||
}px`,
|
||||
height: `${contentHeight}px`,
|
||||
}"
|
||||
></div>
|
||||
</template>
|
||||
@@ -2691,7 +2695,7 @@ const handleAddSuccessor = (task: Task) => {
|
||||
class="sub-day-column"
|
||||
:class="{
|
||||
weekend: subDay.dayOfWeek === 0 || subDay.dayOfWeek === 6,
|
||||
today: isToday(subDay.date)
|
||||
today: isToday(subDay.date),
|
||||
}"
|
||||
:style="{ height: `${contentHeight}px`, width: '8.57px' }"
|
||||
></div>
|
||||
|
||||
Reference in New Issue
Block a user