v1.8.1 - hotfix - move theme settings from HTML root to Gantt Component root.

This commit is contained in:
LINING-PC\lining
2026-02-05 16:24:31 +08:00
parent 90f0f5bc60
commit e1d0180c92
22 changed files with 340 additions and 306 deletions

1
.gitignore vendored
View File

@@ -37,3 +37,4 @@ ehthumbs.db
Desktop.ini
yarn.lock
.ai/

View File

@@ -1864,7 +1864,6 @@ const handleCustomMenuAction = (action: string, task: Task) => {
:tasks="tasks"
:milestones="milestones"
:locale="controlMode === 'props' ? propsLocale : undefined"
:theme="controlMode === 'props' ? propsTheme : undefined"
:time-scale="controlMode === 'props' ? propsTimeScale : undefined"
:fullscreen="controlMode === 'props' ? propsFullscreen : undefined"
:expand-all="controlMode === 'props' ? propsExpandAll : undefined"

View File

@@ -492,5 +492,19 @@
"🎉 Optimized: The assignee and avatar properties of the Task object now accept arrays to mark multiple assignees",
"<span style=\"font-weight: bold; color: #f00;\">Special thanks to yunlbs/fhjfhj/jakepan@gitee for their use and suggestions</span>"
]
},
{
"version": "1.8.1",
"date": "2026-02-05",
"notes": [
"🎉 优化重构组件Theme作用域从全局调整至局部.gantt-root",
"🎉 优化重构组件Theme自动跟随系统设置",
"🎉 优化重构组件完美支持Nuxt3、TailwindCSS等框架",
"<span style=\"font-weight: bold; color: #f00;\">特别感谢andrijor@gmail.com的专业反馈</span>",
"🎉 Optimized: Refactored components, Theme scope adjusted from global to local .gantt-root",
"🎉 Optimized: Refactored components, Theme automatically follows system settings",
"🎉 Optimized: Refactored components, Perfectly supports Nuxt3, TailwindCSS and other frameworks",
"<span style=\"font-weight: bold; color: #f00;\">Special thanks to andrijor@gmail.com for their professional feedback</span>"
]
}
]

View File

