i#ICZHVS 增加task内容支持自定义渲染
This commit is contained in:
+6
-1
@@ -8,6 +8,7 @@ import packageInfo from '../package.json'
|
|||||||
// 导入主题变量
|
// 导入主题变量
|
||||||
import '../src/styles/theme-variables.css'
|
import '../src/styles/theme-variables.css'
|
||||||
import VersionHistoryDrawer from './VersionHistoryDrawer.vue'
|
import VersionHistoryDrawer from './VersionHistoryDrawer.vue'
|
||||||
|
import HtmlContent from './HtmlContent.vue'
|
||||||
import { useMessage } from '../src/composables/useMessage'
|
import { useMessage } from '../src/composables/useMessage'
|
||||||
import { useI18n } from '../src/composables/useI18n'
|
import { useI18n } from '../src/composables/useI18n'
|
||||||
import { getPredecessorIds, predecessorIdsToString } from '../src/utils/predecessorUtils'
|
import { getPredecessorIds, predecessorIdsToString } from '../src/utils/predecessorUtils'
|
||||||
@@ -666,7 +667,11 @@ function onTimerStopped(task: Task) {
|
|||||||
@task-deleted="e => showMessage(`Demo 任务[${e.task.name}] 已删除`, 'info')"
|
@task-deleted="e => showMessage(`Demo 任务[${e.task.name}] 已删除`, 'info')"
|
||||||
@task-added="e => showMessage(`Demo 任务[${e.task.name}] 已创建`, 'info')"
|
@task-added="e => showMessage(`Demo 任务[${e.task.name}] 已创建`, 'info')"
|
||||||
@task-updated="e => showMessage(`Demo 任务[${e.task.name}] 已更新`, 'info')"
|
@task-updated="e => showMessage(`Demo 任务[${e.task.name}] 已更新`, 'info')"
|
||||||
/>
|
>
|
||||||
|
<template #custom-task-content="{ task, type }">
|
||||||
|
<HtmlContent :task="task" :type="type" />
|
||||||
|
</template>
|
||||||
|
</GanttChart>
|
||||||
</div>
|
</div>
|
||||||
<div class="license-info">
|
<div class="license-info">
|
||||||
<p>MIT License @JORDIUM.COM</p>
|
<p>MIT License @JORDIUM.COM</p>
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import type { Task } from '../src/models/Task'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
task: Task,
|
||||||
|
type: 'task-row' | 'task-bar'
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
color: '#409eff',
|
||||||
|
progress: 0,
|
||||||
|
})
|
||||||
|
console.error("props", props)
|
||||||
|
function handleButtonClick() {
|
||||||
|
alert('hello')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="html-content-card" :style="{ borderColor: color }">
|
||||||
|
<div v-if="type==='task-row'" class="task-row" v-html="task.name" />
|
||||||
|
<div v-else-if="type==='task-bar'" class="task-bar" v-html="task.name" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.html-content-card {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-row {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 4px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-bar {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
height: 20px;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+2
-2
@@ -27,7 +27,7 @@
|
|||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": 1101,
|
"id": 1101,
|
||||||
"name": "试验方案设计与伦理审查",
|
"name": "试验方案设计与<span style='font-weight: bold;color:red;'>伦理审查</span>",
|
||||||
"assignee": "方案设计师 李明",
|
"assignee": "方案设计师 李明",
|
||||||
"startDate": "2025-01-01",
|
"startDate": "2025-01-01",
|
||||||
"endDate": "2025-02-28",
|
"endDate": "2025-02-28",
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": 1201,
|
"id": 1201,
|
||||||
"name": "多中心试验启动",
|
"name": "多中心试验<span style='font-weight: bold; color: blue;'>启动</span>",
|
||||||
"assignee": "项目经理 王芳",
|
"assignee": "项目经理 王芳",
|
||||||
"startDate": "2025-09-01",
|
"startDate": "2025-09-01",
|
||||||
"endDate": "2025-11-30",
|
"endDate": "2025-11-30",
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "jordium-gantt-vue3",
|
"name": "jordium-gantt-vue3",
|
||||||
"version": "1.1.0",
|
"version": "1.3.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "jordium-gantt-vue3",
|
"name": "jordium-gantt-vue3",
|
||||||
"version": "1.1.0",
|
"version": "1.3.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
|||||||
@@ -1536,7 +1536,11 @@ function handleTaskDelete(task: Task, deleteChildren?: boolean) {
|
|||||||
@add-predecessor="handleAddPredecessor"
|
@add-predecessor="handleAddPredecessor"
|
||||||
@add-successor="handleAddSuccessor"
|
@add-successor="handleAddSuccessor"
|
||||||
@delete="handleTaskDelete"
|
@delete="handleTaskDelete"
|
||||||
/>
|
>
|
||||||
|
<template v-if="$slots['custom-task-content']" #custom-task-content="rowScope">
|
||||||
|
<slot name="custom-task-content" v-bind="rowScope" />
|
||||||
|
</template>
|
||||||
|
</TaskList>
|
||||||
</div>
|
</div>
|
||||||
<div class="gantt-splitter" @mousedown="onMouseDown">
|
<div class="gantt-splitter" @mousedown="onMouseDown">
|
||||||
<!-- TaskList切换按钮 - 贴合splitter右侧 -->
|
<!-- TaskList切换按钮 - 贴合splitter右侧 -->
|
||||||
@@ -1584,7 +1588,11 @@ function handleTaskDelete(task: Task, deleteChildren?: boolean) {
|
|||||||
@add-predecessor="handleAddPredecessor"
|
@add-predecessor="handleAddPredecessor"
|
||||||
@add-successor="handleAddSuccessor"
|
@add-successor="handleAddSuccessor"
|
||||||
@delete="handleTaskDelete"
|
@delete="handleTaskDelete"
|
||||||
/>
|
>
|
||||||
|
<template v-if="$slots['custom-task-content']" #custom-task-content="barScope">
|
||||||
|
<slot name="custom-task-content" v-bind="barScope" />
|
||||||
|
</template>
|
||||||
|
</Timeline>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onUnmounted, onMounted, nextTick, watch } from 'vue'
|
import { ref, computed, onUnmounted, onMounted, nextTick, watch, useSlots } from 'vue'
|
||||||
import type { Task } from '../models/classes/Task'
|
import type { Task } from '../models/classes/Task'
|
||||||
import { TimelineScale } from '../models/types/TimelineScale'
|
import { TimelineScale } from '../models/types/TimelineScale'
|
||||||
import TaskContextMenu from './TaskContextMenu.vue'
|
import TaskContextMenu from './TaskContextMenu.vue'
|
||||||
@@ -41,11 +41,15 @@ const emit = defineEmits([
|
|||||||
'delete',
|
'delete',
|
||||||
'contextmenu', // 添加原生contextmenu事件声明
|
'contextmenu', // 添加原生contextmenu事件声明
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const slots = useSlots()
|
||||||
const { getTranslation } = useI18n()
|
const { getTranslation } = useI18n()
|
||||||
const t = (key: string): string => {
|
const t = (key: string): string => {
|
||||||
return getTranslation(key)
|
return getTranslation(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasContentSlot = computed(() => Boolean(slots.content))
|
||||||
|
|
||||||
// 日期工具函数 - 处理时区安全的日期创建和操作
|
// 日期工具函数 - 处理时区安全的日期创建和操作
|
||||||
const createLocalDate = (dateString: string | Date | undefined | null): Date | null => {
|
const createLocalDate = (dateString: string | Date | undefined | null): Date | null => {
|
||||||
if (!dateString) return null
|
if (!dateString) return null
|
||||||
@@ -1313,7 +1317,24 @@ onUnmounted(() => {
|
|||||||
@dblclick="handleTaskBarDoubleClick"
|
@dblclick="handleTaskBarDoubleClick"
|
||||||
>
|
>
|
||||||
<!-- 父级任务的标签 -->
|
<!-- 父级任务的标签 -->
|
||||||
<div v-if="isParent" class="parent-label">{{ task.name }} ({{ task.progress || 0 }}%)</div>
|
<div v-if="isParent" class="parent-label">
|
||||||
|
<slot
|
||||||
|
v-if="hasContentSlot"
|
||||||
|
name="content"
|
||||||
|
type="task-bar"
|
||||||
|
:task="task"
|
||||||
|
:status="taskStatus"
|
||||||
|
:status-type="taskStatus.type"
|
||||||
|
:is-parent="isParent"
|
||||||
|
:progress="task.progress || 0"
|
||||||
|
:current-time-scale="currentTimeScale"
|
||||||
|
:row-height="rowHeight"
|
||||||
|
:day-width="dayWidth"
|
||||||
|
/>
|
||||||
|
<block v-else>
|
||||||
|
{{ task.name }} ({{ task.progress || 0 }}%)
|
||||||
|
</block>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 完成进度条(非父级任务) -->
|
<!-- 完成进度条(非父级任务) -->
|
||||||
<div
|
<div
|
||||||
@@ -1340,7 +1361,20 @@ onUnmounted(() => {
|
|||||||
@mousedown="e => (isInteractionDisabled ? null : handleMouseDown(e, 'drag'))"
|
@mousedown="e => (isInteractionDisabled ? null : handleMouseDown(e, 'drag'))"
|
||||||
>
|
>
|
||||||
<!-- 任务名称 -->
|
<!-- 任务名称 -->
|
||||||
<div class="task-name" :style="getNameStyles()">
|
<slot
|
||||||
|
v-if="hasContentSlot"
|
||||||
|
name="content"
|
||||||
|
type="task-bar"
|
||||||
|
:task="task"
|
||||||
|
:status="taskStatus"
|
||||||
|
:status-type="taskStatus.type"
|
||||||
|
:is-parent="isParent"
|
||||||
|
:progress="task.progress || 0"
|
||||||
|
:current-time-scale="currentTimeScale"
|
||||||
|
:row-height="rowHeight"
|
||||||
|
:day-width="dayWidth"
|
||||||
|
/>
|
||||||
|
<div v-else class="task-name" :style="getNameStyles()">
|
||||||
{{ task.name }}
|
{{ task.name }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
import { ref, onMounted, onUnmounted, watch, useSlots, computed } from 'vue'
|
||||||
|
import type { Component } from 'vue'
|
||||||
import TaskRow from './TaskRow.vue'
|
import TaskRow from './TaskRow.vue'
|
||||||
import { useI18n } from '../composables/useI18n'
|
import { useI18n } from '../composables/useI18n'
|
||||||
import type { Task } from '../models/classes/Task'
|
import type { Task } from '../models/classes/Task'
|
||||||
@@ -7,7 +8,7 @@ import type { Task } from '../models/classes/Task'
|
|||||||
interface Props {
|
interface Props {
|
||||||
tasks?: Task[]
|
tasks?: Task[]
|
||||||
onTaskDoubleClick?: (task: Task) => void
|
onTaskDoubleClick?: (task: Task) => void
|
||||||
editComponent?: any
|
editComponent?: Component
|
||||||
useDefaultDrawer?: boolean
|
useDefaultDrawer?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +27,8 @@ const emit = defineEmits<{
|
|||||||
'add-successor': [task: Task] // 新增:添加后置任务事件
|
'add-successor': [task: Task] // 新增:添加后置任务事件
|
||||||
delete: [task: Task, deleteChildren?: boolean]
|
delete: [task: Task, deleteChildren?: boolean]
|
||||||
}>()
|
}>()
|
||||||
|
const slots = useSlots()
|
||||||
|
const hasRowSlot = computed(() => Boolean(slots['custom-task-content']))
|
||||||
|
|
||||||
// 多语言支持
|
// 多语言支持
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@@ -409,7 +412,11 @@ onUnmounted(() => {
|
|||||||
@add-predecessor="handleAddPredecessor"
|
@add-predecessor="handleAddPredecessor"
|
||||||
@add-successor="handleAddSuccessor"
|
@add-successor="handleAddSuccessor"
|
||||||
@delete="handleTaskDelete"
|
@delete="handleTaskDelete"
|
||||||
/>
|
>
|
||||||
|
<template v-if="hasRowSlot" #custom-task-content="rowScope">
|
||||||
|
<slot name="custom-task-content" v-bind="rowScope" />
|
||||||
|
</template>
|
||||||
|
</TaskRow>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+62
-31
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<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 { useI18n } from '../composables/useI18n'
|
||||||
import { formatPredecessorDisplay } from '../utils/predecessorUtils'
|
import { formatPredecessorDisplay } from '../utils/predecessorUtils'
|
||||||
import type { Task } from '../models/classes/Task'
|
import type { Task } from '../models/classes/Task'
|
||||||
@@ -29,8 +29,18 @@ const overtimeText = computed(() => t.value?.overtime ?? '')
|
|||||||
const overdueText = computed(() => t.value?.overdue ?? '')
|
const overdueText = computed(() => t.value?.overdue ?? '')
|
||||||
const daysText = computed(() => t.value?.days ?? '')
|
const daysText = computed(() => t.value?.days ?? '')
|
||||||
|
|
||||||
|
const slots = useSlots()
|
||||||
|
const hasContentSlot = computed(() => Boolean(slots['custom-task-content']))
|
||||||
|
|
||||||
const baseIndent = 10
|
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() {
|
function handleToggle() {
|
||||||
emit('toggle', props.task)
|
emit('toggle', props.task)
|
||||||
}
|
}
|
||||||
@@ -38,8 +48,8 @@ function handleToggle() {
|
|||||||
function handleRowClick() {
|
function handleRowClick() {
|
||||||
// 如果是普通父级任务(story类型或有子任务的任务,非里程碑分组),点击行也可以展开/收起
|
// 如果是普通父级任务(story类型或有子任务的任务,非里程碑分组),点击行也可以展开/收起
|
||||||
if (
|
if (
|
||||||
(props.task.type === 'story' || (props.task.children && props.task.children.length > 0)) &&
|
(isStoryTask.value || hasChildren.value) &&
|
||||||
props.task.type !== 'milestone-group'
|
!isMilestoneGroup.value
|
||||||
) {
|
) {
|
||||||
emit('toggle', props.task)
|
emit('toggle', props.task)
|
||||||
}
|
}
|
||||||
@@ -220,6 +230,29 @@ const handleTaskDelete = (task: Task, deleteChildren?: boolean) => {
|
|||||||
closeContextMenu()
|
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(() => {
|
onMounted(() => {
|
||||||
window.addEventListener('splitter-drag-start', handleSplitterDragStart)
|
window.addEventListener('splitter-drag-start', handleSplitterDragStart)
|
||||||
@@ -243,14 +276,11 @@ onUnmounted(() => {
|
|||||||
class="task-row"
|
class="task-row"
|
||||||
:class="{
|
:class="{
|
||||||
'task-row-hovered': isHovered,
|
'task-row-hovered': isHovered,
|
||||||
'parent-task':
|
'parent-task': isParentTask,
|
||||||
props.task.type === 'story' ||
|
'milestone-group-row': isMilestoneGroup,
|
||||||
(props.task.children && props.task.children.length > 0) ||
|
'task-type-story': isStoryTask,
|
||||||
props.task.type === 'milestone-group',
|
|
||||||
'milestone-group-row': props.task.type === 'milestone-group',
|
|
||||||
'task-type-story': props.task.type === 'story',
|
|
||||||
'task-type-task': props.task.type === 'task',
|
'task-type-task': props.task.type === 'task',
|
||||||
'task-type-milestone': props.task.type === 'milestone',
|
'task-type-milestone': isMilestoneTask
|
||||||
}"
|
}"
|
||||||
@click="handleRowClick"
|
@click="handleRowClick"
|
||||||
@dblclick="handleTaskRowDoubleClick"
|
@dblclick="handleTaskRowDoubleClick"
|
||||||
@@ -260,11 +290,7 @@ onUnmounted(() => {
|
|||||||
>
|
>
|
||||||
<div class="col col-name" :style="{ paddingLeft: indent }">
|
<div class="col col-name" :style="{ paddingLeft: indent }">
|
||||||
<span
|
<span
|
||||||
v-if="
|
v-if="(isStoryTask || hasChildren) && !isMilestoneGroup"
|
||||||
(props.task.type === 'story' ||
|
|
||||||
(props.task.children && props.task.children.length > 0)) &&
|
|
||||||
props.task.type !== 'milestone-group'
|
|
||||||
"
|
|
||||||
class="collapse-btn"
|
class="collapse-btn"
|
||||||
@click.stop="handleToggle"
|
@click.stop="handleToggle"
|
||||||
>
|
>
|
||||||
@@ -282,13 +308,13 @@ onUnmounted(() => {
|
|||||||
</span>
|
</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">
|
<span class="task-icon">
|
||||||
<!-- 里程碑分组图标 - 使用菱形图标 -->
|
<!-- 里程碑分组图标 - 使用菱形图标 -->
|
||||||
<svg
|
<svg
|
||||||
v-if="props.task.type === 'milestone-group'"
|
v-if="isMilestoneGroup"
|
||||||
width="16"
|
width="16"
|
||||||
height="16"
|
height="16"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@@ -301,9 +327,7 @@ onUnmounted(() => {
|
|||||||
</svg>
|
</svg>
|
||||||
<!-- 父级任务图标 -->
|
<!-- 父级任务图标 -->
|
||||||
<svg
|
<svg
|
||||||
v-else-if="
|
v-else-if="isStoryTask || hasChildren"
|
||||||
props.task.type === 'story' || (props.task.children && props.task.children.length > 0)
|
|
||||||
"
|
|
||||||
width="16"
|
width="16"
|
||||||
height="16"
|
height="16"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@@ -333,15 +357,18 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<span
|
<span
|
||||||
class="task-name-text"
|
class="task-name-text"
|
||||||
:class="{
|
:class="{ 'parent-task': isParentTask }"
|
||||||
'parent-task':
|
|
||||||
props.task.type === 'story' ||
|
|
||||||
(props.task.children && props.task.children.length > 0) ||
|
|
||||||
props.task.type === 'milestone-group',
|
|
||||||
}"
|
|
||||||
:title="props.task.name"
|
:title="props.task.name"
|
||||||
>
|
>
|
||||||
|
<slot
|
||||||
|
v-if="hasContentSlot"
|
||||||
|
name="custom-task-content"
|
||||||
|
v-bind="slotPayload"
|
||||||
|
type="task-row"
|
||||||
|
/>
|
||||||
|
<template v-else>
|
||||||
{{ props.task.name }}
|
{{ props.task.name }}
|
||||||
|
</template>
|
||||||
<!-- 计时器显示 -->
|
<!-- 计时器显示 -->
|
||||||
<span
|
<span
|
||||||
v-if="props.task.isTimerRunning || props.task.timerElapsedTime"
|
v-if="props.task.isTimerRunning || props.task.timerElapsedTime"
|
||||||
@@ -359,7 +386,7 @@ onUnmounted(() => {
|
|||||||
</div>
|
</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-pre milestone-empty-col"></div>
|
||||||
<div class="col col-assignee milestone-empty-col"></div>
|
<div class="col col-assignee milestone-empty-col"></div>
|
||||||
<div class="col col-date 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.estimatedHours || '-' }}</div>
|
||||||
<div class="col col-hours">{{ props.task.actualHours || '-' }}</div>
|
<div class="col col-hours">{{ props.task.actualHours || '-' }}</div>
|
||||||
<div class="col col-progress">
|
<div class="col col-progress">
|
||||||
<span class="progress-value" :class="getProgressClass()">
|
<span class="progress-value" :class="progressClass">
|
||||||
{{ props.task.progress != null ? props.task.progress + '%' : '-' }}
|
{{ props.task.progress != null ? props.task.progress + '%' : '-' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<template
|
<template
|
||||||
v-if="props.task.children && !props.task.collapsed && props.task.type !== 'milestone-group'"
|
v-if="hasChildren && !props.task.collapsed && !isMilestoneGroup"
|
||||||
>
|
>
|
||||||
<TaskRow
|
<TaskRow
|
||||||
v-for="child in props.task.children"
|
v-for="child in props.task.children"
|
||||||
@@ -410,7 +437,11 @@ onUnmounted(() => {
|
|||||||
@add-predecessor="emit('add-predecessor', $event)"
|
@add-predecessor="emit('add-predecessor', $event)"
|
||||||
@add-successor="emit('add-successor', $event)"
|
@add-successor="emit('add-successor', $event)"
|
||||||
@delete="handleTaskDelete"
|
@delete="handleTaskDelete"
|
||||||
/>
|
>
|
||||||
|
<template v-if="hasContentSlot" #custom-task-content="childScope">
|
||||||
|
<slot name="custom-task-content" v-bind="childScope" />
|
||||||
|
</template>
|
||||||
|
</TaskRow>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<TaskContextMenu
|
<TaskContextMenu
|
||||||
|
|||||||
@@ -1617,7 +1617,7 @@ const handleMouseDown = (event: MouseEvent) => {
|
|||||||
'input',
|
'input',
|
||||||
'select',
|
'select',
|
||||||
'textarea',
|
'textarea',
|
||||||
'.task-bar-content',
|
'.custom-task-content',
|
||||||
'.progress-bar',
|
'.progress-bar',
|
||||||
'.task-name',
|
'.task-name',
|
||||||
'.task-controls',
|
'.task-controls',
|
||||||
@@ -2754,7 +2754,11 @@ const handleAddSuccessor = (task: Task) => {
|
|||||||
@add-predecessor="handleAddPredecessor"
|
@add-predecessor="handleAddPredecessor"
|
||||||
@add-successor="handleAddSuccessor"
|
@add-successor="handleAddSuccessor"
|
||||||
@delete="handleTaskDelete"
|
@delete="handleTaskDelete"
|
||||||
/>
|
>
|
||||||
|
<template v-if="$slots['custom-task-content']" #content="barScope">
|
||||||
|
<slot name="custom-task-content" v-bind="barScope" />
|
||||||
|
</template>
|
||||||
|
</TaskBar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,4 +19,7 @@ export default defineConfig(({ command }) => ({
|
|||||||
'@': '../src',
|
'@': '../src',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
server: {
|
||||||
|
port: 13000,
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
|
|||||||
Reference in New Issue
Block a user