SpringBoot3+Vue3 项目立项到甘特:审批冻结台账、任务树怎么汇进度

SpringBoot3+Vue3 项目立项到甘特:审批冻结台账、任务树怎么汇进度

🌐 文档地址https://ruoyioffice.com

📦 源码1·GitHubhttps://github.com/yuqing2026/ruoyi-office

📦 源码2·GitCodehttps://gitcode.com/zhouzhongyan/ruoyi-office

📦 源码3·Giteehttps://gitee.com/yqzy1688/ruoyi-office

💬 微信:17156169080(备注「RuoYi Office」)
立项还在改预算,任务已经开工;甘特是项目经理手动画的,和 WBS 对不上。RuoYi Office 把申请单和台账拆开:审批通过才冻结立项、按同一 id 物化台账。进度从叶子填报往上卷,甘特读这棵树,不另存一套条形图。

▲ 提交立项 → 审批冻结 → 台账同 id 物化 → 进度上卷。甘特只读任务树,不要手填父进度


引言:项目难在「申请单和执行簿是两本账」

缺的不是甘特组件,是谁在什么时候变成执行对象

现场 配错之后
立项通过还能改申请单 预算、经理、合同和现场各执一词
手工再建台账 编号对不上,附件丢在申请单里
父任务手填进度 子任务做完了,项目还是 0
甘特另存 Excel 拖完条,任务树不更新
没立项就开工 没有负责人、没有基线,后面全是补票

本文只讲立项 → 台账 → 任务树 → 甘特。资料库 ACL、OnlyOffice 协同是另一条线,不在这里展开。

菜单:项目管理 → 项目立项 / 项目台账。流程 key:project_create


一、产品能力:申请单冻结,台账才开工

PMO 批的是立项申请。项目经理干活看的是台账。两张皮必须在「审批通过」这一下接上。

能力 谁用 和「开一张项目表」差在哪
立项申请 项目经理 / 部门 草稿可改;提交后审批中;通过后冻结
台账 全员执行 id 沿用申请单,状态进入进行中
任务 WBS 执行人 父子树;叶子填报,父节点只汇总
甘特 项目经理 同一棵任务树的时间条,不是第二份计划
里程碑 PMO 计划节点,和任务可关联
合同基线 商务 / PMO 挂了合同且没填预算,回填合同额

申请页 /project/project-apply-info?id= 就能带计划与 WBS、甘特、预算、里程碑------那是草案。通过之前,这些草稿不能冒充现场进度。


二、业务怎么走:冻结、物化、上卷

开发库 PJ202606070002(立项 / 台账 id=18)名称「软件研发项目2026060602」,申请单已立项。台账下 18 条任务,进度由上卷得到约 4.68%;演示数据里台账状态后来被结成已完成,甘特和 WBS 仍是同一棵树。

1. 立项:提交才进审批

保存是草稿。submitProjectInfo 把状态改成审批中(1),流程变量带编号和「项目名称 + 立项申请」,key 是 project_create。驳回 / 撤回退回草稿,现场不能当已批项目用。

/project/project-apply-info?id=18。通过后状态已立项。再改预算应走变更,不是重开申请单

2. 通过:申请单冻结,台账按同一 id 落地

onProcessApproved 做两件事:申请单改已立项(2);upsertFromDraft 把申请单拷成台账。台账主键等于申请单 iddraftProjectId 指回申请单。第一次插入记一条动态「立项审批通过,项目进入台账」;再次回调则更新。

挂了合同、预算为空时,用合同总额当预算基线,避免台账一打开预算是 0。可选地建文档空间、把申请附件编进资料库------那是旁路,主线是台账行必须在。

打开 /project/project-ledger-detail?id=18,默认「任务管理」。这是执行簿:预算 86 万、相对方演示客户,和申请单同一 id,不是另建一行。

/project/project-ledger-detail?id=18 任务管理。叶子填报,父任务进度由子任务加权汇总

3. 进度:叶子填报,父任务上卷

ProjectTaskRollupService 先给没有孩子的叶子按执行记录算进度和实际工时,再从根往下递归:父任务进度 = 子任务加权,工时相加。根任务算完后,把加权进度写回台账 progress

已取消的叶子不自动改状态。叶子全部 100% 且完成 / 归档,父任务才跟着重算。超期未完成标延期。项目经理不该在父任务格子里手填一个「看起来合理」的百分比。

4. 甘特:同一棵树的时间条

台账 Tab「甘特图」读任务的计划起止和进度。拖条、改日期会回刷任务看板和里程碑,避免甘特和 WBS 各改各的。申请单上的甘特是草案预览;现场以台账甘特为准

