i#ICZHVS 增加task内容支持自定义渲染
This commit is contained in:
+63
-32
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, computed, watch } from 'vue'
|
||||
import { ref, onMounted, onUnmounted, computed, watch, useSlots } from 'vue'
|
||||
import { useI18n } from '../composables/useI18n'
|
||||
import { formatPredecessorDisplay } from '../utils/predecessorUtils'
|
||||
import type { Task } from '../models/classes/Task'
|
||||
@@ -29,8 +29,18 @@ const overtimeText = computed(() => t.value?.overtime ?? '')
|
||||
const overdueText = computed(() => t.value?.overdue ?? '')
|
||||
const daysText = computed(() => t.value?.days ?? '')
|
||||
|
||||
const slots = useSlots()
|
||||
const hasContentSlot = computed(() => Boolean(slots['custom-task-content']))
|
||||
|
||||
const baseIndent = 10
|
||||
const indent = `${baseIndent + props.level * 20}px`
|
||||
const indent = computed(() => `${baseIndent + props.level * 20}px`)
|
||||
const hasChildren = computed(() => !!(props.task.children && props.task.children.length > 0))
|
||||
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,
|
||||
)
|
||||
function handleToggle() {
|
||||
emit('toggle', props.task)
|
||||
}
|
||||
@@ -38,8 +48,8 @@ function handleToggle() {
|
||||
function handleRowClick() {
|
||||
// 如果是普通父级任务(story类型或有子任务的任务,非里程碑分组),点击行也可以展开/收起
|
||||
if (
|
||||
(props.task.type === 'story' || (props.task.children && props.task.children.length > 0)) &&
|
||||
props.task.type !== 'milestone-group'
|
||||
(isStoryTask.value || hasChildren.value) &&
|
||||
!isMilestoneGroup.value
|
||||
) {
|
||||
emit('toggle', props.task)
|
||||
}
|
||||
@@ -220,6 +230,29 @@ const handleTaskDelete = (task: Task, deleteChildren?: boolean) => {
|
||||
closeContextMenu()
|
||||
}
|
||||
|
||||
const progressClass = computed(() => getProgressClass())
|
||||
|
||||
const slotPayload = computed(() => ({
|
||||
isRowContent: true,
|
||||
task: props.task,
|
||||
level: props.level,
|
||||
indent: indent.value,
|
||||
isHovered: props.isHovered ?? false,
|
||||
hoveredTaskId: props.hoveredTaskId ?? null,
|
||||
isParent: isParentTask.value,
|
||||
hasChildren: hasChildren.value,
|
||||
collapsed: !!props.task.collapsed,
|
||||
formattedTimer: formattedTimer.value,
|
||||
timerRunning: !!props.task.isTimerRunning,
|
||||
timerElapsed: timerElapsed.value,
|
||||
isOvertime: isOvertime(),
|
||||
overdueDays: overdueDays(),
|
||||
overtimeText: overtimeText.value,
|
||||
overdueText: overdueText.value,
|
||||
daysText: daysText.value,
|
||||
progressClass: progressClass.value,
|
||||
}))
|
||||
|
||||
// 生命周期钩子 - 注册事件监听器
|
||||
onMounted(() => {
|
||||
window.addEventListener('splitter-drag-start', handleSplitterDragStart)
|
||||
@@ -243,14 +276,11 @@ onUnmounted(() => {
|
||||
class="task-row"
|
||||
:class="{
|
||||
'task-row-hovered': isHovered,
|
||||
'parent-task':
|
||||
props.task.type === 'story' ||
|
||||
(props.task.children && props.task.children.length > 0) ||
|
||||
props.task.type === 'milestone-group',
|
||||
'milestone-group-row': props.task.type === 'milestone-group',
|
||||
'task-type-story': props.task.type === 'story',
|
||||
'parent-task': isParentTask,
|
||||
'milestone-group-row': isMilestoneGroup,
|
||||
'task-type-story': isStoryTask,
|
||||
'task-type-task': props.task.type === 'task',
|
||||
'task-type-milestone': props.task.type === 'milestone',
|
||||
'task-type-milestone': isMilestoneTask
|
||||
}"
|
||||
@click="handleRowClick"
|
||||
@dblclick="handleTaskRowDoubleClick"
|
||||
@@ -260,11 +290,7 @@ onUnmounted(() => {
|
||||
>
|
||||
<div class="col col-name" :style="{ paddingLeft: indent }">
|
||||
<span
|
||||
v-if="
|
||||
(props.task.type === 'story' ||
|
||||
(props.task.children && props.task.children.length > 0)) &&
|
||||
props.task.type !== 'milestone-group'
|
||||
"
|
||||
v-if="(isStoryTask || hasChildren) && !isMilestoneGroup"
|
||||
class="collapse-btn"
|
||||
@click.stop="handleToggle"
|
||||
>
|
||||
@@ -282,13 +308,13 @@ onUnmounted(() => {
|
||||
</span>
|
||||
|
||||
<!-- 里程碑分组的占位空间,用于与有折叠按钮的任务对齐 -->
|
||||
<span v-if="props.task.type === 'milestone-group'" class="milestone-spacer"></span>
|
||||
<span v-if="isMilestoneGroup" class="milestone-spacer"></span>
|
||||
|
||||
<!-- 任务图标 -->
|
||||
<span class="task-icon">
|
||||
<!-- 里程碑分组图标 - 使用菱形图标 -->
|
||||
<svg
|
||||
v-if="props.task.type === 'milestone-group'"
|
||||
v-if="isMilestoneGroup"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -301,9 +327,7 @@ onUnmounted(() => {
|
||||
</svg>
|
||||
<!-- 父级任务图标 -->
|
||||
<svg
|
||||
v-else-if="
|
||||
props.task.type === 'story' || (props.task.children && props.task.children.length > 0)
|
||||
"
|
||||
v-else-if="isStoryTask || hasChildren"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -333,15 +357,18 @@ onUnmounted(() => {
|
||||
|
||||
<span
|
||||
class="task-name-text"
|
||||
:class="{
|
||||
'parent-task':
|
||||
props.task.type === 'story' ||
|
||||
(props.task.children && props.task.children.length > 0) ||
|
||||
props.task.type === 'milestone-group',
|
||||
}"
|
||||
:class="{ 'parent-task': isParentTask }"
|
||||
:title="props.task.name"
|
||||
>
|
||||
{{ props.task.name }}
|
||||
<slot
|
||||
v-if="hasContentSlot"
|
||||
name="custom-task-content"
|
||||
v-bind="slotPayload"
|
||||
type="task-row"
|
||||
/>
|
||||
<template v-else>
|
||||
{{ props.task.name }}
|
||||
</template>
|
||||
<!-- 计时器显示 -->
|
||||
<span
|
||||
v-if="props.task.isTimerRunning || props.task.timerElapsedTime"
|
||||
@@ -359,7 +386,7 @@ onUnmounted(() => {
|
||||
</div>
|
||||
|
||||
<!-- 里程碑分组不显示详细信息 -->
|
||||
<template v-if="props.task.type === 'milestone-group'">
|
||||
<template v-if="isMilestoneGroup">
|
||||
<div class="col col-pre milestone-empty-col"></div>
|
||||
<div class="col col-assignee milestone-empty-col"></div>
|
||||
<div class="col col-date milestone-empty-col"></div>
|
||||
@@ -385,14 +412,14 @@ onUnmounted(() => {
|
||||
<div class="col col-hours">{{ props.task.estimatedHours || '-' }}</div>
|
||||
<div class="col col-hours">{{ props.task.actualHours || '-' }}</div>
|
||||
<div class="col col-progress">
|
||||
<span class="progress-value" :class="getProgressClass()">
|
||||
<span class="progress-value" :class="progressClass">
|
||||
{{ props.task.progress != null ? props.task.progress + '%' : '-' }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<template
|
||||
v-if="props.task.children && !props.task.collapsed && props.task.type !== 'milestone-group'"
|
||||
v-if="hasChildren && !props.task.collapsed && !isMilestoneGroup"
|
||||
>
|
||||
<TaskRow
|
||||
v-for="child in props.task.children"
|
||||
@@ -410,7 +437,11 @@ onUnmounted(() => {
|
||||
@add-predecessor="emit('add-predecessor', $event)"
|
||||
@add-successor="emit('add-successor', $event)"
|
||||
@delete="handleTaskDelete"
|
||||
/>
|
||||
>
|
||||
<template v-if="hasContentSlot" #custom-task-content="childScope">
|
||||
<slot name="custom-task-content" v-bind="childScope" />
|
||||
</template>
|
||||
</TaskRow>
|
||||
</template>
|
||||
|
||||
<TaskContextMenu
|
||||
|
||||
Reference in New Issue
Block a user