Files
jordium-gantt-vue3/docs/周视图跨月问题.md

9.6 KiB
Raw Permalink Blame History

周视图跨月问题记录

问题概述

当甘特图使用周视图(Week View)时,存在多个关键问题影响用户体验,主要围绕跨月显示和任务定位的准确性。

已识别的核心问题

1. 任务对齐问题 ⚠️

问题描述:周视图显示时,任务条(task bar)不能与表头上的日期正确对齐,会超出边界跨到别的月份区域。

具体表现

  • 任务开始日期为10月1日但任务条显示在9月区域
  • 任务条位置与表头日期不匹配
  • 视觉上造成混乱,用户难以准确判断任务的实际时间范围

根本原因

  • 位置计算算法没有考虑跨月周的特殊情况
  • 周的归属月份判定逻辑不合理(以周一所在月份为准)

2. 拖拽跳动问题 ⚠️

问题描述:在周视图中拖动任务跨月时,会导致任务长度异常跳动。

具体表现

  • 拖拽过程中任务条长度突然变化
  • 拖拽结束后任务位置与预期不符
  • 跨月边界时拖拽体验不连贯

技术原因

  • 正向位置计算与反向位置计算逻辑不一致
  • 缺少 calculateDateFromPixelPosition 反向计算函数
  • 拖拽过程中的像素-日期转换存在精度问题

3. 月份边界限制问题 ⚠️

问题描述:由于周的定义以周一为起始,导致某些月份的前几天无法在该月份区域内操作。

典型场景

  • 2025年10月6日是周一该周包含9月29日-10月5日
  • 当前实现中10月1-5日无法在10月区域显示和操作
  • 用户无法将任务拖拽到10月的前5天

设计缺陷

  • 周的归属完全基于周一所在月份
  • 没有考虑部分周显示的需求
  • Timeline数据结构不支持跨月周的多重显示

4. 全视图刷新性能问题 (已解决)

问题描述:每次更新单个任务都导致整个甘特图视图重新渲染。

解决方案:移除深度监听,改用引用监听。

技术分析

当前架构限制

  1. Timeline.vue 数据结构

    • 周数据按月份分组,每个周只属于一个月份
    • 跨月的周只在周一所在月份显示
    • 缺少跨月周的分片显示机制
  2. TaskBar.vue 位置计算

    • 缺少跨月位置映射函数
    • 没有反向像素-日期计算逻辑
    • 拖拽时的边界处理不完善

需要的核心功能

Timeline 数据重构

  • 支持周在多个月份中同时显示
  • 每个月份只显示属于该月的日期部分
  • 保持现有的60px周宽度标准
  • 新增 displayMonthdisplayYear 标识

位置计算算法

  • findTargetMonthPosition():查找目标月份显示区域
  • calculateDateFromPixelPosition():像素位置反向计算日期
  • 跨月拖拽的平滑过渡处理
  • 边界条件的鲁棒性处理

已尝试的解决方案

方案1Timeline数据结构改造 (已废弃)

实施状态:部分完成,后续回滚

具体改动

// 修改 generateWeekTimelineData 函数
// 允许跨月周在多个月份中显示
for (const month of monthsToDisplay) {
  results.push({
    ...week,
    displayMonth: month,
    displayYear: year
  });
}

遇到的问题

  • TypeScript类型错误
  • spread操作符类型兼容性问题
  • 可能的性能影响
  • 过度工程化,增加了系统复杂性

方案2TaskBar位置计算增强 (已废弃)

实施状态:部分完成,后续回滚

具体改动

  • 新增 findTargetMonthPosition 函数
  • 实现 calculateDateFromPixelPosition 反向计算
  • 修改拖拽事件处理逻辑

遇到的问题

  • 复杂度增加
  • 边界情况处理不完善
  • 与现有代码集成困难
  • 维护成本高

方案3背景色标记方案 ⚠️ (已废弃)

实施状态:已完成但存在问题

问题:背景色遮挡了 week-label 的显示,影响用户体验

方案4旗帜标记方案 (当前实施)

实施状态:已完成并构建成功

核心理念

  • 使用绝对定位的旗帜标记,避免遮挡文字
  • 在 timeline-month 容器中添加旗帜元素
  • 保持视觉清晰,不影响现有布局

具体改动

<!--  timeline-month 中添加旗帜标记 -->
<template v-if="month.isWeekView && month.weeks">
  <template v-for="(week, weekIndex) in month.weeks">
    <template v-for="(subDay, dayIndex) in week.subDays || []">
      <div
        v-if="subDay.date && subDay.date.getDate() === 1"
        class="month-first-flag"
        :style="{ left: `${weekIndex * 60 + dayIndex * (60/7)}px` }"
      >
        <div class="flag-pole"></div>
        <div class="flag-content">1</div>
      </div>
    </template>
  </template>
</template>
/* 旗帜标记样式 */
.month-first-flag {
  position: absolute;
  top: -8px;
  z-index: 10;
}