▲ 同一 id=18,切到甘特图。条是任务,不是项目经理另画的一张图


三、设计怎么落地

设计思路

决策点 方案 理由
申请和执行是否一张表 project_info + project_ledger 通过前能改草案,通过后执行簿独立
台账 id 怎么来 沿用申请单 id 详情、权限、文档空间不用二次映射
何时物化 onProcessApproved 驳回不能留下半个项目
进度谁说了算 叶子填报 + 加权上卷 父任务手填一定会漂
甘特存不存第二份 不存,读任务树 拖条必须回写任务

#mermaid-svg-1b2whMD1Uk7RQsFA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1b2whMD1Uk7RQsFA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1b2whMD1Uk7RQsFA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1b2whMD1Uk7RQsFA .error-icon{fill:#552222;}#mermaid-svg-1b2whMD1Uk7RQsFA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1b2whMD1Uk7RQsFA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1b2whMD1Uk7RQsFA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1b2whMD1Uk7RQsFA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1b2whMD1Uk7RQsFA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1b2whMD1Uk7RQsFA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1b2whMD1Uk7RQsFA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1b2whMD1Uk7RQsFA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1b2whMD1Uk7RQsFA .marker.cross{stroke:#333333;}#mermaid-svg-1b2whMD1Uk7RQsFA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1b2whMD1Uk7RQsFA p{margin:0;}#mermaid-svg-1b2whMD1Uk7RQsFA .entityBox{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-1b2whMD1Uk7RQsFA .relationshipLabelBox{fill:hsl(80, 100%, 96.2745098039%);opacity:0.7;background-color:hsl(80, 100%, 96.2745098039%);}#mermaid-svg-1b2whMD1Uk7RQsFA .relationshipLabelBox rect{opacity:0.5;}#mermaid-svg-1b2whMD1Uk7RQsFA .labelBkg{background-color:rgba(248.6666666666, 255, 235.9999999999, 0.5);}#mermaid-svg-1b2whMD1Uk7RQsFA .edgeLabel .label{fill:#9370DB;font-size:14px;}#mermaid-svg-1b2whMD1Uk7RQsFA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1b2whMD1Uk7RQsFA .edge-pattern-dashed{stroke-dasharray:8,8;}#mermaid-svg-1b2whMD1Uk7RQsFA .node rect,#mermaid-svg-1b2whMD1Uk7RQsFA .node circle,#mermaid-svg-1b2whMD1Uk7RQsFA .node ellipse,#mermaid-svg-1b2whMD1Uk7RQsFA .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1b2whMD1Uk7RQsFA .relationshipLine{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-1b2whMD1Uk7RQsFA .marker{fill:none!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-1b2whMD1Uk7RQsFA .edgeLabel{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1b2whMD1Uk7RQsFA .edgeLabel .label rect{fill:rgba(232,232,232, 0.8);}#mermaid-svg-1b2whMD1Uk7RQsFA .edgeLabel .label text{fill:#333;}#mermaid-svg-1b2whMD1Uk7RQsFA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} id 相同 / draft_project_id
project_id
parent_id
reports
PROJECT_INFO
bigint
id
PK
string
project_code
int
status
int
process_status
decimal
budget_amount
PROJECT_LEDGER
bigint
id
PK
bigint
draft_project_id
int
status
decimal
progress
PROJECT_TASK
bigint
id
PK
bigint
project_id
bigint
parent_id
decimal
progress
date
plan_start_date
date
plan_end_date
TASK_EXECUTION

