Vue3+Gantt:把任务排期、依赖关系和里程碑放进项目管理

Vue3+Gantt:把任务排期、依赖关系和里程碑放进项目管理

项目里有几十项任务时,一张表能告诉你每项任务的负责人和截止日期,却很难迅速回答:哪些工作正在并行,哪一步晚了会影响后续,验收节点落在哪天?

把任务放进同一条时间轴,项目计划就有了另一种读法。阶段的跨度、子任务的安排、前后工作的衔接,可以放在一个画面里讨论。

RuoYi Office 使用 Vue3 和 dhtmlx-gantt 实现这一视图,SpringBoot 提供任务和依赖数据。这篇从实际项目页面往下拆,重点看一根时间条怎样关联业务任务,以及修改之后怎样写回。

前两张系统截图来自同一演示项目,后面另列一份项目画面对照;均为历史完整画面,实现按当前源码核对;本轮只读查看了该项目,没有执行拖拽、连线或修改业务数据。下面的接入过程是源码解析,末尾另给出可复现的联调步骤。

任务树和甘特图,应该读同一份计划

先看项目台账里的 WBS。需求分析、系统设计、开发实现、测试与上线,分别作为阶段组织任务。这里既有计划日期,也有任务状态与进度,适合逐项核对。

历史完整画面:任务管理视图展示项目的阶段划分,左侧保留项目模块菜单。

切到甘特图,同一项目的需求分析可以继续展开为需求调研、需求规格说明书编写、需求评审;调研下面又有 HR 和 OA 两项工作。表格里的层级关系被保留下来,日期则变成横向的位置和长度。

历史完整画面:HR 与 OA 调研落在同一时段,需求规格说明书安排在后续日期。这里展示已有计划,不是本轮拖动前后对比。

这两种视图各有用途。WBS 方便定位"谁负责、处于什么状态";甘特图适合讨论"什么时候做、与哪些工作重叠"。如果两边各存一份日期,项目经理在图上改了计划,经办人仍可能在任务表里看到旧值。

当前接入选择从业务任务生成图表数据。getGanttData(projectId) 读取项目任务和依赖,返回 data 与 links;甘特图里的任务 ID 直接沿用业务任务 ID。后续更新因此能定位到同一条业务记录。

先把三种关系分开,图才不会越画越乱

一个常见误区,是用任务的上下排列表示先后顺序。实际上,"属于哪个阶段"和"依赖哪项任务"是两回事。

HR 调研与 OA 调研可以属于同一个父任务,同时进行;需求评审即使位于另一层,也可能需要等待需求文档完成。前者由 parent 表达,后者由 links 表达。里程碑则回答另一个问题:到某个节点,应该交付或确认什么。

数据关系示意:采用当前实现中的对象和字段,连线表示业务关联,不代表数据库外键约束。

后端转换的关键字段不多,但每一个都影响图的含义:

业务任务数据 甘特图字段 实际转换
id、taskName id、text 保留业务身份和任务名称
parentId parent 无父任务时用 0
taskType type 1 对应阶段 project,3 对应 milestone,其余为 task
计划开始、结束 start_date、end_date 格式化为 yyyy-MM-dd HH:mm:ss
百分比进度 progress 业务值除以 100,例如 25 转成 0.25

这层转换还能隔离组件协议。任务表继续服务表单、统计和项目业务,甘特图需要什么形状的数据,由接口统一适配。将来调整展示组件,不必把整个任务模型改成另一套命名。

日期尤其值得单独检查。当前接口用两个计划时间的 Duration.between(...).toDays() 计算工期,最小返回 1。例如从 6 月 7 日零点到 6 月 13 日零点,按这段代码是 6 天,而不是把首尾日期都算进去的 7 天;不足一天的时间差也会受整天取值影响。

因此,不能把这里的"天数"直接解释为扣除周末、节假日后的工作日。若项目要求工作日排期,要进一步统一日历、日期边界和服务端算法。只在前端改一下时间刻度,业务工期口径不会跟着改变。

拖动一根时间条,真正要保存什么

组件接入可以分成加载、映射、绑定事件三步。ProjectGanttPanel 动态导入库与 CSS,配置日期格式、树形名称列和编辑开关,随后调用接口,使用 clearAll() 与 parse(data) 装载当前项目。

编辑能力取决于 readonly 和项目 ID。新增、双击编辑没有另做一套简化表单,而是打开现有的 Vben 任务表单,成功后重新加载甘特图。这使任务字段和校验可以沿用已有业务入口。