.flag-pole {
  width: 1px;
  height: 20px;
  background-color: var(--gantt-primary, #409eff);
}

.flag-content {
  background-color: var(--gantt-primary, #409eff);
  color: white;
  font-size: 10px;
  padding: 1px 4px;
  border-radius: 2px;
}

优势

  • 不遮挡任何现有文字内容
  • 视觉效果突出且美观
  • 精确定位到每月1号的位置
  • 支持暗色主题
  • 代码清晰,易于维护

推荐的解决路径

Phase 1: 基础重构 (已废弃)

  1. 简化Timeline数据结构支持跨月显示
  2. 实现基本的位置计算函数
  3. 确保类型安全和向后兼容

新方案: UI视觉优化 (已实施)

核心思路:这不是逻辑问题,而是交互呈现问题。通过视觉提示让用户理解跨月周的含义。

实施方案

  1. 在周视图表头的每月1号位置添加特殊标记
  2. 使用颜色区分和边框线突出显示月份边界
  3. 保持现有数据结构和算法不变

技术实现

  • 修改 Timeline.vue 模板,为 week-sub-day 添加 month-first-day
  • 添加CSS样式蓝色背景和左边框标记每月1号
  • 同时在表头和背景列中应用相同的视觉提示

当前状态

最新更新2025-09-25

问题状态

  • 全视图刷新问题已解决
  • 月份边界显示问题已通过旗帜标记解决
  • ⚠️ 任务对齐问题需进一步验证
  • ⚠️ 拖拽跳动问题需进一步验证

旗帜标记方案效果

  • 用户可以清晰看到每月1号在周视图中的精确位置
  • 旗帜不遮挡任何现有内容,保持布局清爽
  • 跨月周的理解变得直观且美观
  • 无需复杂的数据结构重构
  • 保持了代码的简洁性和性能
  • 支持主题切换,视觉效果一致

测试用例

场景1旗帜标识测试 (已升级)

  • 测试内容周视图中每月1号的旗帜标记
  • 预期每月1号位置有蓝色旗帜标记不遮挡文字
  • 实施结果: 已实现旗帜方案,构建成功,视觉效果优秀

场景210月边界显示测试

  • 时间2025年10月1-5日周一是10月6日
  • 预期能看到10月1日在周视图中的明确标记
  • 当前结果: 通过视觉标记解决了理解问题

场景3跨月拖拽测试

  • 操作将任务从9月30日拖拽到10月1日
  • 预期:平滑过渡,无长度跳动
  • 当前结果:⚠️ 需要进一步测试验证

场景4对齐精度测试

  • 检查:任务条与表头日期的对齐精度
  • 预期:像素级精确对齐
  • 当前结果:⚠️ 需要进一步测试验证

场景5用户理解测试

  • 测试内容:用户能否快速理解跨月周的含义
  • 预期通过月份1号标记用户能直观理解日期归属
  • 实施结果: 视觉提示清晰明了

最终实施方案总结

方案选择的智慧

经过多次复杂方案的尝试和回滚,我们最终选择了最简洁有效的解决路径:

从复杂到简单的思维转变

  1. 原始想法:重构数据结构,实现复杂的跨月显示逻辑
  2. 问题重新定义:这是用户体验问题,不是技术逻辑问题
  3. 优雅解决:通过视觉提示让用户理解现有逻辑

实施细节

最终代码修改内容 (旗帜方案)

  1. Timeline.vue模板修改

    • timeline-month 容器中添加旗帜标记元素
    • 使用嵌套 template 遍历周和日期数据
    • 精确计算每月1号的位置weekIndex * 60 + dayIndex * (60/7)
  2. CSS样式设计

    .month-first-flag {
      position: absolute;
      top: -8px;
      z-index: 10;
    }
    
    .flag-pole {
      width: 1px;
      height: 20px;
      background-color: var(--gantt-primary, #409eff);
    }
    
    .flag-content {
      background-color: var(--gantt-primary, #409eff);
      color: white;
      font-size: 10px;
      padding: 1px 4px;
      border-radius: 2px;
    }
    
  3. 主题适配

    • 完整支持暗色主题
    • 使用CSS变量确保主题一致性
    • 添加阴影效果提升视觉层次

旗帜方案优势

  1. 视觉清晰:旗帜标记突出,不遮挡任何内容
  2. 精确定位准确标记每月1号的具体位置
  3. 美观设计:类似旗帜的设计符合用户直觉
  4. 性能优秀使用CSS定位无JavaScript计算开销
  5. 主题友好:完整支持亮色和暗色主题
  6. 易于维护:代码结构清晰,样式独立
  7. 向后兼容:不影响现有功能和布局

经验教训

  1. 问题定义很关键:重新审视问题本质比盲目优化更重要
  2. 简单往往更好:复杂方案不一定是最佳方案
  3. 用户视角思考:站在用户角度理解问题,而不只是技术角度
  4. 迭代优于革命:渐进式改进比颠覆式重构更安全

此文档记录了从问题发现、复杂方案尝试到最终优雅解决的完整过程,为后续类似问题提供参考