申请单状态:0 草稿、1 审批中、2 已立项。台账通过后进进行中(2)。任务树用 parent_id,根是 0。
进度上卷 台账 Flowable 立项服务 Vue3 立项页 进度上卷 台账 Flowable 立项服务 Vue3 立项页 #mermaid-svg-eVR3Xcsegha5LI8N{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eVR3Xcsegha5LI8N .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eVR3Xcsegha5LI8N .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eVR3Xcsegha5LI8N .error-icon{fill:#552222;}#mermaid-svg-eVR3Xcsegha5LI8N .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eVR3Xcsegha5LI8N .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eVR3Xcsegha5LI8N .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eVR3Xcsegha5LI8N .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eVR3Xcsegha5LI8N .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eVR3Xcsegha5LI8N .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eVR3Xcsegha5LI8N .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eVR3Xcsegha5LI8N .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eVR3Xcsegha5LI8N .marker.cross{stroke:#333333;}#mermaid-svg-eVR3Xcsegha5LI8N svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eVR3Xcsegha5LI8N p{margin:0;}#mermaid-svg-eVR3Xcsegha5LI8N .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-eVR3Xcsegha5LI8N text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-eVR3Xcsegha5LI8N .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-eVR3Xcsegha5LI8N .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-eVR3Xcsegha5LI8N .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-eVR3Xcsegha5LI8N .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-eVR3Xcsegha5LI8N #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-eVR3Xcsegha5LI8N .sequenceNumber{fill:white;}#mermaid-svg-eVR3Xcsegha5LI8N #sequencenumber{fill:#333;}#mermaid-svg-eVR3Xcsegha5LI8N #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-eVR3Xcsegha5LI8N .messageText{fill:#333;stroke:none;}#mermaid-svg-eVR3Xcsegha5LI8N .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-eVR3Xcsegha5LI8N .labelText,#mermaid-svg-eVR3Xcsegha5LI8N .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-eVR3Xcsegha5LI8N .loopText,#mermaid-svg-eVR3Xcsegha5LI8N .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-eVR3Xcsegha5LI8N .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-eVR3Xcsegha5LI8N .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-eVR3Xcsegha5LI8N .noteText,#mermaid-svg-eVR3Xcsegha5LI8N .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-eVR3Xcsegha5LI8N .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-eVR3Xcsegha5LI8N .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-eVR3Xcsegha5LI8N .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-eVR3Xcsegha5LI8N .actorPopupMenu{position:absolute;}#mermaid-svg-eVR3Xcsegha5LI8N .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-eVR3Xcsegha5LI8N .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-eVR3Xcsegha5LI8N .actor-man circle,#mermaid-svg-eVR3Xcsegha5LI8N line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-eVR3Xcsegha5LI8N :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} PM 提交立项submitProjectInfoproject_createonProcessApprovedstatus=已立项upsertFromDraft(同一 id)叶子填报进度rollupProject叶子→父任务加权写回 progress PM

提交先改审批中再启流程。通过才物化。进度从执行记录往上卷,最后写台账一行。

java 复制代码
public Long submitProjectInfo(ProjectInfoSaveReqVO saveReqVO) {
    Long id = saveProjectInfo(saveReqVO);
    ProjectInfoDO projectInfo = projectInfoMapper.selectById(id);
    projectInfo.setStatus(STATUS_APPROVING); // 1 审批中
    projectInfo.setProcessStatus(BpmTaskStatusEnum.RUNNING.getStatus());
    String processInstanceId = processInstanceApi.submitProcessInstance(
            ..., new BpmProcessInstanceCreateReqDTO()
                    .setProcessDefinitionKey("project_create")
                    .setBusinessKey(String.valueOf(id)))
            .getCheckedData();
    projectInfo.setProcessInstanceId(processInstanceId);
    projectInfoMapper.updateById(projectInfo);
    return id;
}

通过回调冻结申请单,并物化台账。台账主键故意等于申请单,详情路由不用再翻译。

java 复制代码
public void onProcessApproved(String businessKey) {
    Long id = Long.parseLong(businessKey);
    ProjectInfoDO project = validateProjectInfoExists(id);
    projectInfoMapper.updateById(new ProjectInfoDO()
            .setId(id).setStatus(STATUS_APPROVED)); // 2 已立项
    projectLedgerService.upsertFromDraft(project, LEDGER_STATUS_IN_PROGRESS);
    initLedgerMembers(project);
}

upsertFromDraft 拷字段、写 draftProjectId、必要时回填合同预算。已有行就更新,避免通过回调重入插两条。

java 复制代码
public ProjectLedgerDO upsertFromDraft(ProjectInfoDO draft, Integer status) {
    ProjectLedgerDO ledger = BeanUtils.toBean(draft, ProjectLedgerDO.class);
    ledger.setId(draft.getId());
    ledger.setDraftProjectId(draft.getId());
    ledger.setStatus(status);
    if (draft.getContractId() != null && draft.getBudgetAmount() == null) {
        // 合同总额回填预算基线
    }
    if (projectLedgerMapper.selectById(ledger.getId()) == null) {
        projectLedgerMapper.insert(ledger);
    } else {
        projectLedgerMapper.updateById(ledger);
    }
    return ledger;
}

上卷先处理叶子执行,再递归父节点,最后把根的加权进度写回台账。父任务没有自己的「拍脑袋百分比」。


四、和「Excel 甘特」差在哪