拖动则走更短的更新路径。下面是现有事件处理的关键片段:

typescript 复制代码
gantt.attachEvent('onAfterTaskDrag', async (id: any) => {
  const task = gantt.getTask(id);
  try {
    await updateGanttTask({
      id: task.id,
      start_date: gantt.templates.format_date(task.start_date),
      end_date: gantt.templates.format_date(task.end_date),
      duration: task.duration,
      progress: task.progress,
    });
    emit('changed');
  } catch {
    message.error('更新失败');
  }
});

这个时点很关键:DHTMLX 文档中的 onAfterTaskDrag 用于拖动结束后的处理,组件内存里的位置已经改变;业务保存由我们接着完成。用户看见条形移动,并不代表数据库已经写入。

请求到达 /project/gantt/task 后,后端把开始、结束字符串解析为计划时间,把进度乘回 100,更新任务,再调用里程碑同步服务。前端虽然传了 duration,当前更新方法并未把它单独写入任务;下次查询仍由起止时间计算工期。

保存链路示意:重新打开后的读取是核对持久化结果的一步,不是一次成功拖拽截图的替代品。

这里有个二开时应补齐的交互:现有拖动失败分支只有错误提示,没有恢复原位置。网络异常时,屏幕上的日期可能暂时与数据库不一致。可考虑保存拖动前的快照,失败后还原;如果结果不明,先重新读取服务端数据,再让用户继续编辑。它属于改进建议,当前代码不能据此宣传为"自动回滚已完善"。

依赖线要有业务 ID,才经得住刷新

拖出一条依赖线时,组件会先拥有一个临时 ID。后端保存的依赖记录却有自己的主键。如果不处理这两个身份,首次画线可能正常,随后删除或刷新就容易对不上。

现有实现把创建分为两段:保存 source、target、type、lag,然后用返回的主键替换临时 ID。

typescript 复制代码
gantt.attachEvent('onAfterLinkAdd', async (id: any, link: any) => {
  try {
    const result = await createGanttLink({
      source: link.source,
      target: link.target,
      type: String(link.type),
      lag: link.lag || 0,
    });
    gantt.changeLinkId(id, result);
  } catch {
    gantt.deleteLink(id);
    message.error('创建依赖失败');
  }
});

后端从前置任务读取所属项目,将两端任务 ID、依赖类型和延迟值写入 ProjectTaskLinkDO。下一次查询按项目取回这些记录,重新转换成 links。删除依赖则把连线 ID 发回后端。

这里完成的是依赖关系的记录与展示 。代码里有 lag 字段,不代表调整前置任务后,后面所有任务都会自动重排;保存了关系,也不能代替循环依赖、跨项目连接或关键路径分析的验证。

例如"设计完成后才能开发",至少要分别处理两层需求:一层是把关系存下来,让计划可读;另一层是日期冲突时提醒、阻止还是自动调整。第二层需要明确的排程规则。做二开方案时,应把它单列出来估算,避免把"能连线"当成"已有完整调度引擎"。

一个里程碑,既在时间轴上,也在业务记录里

"需求评审"可以作为一个检查节点。甘特图需要知道它是 milestone 类型,业务管理还需要名称、计划日期、达成状态和实际日期。把这些内容全部塞进图形属性,会让其他页面难以使用。

当前服务通过 taskId 关联里程碑记录。任务更新以后,syncFromTask 判断是否为类型 3;是则创建或更新关联记录,否则解除原有关联。关键判断如下:

java 复制代码
@Transactional(rollbackFor = Exception.class)
public void syncFromTask(ProjectTaskDO task) {
    if (task == null || task.getId() == null) {
        return;
    }
    if (!Objects.equals(task.getTaskType(), TASK_TYPE_MILESTONE)) {
        unlinkFromTask(task.getId());
        return;
    }
    upsertFromTask(task);
}

upsertFromTask 按任务 ID 查已有记录:没有就创建,并把里程碑 ID 回写任务;已有则更新名称、计划日期、排序等字段。计划日期优先取任务计划结束时间的日期部分,没有结束时间才退到开始时间。

由此可以推出一个具体的联调点:调整里程碑任务的结束时间后,关联记录的计划日期应相应变化。这里不能只对着图形的位置判断,要核对业务字段;尤其当前取的是结束日期,而非无条件取图上节点的起始日期。

