723 lines
19 KiB
Vue
723 lines
19 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
|
import { useI18n } from '../composables/useI18n'
|
|
import type { Task } from '../models/classes/Task'
|
|
|
|
interface Props {
|
|
task: Task
|
|
level: number
|
|
onDoubleClick?: (task: Task) => void
|
|
isHovered?: boolean
|
|
hoveredTaskId?: number | null
|
|
onHover?: (taskId: number | null) => void
|
|
}
|
|
const props = defineProps<Props>()
|
|
const emit = defineEmits(['toggle', 'dblclick'])
|
|
const { t } = useI18n()
|
|
const overtimeText = computed(() => t.value?.overtime ?? '')
|
|
const overdueText = computed(() => t.value?.overdue ?? '')
|
|
const daysText = computed(() => t.value?.days ?? '')
|
|
|
|
const baseIndent = 10
|
|
const indent = `${baseIndent + props.level * 20}px`
|
|
function handleToggle() {
|
|
emit('toggle', props.task)
|
|
}
|
|
|
|
function handleRowClick() {
|
|
// 如果是普通父级任务(story类型或有子任务的任务,非里程碑分组),点击行也可以展开/收起
|
|
if (
|
|
(props.task.type === 'story' || (props.task.children && props.task.children.length > 0)) &&
|
|
props.task.type !== 'milestone-group'
|
|
) {
|
|
emit('toggle', props.task)
|
|
}
|
|
}
|
|
|
|
// 处理TaskRow双击事件 (与TaskBar逻辑保持一致)
|
|
const handleTaskRowDoubleClick = (e: MouseEvent) => {
|
|
// 阻止事件冒泡
|
|
e.stopPropagation()
|
|
|
|
// 优先调用外部传入的双击处理器
|
|
if (props.onDoubleClick && typeof props.onDoubleClick === 'function') {
|
|
props.onDoubleClick(props.task)
|
|
} else {
|
|
// 默认行为:发出双击事件给父组件
|
|
emit('dblclick', props.task)
|
|
}
|
|
}
|
|
|
|
// 处理悬停事件
|
|
const handleMouseEnter = () => {
|
|
// 如果正在拖拽Splitter,忽略悬停事件
|
|
if (isSplitterDragging.value) return
|
|
|
|
if (props.onHover) {
|
|
props.onHover(props.task.id)
|
|
}
|
|
}
|
|
|
|
const handleMouseLeave = () => {
|
|
// 如果正在拖拽Splitter,忽略悬停事件
|
|
if (isSplitterDragging.value) return
|
|
|
|
if (props.onHover) {
|
|
props.onHover(null)
|
|
}
|
|
}
|
|
|
|
// 获取进度值的样式类
|
|
function getProgressClass() {
|
|
const progress = props.task.progress || 0
|
|
const today = new Date()
|
|
const endDate = props.task.endDate ? new Date(props.task.endDate) : null
|
|
|
|
// 超期且未完成
|
|
if (endDate && today > endDate && progress < 100) {
|
|
return 'progress-danger'
|
|
}
|
|
|
|
// 已完成
|
|
if (progress >= 100) {
|
|
return 'progress-success'
|
|
}
|
|
|
|
// 进行中
|
|
if (progress > 0) {
|
|
return 'progress-warning'
|
|
}
|
|
|
|
return ''
|
|
}
|
|
|
|
// 检查是否超时
|
|
function isOvertime() {
|
|
return (
|
|
props.task.actualHours &&
|
|
props.task.estimatedHours &&
|
|
props.task.actualHours > props.task.estimatedHours
|
|
)
|
|
}
|
|
|
|
// 检查是否逾期,返回天数
|
|
function overdueDays() {
|
|
const today = new Date()
|
|
const endDate = props.task.endDate ? new Date(props.task.endDate) : null
|
|
const progress = props.task.progress || 0
|
|
if (endDate && today > endDate && progress < 100) {
|
|
// 只计算日期部分
|
|
const t = new Date(today.getFullYear(), today.getMonth(), today.getDate())
|
|
const e = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate())
|
|
const diff = Math.floor((t.getTime() - e.getTime()) / (1000 * 60 * 60 * 24))
|
|
return diff
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// 拖拽状态管理
|
|
const isSplitterDragging = ref(false)
|
|
|
|
// 处理拖拽开始事件
|
|
const handleSplitterDragStart = () => {
|
|
isSplitterDragging.value = true
|
|
}
|
|
|
|
// 处理拖拽结束事件
|
|
const handleSplitterDragEnd = () => {
|
|
isSplitterDragging.value = false
|
|
}
|
|
|
|
// 生命周期钩子 - 注册事件监听器
|
|
onMounted(() => {
|
|
window.addEventListener('splitter-drag-start', handleSplitterDragStart)
|
|
window.addEventListener('splitter-drag-end', handleSplitterDragEnd)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
window.removeEventListener('splitter-drag-start', handleSplitterDragStart)
|
|
window.removeEventListener('splitter-drag-end', handleSplitterDragEnd)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<div
|
|
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',
|
|
'task-type-task': props.task.type === 'task',
|
|
'task-type-milestone': props.task.type === 'milestone',
|
|
}"
|
|
@click="handleRowClick"
|
|
@dblclick="handleTaskRowDoubleClick"
|
|
@mouseenter="handleMouseEnter"
|
|
@mouseleave="handleMouseLeave"
|
|
>
|
|
<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'
|
|
"
|
|
class="collapse-btn"
|
|
@click.stop="handleToggle"
|
|
>
|
|
<svg
|
|
width="12"
|
|
height="12"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<polyline v-if="props.task.collapsed" points="9,18 15,12 9,6" />
|
|
<polyline v-else points="18,15 12,9 6,15" />
|
|
</svg>
|
|
</span>
|
|
|
|
<!-- 里程碑分组的占位空间,用于与有折叠按钮的任务对齐 -->
|
|
<span v-if="props.task.type === 'milestone-group'" class="milestone-spacer"></span>
|
|
|
|
<!-- 任务图标 -->
|
|
<span class="task-icon">
|
|
<!-- 里程碑分组图标 - 使用菱形图标 -->
|
|
<svg
|
|
v-if="props.task.type === 'milestone-group'"
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
class="milestone-group-icon"
|
|
>
|
|
<polygon points="12,2 22,12 12,22 2,12" />
|
|
</svg>
|
|
<!-- 父级任务图标 -->
|
|
<svg
|
|
v-else-if="
|
|
props.task.type === 'story' || (props.task.children && props.task.children.length > 0)
|
|
"
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-5l-2-2H5a2 2 0 00-2 2z" />
|
|
</svg>
|
|
<!-- 普通任务图标 -->
|
|
<svg
|
|
v-else
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" />
|
|
<polyline points="14,2 14,8 20,8" />
|
|
<line x1="16" y1="13" x2="8" y2="13" />
|
|
<line x1="16" y1="17" x2="8" y2="17" />
|
|
<polyline points="10,9 9,9 8,9" />
|
|
</svg>
|
|
</span>
|
|
|
|
<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',
|
|
}"
|
|
:title="props.task.name"
|
|
>
|
|
{{ props.task.name }}
|
|
<span v-if="isOvertime()" class="status-badge overtime">{{ overtimeText }}</span>
|
|
<span v-if="overdueDays() > 0" class="status-badge overdue">
|
|
{{ overdueText }}{{ overdueDays() > 0 ? overdueDays() + daysText : '' }}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
|
|
<!-- 里程碑分组不显示详细信息 -->
|
|
<template v-if="props.task.type === 'milestone-group'">
|
|
<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>
|
|
<div class="col col-date milestone-empty-col"></div>
|
|
<div class="col col-hours milestone-empty-col"></div>
|
|
<div class="col col-hours milestone-empty-col"></div>
|
|
<div class="col col-progress milestone-empty-col"></div>
|
|
</template>
|
|
|
|
<!-- 普通任务显示详细信息 -->
|
|
<template v-else>
|
|
<div class="col col-pre">{{ props.task.predecessor || '-' }}</div>
|
|
<div class="col col-assignee">
|
|
<div class="assignee-info">
|
|
<div class="avatar">
|
|
{{ props.task.assignee ? props.task.assignee.charAt(0) : '-' }}
|
|
</div>
|
|
<span class="assignee-name">{{ props.task.assignee || '-' }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="col col-date">{{ props.task.startDate || '-' }}</div>
|
|
<div class="col col-date">{{ props.task.endDate || '-' }}</div>
|
|
<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()">
|
|
{{ 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'"
|
|
>
|
|
<TaskRow
|
|
v-for="child in props.task.children"
|
|
:key="child.id"
|
|
:task="child"
|
|
:level="props.level + 1"
|
|
:is-hovered="props.hoveredTaskId === child.id"
|
|
:hovered-task-id="props.hoveredTaskId"
|
|
:on-double-click="props.onDoubleClick"
|
|
:on-hover="props.onHover"
|
|
@toggle="emit('toggle', $event)"
|
|
@dblclick="emit('dblclick', $event)"
|
|
/>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
@import '../styles/theme-variables.css';
|
|
|
|
.task-row {
|
|
display: flex;
|
|
border-bottom: 1px solid var(--gantt-border-light);
|
|
height: 50px;
|
|
background: var(--gantt-bg-primary);
|
|
align-items: center;
|
|
color: var(--gantt-text-secondary);
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
transform: scale(1);
|
|
transform-origin: 5px center; /* 从左侧偏右5px的位置作为放大中心 */
|
|
z-index: 1;
|
|
position: relative;
|
|
}
|
|
|
|
.task-row:hover {
|
|
background-color: var(--gantt-bg-hover);
|
|
transform: scale(1.02);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
z-index: 10;
|
|
}
|
|
|
|
.task-row-hovered {
|
|
background-color: var(--gantt-bg-hover) !important;
|
|
transform: scale(1.02) !important;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
|
|
z-index: 10 !important;
|
|
}
|
|
|
|
.task-row.parent-task {
|
|
background: var(--gantt-bg-tertiary);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.task-row.parent-task:hover {
|
|
background: var(--gantt-bg-hover-parent, var(--gantt-bg-hover));
|
|
transform: scale(1.02);
|
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
|
|
z-index: 10;
|
|
}
|
|
|
|
.task-row.parent-task.task-row-hovered {
|
|
background: var(--gantt-bg-hover-parent, var(--gantt-bg-hover)) !important;
|
|
transform: scale(1.02) !important;
|
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2) !important;
|
|
z-index: 10 !important;
|
|
}
|
|
|
|
/* 里程碑分组行特殊样式 - 使用红色边框 */
|
|
.milestone-group-row {
|
|
border-left: 3px solid var(--gantt-danger, #f56c6c);
|
|
background: linear-gradient(90deg, var(--gantt-bg-tertiary) 0%, var(--gantt-bg-primary) 100%);
|
|
}
|
|
|
|
.milestone-group-row:hover {
|
|
background: linear-gradient(90deg, var(--gantt-bg-hover-parent) 0%, var(--gantt-bg-hover) 100%);
|
|
transform: scale(1.02);
|
|
box-shadow:
|
|
0 6px 16px rgba(245, 108, 108, 0.3),
|
|
0 2px 8px rgba(0, 0, 0, 0.1);
|
|
z-index: 10;
|
|
border-left-color: var(--gantt-danger, #f56c6c);
|
|
border-left-width: 4px; /* 悬停时边框稍微加粗 */
|
|
}
|
|
|
|
/* 任务类型左边框颜色 */
|
|
.task-type-story {
|
|
border-left: 3px solid var(--gantt-primary, #409eff);
|
|
}
|
|
|
|
.task-type-task {
|
|
border-left: 3px solid var(--gantt-warning, #e6a23c);
|
|
}
|
|
|
|
.task-type-milestone {
|
|
border-left: 3px solid var(--gantt-danger, #f56c6c);
|
|
}
|
|
|
|
/* 任务类型悬停时保持并增强左边框 */
|
|
.task-type-story:hover {
|
|
border-left: 5px solid var(--gantt-primary, #409eff);
|
|
}
|
|
|
|
.task-type-task:hover {
|
|
border-left: 5px solid var(--gantt-warning, #e6a23c);
|
|
}
|
|
|
|
.task-type-milestone:hover {
|
|
border-left: 5px solid var(--gantt-danger, #f56c6c);
|
|
}
|
|
|
|
/* 悬停状态下的左边框保持 */
|
|
.task-row-hovered.task-type-story {
|
|
border-left: 5px solid var(--gantt-primary, #409eff) !important;
|
|
}
|
|
|
|
.task-row-hovered.task-type-task {
|
|
border-left: 5px solid var(--gantt-warning, #e6a23c) !important;
|
|
}
|
|
|
|
.task-row-hovered.task-type-milestone {
|
|
border-left: 5px solid var(--gantt-danger, #f56c6c) !important;
|
|
}
|
|
|
|
:global(html[data-theme='dark']) .milestone-group-row {
|
|
border-left-color: var(--gantt-danger, #f67c7c);
|
|
}
|
|
|
|
/* 暗黑模式下的任务类型左边框颜色 */
|
|
:global(html[data-theme='dark']) .task-type-story {
|
|
border-left-color: var(--gantt-primary, #7db4f0);
|
|
}
|
|
|
|
:global(html[data-theme='dark']) .task-type-task {
|
|
border-left-color: var(--gantt-warning, #f0b83c);
|
|
}
|
|
|
|
:global(html[data-theme='dark']) .task-type-milestone {
|
|
border-left-color: var(--gantt-danger, #f67c7c);
|
|
}
|
|
|
|
.collapse-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 18px;
|
|
height: 18px;
|
|
cursor: pointer;
|
|
margin-right: 4px;
|
|
color: var(--gantt-primary);
|
|
border-radius: 2px;
|
|
transition: background-color 0.2s ease;
|
|
}
|
|
|
|
.collapse-btn:hover {
|
|
background-color: var(--gantt-primary-light);
|
|
}
|
|
|
|
.collapse-btn svg {
|
|
transition: transform 0.2s ease;
|
|
}
|
|
|
|
/* 里程碑分组占位空间 - 与折叠按钮对齐 */
|
|
.milestone-spacer {
|
|
display: inline-flex;
|
|
width: 18px;
|
|
height: 18px;
|
|
margin-right: 4px;
|
|
}
|
|
|
|
.col {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 100%;
|
|
border-right: 1px solid var(--gantt-border-light);
|
|
box-sizing: border-box;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.col:last-child {
|
|
border-right: none;
|
|
}
|
|
|
|
.col-name {
|
|
flex: 2 0 300px;
|
|
min-width: 300px;
|
|
justify-content: flex-start;
|
|
}
|
|
|
|
.col-pre {
|
|
flex: 1 0 120px;
|
|
min-width: 120px;
|
|
}
|
|
|
|
.col-assignee {
|
|
flex: 1 0 120px;
|
|
min-width: 120px;
|
|
}
|
|
|
|
.col-date {
|
|
flex: 1.2 0 140px;
|
|
min-width: 140px;
|
|
}
|
|
|
|
.col-hours {
|
|
flex: 1 0 100px;
|
|
min-width: 100px;
|
|
}
|
|
|
|
.col-progress {
|
|
flex: 1 0 100px;
|
|
min-width: 100px;
|
|
}
|
|
|
|
.task-name-text {
|
|
display: inline-block;
|
|
max-width: calc(100% - 24px);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.task-name-text.parent-task {
|
|
font-weight: bold;
|
|
color: var(--gantt-text-parent, var(--gantt-text-primary));
|
|
}
|
|
|
|
.task-icon {
|
|
margin-right: 4px;
|
|
color: var(--gantt-text-muted);
|
|
}
|
|
|
|
.task-icon svg {
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.assignee-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.avatar {
|
|
width: 24px;
|
|
height: 24px;
|
|
border-radius: 50%;
|
|
background: var(--gantt-primary);
|
|
color: var(--gantt-text-white);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
border: 2px solid var(--gantt-border-medium);
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.assignee-name {
|
|
font-size: 14px;
|
|
color: var(--gantt-text-secondary);
|
|
}
|
|
|
|
.progress-value {
|
|
font-weight: 500;
|
|
color: var(--gantt-text-secondary);
|
|
}
|
|
|
|
.progress-success {
|
|
color: var(--gantt-success);
|
|
}
|
|
|
|
.progress-warning {
|
|
color: var(--gantt-warning);
|
|
}
|
|
|
|
.progress-danger {
|
|
color: var(--gantt-danger);
|
|
}
|
|
|
|
.status-badge {
|
|
display: inline-block;
|
|
padding: 2px 6px;
|
|
border-radius: 2px;
|
|
font-size: 10px;
|
|
font-weight: bold;
|
|
margin-left: 6px;
|
|
color: white;
|
|
}
|
|
|
|
.status-badge.overtime {
|
|
background-color: transparent;
|
|
border: 1px solid var(--gantt-danger);
|
|
color: var(--gantt-danger);
|
|
}
|
|
|
|
.status-badge.overdue {
|
|
background-color: var(--gantt-danger);
|
|
}
|
|
|
|
/* 里程碑分组图标样式 - 统一使用红色并添加发光效果 */
|
|
.milestone-group-icon {
|
|
color: var(--gantt-danger, #f56c6c);
|
|
fill: var(--gantt-danger, #f56c6c);
|
|
opacity: 0.9;
|
|
filter: drop-shadow(0 0 6px var(--gantt-danger, #f56c6c));
|
|
animation: milestone-icon-glow 2.5s ease-in-out infinite alternate;
|
|
}
|
|
|
|
/* 里程碑图标悬停效果 */
|
|
.task-row:hover .milestone-group-icon {
|
|
filter: drop-shadow(0 0 10px var(--gantt-danger, #f56c6c))
|
|
drop-shadow(0 0 16px rgba(245, 108, 108, 0.4));
|
|
animation: milestone-icon-glow-intense 1.8s ease-in-out infinite alternate;
|
|
}
|
|
|
|
/* 里程碑图标发光动画 */
|
|
@keyframes milestone-icon-glow {
|
|
from {
|
|
filter: drop-shadow(0 0 3px var(--gantt-danger, #f56c6c));
|
|
}
|
|
to {
|
|
filter: drop-shadow(0 0 8px var(--gantt-danger, #f56c6c))
|
|
drop-shadow(0 0 12px rgba(245, 108, 108, 0.3));
|
|
}
|
|
}
|
|
|
|
@keyframes milestone-icon-glow-intense {
|
|
from {
|
|
filter: drop-shadow(0 0 8px var(--gantt-danger, #f56c6c))
|
|
drop-shadow(0 0 12px rgba(245, 108, 108, 0.3));
|
|
}
|
|
to {
|
|
filter: drop-shadow(0 0 12px var(--gantt-danger, #f56c6c))
|
|
drop-shadow(0 0 20px rgba(245, 108, 108, 0.5));
|
|
}
|
|
}
|
|
|
|
/* 里程碑行样式 - 统一使用红色 */
|
|
.milestone-item-icon {
|
|
color: var(--gantt-danger, #f56c6c);
|
|
}
|
|
|
|
.milestone-empty-col {
|
|
color: var(--gantt-text-disabled, #c0c4cc);
|
|
/* 确保边框颜色与普通数据行一致 */
|
|
border-right-color: var(--gantt-border-light) !important;
|
|
}
|
|
|
|
.milestone-empty-col::after {
|
|
content: '-';
|
|
}
|
|
|
|
/* 暗黑模式适配 */
|
|
:global(html[data-theme='dark']) .milestone-row-icon {
|
|
color: var(--gantt-danger, #f67c7c);
|
|
}
|
|
|
|
/* 暗黑模式下的里程碑图标发光效果 */
|
|
:global(html[data-theme='dark']) .milestone-group-icon {
|
|
color: var(--gantt-danger, #f67c7c);
|
|
fill: var(--gantt-danger, #f67c7c);
|
|
filter: drop-shadow(0 0 6px var(--gantt-danger, #f67c7c));
|
|
animation: milestone-icon-glow-dark 2.5s ease-in-out infinite alternate;
|
|
}
|
|
|
|
:global(html[data-theme='dark']) .task-row:hover .milestone-group-icon {
|
|
filter: drop-shadow(0 0 10px var(--gantt-danger, #f67c7c))
|
|
drop-shadow(0 0 16px rgba(246, 124, 124, 0.4));
|
|
animation: milestone-icon-glow-intense-dark 1.8s ease-in-out infinite alternate;
|
|
}
|
|
|
|
/* 暗黑模式发光动画 */
|
|
@keyframes milestone-icon-glow-dark {
|
|
from {
|
|
filter: drop-shadow(0 0 3px var(--gantt-danger, #f67c7c));
|
|
}
|
|
to {
|
|
filter: drop-shadow(0 0 8px var(--gantt-danger, #f67c7c))
|
|
drop-shadow(0 0 12px rgba(246, 124, 124, 0.3));
|
|
}
|
|
}
|
|
|
|
@keyframes milestone-icon-glow-intense-dark {
|
|
from {
|
|
filter: drop-shadow(0 0 8px var(--gantt-danger, #f67c7c))
|
|
drop-shadow(0 0 12px rgba(246, 124, 124, 0.3));
|
|
}
|
|
to {
|
|
filter: drop-shadow(0 0 12px var(--gantt-danger, #f67c7c))
|
|
drop-shadow(0 0 20px rgba(246, 124, 124, 0.5));
|
|
}
|
|
}
|
|
|
|
:global(html[data-theme='dark']) .milestone-empty-col {
|
|
color: var(--gantt-text-disabled, #606266);
|
|
/* 确保暗黑模式下边框颜色与普通数据行一致 */
|
|
border-right-color: var(--gantt-border-light) !important;
|
|
}
|
|
|
|
/* 暗黑模式的悬停效果 */
|
|
:global(html[data-theme='dark']) .task-row:hover {
|
|
box-shadow:
|
|
0 4px 12px rgba(255, 255, 255, 0.1),
|
|
0 2px 8px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
:global(html[data-theme='dark']) .task-row.task-row-hovered {
|
|
background-color: var(--gantt-bg-hover) !important;
|
|
box-shadow:
|
|
0 4px 12px rgba(255, 255, 255, 0.1),
|
|
0 2px 8px rgba(0, 0, 0, 0.3) !important;
|
|
}
|
|
|
|
:global(html[data-theme='dark']) .task-row.parent-task:hover {
|
|
box-shadow:
|
|
0 6px 16px rgba(255, 255, 255, 0.15),
|
|
0 2px 8px rgba(0, 0, 0, 0.4);
|
|
}
|
|
|
|
:global(html[data-theme='dark']) .task-row.parent-task.task-row-hovered {
|
|
background: var(--gantt-bg-hover-parent) !important;
|
|
box-shadow:
|
|
0 6px 16px rgba(255, 255, 255, 0.15),
|
|
0 2px 8px rgba(0, 0, 0, 0.4) !important;
|
|
}
|
|
|
|
:global(html[data-theme='dark']) .milestone-group-row:hover {
|
|
box-shadow:
|
|
0 6px 16px rgba(246, 124, 124, 0.4),
|
|
0 2px 8px rgba(255, 255, 255, 0.1);
|
|
}
|
|
</style>
|