设计要点 落地 对业务的价值
申请 / 执行分表 info 冻结 + ledger 物化 通过后不能偷偷改立项口径
同一 id ledger.id = info.id 权限、文档、甘特共用主键
进度上卷 叶子执行 → 父加权 → 台账 项目进度能解释
甘特不双写 读任务起止 拖条即改计划
驳回回草稿 onProcessRejected 现场不会出现「批了一半的项目」

资料库可以在物化时 ensureSpace,但验收这条链时先看:申请单已立项、台账打得开、任务能上卷、甘特条和任务对得上。


五、快速体验

在线演示:https://ruoyioffice.com/web/(账号 admin / admin123)

菜单 看什么
1 项目管理 → 项目立项 打开已通过单,状态应是已立项
2 切「计划与 WBS / 甘特图」 这是草案痕迹,对照台账
3 项目台账 用同一 id 打开详情
4 Tab 任务管理 父子树、叶子进度
5 Tab 甘特图 条与任务起止一致
6 给叶子填一笔执行 父任务和台账进度应上涨
7 不要在父任务手填进度 下一轮上卷会被覆盖

源码仓库:GitHub:https://github.com/yuqing2026/ruoyi-office | GitCode:https://gitcode.com/zhouzhongyan/ruoyi-office | Gitee:https://gitee.com/yqzy1688/ruoyi-office


常见问题

立项通过后还能改申请单吗?

产品上申请单已冻结。要改预算、经理、范围,应走变更,不要把已立项单当草稿改。驳回的单才退回草稿。

为什么台账 id 不另生成?

另生成就要处处映射:权限、文档空间、甘特、动态。沿用申请单 id,路由 /project/project-ledger-detail?id= 和立项 id 对得上。

父任务进度能不能手填?

上卷会按子任务加权覆盖。手填只在下一次填报之前看起来对。要调整体进度,改叶子执行或拆任务。

申请页也有甘特,和台账甘特有何不同?

申请页是草案预览,给审批人看计划长什么样。台账甘特跟执行走,拖条回写任务。现场以台账为准。

和项目文档协同是不是同一篇文章?

不是。文档树、ACL、在线编辑是资料空间。本文只保证立项冻结和进度能解释。两套可以同时开,不要用文件夹代替 WBS。


结语

立项链能跑住,靠的是:通过才冻结,台账沿用同一 id,进度从叶子往上卷。 甘特是这棵树的视图,不是第二本计划。Excel 条形图管不住的,是「申请口径」和「现场口径」什么时候分开。

同一套「单据冻结 → 台账物化 → 明细上卷」还可以用在预算编制对执行台账、销售订单对出库进度。先把项目这一条走通,再谈看板和盈利分析。


💡 想要体验 RuoYi Office 的强大功能?

🌐 在线演示https://ruoyioffice.com/web/(账号 admin / admin123)

📦 源码仓库:GitHub:https://github.com/yuqing2026/ruoyi-office | GitCode:https://gitcode.com/zhouzhongyan/ruoyi-office | Gitee:https://gitee.com/yqzy1688/ruoyi-office

💬 技术咨询 :添加微信 17156169080,备注「RuoYi Office」

如果觉得不错,请给个 Star 支持一下!

相关推荐
user_admin_god1 小时前
第 03 篇:Java HttpClient 手写第一个 Chat 请求
java·人工智能·spring boot·语言模型
gis开发之家1 小时前
Spring Boot 4 定时任务与异步线程池:从 @Scheduled 到高并发任务编排(生产级实战)
java·spring boot·后端·wpf·spring boot4
MetaLite10 小时前
SpringBoot接口分层规范-外网网关内部服务与参数边界
java·数据库·spring boot
凤山老林11 小时前
聊聊企业级 API 安全:Spring Boot 落地 OAuth 2.1、mTLS 与接口签名防篡改
spring boot·后端·安全·oauth
Bs_MoneyMagnet18 小时前
基于springboot+vue的旅游景区点评系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·spring·旅游
ID346107442018 小时前
【课程设计】基于Spring Boot+Vue的校园共享无人机服务系统设计与实现-计算机毕设 附源码44219
javascript·vue.js·spring boot·python·node.js·php·课程设计
vx-程序开发20 小时前
【毕设分享】springboot废品上门回收微信小程序---附源码94491
java·spring boot·毕业设计·源码·课程设计·毕设·大作业
行百里er21 小时前
Spring Insight 里如何对 Span 进行清洗
spring boot·spring cloud·监控
谢亮_vipxieliang21 小时前
ValidX时间段验证详解:ISO 8601标准与简化格式
java·spring boot·后端·spring·spring cloud·hibernate