计划移动和实际达成是两个动作。 同步已有记录时,没有因为拖动或进度变化就覆盖达成状态、实际日期;achieveMilestone 才负责设置达成状态和当天的实际日期。否则把验收节点往后挪一天,就可能误写成当天已经验收。

另一个容易忽略的区别是:单独建立的业务里程碑,不会仅因为存在于里程碑表,就自动出现在这份甘特图里。当前图表数据来自任务列表,必须结合对应任务与类型理解展示范围。

放进 Vue3 页签之后,还要处理"再打开一次"

同一套组件还会被不同项目使用。下面这份历史画面的项目编号是 PJ202607290001,计划从 8 月开始;前文的 PJ202606070002 则从 6 月开始。阶段名称相似,任务 ID、日期和数据范围仍属于各自项目。

另一项目的历史完整画面,用于说明项目上下文;不是前文项目改期后的结果,也不是多页签切换测试截图。

因此,组件复用时要传递明确的 projectId,切换后清理旧图表数据,再加载目标项目。当前实现监听项目 ID 与只读属性变化,读取对应数据并更新编辑开关。

独立演示页能显示,不代表放进项目台账后就结束了。甘特图位于页签中,组件挂载时容器可能隐藏;用户又会切换任务管理、甘特图或离开页面。这些时机都影响尺寸和事件。

现有组件会等待容器出现,用初始化代次标记放弃过期的初始化过程;切回激活页签时,先等待 DOM 更新,再执行 setSizes()、render() 并读取数据。卸载时解绑自定义事件、清空数据,避免旧回调继续承担当前页面的操作。初始化失败还提供表格降级和手动重试。

有一处值得直接提醒复制代码的开发者:名为 sharedGantt 的变量声明在 <script setup> 内。按 Vue 官方的执行语义,这里的代码会随组件实例创建而执行。变量名叫 shared,并不能证明它是跨组件的模块级单例。 当前组件也没有调用 destructor(),但不能据此推导所有版本、所有多页签场景都应该禁止销毁;实例归属与库版本要一起确认。

如果自己接入,可以在独立测试项目中放入"设计、开发、验收"三个任务,按下面的顺序检查。它们是联调步骤,不是本文已执行的结果:

  1. 移动普通任务后,核对更新请求的 ID 和日期,再刷新、打开任务表单对照。
  2. 新建依赖,核对临时 ID 已替换;刷新后再删除,确认删的是同一条服务端记录。
  3. 调整里程碑任务日期,对照关联计划日期;再检查达成状态没有被排期操作覆盖。
  4. 切换页签、关闭后重开,检查尺寸、项目数据和一次操作对应的请求次数。
  5. 模拟保存失败,观察界面是否与服务端一致;单独验证跨项目依赖和只读角色的服务端权限。

完成这些核对,项目经理讨论的时间条才有可追踪的业务依据:它对应哪项任务、改了什么日期、关联哪条依赖,以及这个节点是否真的达成。Vue3+Gantt 的接入价值,就体现在这些具体联系里。

相关推荐
AI源码1 天前
学生成绩管理系统
springboot
AI开源软件2 天前
SpringBoot3+Vue3 财务管理系统如何设计:从业务单据到总账的模块划分
spring boot·vue3·领域建模·会计凭证·ruoyi office·业财协同·财务系统设计
无所谓^_^3 天前
个人健康管理系统 | Springboot3 | Vue3
java·vue·毕业设计·springboot·课程设计·毕设·前后端分离
-星空下无敌3 天前
Windows Docker K8s 安装部署Naocs ,SpringBoot 3.x 4.x 整合Naocs 版本兼容 配置中心、服务注册与发现
windows·docker·nacos·kubernetes·springboot·alibaba
RuoyiOffice3 天前
Hyper-V+Kubernetes 搭建实战:保留 Windows,构建三节点 Linux 容器集群
linux·spring boot·kubernetes·containerd·hyper-v·微服务部署·ruoyi office
欢醉4 天前
前后端Long精度丢失的踩坑
springboot·springcloud
JD技术委员会4 天前
研发项目管理软件有哪些?8款工具的任务跟踪能力对比
项目管理·企业管理·研发项目管理软件
这个昵称不容易5 天前
SpringBoot 墙绘交易平台毕设:128个文件154个接口含论文,带运行截图
java·springboot·毕设
JD技术委员会5 天前
看板“进行中”总堆积?比较8款工具的处理方式
项目管理·企业管理·看板工具