@@ -1,6 +1,6 @@
{
"name": "jordium-gantt-vue3",
"version": "1.8.0",
"version": "1.8.1",
"type": "module",
"main": "npm-package/dist/jordium-gantt-vue3.cjs.js",
"module": "npm-package/dist/jordium-gantt-vue3.es.js",

View File

@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, watch, computed } from 'vue'
import { useI18n } from '../composables/useI18n'
const props = defineProps({
@@ -78,7 +78,7 @@ const messageTaskName = computed(() => {
gap: 16px;
}
:global(html[data-theme='dark']) .confirm-timer-dialog {
:global(.gantt-root[data-theme='dark']) .confirm-timer-dialog {
background: var(--gantt-bg-primary, #6b6b6b);
}
.dialog-message {

View File

@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
import { useI18n } from '../composables/useI18n'
@@ -1519,48 +1519,48 @@ const timePickerStyle = computed(() => {
}
/* 暗黑模式适配 */
:global(html[data-theme='dark']) .el-input__wrapper {
:global(.gantt-root[data-theme='dark']) .el-input__wrapper {
background-color: var(--gantt-bg-secondary, #2c2c2c);
box-shadow: 0 0 0 1px var(--gantt-border-dark, #414243) inset;
}
:global(html[data-theme='dark']) .el-input__inner,
:global(html[data-theme='dark']) .el-range-input__start,
:global(html[data-theme='dark']) .el-range-input__end,
:global(html[data-theme='dark']) .el-range-separator {
:global(.gantt-root[data-theme='dark']) .el-input__inner,
:global(.gantt-root[data-theme='dark']) .el-range-input__start,
:global(.gantt-root[data-theme='dark']) .el-range-input__end,
:global(.gantt-root[data-theme='dark']) .el-range-separator {
color: var(--gantt-text-white, #ffffff);
}
:global(html[data-theme='dark']) .el-input__inner-input::placeholder,
:global(html[data-theme='dark']) .el-range-input__start::placeholder,
:global(html[data-theme='dark']) .el-range-input__end::placeholder {
:global(.gantt-root[data-theme='dark']) .el-input__inner-input::placeholder,
:global(.gantt-root[data-theme='dark']) .el-range-input__start::placeholder,
:global(.gantt-root[data-theme='dark']) .el-range-input__end::placeholder {
color: var(--gantt-text-muted, #9e9e9e);
}
:global(html[data-theme='dark']) .el-date-picker.is-focused .el-input__wrapper {
:global(.gantt-root[data-theme='dark']) .el-date-picker.is-focused .el-input__wrapper {
box-shadow: 0 0 0 1px var(--gantt-primary, #409eff) inset;
}
:global(html[data-theme='dark']) .el-date-picker:hover .el-input__wrapper {
:global(.gantt-root[data-theme='dark']) .el-date-picker:hover .el-input__wrapper {
box-shadow: 0 0 0 1px var(--gantt-border-hover, #606266) inset;
}
:global(html[data-theme='dark']) .el-date-picker.is-focused:hover .el-input__wrapper {
:global(.gantt-root[data-theme='dark']) .el-date-picker.is-focused:hover .el-input__wrapper {
box-shadow: 0 0 0 1px var(--gantt-primary, #409eff) inset;
}
:global(html[data-theme='dark']) .el-date-picker.is-disabled .el-input__wrapper {
:global(.gantt-root[data-theme='dark']) .el-date-picker.is-disabled .el-input__wrapper {
background-color: var(--gantt-bg-disabled, #3c3e40);
box-shadow: 0 0 0 1px var(--gantt-border-disabled, #4c4d4f) inset;
}
:global(html[data-theme='dark']) .el-date-picker.is-disabled .el-input__inner,
:global(html[data-theme='dark']) .el-date-picker.is-disabled .el-range-input__start,
:global(html[data-theme='dark']) .el-date-picker.is-disabled .el-range-input__end {
:global(.gantt-root[data-theme='dark']) .el-date-picker.is-disabled .el-input__inner,
:global(.gantt-root[data-theme='dark']) .el-date-picker.is-disabled .el-range-input__start,
:global(.gantt-root[data-theme='dark']) .el-date-picker.is-disabled .el-range-input__end {
color: var(--gantt-text-disabled, #73767a);
}
:global(html[data-theme='dark']) .el-input__clear:hover {
:global(.gantt-root[data-theme='dark']) .el-input__clear:hover {
color: var(--gantt-text-secondary, #909399);
}
@@ -1894,51 +1894,51 @@ const timePickerStyle = computed(() => {
}
/* 暗黑模式下的日期选择器面板 */
:global(html[data-theme='dark']) .el-picker-panel {
:global(.gantt-root[data-theme='dark']) .el-picker-panel {
background: var(--gantt-bg-secondary, #2c2c2c);
border-color: var(--gantt-border-dark, #414243);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
:global(html[data-theme='dark']) .el-date-picker__header {
:global(.gantt-root[data-theme='dark']) .el-date-picker__header {
border-bottom-color: var(--gantt-border-dark, #414243);
}
:global(html[data-theme='dark']) .el-date-picker__header-label {
:global(.gantt-root[data-theme='dark']) .el-date-picker__header-label {
color: var(--gantt-text-white, #ffffff);
}
:global(html[data-theme='dark']) .el-picker-panel__icon-btn {
:global(.gantt-root[data-theme='dark']) .el-picker-panel__icon-btn {
color: var(--gantt-text-secondary, #909399);
}
:global(html[data-theme='dark']) .el-picker-panel__icon-btn:hover {
:global(.gantt-root[data-theme='dark']) .el-picker-panel__icon-btn:hover {
background: var(--gantt-bg-hover-dark, #3c3e40);
color: var(--gantt-text-white, #ffffff);
}
:global(html[data-theme='dark']) .el-date-table__header-cell {
:global(.gantt-root[data-theme='dark']) .el-date-table__header-cell {
color: var(--gantt-text-secondary, #909399);
}
:global(html[data-theme='dark']) .el-date-table__cell:hover {
:global(.gantt-root[data-theme='dark']) .el-date-table__cell:hover {
background: var(--gantt-bg-hover-dark, #3c3e40);
}
:global(html[data-theme='dark']) .el-date-table__cell-inner {
:global(.gantt-root[data-theme='dark']) .el-date-table__cell-inner {
color: var(--gantt-text-white, #ffffff);
}
:global(html[data-theme='dark']) .el-date-table__cell.is-other-month .el-date-table__cell-inner {
:global(.gantt-root[data-theme='dark']) .el-date-table__cell.is-other-month .el-date-table__cell-inner {
color: var(--gantt-text-muted, #73767a);
}
:global(html[data-theme='dark']) .el-date-table__cell.is-in-range {
:global(.gantt-root[data-theme='dark']) .el-date-table__cell.is-in-range {
background: rgba(64, 158, 255, 0.2);
}
/* 暗黑模式下的日期选择器确认按钮 */
:global(html[data-theme='dark']) .el-date-picker-footer {
:global(.gantt-root[data-theme='dark']) .el-date-picker-footer {
border-top-color: var(--gantt-border-dark, #414243);
}
@@ -2182,69 +2182,69 @@ const timePickerStyle = computed(() => {
}
/* 暗黑模式下的时间选择器 */
:global(html[data-theme='dark']) .el-time-picker-input {
:global(.gantt-root[data-theme='dark']) .el-time-picker-input {
border-top-color: var(--gantt-border-dark, #414243);
}
:global(html[data-theme='dark']) .el-time-picker-label {
:global(.gantt-root[data-theme='dark']) .el-time-picker-label {
color: var(--gantt-text-secondary, #909399);
}
:global(html[data-theme='dark']) .el-time-input {
:global(.gantt-root[data-theme='dark']) .el-time-input {
background: var(--gantt-bg-secondary, #2c2c2c);
border-color: var(--gantt-border-dark, #414243);
color: var(--gantt-text-white, #ffffff);
}
:global(html[data-theme='dark']) .el-time-input:hover {
:global(.gantt-root[data-theme='dark']) .el-time-input:hover {
border-color: var(--gantt-border-hover, #606266);
}
:global(html[data-theme='dark']) .el-time-picker-panel {
:global(.gantt-root[data-theme='dark']) .el-time-picker-panel {
background: var(--gantt-bg-secondary, #2c2c2c);
border-color: var(--gantt-border-dark, #414243);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
:global(html[data-theme='dark']) .el-time-picker-header {
:global(.gantt-root[data-theme='dark']) .el-time-picker-header {
border-bottom-color: var(--gantt-border-dark, #414243);
}
:global(html[data-theme='dark']) .el-time-picker-title {
:global(.gantt-root[data-theme='dark']) .el-time-picker-title {
color: var(--gantt-text-white, #ffffff);
}
:global(html[data-theme='dark']) .el-time-column-header {
:global(.gantt-root[data-theme='dark']) .el-time-column-header {
color: var(--gantt-text-white, #ffffff);
}
:global(html[data-theme='dark']) .el-time-column-list {
:global(.gantt-root[data-theme='dark']) .el-time-column-list {
border-color: var(--gantt-border-dark, #414243);
}
:global(html[data-theme='dark']) .el-time-column-list:hover::-webkit-scrollbar-thumb {
:global(.gantt-root[data-theme='dark']) .el-time-column-list:hover::-webkit-scrollbar-thumb {
background: var(--gantt-border-hover, #606266);
}
:global(html[data-theme='dark']) .el-time-item {
:global(.gantt-root[data-theme='dark']) .el-time-item {
color: var(--gantt-text-white, #ffffff);
}
:global(html[data-theme='dark']) .el-time-item:hover {
:global(.gantt-root[data-theme='dark']) .el-time-item:hover {
background: var(--gantt-bg-hover-dark, #3c3e40);
}
:global(html[data-theme='dark']) .el-time-picker-footer {
:global(.gantt-root[data-theme='dark']) .el-time-picker-footer {
border-top-color: var(--gantt-border-dark, #414243);
}
:global(html[data-theme='dark']) .el-time-picker-btn--cancel {
:global(.gantt-root[data-theme='dark']) .el-time-picker-btn--cancel {
background: var(--gantt-bg-secondary, #2c2c2c);
border-color: var(--gantt-border-dark, #414243);
color: var(--gantt-text-white, #ffffff);
}
:global(html[data-theme='dark']) .el-time-picker-btn--cancel:hover {
:global(.gantt-root[data-theme='dark']) .el-time-picker-btn--cancel:hover {
background: var(--gantt-bg-hover-dark, #3c3e40);
border-color: var(--gantt-border-hover, #606266);
}

View File

@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, onUnmounted, onMounted, computed, watch, nextTick, useSlots, provide } from 'vue'
import type { StyleValue } from 'vue'
import TaskList from './TaskList/TaskList.vue'
@@ -27,6 +27,9 @@ import type { TaskBarConfig } from '../models/configs/TaskBarConfig'
import { TimelineScale, SCALE_CONFIGS } from '../models/types/TimelineScale'
import { useMessage } from '../composables/useMessage'
// 根元素引用
const ganttRootRef = ref<HTMLElement>()
const props = withDefaults(defineProps<Props>(), {
tasks: () => [],
milestones: () => [],
@@ -65,7 +68,7 @@ const props = withDefaults(defineProps<Props>(), {
expandAll: true,
locale: 'zh-CN',
timeScale: 'week',
theme: 'light',
theme: undefined, // 不设置默认值,允许自动检测系统主题
})
const emit = defineEmits([
@@ -111,6 +114,20 @@ provide('enable-task-bar-context-menu', computed(() => props.enableTaskBarContex
// 提供 LinkAnchor 配置给子组件
provide('enable-link-anchor', computed(() => props.enableLinkAnchor))
// 检测系统主题偏好
const detectSystemTheme = (): 'light' | 'dark' => {
if (typeof window !== 'undefined' && window.matchMedia) {
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches
return isDark ? 'dark' : 'light'
}
return 'light'
}
// 提供当前主题给子组件用于GanttLinks等组件
// 初始值:先设置为'light'在onMounted中根据props或系统主题设置
const currentThemeMode = ref<'light' | 'dark'>('light')
provide('gantt-theme', currentThemeMode)
// 使用声明式右键菜单 composables
const { hasDeclarativeContextMenu: hasDeclarativeTaskListContextMenu, declarativeContextMenu: declarativeTaskListContextMenu } =
useTaskListContextMenu(slots)
@@ -238,7 +255,6 @@ interface Props {
// 总计: 1160px (已在 TaskListConfig 中定义)
// 甘特图容器宽度
const ganttRootRef = ref<HTMLElement | null>(null)
const ganttContainerWidth = ref(1920) // 默认使用常见的屏幕宽度作为初始值
// 监听容器宽度变化
@@ -295,6 +311,16 @@ onMounted(() => {
updateContainerWidth()
// 使用节流版本监听窗口大小变化
window.addEventListener('resize', throttledUpdateContainerWidth)
// 确保主题在DOM挂载后正确应用
nextTick(() => {
if (ganttRootRef.value) {
// 如果明确设置了props.theme使用设置的值否则检测系统主题
const initialTheme = props.theme ?? detectSystemTheme()
ganttRootRef.value.setAttribute('data-theme', initialTheme)
currentThemeMode.value = initialTheme
}
})
})
onUnmounted(() => {
@@ -940,8 +966,7 @@ onUnmounted(() => {
window.removeEventListener('context-menu', handleTaskContextMenu as EventListener)
})
// 主题状态管理
const currentThemeMode = ref<'light' | 'dark'>('light')
// 主题状态管理已在上面provide部分定义
/**
* 设置主题模式
@@ -950,7 +975,10 @@ const currentThemeMode = ref<'light' | 'dark'>('light')
const setTheme = (mode?: 'light' | 'dark') => {
const targetMode = mode || 'dark'
currentThemeMode.value = targetMode
document.documentElement.setAttribute('data-theme', targetMode)
// 在组件根元素上设置data-theme而不是document.documentElement
if (ganttRootRef.value) {
ganttRootRef.value.setAttribute('data-theme', targetMode)
}
}
/**
@@ -964,13 +992,37 @@ const currentTheme = (): string => {
watch(
() => props.theme,
(newTheme) => {
if (newTheme && newTheme !== currentThemeMode.value) {
setTheme(newTheme)
// 只在组件已挂载后处理
if (!ganttRootRef.value) return
// 如果明确设置了theme使用设置的值如果是undefined检测系统主题
const targetTheme = newTheme ?? detectSystemTheme()
if (targetTheme !== currentThemeMode.value) {
setTheme(targetTheme)
}
},
{ immediate: true },
)
// 监听系统主题变化仅在未明确设置theme时生效
if (typeof window !== 'undefined' && window.matchMedia) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
const handleSystemThemeChange = (e: MediaQueryListEvent) => {
// 只有在用户未明确设置theme prop时才跟随系统
if (!props.theme) {
const systemTheme = e.matches ? 'dark' : 'light'
setTheme(systemTheme)
}
}
// 添加监听器
mediaQuery.addEventListener('change', handleSystemThemeChange)
// 组件卸载时清理
onUnmounted(() => {
mediaQuery.removeEventListener('change', handleSystemThemeChange)
})
}
// 全屏状态管理
const isFullscreen = ref(false)
@@ -1599,6 +1651,12 @@ const handleTimelineScaleChanged = (scale: TimelineScale) => {
})
}
// 主题变化处理函数
const handleThemeChange = (isDark: boolean) => {
const newTheme = isDark ? 'dark' : 'light'
setTheme(newTheme)
}
// === 时间维度相关方法 ===
// 时间刻度顺序定义
const TIME_SCALE_ORDER: TimelineScale[] = ['hour', 'day', 'week', 'month', 'quarter', 'year']
@@ -2755,6 +2813,7 @@ defineExpose({
@add-milestone="milestoneAddHandler"
@expand-all="handleExpandAll"
@collapse-all="handleCollapseAll"
@theme-change="handleThemeChange"
/>
<!-- 甘特图主体 -->
@@ -3111,42 +3170,42 @@ defineExpose({
}
/* 暗色主题支持 */
:global(html[data-theme='dark']) .gantt-root {
:global(.gantt-root[data-theme='dark']) .gantt-root {
background: #1e1e1e !important;
color: #e5e5e5 !important;
}
:global(html[data-theme='dark']) .gantt-panel {
:global(.gantt-root[data-theme='dark']) .gantt-panel {
background: #2c2c2c !important;
}
:global(html[data-theme='dark']) .gantt-panel-left {
:global(.gantt-root[data-theme='dark']) .gantt-panel-left {
border-right-color: #4c4c4c !important;
}
:global(html[data-theme='dark']) .focus-close-button {
:global(.gantt-root[data-theme='dark']) .focus-close-button {
background: #d85555;
border-color: #d85555;
box-shadow: 0 4px 12px rgba(216, 85, 85, 0.4);
}
:global(html[data-theme='dark']) .focus-close-button:hover {
:global(.gantt-root[data-theme='dark']) .focus-close-button:hover {
background: #e67676;
border-color: #e67676;
box-shadow: 0 6px 16px rgba(216, 85, 85, 0.5);
}
:global(html[data-theme='dark']) .focus-close-button .close-icon,
:global(html[data-theme='dark']) .focus-close-button .close-text {
:global(.gantt-root[data-theme='dark']) .focus-close-button .close-icon,
:global(.gantt-root[data-theme='dark']) .focus-close-button .close-text {
color: #ffffff;
}
:global(html[data-theme='dark']) .focus-close-button:hover .close-icon,
:global(html[data-theme='dark']) .focus-close-button:hover .close-text {
:global(.gantt-root[data-theme='dark']) .focus-close-button:hover .close-icon,
:global(.gantt-root[data-theme='dark']) .focus-close-button:hover .close-text {
color: #ffffff;
}
:global(html[data-theme='dark']) .gantt-splitter {
:global(.gantt-root[data-theme='dark']) .gantt-splitter {
background: linear-gradient(
to right,
transparent,
@@ -3157,7 +3216,7 @@ defineExpose({
box-shadow: inset 0 0 2px rgba(255, 255, 255, 0.1) !important;
}
:global(html[data-theme='dark']) .gantt-splitter:hover {
:global(.gantt-root[data-theme='dark']) .gantt-splitter:hover {
background: linear-gradient(
to right,
transparent,
@@ -3173,24 +3232,24 @@ defineExpose({
}
/* 暗色主题支持 - TaskList切换按钮 */
:global(html[data-theme='dark']) .task-list-toggle {
:global(.gantt-root[data-theme='dark']) .task-list-toggle {
background: rgba(42, 42, 42, 0.95) !important;
border-color: #555555 !important;
color: #cccccc !important;
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.4) !important;
}
:global(html[data-theme='dark']) .task-list-toggle.collapsed {
:global(.gantt-root[data-theme='dark']) .task-list-toggle.collapsed {
box-shadow: -1px 1px 4px rgba(0, 0, 0, 0.4) !important;
}
:global(html[data-theme='dark']) .task-list-toggle:hover {
:global(.gantt-root[data-theme='dark']) .task-list-toggle:hover {
background: rgba(42, 42, 42, 1) !important;
color: #569cd6 !important;
box-shadow: 1px 2px 8px rgba(86, 156, 214, 0.3) !important;
}
:global(html[data-theme='dark']) .task-list-toggle.collapsed:hover {
:global(.gantt-root[data-theme='dark']) .task-list-toggle.collapsed:hover {
box-shadow: -1px 2px 8px rgba(86, 156, 214, 0.3) !important;
}
@@ -3245,11 +3304,11 @@ defineExpose({
}
/* 暗色主题下的全屏模式 */
:global(html[data-theme='dark']) .gantt-fullscreen {
:global(.gantt-root[data-theme='dark']) .gantt-fullscreen {
background: #1e1e1e !important;
}
:global(html[data-theme='dark']) .gantt-fullscreen .gantt-toolbar {
:global(.gantt-root[data-theme='dark']) .gantt-fullscreen .gantt-toolbar {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important;
}

View File

@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import '../styles/app.css'
const props = defineProps({
visible: Boolean,
@@ -102,7 +102,7 @@ const onNo = () => emit('no')
gap: 12px;
}
:global(html[data-theme='dark']) .gantt-confirm-dialog {
:global(.gantt-root[data-theme='dark']) .gantt-confirm-dialog {
background: var(--gantt-bg-secondary, #f8f9fa) !important;
border-color: var(--gantt-border-dark, #999999) !important;
}

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, watch, nextTick, onMounted, onUnmounted } from 'vue'
import { ref, watch, nextTick, onMounted, onUnmounted, computed, inject, type Ref } from 'vue'
import type { Task } from '../models/classes/Task'
import { getPredecessorIds } from '../utils/predecessorUtils'
import { CanvasContextManager } from '../utils/canvasUtils'
@@ -32,6 +32,7 @@ interface Props {
// 月份分隔线配置
verticalLines?: VerticalLine[]
showVerticalLines?: boolean
// 主题模式通过inject获取不再需要props
}
const props = withDefaults(defineProps<Props>(), {
@@ -41,6 +42,9 @@ const props = withDefaults(defineProps<Props>(), {
offsetTop: 0,
})
// 通过inject获取主题
const injectedTheme = inject<Ref<'light' | 'dark'>>('gantt-theme', ref('light'))
// Canvas 引用
const canvasRef = ref<HTMLCanvasElement | null>(null)
@@ -50,16 +54,9 @@ const canvasManager = new CanvasContextManager()
// requestAnimationFrame 防抖控制
let rafId: number | null = null
let pendingRedraw = false
let themeObserver: MutationObserver | null = null
// 当前主题(用于分隔线颜色)
const isDarkTheme = ref(document.documentElement.getAttribute('data-theme') === 'dark')
// 监听主题变化
const updateTheme = () => {
isDarkTheme.value = document.documentElement.getAttribute('data-theme') === 'dark'
scheduleRedraw()
}
const isDarkTheme = computed(() => injectedTheme.value === 'dark')
/**
* 绘制关系线到 Canvas
@@ -445,6 +442,7 @@ watch(
() => props.showVerticalLines,
() => props.offsetLeft, // 监听虚拟渲染的偏移量变化
() => props.offsetTop,
injectedTheme, // 监听inject的主题变化computed会自动响应
],
() => {
// 使用 RAF 调度重绘,合并连续的多次变化为单次绘制
@@ -455,21 +453,7 @@ watch(
// 组件挂载后初始化绘制
onMounted(() => {
// 监听主题变化
themeObserver = new MutationObserver(mutations => {
for (const mutation of mutations) {
if (mutation.attributeName === 'data-theme') {
updateTheme()
break
}
}
})
themeObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-theme'],
})
// 不再监听 document.documentElement直接使用props.theme
nextTick(() => {
drawLinks()
})
@@ -477,12 +461,6 @@ onMounted(() => {
// 组件卸载时清理
onUnmounted(() => {
// 清理主题观察器
if (themeObserver) {
themeObserver.disconnect()
themeObserver = null
}
// 取消待处理的 RAF
if (rafId !== null) {
cancelAnimationFrame(rafId)

View File

@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed, watch } from 'vue'
import { useI18n } from '../composables/useI18n'
import type { ToolbarConfig } from '../models/configs/ToolbarConfig'
@@ -117,7 +117,7 @@ watch(
const newMode = newTheme === 'dark'
if (isDarkMode.value !== newMode) {
isDarkMode.value = newMode
document.documentElement.setAttribute('data-theme', newTheme)
// 不再直接设置document.documentElement由GanttChart统一管理
}
}
},
@@ -304,7 +304,7 @@ const handleThemeToggle = () => {
// 立即应用主题变化
isDarkMode.value = newTheme
document.documentElement.setAttribute('data-theme', newTheme ? 'dark' : 'light')
// 不再直接设置document.documentElement由GanttChart统一管理
// 立即触发主题变化事件
if (props.onThemeChange && typeof props.onThemeChange === 'function') {
@@ -447,23 +447,12 @@ onMounted(() => {
const currentLocale = locale.value
currentLanguage.value = currentLocale === 'zh-CN' ? 'zh' : 'en'
// 初始化主题状态并应用到DOM
document.documentElement.setAttribute('data-theme', isDarkMode.value ? 'dark' : 'light')
// 不再直接设置document.documentElement由GanttChart统一管理
// 添加点击外部关闭下拉菜单的监听
document.addEventListener('click', handleClickOutside)
// 监听系统主题变化(仅在用户未手动设置时)
if (
window.matchMedia &&
typeof window !== 'undefined' &&
!localStorage.getItem(THEME_STORAGE_KEY)
) {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
isDarkMode.value = e.matches
document.documentElement.setAttribute('data-theme', e.matches ? 'dark' : 'light')
})
}
// 不再监听系统主题变化由GanttChart统一管理
})
onUnmounted(() => {
@@ -1058,34 +1047,34 @@ onUnmounted(() => {
}
/* 暗黑模式样式 */
:global(html[data-theme='dark']) .toolbar-icon-btn:hover {
:global(.gantt-root[data-theme='dark']) .toolbar-icon-btn:hover {
background: var(--gantt-bg-hover, rgba(255, 255, 255, 0.08));
color: var(--gantt-primary, #66b1ff);
}
:global(html[data-theme='dark']) .toolbar-icon-btn:focus {
:global(.gantt-root[data-theme='dark']) .toolbar-icon-btn:focus {
background: var(--gantt-bg-hover, rgba(255, 255, 255, 0.1));
color: var(--gantt-primary, #66b1ff);
}
:global(html[data-theme='dark']) .toolbar-lang-btn:hover {
:global(.gantt-root[data-theme='dark']) .toolbar-lang-btn:hover {
background: var(--gantt-bg-hover, rgba(255, 255, 255, 0.06));
border-color: var(--gantt-primary, #66b1ff);
color: var(--gantt-primary, #66b1ff);
}
:global(html[data-theme='dark']) .toolbar-lang-btn:focus {
:global(.gantt-root[data-theme='dark']) .toolbar-lang-btn:focus {
background: var(--gantt-bg-hover, rgba(255, 255, 255, 0.08));
border-color: var(--gantt-primary, #66b1ff);
color: var(--gantt-primary, #66b1ff);
box-shadow: 0 0 0 2px rgba(102, 177, 255, 0.4);
}
:global(html[data-theme='dark']) .dialog-content {
:global(.gantt-root[data-theme='dark']) .dialog-content {
background: var(--gantt-bg-secondary, #2c2c2c);
}
:global(html[data-theme='dark']) .dialog-message {
:global(.gantt-root[data-theme='dark']) .dialog-message {
color: var(--gantt-text-primary, #e5eaf3);
}
@@ -1270,40 +1259,40 @@ onUnmounted(() => {
}
/* 暗黑模式下的按钮组样式 */
:global(html[data-theme='dark']) .gantt-btn-group {
:global(.gantt-root[data-theme='dark']) .gantt-btn-group {
box-shadow:
0 1px 3px 0 rgba(0, 0, 0, 0.3),
0 1px 2px -1px rgba(0, 0, 0, 0.3);
}
:global(html[data-theme='dark']) .gantt-btn-group:hover,
:global(html[data-theme='dark']) .gantt-btn-group:focus-within {
:global(.gantt-root[data-theme='dark']) .gantt-btn-group:hover,
:global(.gantt-root[data-theme='dark']) .gantt-btn-group:focus-within {
box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.5);
}
:global(html[data-theme='dark']) .add-btn-group .gantt-btn-group-item {
:global(.gantt-root[data-theme='dark']) .add-btn-group .gantt-btn-group-item {
background: #337ecc;
border-color: #337ecc;
color: #ffffff;
}
:global(html[data-theme='dark']) .add-btn-group .gantt-btn-group-item:hover {
:global(.gantt-root[data-theme='dark']) .add-btn-group .gantt-btn-group-item:hover {
background: #4d94d4;
border-color: #4d94d4;
}
:global(html[data-theme='dark']) .add-btn-group .gantt-btn-group-item:focus {
:global(.gantt-root[data-theme='dark']) .add-btn-group .gantt-btn-group-item:focus {
background: #2c5aa0;
border-color: #2c5aa0;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
}
:global(html[data-theme='dark']) .add-btn-group .gantt-btn-group-item:active {
:global(.gantt-root[data-theme='dark']) .add-btn-group .gantt-btn-group-item:active {
background: #1f4872;
border-color: #1f4872;
}
:global(html[data-theme='dark']) .gantt-btn-group:not(.gantt-add-btn-group) .gantt-btn-group-item {
:global(.gantt-root[data-theme='dark']) .gantt-btn-group:not(.gantt-add-btn-group) .gantt-btn-group-item {
background: #2c2c2c;
border-color: #4c4c4c;
color: #e5e5e5;
@@ -1396,41 +1385,41 @@ onUnmounted(() => {
}
/* 暗黑模式下的分段控制器样式 */
:global(html[data-theme='dark']) .segmented-control {
:global(.gantt-root[data-theme='dark']) .segmented-control {
background: var(--gantt-bg-secondary, #4b4b4b);
border-color: var(--gantt-border-color, #808080);
}
:global(html[data-theme='dark']) .segmented-control:hover {
:global(.gantt-root[data-theme='dark']) .segmented-control:hover {
border-color: var(--gantt-primary, #3399ff);
}
:global(html[data-theme='dark']) .segmented-thumb {
:global(.gantt-root[data-theme='dark']) .segmented-thumb {
background: var(--gantt-primary, #3399ff);
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.3),
0 1px 6px -1px rgba(0, 0, 0, 0.3);
}
:global(html[data-theme='dark']) .segmented-item {
:global(.gantt-root[data-theme='dark']) .segmented-item {
color: #ffffff !important; /* 强制使用纯白色,更加突出 */
}
/* 特别针对时间刻度分段控制器的暗黑模式样式 */
:global(html[data-theme='dark']) .time-scale-segmented .segmented-item {
:global(.gantt-root[data-theme='dark']) .time-scale-segmented .segmented-item {
color: #ffffff !important; /* 确保时间刻度按钮也使用纯白色 */
}
:global(html[data-theme='dark']) .segmented-item:hover:not(.active) {
:global(.gantt-root[data-theme='dark']) .segmented-item:hover:not(.active) {
color: var(--gantt-primary, #3399ff); /* 使用主色调,更加鲜艳 */
background: rgba(51, 153, 255, 0.12); /* 调整背景透明度,与主色调匹配 */
}
:global(html[data-theme='dark']) .segmented-item:active:not(.active) {
:global(.gantt-root[data-theme='dark']) .segmented-item:active:not(.active) {
background: rgba(51, 153, 255, 0.2); /* 调整背景透明度,与主色调匹配 */
}
:global(html[data-theme='dark']) .segmented-item.active {
:global(.gantt-root[data-theme='dark']) .segmented-item.active {
color: #ffffff;
}
</style>

View File

@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { computed, ref, onUnmounted } from 'vue'
interface Props {
@@ -245,16 +245,16 @@ onUnmounted(() => {
}
/* 暗色主题支持 */
:global(html[data-theme='dark']) .link-anchor {
:global(.gantt-root[data-theme='dark']) .link-anchor {
border-color: #1a1a1a;
}
:global(html[data-theme='dark']) .link-anchor.valid-target,
:global(html[data-theme='dark']) .link-anchor.invalid-target {
:global(.gantt-root[data-theme='dark']) .link-anchor.valid-target,
:global(.gantt-root[data-theme='dark']) .link-anchor.invalid-target {
border-color: #1a1a1a;
}
:global(html[data-theme='dark']) .anchor-tooltip {
:global(.gantt-root[data-theme='dark']) .anchor-tooltip {
background: rgba(255, 255, 255, 0.9);
color: #1a1a1a;
}

View File

@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, reactive, watch, computed } from 'vue'
import { useI18n } from '../composables/useI18n'
import DatePicker from './DatePicker.vue'
@@ -792,12 +792,12 @@ const t = (key: string) => {
}
/* 暗黑模式下的确认弹窗 */
:global(html[data-theme='dark']) .milestone-confirm-dialog {
:global(.gantt-root[data-theme='dark']) .milestone-confirm-dialog {
background: var(--gantt-bg-dark, #1d1e1f);
border-color: var(--gantt-border-dark, #3c3e40);
}
:global(html[data-theme='dark']) .milestone-confirm-footer {
:global(.gantt-root[data-theme='dark']) .milestone-confirm-footer {
background: var(--gantt-bg-darker, #141414);
border-color: var(--gantt-border-dark, #3c3e40);
}

View File

@@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-explicit-any */
<script setup lang="ts">
import { computed, ref, onUnmounted } from 'vue'
import type { Milestone } from '../models/classes/Milestone'
@@ -1067,23 +1067,23 @@ const calculateMilestonePositionFromTimelineData = (
}
/* 暗黑模式下的适配 */
:global(html[data-theme='dark']) .milestone-label {
:global(.gantt-root[data-theme='dark']) .milestone-label {
color: var(--gantt-text-white, #ffffff) !important;
}
/* 暗黑模式下的粘性标签样式 */
:global(html[data-theme='dark']) .milestone-label[style*='position: absolute'] {
:global(.gantt-root[data-theme='dark']) .milestone-label[style*='position: absolute'] {
background: rgba(30, 30, 30, 0.9) !important;
border-color: rgba(246, 124, 124, 0.3) !important;
color: #ffffff !important;
}
:global(html[data-theme='dark']) .milestone svg {
:global(.gantt-root[data-theme='dark']) .milestone svg {
filter: drop-shadow(0 0 8px var(--gantt-danger, #f67c7c));
animation: milestone-glow-dark 2s ease-in-out infinite alternate;
}
:global(html[data-theme='dark']) .milestone:hover svg {
:global(.gantt-root[data-theme='dark']) .milestone:hover svg {
filter: drop-shadow(0 0 16px var(--gantt-danger, #f67c7c))
drop-shadow(0 0 24px rgba(246, 124, 124, 0.4));
animation: milestone-glow-intense-dark 1.5s ease-in-out infinite alternate;
@@ -1125,7 +1125,7 @@ const calculateMilestonePositionFromTimelineData = (
animation: none;
}
:global(html[data-theme='dark']) .milestone.dragging svg {
:global(.gantt-root[data-theme='dark']) .milestone.dragging svg {
filter: drop-shadow(0 0 20px var(--gantt-danger, #f67c7c))
drop-shadow(0 0 32px rgba(246, 124, 124, 0.6));
}
@@ -1164,14 +1164,14 @@ const calculateMilestonePositionFromTimelineData = (
}
/* 暗黑模式下的停靠状态样式 */
:global(html[data-theme='dark']) .milestone-sticky-left svg,
:global(html[data-theme='dark']) .milestone-sticky-right svg {
:global(.gantt-root[data-theme='dark']) .milestone-sticky-left svg,
:global(.gantt-root[data-theme='dark']) .milestone-sticky-right svg {
animation: milestone-glow-sticky-dark 3s ease-in-out infinite alternate;
}
/* 暗黑模式下半图标显示时取消发光效果 */
:global(html[data-theme='dark']) .milestone-sticky-left svg[style*='clip-path'],
:global(html[data-theme='dark']) .milestone-sticky-right svg[style*='clip-path'] {
:global(.gantt-root[data-theme='dark']) .milestone-sticky-left svg[style*='clip-path'],
:global(.gantt-root[data-theme='dark']) .milestone-sticky-right svg[style*='clip-path'] {
animation: none;
filter: none;
}
@@ -1259,7 +1259,7 @@ const calculateMilestonePositionFromTimelineData = (
}
/* 暗黑模式下的Tooltip样式 */
:global(html[data-theme='dark']) .milestone-tooltip {
:global(.gantt-root[data-theme='dark']) .milestone-tooltip {
background: rgba(30, 30, 30, 0.95) !important;
color: #ffffff !important;
}

View File

@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import type { Task } from '../models/classes/Task'
@@ -184,17 +184,17 @@ watch(
}
/* 暗黑模式 */
:global(html[data-theme='dark']) .predecessor-tag {
:global(.gantt-root[data-theme='dark']) .predecessor-tag {
background: var(--gantt-primary-dark, #337ecc);
}
:global(html[data-theme='dark']) .form-select {
:global(.gantt-root[data-theme='dark']) .form-select {
background: var(--gantt-bg-primary, #2b2b2b);
border-color: var(--gantt-border-medium, #4c4d4f);
color: var(--gantt-text-primary, #e5eaf3);
}
:global(html[data-theme='dark']) .form-select:focus {
:global(.gantt-root[data-theme='dark']) .form-select:focus {
border-color: var(--gantt-primary, #409eff);
}
</style>

View File

@@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-explicit-any */
<script setup lang="ts">
import { ref, computed, onUnmounted, onMounted, nextTick, watch, useSlots, inject, type ComputedRef } from 'vue'
import type { Task } from '../models/classes/Task'
@@ -3406,7 +3406,7 @@ const handleAnchorDragEnd = (anchorEvent: { taskId: number; type: 'predecessor'
}
/* 暗黑主题下的标题颜色 */
:global(html[data-theme='dark']) .actual-task-name {
:global(.gantt-root[data-theme='dark']) .actual-task-name {
color: #ffffff !important;
}
@@ -3971,14 +3971,14 @@ const handleAnchorDragEnd = (anchorEvent: { taskId: number; type: 'predecessor'
}
/* 暗色主题支持 */
:global(html[data-theme='dark']) .task-bar {
:global(.gantt-root[data-theme='dark']) .task-bar {
border-color: #111827 !important;
box-shadow:
0 4px 12px rgba(0, 0, 0, 0.7),
0 2px 4px rgba(0, 0, 0, 0.3) !important;
}
:global(html[data-theme='dark']) .task-bar:hover {
:global(.gantt-root[data-theme='dark']) .task-bar:hover {
box-shadow:
0 6px 20px rgba(0, 0, 0, 0.8),
0 4px 8px rgba(0, 0, 0, 0.4) !important;
@@ -3986,58 +3986,58 @@ const handleAnchorDragEnd = (anchorEvent: { taskId: number; type: 'predecessor'
transition: all 0.2s ease;
}
:global(html[data-theme='dark']) .task-bar:hover::after {
:global(.gantt-root[data-theme='dark']) .task-bar:hover::after {
background: rgba(7, 10, 15, 0.98) !important;
color: #f9fafb !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6) !important;
}
:global(html[data-theme='dark']) .task-bar.normal {
:global(.gantt-root[data-theme='dark']) .task-bar.normal {
background: linear-gradient(135deg, #1e40af, #1e3a8a) !important;
border-color: #1e3a8a !important;
}
:global(html[data-theme='dark']) .task-bar.milestone {
:global(.gantt-root[data-theme='dark']) .task-bar.milestone {
background: linear-gradient(135deg, #c2410c, #9a3412) !important;
border-color: #9a3412 !important;
}
:global(html[data-theme='dark']) .task-bar.completed {
:global(.gantt-root[data-theme='dark']) .task-bar.completed {
background: linear-gradient(135deg, #14532d, #16a34a) !important;
border-color: #14532d !important;
}
:global(html[data-theme='dark']) .task-bar.delayed {
:global(.gantt-root[data-theme='dark']) .task-bar.delayed {
background: linear-gradient(135deg, #991b1b, #dc2626) !important;
border-color: #991b1b !important;
}
:global(html[data-theme='dark']) .task-bar.parent {
:global(.gantt-root[data-theme='dark']) .task-bar.parent {
background: linear-gradient(135deg, #581c87, #7c3aed) !important;
border-color: #581c87 !important;
}
:global(html[data-theme='dark']) .task-content {
:global(.gantt-root[data-theme='dark']) .task-content {
color: #ffffff !important;
}
:global(html[data-theme='dark']) .task-name {
:global(.gantt-root[data-theme='dark']) .task-name {
color: #ffffff !important;
}
:global(html[data-theme='dark']) .progress-bar {
:global(.gantt-root[data-theme='dark']) .progress-bar {
background: rgba(255, 255, 255, 0.2) !important;
}
:global(html[data-theme='dark']) .progress-fill {
:global(.gantt-root[data-theme='dark']) .progress-fill {
background: rgba(255, 255, 255, 0.8) !important;
}
:global(html[data-theme='dark']) .resize-handle {
:global(.gantt-root[data-theme='dark']) .resize-handle {
background: rgba(255, 255, 255, 0.1) !important;
}
:global(html[data-theme='dark']) .resize-handle:hover {
:global(.gantt-root[data-theme='dark']) .resize-handle:hover {
background: rgba(255, 255, 255, 0.3) !important;
}
@@ -4099,42 +4099,42 @@ const handleAnchorDragEnd = (anchorEvent: { taskId: number; type: 'predecessor'
}
/* 暗色主题下的短TaskBar溢出效果 */
:global(html[data-theme='dark']) .task-bar.overflow-effect .resize-handle {
:global(.gantt-root[data-theme='dark']) .task-bar.overflow-effect .resize-handle {
background: rgba(255, 255, 255, 0.15);
}
:global(html[data-theme='dark']) .task-bar.overflow-effect .resize-handle:hover {
:global(.gantt-root[data-theme='dark']) .task-bar.overflow-effect .resize-handle:hover {
background: rgba(255, 255, 255, 0.3);
}
/* 暗色主题下的进度百分比样式 */
:global(html[data-theme='dark']) .task-bar.overflow-effect .task-progress {
:global(.gantt-root[data-theme='dark']) .task-bar.overflow-effect .task-progress {
background: rgba(0, 0, 0, 0.9);
color: white;
}
:global(html[data-theme='dark']) .task-bar.week-view {
:global(.gantt-root[data-theme='dark']) .task-bar.week-view {
border-color: var(--gantt-border-light, #555555);
}
/* 暗色主题下的头像样式 */
:global(html[data-theme='dark']) .task-avatar {
:global(.gantt-root[data-theme='dark']) .task-avatar {
border-color: rgba(255, 255, 255, 0.3);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}
/* 暗色主题下的默认头像 */
:global(html[data-theme='dark']) .task-avatar.avatar-default {
:global(.gantt-root[data-theme='dark']) .task-avatar.avatar-default {
background: var(--gantt-bg-tertiary, #4a5568);
color: var(--gantt-text-tertiary, #718096);
}
/* 暗色主题下的图标颜色 */
:global(html[data-theme='dark']) .task-avatar .avatar-icon {
:global(.gantt-root[data-theme='dark']) .task-avatar .avatar-icon {
color: var(--gantt-text-tertiary, #718096);
}
:global(html[data-theme='dark']) .task-avatar.avatar-outside {
:global(.gantt-root[data-theme='dark']) .task-avatar.avatar-outside {
border-color: rgba(255, 255, 255, 0.4);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
}

View File

@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, nextTick, computed } from 'vue'
import { useI18n } from '../composables/useI18n'
import type { Task } from '../models/classes/Task'
@@ -661,23 +661,23 @@ onUnmounted(() => {
}
/* 暗色主题支持 */
:global(html[data-theme='dark']) .task-context-menu {
:global(.gantt-root[data-theme='dark']) .task-context-menu {
background-color: #2c2c2c;
border-color: #444444;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
}
:global(html[data-theme='dark']) .menu-item {
:global(.gantt-root[data-theme='dark']) .menu-item {
color: #e5e5e5;
}
:global(html[data-theme='dark']) .menu-item:hover {
:global(.gantt-root[data-theme='dark']) .menu-item:hover {
background-color: #3a3a3a;
color: #409eff;
}
/* 暗色主题下箭头颜色 */
:global(html[data-theme='dark']) .menu-arrow {
:global(.gantt-root[data-theme='dark']) .menu-arrow {
border-bottom-color: #2c2c2c; /* 匹配暗色菜单背景色 */
filter: drop-shadow(0 -1px 2px rgba(0, 0, 0, 0.25));
}
@@ -784,13 +784,13 @@ onUnmounted(() => {
}
/* 暗色主题下的子菜单 */
:global(html[data-theme='dark']) .submenu {
:global(.gantt-root[data-theme='dark']) .submenu {
background-color: #2c2c2c;
border-color: #444444;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
}
:global(html[data-theme='dark']) .submenu-item:hover {
:global(.gantt-root[data-theme='dark']) .submenu-item:hover {
background-color: #3a1f1f;
color: #f56c6c;
}

View File

@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, reactive, watch, computed, onMounted, onUnmounted, nextTick } from 'vue'
import { useI18n } from '../composables/useI18n'
import { useMessage } from '../composables/useMessage'
@@ -1467,29 +1467,29 @@ const taskStatus = computed(() => {
}
/* 暗黑模式样式 */
:global(html[data-theme='dark']) .progress-input-group {
:global(.gantt-root[data-theme='dark']) .progress-input-group {
border-color: var(--gantt-border-medium, #4c4d4f);
background: var(--gantt-bg-primary, #2b2b2b);
}
:global(html[data-theme='dark']) .progress-input-group:hover {
:global(.gantt-root[data-theme='dark']) .progress-input-group:hover {
border-color: var(--gantt-primary, #409eff);
}
:global(html[data-theme='dark']) .progress-input-group:focus-within {
:global(.gantt-root[data-theme='dark']) .progress-input-group:focus-within {
border-color: var(--gantt-primary, #409eff);
}
:global(html[data-theme='dark']) .progress-input {
:global(.gantt-root[data-theme='dark']) .progress-input {
color: var(--gantt-text-primary, #e5eaf3);
background: transparent;
}
:global(html[data-theme='dark']) .progress-input::placeholder {
:global(.gantt-root[data-theme='dark']) .progress-input::placeholder {
color: var(--gantt-text-placeholder, #8b949e);
}
:global(html[data-theme='dark']) .progress-unit {
:global(.gantt-root[data-theme='dark']) .progress-unit {
color: var(--gantt-text-secondary, #a8a8a8);
}
@@ -1547,27 +1547,27 @@ const taskStatus = computed(() => {
}
/* 暗黑模式样式优化 */
:global(html[data-theme='dark']) .drawer-overlay {
:global(.gantt-root[data-theme='dark']) .drawer-overlay {
background: rgba(0, 0, 0, 0.7) !important;
}
:global(html[data-theme='dark']) .drawer-container {
:global(.gantt-root[data-theme='dark']) .drawer-container {
box-shadow: -4px 0 15px rgba(0, 0, 0, 0.4) !important;
}
:global(html[data-theme='dark']) .drawer-close-btn:hover {
:global(.gantt-root[data-theme='dark']) .drawer-close-btn:hover {
background: var(--gantt-bg-hover, rgba(255, 255, 255, 0.1)) !important;
border-radius: 4px;
}
:global(html[data-theme='dark']) .form-input:focus,
:global(html[data-theme='dark']) .form-select:focus,
:global(html[data-theme='dark']) .form-textarea:focus {
:global(.gantt-root[data-theme='dark']) .form-input:focus,
:global(.gantt-root[data-theme='dark']) .form-select:focus,
:global(.gantt-root[data-theme='dark']) .form-textarea:focus {
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2) !important;
}
:global(html[data-theme='dark']) .form-input::placeholder,
:global(html[data-theme='dark']) .form-textarea::placeholder {
:global(.gantt-root[data-theme='dark']) .form-input::placeholder,
:global(.gantt-root[data-theme='dark']) .form-textarea::placeholder {
color: var(--gantt-text-muted, #9e9e9e) !important;
}
</style>

View File

@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, computed, useSlots, toRef, inject, type ComputedRef } from 'vue'
import type { StyleValue } from 'vue'
import { useI18n } from '../../../composables/useI18n'
@@ -636,20 +636,20 @@ const assigneeDisplayData = computed(() => {
border-left: 5px solid var(--gantt-danger, #f56c6c) !important;
}
:global(html[data-theme='dark']) .milestone-group-row {
:global(.gantt-root[data-theme='dark']) .milestone-group-row {
border-left-color: var(--gantt-danger, #f67c7c);
}
/* 暗黑模式下的任务类型左边框颜色 */
:global(html[data-theme='dark']) .task-type-story {
:global(.gantt-root[data-theme='dark']) .task-type-story {
border-left-color: var(--gantt-primary, #7db4f0);
}
:global(html[data-theme='dark']) .task-type-task {
:global(.gantt-root[data-theme='dark']) .task-type-task {
border-left-color: var(--gantt-warning, #f0b83c);
}
:global(html[data-theme='dark']) .task-type-milestone {
:global(.gantt-root[data-theme='dark']) .task-type-milestone {
border-left-color: var(--gantt-danger, #f67c7c);
}
@@ -775,44 +775,44 @@ const assigneeDisplayData = computed(() => {
}
/* 暗黑模式适配 */
:global(html[data-theme='dark']) .milestone-row-icon {
:global(.gantt-root[data-theme='dark']) .milestone-row-icon {
color: var(--gantt-danger, #f67c7c);
}
:global(html[data-theme='dark']) .milestone-empty-col {
:global(.gantt-root[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 {
:global(.gantt-root[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 {
:global(.gantt-root[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 {
:global(.gantt-root[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 {
:global(.gantt-root[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 {
:global(.gantt-root[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);
@@ -835,11 +835,11 @@ const assigneeDisplayData = computed(() => {
background-color: rgba(64, 158, 255, 0.05) !important;
}
:global(html[data-theme='dark']) .task-row-drop-target.drop-after {
:global(.gantt-root[data-theme='dark']) .task-row-drop-target.drop-after {
background-color: rgba(125, 180, 240, 0.1) !important;
}
:global(html[data-theme='dark']) .task-row-drop-target.drop-child {
:global(.gantt-root[data-theme='dark']) .task-row-drop-target.drop-child {
background-color: rgba(125, 180, 240, 0.1) !important;
}

View File

@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
interface Props {
isMilestoneGroup: boolean
isParentTask: boolean
@@ -75,7 +75,7 @@ withDefaults(defineProps<Props>(), {
}
/* 暗黑模式下的里程碑图标 */
:global(html[data-theme='dark']) .milestone-group-icon {
:global(.gantt-root[data-theme='dark']) .milestone-group-icon {
color: var(--gantt-danger, #f67c7c);
fill: var(--gantt-danger, #f67c7c);
}

View File

@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed, watch, nextTick, shallowRef } from 'vue'
import TaskBar from './TaskBar.vue'
import MilestonePoint from './MilestonePoint.vue'
@@ -4784,11 +4784,11 @@ const handleAddSuccessor = (task: Task) => {
}
/* 暗色主题下的旗帜样式 */
:global(html[data-theme='dark']) .flag-pole {
:global(.gantt-root[data-theme='dark']) .flag-pole {
background-color: var(--gantt-primary-light, #66b1ff);
}
:global(html[data-theme='dark']) .flag-content {
:global(.gantt-root[data-theme='dark']) .flag-content {
background-color: var(--gantt-primary-light, #66b1ff);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
@@ -5024,72 +5024,72 @@ const handleAddSuccessor = (task: Task) => {
}
/* 暗色主题支持 */
:global(html[data-theme='dark']) .timeline {
:global(.gantt-root[data-theme='dark']) .timeline {
background: var(--gantt-bg-primary, #3a3a3a) !important;
color: var(--gantt-text-primary, #e5e5e5) !important;
}
:global(html[data-theme='dark']) .timeline-header {
:global(.gantt-root[data-theme='dark']) .timeline-header {
background: var(--gantt-bg-secondary, #1a1a1a) !important;
}
:global(html[data-theme='dark']) .timeline-header-row {
:global(.gantt-root[data-theme='dark']) .timeline-header-row {
border-bottom-color: var(--gantt-border-medium, #333333) !important;
}
:global(html[data-theme='dark']) .timeline-month {
:global(.gantt-root[data-theme='dark']) .timeline-month {
border-right-color: var(--gantt-border-medium, #333333) !important;
}
:global(html[data-theme='dark']) .year-month-label {
:global(.gantt-root[data-theme='dark']) .year-month-label {
color: var(--gantt-text-header, #ffffff) !important;
}
:global(html[data-theme='dark']) .timeline-day {
:global(.gantt-root[data-theme='dark']) .timeline-day {
border-right-color: var(--gantt-border-light, #555555) !important;
}
:global(html[data-theme='dark']) .timeline-day.today {
:global(.gantt-root[data-theme='dark']) .timeline-day.today {
background: #1a365d !important;
color: #e3f2fd !important;
}
:global(html[data-theme='dark']) .timeline-day.weekend {
:global(.gantt-root[data-theme='dark']) .timeline-day.weekend {
background: var(--gantt-bg-secondary, #1a1a1a) !important;
color: var(--gantt-text-muted, #b0b0b0) !important;
}
:global(html[data-theme='dark']) .day-label {
:global(.gantt-root[data-theme='dark']) .day-label {
color: var(--gantt-text-header, #ffffff) !important;
}
:global(html[data-theme='dark']) .timeline-body {
:global(.gantt-root[data-theme='dark']) .timeline-body {
background: var(--gantt-bg-primary, #6b6b6b) !important;
}
:global(html[data-theme='dark']) .timeline-body-content {
:global(.gantt-root[data-theme='dark']) .timeline-body-content {
background: var(--gantt-bg-primary, #6b6b6b) !important;
}
:global(html[data-theme='dark']) .day-columns {
:global(.gantt-root[data-theme='dark']) .day-columns {
border-right-color: var(--gantt-border-light, #555555) !important;
}
:global(html[data-theme='dark']) .month-day-columns {
:global(.gantt-root[data-theme='dark']) .month-day-columns {
border-right-color: var(--gantt-border-light, #555555) !important;
}
:global(html[data-theme='dark']) .day-column {
:global(.gantt-root[data-theme='dark']) .day-column {
border-right-color: var(--gantt-border-light, #555555) !important;
/* 非周末列透明背景继承timeline-body的背景色 */
}
:global(html[data-theme='dark']) .day-column.today {
:global(.gantt-root[data-theme='dark']) .day-column.today {
border-left-color: var(--gantt-primary, #409eff) !important;
background-color: var(--gantt-primary, #409eff) !important;
}
:global(html[data-theme='dark']) .day-column.today::before {
:global(.gantt-root[data-theme='dark']) .day-column.today::before {
background: linear-gradient(
to bottom,
rgba(64, 158, 255, 0.15) 0%,
@@ -5098,17 +5098,17 @@ const handleAddSuccessor = (task: Task) => {
) !important;
}
:global(html[data-theme='dark']) .day-column.today.weekend {
:global(.gantt-root[data-theme='dark']) .day-column.today.weekend {
background-color: var(--gantt-primary-color, #409eff) !important;
}
/* 暗色主题下的今日定位高亮效果 */
:global(html[data-theme='dark']) .day-column.today-highlight {
:global(.gantt-root[data-theme='dark']) .day-column.today-highlight {
background-color: var(--gantt-primary-color, #409eff) !important;
box-shadow: 0 0 8px rgba(64, 158, 255, 0.6) !important;
}
:global(html[data-theme='dark']) .day-column.today-highlight::before {
:global(.gantt-root[data-theme='dark']) .day-column.today-highlight::before {
background: linear-gradient(
to bottom,
rgba(64, 158, 255, 0.4) 0%,
@@ -5118,23 +5118,23 @@ const handleAddSuccessor = (task: Task) => {
}
/* 暗黑模式下的任务行悬停效果 */
:global(html[data-theme='dark']) .timeline-body .task-row-hovered {
:global(.gantt-root[data-theme='dark']) .timeline-body .task-row-hovered {
background-color: var(--gantt-bg-hover) !important; /* 与TaskList保持一致使用透明背景 */
/* 降低层级,避免覆盖任务条等元素 */
z-index: 11 !important;
}
/* 确保暗黑模式下子元素能继续响应事件 */
:global(html[data-theme='dark']) .timeline-body .task-row-hovered > * {
:global(.gantt-root[data-theme='dark']) .timeline-body .task-row-hovered > * {
pointer-events: auto !important;
}
/* 暗黑模式下的非工作时间样式 */
:global(html[data-theme='dark']) .timeline-hour-item.non-working-hour {
:global(.gantt-root[data-theme='dark']) .timeline-hour-item.non-working-hour {
background-color: var(--gantt-bg-secondary, #1a1a1a) !important;
}
:global(html[data-theme='dark']) .timeline-hour-item.non-working-hour .hour-label {
:global(.gantt-root[data-theme='dark']) .timeline-hour-item.non-working-hour .hour-label {
color: var(--gantt-text-muted, #b0b0b0) !important;
}
@@ -5300,98 +5300,98 @@ const handleAddSuccessor = (task: Task) => {
}
/* 月度视图暗色主题样式 */
:global(html[data-theme='dark']) .year-row {
:global(.gantt-root[data-theme='dark']) .year-row {
background: var(--gantt-bg-secondary, #1a1a1a) !important;
border-bottom-color: var(--gantt-border-medium, #333333) !important;
}
:global(html[data-theme='dark']) .timeline-year {
:global(.gantt-root[data-theme='dark']) .timeline-year {
border-right-color: var(--gantt-border-medium, #333333) !important;
}
:global(html[data-theme='dark']) .year-label {
:global(.gantt-root[data-theme='dark']) .year-label {
color: var(--gantt-text-header, #ffffff) !important;
}
:global(html[data-theme='dark']) .months-row {
:global(.gantt-root[data-theme='dark']) .months-row {
background: var(--gantt-bg-secondary, #1a1a1a) !important;
border-bottom-color: var(--gantt-border-medium, #333333) !important;
}
:global(html[data-theme='dark']) .timeline-month-item {
:global(.gantt-root[data-theme='dark']) .timeline-month-item {
border-right-color: var(--gantt-border-light, #555555) !important;
border-bottom-color: var(--gantt-border-light, #555555) !important;
}
:global(html[data-theme='dark']) .timeline-month-item.today {
:global(.gantt-root[data-theme='dark']) .timeline-month-item.today {
background-color: var(--gantt-primary);
border-left-color: var(--gantt-primary, #409eff) !important;
}
:global(html[data-theme='dark']) .month-label {
:global(.gantt-root[data-theme='dark']) .month-label {
color: var(--gantt-text-header, #ffffff) !important;
}
/* 季度视图暗色主题样式 */
:global(html[data-theme='dark']) .quarters-row {
:global(.gantt-root[data-theme='dark']) .quarters-row {
background: var(--gantt-bg-secondary, #1a1a1a) !important;
border-bottom-color: var(--gantt-border-medium, #333333) !important;
}
:global(html[data-theme='dark']) .timeline-quarter-item {
:global(.gantt-root[data-theme='dark']) .timeline-quarter-item {
border-right-color: var(--gantt-border-light, #555555) !important;
border-bottom-color: var(--gantt-border-light, #555555) !important;
}
:global(html[data-theme='dark']) .timeline-quarter-item.today {
:global(.gantt-root[data-theme='dark']) .timeline-quarter-item.today {
background-color: var(--gantt-primary);
border-left-color: var(--gantt-primary, #409eff) !important;
}
:global(html[data-theme='dark']) .quarter-label {
:global(.gantt-root[data-theme='dark']) .quarter-label {
color: var(--gantt-text-header, #ffffff) !important;
}
:global(html[data-theme='dark']) .quarter-column {
:global(.gantt-root[data-theme='dark']) .quarter-column {
border-right-color: var(--gantt-border-light, #555555) !important;
background-color: var(--gantt-bg-primary, #6b6b6b) !important;
}
:global(html[data-theme='dark']) .quarter-column.today {
:global(.gantt-root[data-theme='dark']) .quarter-column.today {
background-color: var(--gantt-primary, #409eff) !important;
}
:global(html[data-theme='dark']) .month-column {
:global(.gantt-root[data-theme='dark']) .month-column {
border-right-color: var(--gantt-border-light, #555555) !important;
}
:global(html[data-theme='dark']) .month-column.today {
:global(.gantt-root[data-theme='dark']) .month-column.today {
background-color: var(--gantt-primary-color, #409eff);
border-left-color: var(--gantt-primary-color, #409eff) !important;
}
/* 年度视图暗色主题样式 */
:global(html[data-theme='dark']) .half-years-row {
:global(.gantt-root[data-theme='dark']) .half-years-row {
background: var(--gantt-bg-secondary, #1a1a1a) !important;
border-bottom-color: var(--gantt-border-medium, #333333) !important;
}
:global(html[data-theme='dark']) .timeline-half-year-item {
:global(.gantt-root[data-theme='dark']) .timeline-half-year-item {
border-right-color: var(--gantt-border-light, #555555) !important;
border-bottom-color: var(--gantt-border-light, #555555) !important;
}
:global(html[data-theme='dark']) .half-year-label {
:global(.gantt-root[data-theme='dark']) .half-year-label {
color: var(--gantt-text-header, #ffffff) !important;
}
/* 年度视图背景列暗色主题样式 */
:global(html[data-theme='dark']) .half-year-column {
:global(.gantt-root[data-theme='dark']) .half-year-column {
border-right-color: var(--gantt-border-light, #555555) !important;
background-color: var(--gantt-bg-primary, #6b6b6b) !important;
}
:global(html[data-theme='dark']) .half-year-column:hover {
:global(.gantt-root[data-theme='dark']) .half-year-column:hover {
background-color: var(--gantt-bg-hover, rgba(64, 158, 255, 0.1)) !important;
}
@@ -5407,7 +5407,7 @@ const handleAddSuccessor = (task: Task) => {
}
/* 暗黑模式下的年度视图今日标记线 */
:global(html[data-theme='dark']) .today-line-year-view {
:global(.gantt-root[data-theme='dark']) .today-line-year-view {
background-color: var(--gantt-primary, #66b1ff);
box-shadow: 0 0 4px rgba(102, 177, 255, 0.4);
}
@@ -5569,65 +5569,65 @@ const handleAddSuccessor = (task: Task) => {
}
/* 小时视图暗色主题样式 */
:global(html[data-theme='dark']) .date-row {
:global(.gantt-root[data-theme='dark']) .date-row {
background: var(--gantt-bg-secondary, #1a1a1a) !important;
border-bottom-color: var(--gantt-border-medium, #333333) !important;
}
:global(html[data-theme='dark']) .timeline-day-item {
:global(.gantt-root[data-theme='dark']) .timeline-day-item {
border-right-color: var(--gantt-border-medium, #333333) !important;
}
:global(html[data-theme='dark']) .date-label {
:global(.gantt-root[data-theme='dark']) .date-label {
color: var(--gantt-text-header, #ffffff) !important;
}
:global(html[data-theme='dark']) .hours-row {
:global(.gantt-root[data-theme='dark']) .hours-row {
background: var(--gantt-bg-secondary, #1a1a1a) !important;
border-bottom-color: var(--gantt-border-medium, #333333) !important;
}
:global(html[data-theme='dark']) .timeline-hour-item {
:global(.gantt-root[data-theme='dark']) .timeline-hour-item {
border-right-color: var(--gantt-border-light, #555555) !important;
border-bottom-color: var(--gantt-border-light, #555555) !important;
}
:global(html[data-theme='dark']) .timeline-hour-item.today {
:global(.gantt-root[data-theme='dark']) .timeline-hour-item.today {
background-color: var(--gantt-primary, #1a365d) !important;
color: var(--gantt-text-white, #e3f2fd) !important;
}
:global(html[data-theme='dark']) .hour-label {
:global(.gantt-root[data-theme='dark']) .hour-label {
color: var(--gantt-text-primary, #e5e5e5) !important;
}
:global(html[data-theme='dark']) .timeline-hour-item.today .hour-label {
:global(.gantt-root[data-theme='dark']) .timeline-hour-item.today .hour-label {
color: var(--gantt-text-white, #e3f2fd) !important;
}
:global(html[data-theme='dark']) .hour-column {
:global(.gantt-root[data-theme='dark']) .hour-column {
border-right-color: var(--gantt-border-light, #555555) !important;
}
:global(html[data-theme='dark']) .hour-column.weekend {
:global(.gantt-root[data-theme='dark']) .hour-column.weekend {
background-color: var(--gantt-bg-secondary, #1a1a1a) !important;
}
:global(html[data-theme='dark']) .hour-column.rest-hour {
:global(.gantt-root[data-theme='dark']) .hour-column.rest-hour {
background-color: var(--gantt-bg-secondary, #1a1a1a) !important;
}
:global(html[data-theme='dark']) .hour-column.working-hour {
:global(.gantt-root[data-theme='dark']) .hour-column.working-hour {
background-color: var(--gantt-bg-primary, #6b6b6b) !important;
}
:global(html[data-theme='dark']) .hour-column.today {
:global(.gantt-root[data-theme='dark']) .hour-column.today {
background-color: var(--gantt-primary-color, #409eff) !important;
border-left-color: var(--gantt-primary-color, #409eff) !important;
}
/* 暗色主题15分钟刻度线样式 */
:global(html[data-theme='dark']) .quarter-line {
:global(.gantt-root[data-theme='dark']) .quarter-line {
background-color: var(--gantt-border-light, #555555) !important;
}
</style>

View File

@@ -36,13 +36,13 @@
}
/* 暗黑主题下 .gantt-btn-default 全局样式 */
:global(html[data-theme='dark']) .gantt-btn-default {
:global(.gantt-root[data-theme='dark']) .gantt-btn-default {
background: var(--gantt-bg-tertiary, #454545) !important;
border-color: var(--gantt-border-color, #dcdfe6) !important;
color: var(--gantt-text-white, #ffffff) !important;
}
:global(html[data-theme='dark']) .gantt-btn-default:hover:not(:disabled) {
:global(.gantt-root[data-theme='dark']) .gantt-btn-default:hover:not(:disabled) {
background: var(--gantt-bg-hover, rgba(255, 255, 255, 0.1)) !important;
border-color: var(--gantt-primary, #409eff) !important;
color: var(--gantt-primary, #409eff) !important;
@@ -82,23 +82,17 @@
}
/* 黑暗模式全局样式 */
html[data-theme='dark'] {
background: #1e1e1e !important;
color-scheme: dark !important;
.gantt-root[data-theme='dark'] {
background: #1e1e1e;
color-scheme: dark;
}
html[data-theme='dark'] body {
background: #1e1e1e !important;
color: #e5e5e5 !important;
}
/* 移除body样式因为不再全局污染 */
/* 明亮模式全局样式 */
html[data-theme='light'] {
background: #ffffff !important;
color-scheme: light !important;
.gantt-root[data-theme='light'] {
background: #ffffff;
color-scheme: light;
}
html[data-theme='light'] body {
background: #ffffff !important;
color: #333333 !important;
}
/* 移除body样式因为不再全局污染 */

View File

@@ -38,7 +38,7 @@
}
/* 暗黑主题 */
html[data-theme='dark'] {
.gantt-root[data-theme='dark'] {
/* 背景色 - 大幅提升亮度,接近中性灰 */
--gantt-bg-primary: #6b6b6b; /* Body主体 - 明显更亮,接近中性灰 */
--gantt-bg-secondary: #4b4b4b; /* Header背景 - 中等亮度,保持层次 */