v1.7.1 - Add Props & Exposes
This commit is contained in:
+383
-14
@@ -58,6 +58,11 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
taskListRowStyle: undefined,
|
||||
enableTaskListContextMenu: true,
|
||||
enableTaskBarContextMenu: true,
|
||||
fullscreen: false,
|
||||
expandAll: true,
|
||||
locale: 'zh-CN',
|
||||
timeScale: 'week',
|
||||
theme: 'light',
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
@@ -189,6 +194,16 @@ interface Props {
|
||||
// 当设置为 true 且声明了 TaskBarContextMenu 组件时,使用自定义菜单
|
||||
// 当设置为 false 时,无论是否声明组件,TaskBar 右键菜单都失效
|
||||
enableTaskBarContextMenu?: boolean
|
||||
// 全屏状态控制(响应式)
|
||||
fullscreen?: boolean
|
||||
// 展开/收起所有任务(响应式)
|
||||
expandAll?: boolean
|
||||
// 语言设置(响应式)
|
||||
locale?: 'zh-CN' | 'en-US'
|
||||
// 时间刻度(响应式)
|
||||
timeScale?: TimelineScale
|
||||
// 主题模式(响应式)
|
||||
theme?: 'light' | 'dark'
|
||||
}
|
||||
|
||||
// TaskList的固定总长度(所有列的最小宽度之和 + 边框等额外空间)
|
||||
@@ -694,7 +709,7 @@ const handleTaskRowMoved = (payload: {
|
||||
// 通知 TaskList 更新父级任务数据
|
||||
nextTick(() => {
|
||||
window.dispatchEvent(new CustomEvent('task-updated', {
|
||||
detail: result.movedTask
|
||||
detail: result.movedTask,
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -731,6 +746,90 @@ const handleCollapseAll = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// === 展开/收起相关方法 ===
|
||||
/**
|
||||
* 展开所有任务
|
||||
*/
|
||||
const expandAllTasks = () => {
|
||||
handleExpandAll()
|
||||
}
|
||||
|
||||
/**
|
||||
* 收起所有任务
|
||||
*/
|
||||
const collapseAllTasks = () => {
|
||||
handleCollapseAll()
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换展开/收起所有任务
|
||||
*/
|
||||
const toggleExpandAllTasks = () => {
|
||||
// 检查是否所有有子任务的任务都已展开
|
||||
const checkAllExpanded = (tasks: Task[]): boolean => {
|
||||
for (const task of tasks) {
|
||||
if (task.children && task.children.length > 0) {
|
||||
if (task.collapsed) {
|
||||
return false
|
||||
}
|
||||
if (!checkAllExpanded(task.children)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if (props.tasks) {
|
||||
const allExpanded = checkAllExpanded(props.tasks)
|
||||
if (allExpanded) {
|
||||
collapseAllTasks()
|
||||
} else {
|
||||
expandAllTasks()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前是否所有任务都已展开
|
||||
*/
|
||||
const getIsExpandAll = (): boolean => {
|
||||
if (!props.tasks || props.tasks.length === 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
const checkAllExpanded = (tasks: Task[]): boolean => {
|
||||
for (const task of tasks) {
|
||||
if (task.children && task.children.length > 0) {
|
||||
if (task.collapsed) {
|
||||
return false
|
||||
}
|
||||
if (!checkAllExpanded(task.children)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return checkAllExpanded(props.tasks)
|
||||
}
|
||||
|
||||
// 监听 Props expandAll 变化
|
||||
watch(
|
||||
() => props.expandAll,
|
||||
(newValue) => {
|
||||
if (newValue !== undefined) {
|
||||
if (newValue) {
|
||||
expandAllTasks()
|
||||
} else {
|
||||
collapseAllTasks()
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 处理TaskDrawer请求任务列表
|
||||
const handleRequestTaskList = () => {
|
||||
// 创建扁平化的任务列表,包含所有任务和里程碑
|
||||
@@ -817,11 +916,42 @@ onUnmounted(() => {
|
||||
window.removeEventListener('context-menu', handleTaskContextMenu as EventListener)
|
||||
})
|
||||
|
||||
// 主题状态管理
|
||||
const currentThemeMode = ref<'light' | 'dark'>('light')
|
||||
|
||||
/**
|
||||
* 设置主题模式
|
||||
* @param mode 主题模式,默认为 'dark'
|
||||
*/
|
||||
const setTheme = (mode?: 'light' | 'dark') => {
|
||||
const targetMode = mode || 'dark'
|
||||
currentThemeMode.value = targetMode
|
||||
document.documentElement.setAttribute('data-theme', targetMode)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前主题模式
|
||||
*/
|
||||
const currentTheme = (): string => {
|
||||
return currentThemeMode.value
|
||||
}
|
||||
|
||||
// 监听 Props theme 变化
|
||||
watch(
|
||||
() => props.theme,
|
||||
(newTheme) => {
|
||||
if (newTheme && newTheme !== currentThemeMode.value) {
|
||||
setTheme(newTheme)
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 全屏状态管理
|
||||
const isFullscreen = ref(false)
|
||||
|
||||
// 多语言支持
|
||||
const { t, locale } = useI18n()
|
||||
const { t, locale: i18nLocale } = useI18n()
|
||||
const collapseTaskListText = computed(() => t.value.collapseTaskList)
|
||||
const expandTaskListText = computed(() => t.value.expandTaskList)
|
||||
|
||||
@@ -1443,6 +1573,63 @@ const handleTimelineScaleChanged = (scale: TimelineScale) => {
|
||||
})
|
||||
}
|
||||
|
||||
// === 时间维度相关方法 ===
|
||||
// 时间刻度顺序定义
|
||||
const TIME_SCALE_ORDER: TimelineScale[] = ['hour', 'day', 'week', 'month', 'quarter', 'year']
|
||||
|
||||
/**
|
||||
* 设置时间刻度
|
||||
* @param scale 时间刻度,默认为 'week'
|
||||
*/
|
||||
const setTimeScale = (scale?: TimelineScale) => {
|
||||
const targetScale = scale || 'week'
|
||||
if (TIME_SCALE_ORDER.includes(targetScale)) {
|
||||
handleTimeScaleChange(targetScale)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 放大时间刻度(显示更细粒度)
|
||||
* year -> quarter -> month -> week -> day -> hour
|
||||
*/
|
||||
const zoomIn = () => {
|
||||
const currentIndex = TIME_SCALE_ORDER.indexOf(currentTimeScale.value)
|
||||
if (currentIndex > 0) {
|
||||
const newScale = TIME_SCALE_ORDER[currentIndex - 1]
|
||||
handleTimeScaleChange(newScale)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩小时间刻度(显示更粗粒度)
|
||||
* hour -> day -> week -> month -> quarter -> year
|
||||
*/
|
||||
const zoomOut = () => {
|
||||
const currentIndex = TIME_SCALE_ORDER.indexOf(currentTimeScale.value)
|
||||
if (currentIndex < TIME_SCALE_ORDER.length - 1) {
|
||||
const newScale = TIME_SCALE_ORDER[currentIndex + 1]
|
||||
handleTimeScaleChange(newScale)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时间刻度
|
||||
*/
|
||||
const currentScale = (): string => {
|
||||
return currentTimeScale.value
|
||||
}
|
||||
|
||||
// 监听 Props timeScale 变化
|
||||
watch(
|
||||
() => props.timeScale,
|
||||
(newScale) => {
|
||||
if (newScale && newScale !== currentTimeScale.value) {
|
||||
handleTimeScaleChange(newScale)
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 处理关闭高亮
|
||||
const handleClearHighlight = () => {
|
||||
if (timelineRef.value?.clearHighlight) {
|
||||
@@ -1672,7 +1859,7 @@ const pdfExportHandler = async () => {
|
||||
|
||||
// 添加日期
|
||||
pdf.setFontSize(10)
|
||||
const currentDate = new Date().toLocaleDateString(locale.value)
|
||||
const currentDate = new Date().toLocaleDateString(i18nLocale.value)
|
||||
pdf.text(`${dateLabel}: ${currentDate}`, pdfWidth - 10, 10, { align: 'right' })
|
||||
|
||||
// 添加甘特图图片
|
||||
@@ -1730,6 +1917,78 @@ const handleFullscreenToggle = (event: CustomEvent) => {
|
||||
}, 500) // 比动画时间稍长一点,确保完全完成
|
||||
}
|
||||
|
||||
// === 全屏相关方法 ===
|
||||
/**
|
||||
* 进入全屏模式
|
||||
*/
|
||||
const enterFullscreen = () => {
|
||||
if (!isFullscreen.value) {
|
||||
isFullscreen.value = true
|
||||
if (props.onFullscreenChange && typeof props.onFullscreenChange === 'function') {
|
||||
props.onFullscreenChange(true)
|
||||
}
|
||||
setTimeout(() => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('timeline-container-resized', {
|
||||
detail: { source: 'fullscreen-toggle' },
|
||||
}),
|
||||
)
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出全屏模式
|
||||
*/
|
||||
const exitFullscreen = () => {
|
||||
if (isFullscreen.value) {
|
||||
isFullscreen.value = false
|
||||
if (props.onFullscreenChange && typeof props.onFullscreenChange === 'function') {
|
||||
props.onFullscreenChange(false)
|
||||
}
|
||||
setTimeout(() => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('timeline-container-resized', {
|
||||
detail: { source: 'fullscreen-toggle' },
|
||||
}),
|
||||
)
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换全屏模式
|
||||
*/
|
||||
const toggleFullscreen = () => {
|
||||
if (isFullscreen.value) {
|
||||
exitFullscreen()
|
||||
} else {
|
||||
enterFullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前是否全屏
|
||||
*/
|
||||
const getIsFullscreen = (): boolean => {
|
||||
return isFullscreen.value
|
||||
}
|
||||
|
||||
// 监听 Props fullscreen 变化
|
||||
watch(
|
||||
() => props.fullscreen,
|
||||
(newValue) => {
|
||||
if (newValue !== undefined && newValue !== isFullscreen.value) {
|
||||
if (newValue) {
|
||||
enterFullscreen()
|
||||
} else {
|
||||
exitFullscreen()
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 更新或添加里程碑到列表中
|
||||
const updateOrAddMilestone = (milestones: Task[], milestone: Task): boolean => {
|
||||
const existingIndex = milestones.findIndex(m => m.id === milestone.id)
|
||||
@@ -1898,22 +2157,94 @@ const defaultTodayLocate = () => {
|
||||
const offset = timelinePanelW ? timelinePanelW / 2 : 200 // 偏移量让今天居中显示
|
||||
const scrollPosition = (totalDays - 1) * 30 - offset
|
||||
|
||||
// 滚动到指定位置
|
||||
const timeline = document.querySelector('.timeline') as HTMLElement
|
||||
if (timeline) {
|
||||
timeline.scrollLeft = Math.max(0, scrollPosition)
|
||||
if (timelinePanel) {
|
||||
timelinePanel.scrollLeft = Math.max(0, scrollPosition)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加今日高亮效果到对应的日期列
|
||||
const todayColumn = timeline.querySelector('.day-column.today') as HTMLElement
|
||||
if (todayColumn) {
|
||||
todayColumn.classList.add('today-highlight')
|
||||
setTimeout(() => {
|
||||
todayColumn.classList.remove('today-highlight')
|
||||
}, 2000)
|
||||
// === 今日定位相关方法 ===
|
||||
/**
|
||||
* 滚动到今日位置
|
||||
*/
|
||||
const scrollToToday = () => {
|
||||
if (timelineRef.value && typeof timelineRef.value.scrollToTodayCenter === 'function') {
|
||||
timelineRef.value.scrollToTodayCenter()
|
||||
} else {
|
||||
defaultTodayLocate()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动到指定任务
|
||||
* @param taskId 任务ID
|
||||
*/
|
||||
const scrollToTask = (taskId: string | number) => {
|
||||
// 查找任务并滚动到对应位置
|
||||
const findTaskById = (tasks: Task[], id: string | number): Task | null => {
|
||||
for (const task of tasks) {
|
||||
if (task.id === id || String(task.id) === String(id)) {
|
||||
return task
|
||||
}
|
||||
if (task.children && task.children.length > 0) {
|
||||
const found = findTaskById(task.children, id)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
if (props.tasks) {
|
||||
const task = findTaskById(props.tasks, taskId)
|
||||
if (task && task.startDate) {
|
||||
// 使用Timeline的scrollToDate方法
|
||||
if (timelineRef.value && typeof timelineRef.value.scrollToDate === 'function') {
|
||||
timelineRef.value.scrollToDate(task.startDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动到指定日期
|
||||
* @param date 日期(Date对象或日期字符串)
|
||||
*/
|
||||
const scrollToDate = (date: Date | string) => {
|
||||
// 使用Timeline的scrollToDate方法
|
||||
if (timelineRef.value && typeof timelineRef.value.scrollToDate === 'function') {
|
||||
timelineRef.value.scrollToDate(date)
|
||||
}
|
||||
}
|
||||
|
||||
// === 语言切换相关方法 ===
|
||||
/**
|
||||
* 获取当前语言
|
||||
*/
|
||||
const currentLocale = (): string => {
|
||||
return i18nLocale.value
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置语言
|
||||
* @param locale 语言代码
|
||||
*/
|
||||
const setLocale = (locale: 'zh-CN' | 'en-US') => {
|
||||
const { setLocale: setI18nLocale } = useI18n()
|
||||
setI18nLocale(locale)
|
||||
}
|
||||
|
||||
// 监听 Props locale 变化
|
||||
watch(
|
||||
() => props.locale,
|
||||
(newLocale) => {
|
||||
if (newLocale && newLocale !== i18nLocale.value) {
|
||||
// 导入 setLocale 方法
|
||||
const { setLocale } = useI18n()
|
||||
setLocale(newLocale)
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 窗口大小变化处理函数
|
||||
const handleWindowResize = () => {
|
||||
// 直接检查当前宽度是否仍然有效,如果超出限制则调整
|
||||
@@ -2335,6 +2666,40 @@ function handleMilestoneDialogDelete(milestoneId: number) {
|
||||
// 4. 关闭对话框
|
||||
handleMilestoneDialogClose()
|
||||
}
|
||||
|
||||
// 暴露方法供外部调用
|
||||
defineExpose({
|
||||
// 全屏相关
|
||||
enterFullscreen,
|
||||
exitFullscreen,
|
||||
toggleFullscreen,
|
||||
isFullscreen: getIsFullscreen,
|
||||
|
||||
// 展开/收起相关
|
||||
expandAll: expandAllTasks,
|
||||
collapseAll: collapseAllTasks,
|
||||
toggleExpandAll: toggleExpandAllTasks,
|
||||
isExpandAll: getIsExpandAll,
|
||||
|
||||
// 今日定位相关
|
||||
scrollToToday,
|
||||
scrollToTask,
|
||||
scrollToDate,
|
||||
|
||||
// 语言切换相关
|
||||
setLocale,
|
||||
currentLocale,
|
||||
|
||||
// 时间维度相关
|
||||
setTimeScale,
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
currentScale,
|
||||
|
||||
// 主题相关
|
||||
setTheme,
|
||||
currentTheme,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -2347,6 +2712,10 @@ function handleMilestoneDialogDelete(milestoneId: number) {
|
||||
<GanttToolbar
|
||||
v-if="props.showToolbar"
|
||||
:config="props.toolbarConfig"
|
||||
:time-scale="currentTimeScale"
|
||||
:theme="currentThemeMode"
|
||||
:fullscreen="isFullscreen"
|
||||
:expand-all="getIsExpandAll()"
|
||||
:on-today-locate="todayLocateHandler"
|
||||
:on-export-csv="csvExportHandler"
|
||||
:on-export-pdf="pdfExportHandler"
|
||||
|
||||
@@ -10,6 +10,10 @@ type Language = 'zh' | 'en'
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
config: () => ({}),
|
||||
timeScale: undefined,
|
||||
theme: undefined,
|
||||
fullscreen: undefined,
|
||||
expandAll: undefined,
|
||||
onAddTask: undefined,
|
||||
onAddMilestone: undefined,
|
||||
onTodayLocate: undefined,
|
||||
@@ -50,6 +54,10 @@ const localeMap: Record<Language, 'zh-CN' | 'en-US'> = {
|
||||
|
||||
interface Props {
|
||||
config?: ToolbarConfig
|
||||
timeScale?: TimelineScale
|
||||
theme?: 'light' | 'dark'
|
||||
fullscreen?: boolean
|
||||
expandAll?: boolean
|
||||
// 自定义事件处理器
|
||||
onAddTask?: () => void
|
||||
onAddMilestone?: () => void
|
||||
@@ -90,6 +98,45 @@ const isFullscreen = ref(false)
|
||||
const showLanguageDropdown = ref(false)
|
||||
const currentTimeScale = ref<TimelineScale>(TimelineScale.DAY)
|
||||
|
||||
// 如果外部通过 Prop 传入 timeScale,则同步到本地状态
|
||||
watch(
|
||||
() => props.timeScale,
|
||||
(newScale) => {
|
||||
if (newScale && newScale !== currentTimeScale.value) {
|
||||
currentTimeScale.value = newScale
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 监听 theme prop
|
||||
watch(
|
||||
() => props.theme,
|
||||
(newTheme) => {
|
||||
if (newTheme) {
|
||||
const newMode = newTheme === 'dark'
|
||||
if (isDarkMode.value !== newMode) {
|
||||
isDarkMode.value = newMode
|
||||
document.documentElement.setAttribute('data-theme', newTheme)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 监听 fullscreen prop
|
||||
watch(
|
||||
() => props.fullscreen,
|
||||
(newFullscreen) => {
|
||||
if (newFullscreen !== undefined && isFullscreen.value !== newFullscreen) {
|
||||
isFullscreen.value = newFullscreen
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 监听 expandAll prop (注:这里不需要内部状态,只是用于 UI 显示)
|
||||
|
||||
// 翻译函数 - 使用 useI18n 提供的 getTranslation 函数
|
||||
const t = (key: string): string => {
|
||||
return getTranslation(key)
|
||||
|
||||
@@ -2504,6 +2504,154 @@ const scrollToToday = () => {
|
||||
}, 500) // 等待滚动完成后再添加高亮
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动到指定日期(居中显示)
|
||||
* @param date 日期(Date对象或日期字符串)
|
||||
*/
|
||||
const scrollToDate = (date: Date | string) => {
|
||||
const targetDate = typeof date === 'string' ? new Date(date) : date
|
||||
const timelineStart = timelineConfig.value.startDate
|
||||
|
||||
// 确保日期计算的精确性 - 使用年月日,忽略时分秒
|
||||
const targetNormalized = new Date(
|
||||
targetDate.getFullYear(),
|
||||
targetDate.getMonth(),
|
||||
targetDate.getDate(),
|
||||
)
|
||||
|
||||
// 根据不同的时间刻度使用不同的起始日期
|
||||
let startNormalized: Date
|
||||
if (
|
||||
currentTimeScale.value === TimelineScale.YEAR ||
|
||||
currentTimeScale.value === TimelineScale.QUARTER
|
||||
) {
|
||||
const yearRange = getYearTimelineRange()
|
||||
startNormalized = new Date(
|
||||
yearRange.startDate.getFullYear(),
|
||||
yearRange.startDate.getMonth(),
|
||||
yearRange.startDate.getDate(),
|
||||
)
|
||||
} else if (currentTimeScale.value === TimelineScale.MONTH) {
|
||||
const monthRange = getMonthTimelineRange()
|
||||
startNormalized = new Date(
|
||||
monthRange.startDate.getFullYear(),
|
||||
monthRange.startDate.getMonth(),
|
||||
monthRange.startDate.getDate(),
|
||||
)
|
||||
} else {
|
||||
startNormalized = new Date(
|
||||
timelineStart.getFullYear(),
|
||||
timelineStart.getMonth(),
|
||||
timelineStart.getDate(),
|
||||
)
|
||||
}
|
||||
|
||||
// 计算目标日期距离时间线开始日期的天数
|
||||
const timeDiff = targetNormalized.getTime() - startNormalized.getTime()
|
||||
const daysDiff = Math.floor(timeDiff / (1000 * 60 * 60 * 24))
|
||||
|
||||
// 计算目标日期在时间线中的像素位置(根据当前时间刻度)
|
||||
let datePosition: number
|
||||
|
||||
if (currentTimeScale.value === TimelineScale.HOUR) {
|
||||
// 小时视图:精确到小时的定位
|
||||
const targetHour = targetDate.getHours()
|
||||
const targetMinute = targetDate.getMinutes()
|
||||
|
||||
// 基础天数偏移(到目标日0点的位置)
|
||||
const baseDayPosition = daysDiff * dayWidth.value
|
||||
|
||||
// 小时偏移:每小时40px
|
||||
const hourOffset = targetHour * 40
|
||||
|
||||
// 分钟偏移:在当前小时内的精确位置
|
||||
const minuteOffset = (targetMinute / 60) * 40
|
||||
|
||||
datePosition = baseDayPosition + hourOffset + minuteOffset
|
||||
} else if (currentTimeScale.value === TimelineScale.QUARTER) {
|
||||
// 季度视图:计算季度偏移
|
||||
const targetYear = targetNormalized.getFullYear()
|
||||
const baseYear = startNormalized.getFullYear()
|
||||
|
||||
const yearWidth = 240 // 每年4季度 * 60px
|
||||
const quarterWidth = 60
|
||||
|
||||
// 计算年份偏移
|
||||
const yearOffset = targetYear - baseYear
|
||||
datePosition = yearOffset * yearWidth
|
||||
|
||||
// 计算季度内的偏移
|
||||
const targetQuarter = Math.floor(targetNormalized.getMonth() / 3)
|
||||
datePosition += targetQuarter * quarterWidth
|
||||
|
||||
// 计算季度内的天数偏移(季度内的细微定位)
|
||||
const quarterStartMonth = targetQuarter * 3
|
||||
const quarterStartDate = new Date(targetYear, quarterStartMonth, 1)
|
||||
const daysIntoQuarter = Math.floor(
|
||||
(targetNormalized.getTime() - quarterStartDate.getTime()) / (1000 * 60 * 60 * 24),
|
||||
)
|
||||
const avgDaysInQuarter = 91 // 平均每季度91天
|
||||
datePosition += (daysIntoQuarter / avgDaysInQuarter) * quarterWidth
|
||||
} else if (currentTimeScale.value === TimelineScale.YEAR) {
|
||||
// 年视图:计算年内偏移
|
||||
const targetYear = targetNormalized.getFullYear()
|
||||
const baseYear = startNormalized.getFullYear()
|
||||
|
||||
const yearWidth = 360 // 每年360px
|
||||
|
||||
// 计算年份偏移
|
||||
const yearOffset = targetYear - baseYear
|
||||
datePosition = yearOffset * yearWidth
|
||||
|
||||
// 计算年内的天数偏移
|
||||
const yearStartDate = new Date(targetYear, 0, 1)
|
||||
const daysIntoYear = Math.floor(
|
||||
(targetNormalized.getTime() - yearStartDate.getTime()) / (1000 * 60 * 60 * 24),
|
||||
)
|
||||
const daysInYear = 365 // 不考虑闰年的简化处理
|
||||
datePosition += (daysIntoYear / daysInYear) * yearWidth
|
||||
} else if (currentTimeScale.value === TimelineScale.MONTH) {
|
||||
// 月视图:需要累计每个月的实际宽度
|
||||
const targetYear = targetNormalized.getFullYear()
|
||||
const targetMonth = targetNormalized.getMonth()
|
||||
const baseYear = startNormalized.getFullYear()
|
||||
const baseMonth = startNormalized.getMonth()
|
||||
|
||||
const monthWidth = 60 // 每月60px
|
||||
|
||||
// 计算跨越的月数
|
||||
const monthsDiff = (targetYear - baseYear) * 12 + (targetMonth - baseMonth)
|
||||
datePosition = monthsDiff * monthWidth
|
||||
|
||||
// 计算月内的天数偏移
|
||||
const targetDay = targetNormalized.getDate()
|
||||
const daysInMonth = new Date(targetYear, targetMonth + 1, 0).getDate()
|
||||
datePosition += (targetDay / daysInMonth) * monthWidth
|
||||
} else if (currentTimeScale.value === TimelineScale.WEEK) {
|
||||
// 周视图:每周60px
|
||||
const weekWidth = 60
|
||||
datePosition = (daysDiff / 7) * weekWidth
|
||||
} else {
|
||||
// 日视图:每天30px
|
||||
datePosition = daysDiff * dayWidth.value
|
||||
}
|
||||
|
||||
// 使用缓存的容器元素
|
||||
const timeline = timelineContainerElement.value
|
||||
if (!timeline) return
|
||||
|
||||
const containerWidth = timeline.clientWidth
|
||||
|
||||
// 计算居中滚动位置
|
||||
const centeredScrollPosition = datePosition - containerWidth / 2
|
||||
|
||||
// 滚动到指定位置,确保目标日期在中间
|
||||
timeline.scrollTo({
|
||||
left: Math.max(0, centeredScrollPosition),
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
|
||||
// 更新任务
|
||||
const updateTask = (updatedTask: Task) => {
|
||||
// 不直接修改props数据,而是通过事件通知父组件
|
||||
@@ -3366,6 +3514,7 @@ defineExpose({
|
||||
scrollToTasks,
|
||||
scrollToToday,
|
||||
scrollToTodayCenter,
|
||||
scrollToDate,
|
||||
// 时间线配置
|
||||
timelineConfig,
|
||||
// 时间刻度更新
|
||||
|
||||
Reference in New Issue
Block a user