面向地铁机电(BAS、门禁、综合监控等)施工交底场景的纯前端单文件工具箱。 打开是功能选择首页,里面两个互相独立的工具:
| 工具 | 做什么 | 数据 |
|---|---|---|
| 甘特网络图 | 横道图式进度计划,改期改工期、依赖连线、导出 PNG/PDF;需要核对逻辑关系时一键整屏切到网络计划图 | 存 crsc:progress-plan:v1 |
| 时标网络图 | 双代号时标网络计划,实箭线即工期、波形线标自由时差、关键线路标红,带时间参数表,A3 打印 | 存 tsnd.timescale.v2 |
两个工具互不影响:各存各的存档键,改一个不会动另一个。
双击
index.html即可使用,不需要 npm、不需要构建、不需要本地服务器。 首次打开需联网(React / Tailwind / html2canvas 等库从 CDN 加载,字体从 Google Fonts 加载)。
直接双击根目录的 index.html 即可。也可以用任意静态服务器打开:
python -m http.server 8080
# 然后访问 http://127.0.0.1:8080/index.html进哪个工具就点哪张卡片。两个工具都能用 URL 直接进:
| 地址 | 效果 |
|---|---|
index.html |
功能选择首页 |
index.html#gantt |
直接进甘特网络图(刷新后仍停在这) |
index.html#timescale |
直接进时标网络图 |
两个工具里都自带一份可直接编辑的示例数据(地铁车站 BAS / 门禁系统), 甘特图的日期相对「今天」生成,因此能立刻看到「今天」标记线与真实的进度状态。
所有改动会自动保存在本机浏览器,下次打开不会重置。详见 本地保存。
首页是深色制图台风格,一屏装完,不用滚动(1366×768 及以上)。
自上而下五块:
- 顶栏:LOGO + 「工程进度计划工具箱」+ 版本号,下面一条细线通到屏幕两边(图幅分隔线)
- 主视觉:左侧大标题「排得清工期,讲得清工序。」+ 一句说明 + 三格图签(工具 / 存档 / 导出); 右侧是一张进度计划样张——带日刻度网格、任务条(按进度填充)、逻辑关系虚线和一条铜色的「今日」线
- 两个工具条目:不是卡片,是一条条通栏的「索引行」,左侧序号
01 / 02, 中间是标题、一句话说明、5 个能力标签,右侧一张小示意图和进入箭头 - 作者:「宇哥」+ 一句说明
- 页脚:版权与「数据只存本机」提醒
设计上刻意避开的几件事:不用满屏高饱和色(只留品牌蓝一个强调色 + 一点铜色做「今日」)、 不用等尺寸卡片矩阵、不堆无意义的渐变发光、不用 emoji 当图标、正文字号一律 ≥12px (校验脚本会实测对比度 ≥4.5 与字号下限)。
LOGO 为什么要单独处理:原始图是「白底 + 深蓝底板 + 黑字」,直接放到深色首页上黑字会消失。
构建时用 build/mkdlogo.mjs 把它转成「透明底 + 亮蓝底板 + 浅色字」的 PNG
(build/assets/logo-dark.png),再内联成 base64。换 LOGO 时要重跑这个脚本。
进入工具后,顶部多出一条 44px 的操作条:← 返回首页 + 当前工具名 + 一句操作提示。
按 Esc 也能返回首页(弹窗自己会先吃掉 Esc,不会误退)。
两个工具是整屏互斥的,共用同一个宿主页面但各自挂载:
- 点进去才初始化(按需挂载),首屏只加载首页,比一上来就把两个工具都渲染出来更快;
- 一个工具初始化过就不会重复初始化,来回切换不会重建 DOM、不会丢滚动位置以外的状态;
- 甘特图的深色模式只作用在它自己的容器上,不会把时标图(原生浅色)一起带黑; 甘特图变深色时,顶部那条操作条会跟着变深,避免「上浅下深」割裂。
以下讲的是甘特网络图工具(时标网络图是另一套界面,见 时标网络图)。
甘特网络图内部自上而下分三块:顶部标题区、工具栏、主体区(主体区里再分「进度计划图 / 网络计划图」两个全屏视图)。
| 元素 | 说明 |
|---|---|
| 工程名称 | 居中大标题,点击即可直接改(单行输入框,自动伸缩) |
| 概览统计 | 共 N 项任务 | 计划周期 X 天 | 总完成率 Y% | 关键工作 Z 项 | 起止日期 |
| 完成率进度条 | 蓝紫渐变,实时反映整体完成率 |
「总完成率」按工期加权计算,不是简单求平均,因此长工期任务对结果影响更大。
从左到右:
| 控件 | 作用 |
|---|---|
| 搜索框 | 实时过滤任务(匹配任务名称 + 任务内容),右侧显示 命中 n/N,关键词在列表中高亮,带清空按钮 |
| 日 / 周 / 月 | 切换右侧时间轴的刻度密度(仅在进度计划图下显示) |
| 网络计划图 | 整屏切换到网络视图;再次点击切回;蓝色高亮表示当前处于网络视图 |
| 关键路径 | 开 / 关「关键工作」的标红标注(勾选动作本身在「编辑任务」弹窗里完成) |
| 已保存 | 本地保存状态;点开可手动保存、导出/导入 json 备份、恢复示例数据,见 本地保存 |
| 深浅色 | 深色 / 浅色主题切换 |
| 导出 | 下拉菜单,见 导出 |
| 新建任务 | 打开空白任务弹窗 |
进度计划图(默认)或 网络计划图。两者共用同一份 tasks 数据,切换不会重置任何内容。
左侧任务表
- 列:
序(拖拽排序柄)|任务名称|任务内容|开始|结束|工期 - 行高 40px、表内文字 12px(2026-10-11 曾试过放大到 52px / 14px,整屏偏大,已改回)
- 没有「关键」列(2026-10-12 按要求取消):主界面不再有勾选框, 关键工作统一在「编辑任务」弹窗里勾「设为关键工作」
- 任务名称列会显示两个标识:关键工作(红点)、里程碑(橙色小旗)——红点即关键工作的状态提示
- 表内文字统一为黑色(2026-10-12 按要求把
任务名称 / 任务内容 / 开始 / 结束 / 工期全部改黑,深色主题下为浅色); 只有「序」号列和表头仍保持灰色,里程碑在「工期」列的「节点」二字保留琥珀色 - 每一列的右边界都可拖拽调宽;鼠标移到列头缝隙出现拖拽柄
- 整个任务表宽度可拖拽(左侧分隔条),双击复位到 584px
- 行可拖拽排序:抓住「序」号拖动到目标行即可换位
- 底部有「+ 新建任务」行,点击打开新建弹窗
- 右键菜单:编辑任务 / 上移一行 / 下移一行 / 删除任务
右侧时间轴
- 两行表头:
- 上排 = 分组行(日 / 周视图按月分组,月视图按年分组)
- 下排 = 刻度格,每一格都带完整时间文字
- 网格:竖格线、周末底色、完整外框(最下横线与左侧最后一行任务的下边框严格对齐,右侧竖线与时间轴右端对齐)
- 今天高亮列 + 顶部「今天」小徽标
- 任务条:百分比画在进度条「里面」的左端(贴条左端内侧 6px)+ 任务名称挂在进度条「后面」(右外侧 8px),
整行读作
[100% ███░░] 任务名称;里程碑渲染为菱形节点,名称留在菱形右侧- 百分比 11px、位置固定在条左端内侧、不随进度移动,只有颜色会翻转: 填充盖住这块标签区 → 白字(衬在深色填充上),没盖住 → 深字(衬在浅色未填充底上), 所以任何进度下都看得清
- 条太窄(宽 < 34px)时整块不显示百分比,避免被条的圆角/边界裁成半截数字(月视图缩到最小时才有这种情况)
- 名称 11px、黑色(深色主题为浅灰);关键工作的名称标红(深色主题为浅红), 条内百分比不跟关键色走
- 文字层
pointer-events: none,不挡任务条的拖拽与两端改期 - 进度条高 30px(行高 40px 内居中,上下各留 5px 净空),圆角 6px
- 里程碑菱形 18px,名称字号 11px
⚠️ 任务条贴到时间轴最右端时,右侧文字会被时间轴边界裁掉属于正常现象(可横向滚动查看)
- 依赖连线:保留连线本身、但不画箭头(箭头只在网络计划图里出现)
- 非关键线路为黑色虚线(
5 3、1.4px)、关键线路为红色实线(#dc2626、2px) - 连线一律从紧前任务的右端出发、走到紧后任务的左端收住,按间隔分两种走线:
情况 走线 间隔 ≥ 0(含紧后次日开工的 0 间隔) 一条平滑 S 曲线直达紧后任务左端 间隔 < 0(下面任务的开始时间在上面任务前面) 曲线绕道:从紧前条右端伸出 → 拐到两行之间的净空走廊 → 向左绕到进度条前端(左端)之前 → 再拐回来接入左端 - S 曲线的控制点横向外扩量 =
max(20px, 间隔/2)、上限 64px。 下限 20px 是关键:间隔为 0(截图里那种"下面开始日 = 上面结束次日")时它保证线依然是一段柔和的曲线, 不会退化成一根竖直直杠或生硬的直角折线 - S 曲线横向最大偏移只有控制点的约 0.29 倍(0 间隔时约 5.8px),且向右的鼓包落在紧前条右缘之外、 向左的鼓包落在紧后条左缘之外,所以曲线全程在两条任务条之间的空白里摆动,不会压到任务条
- 绕道的横向走廊取在紧后任务所在行的行边界上:任务条占行内
top+5 ~ top+(行高-5),行边界上下各有 5px 净空,所以走廊不会压到任何任务条;同一个任务有多个紧前工作时,各条线在走廊里上下错开 3px 避免重叠 - 深色主题下纯黑连线看不见,故非关键线自动转为浅灰
- 连图层
pointer-events: none,不影响任务条的拖拽与两端改期
- 非关键线路为黑色虚线(
- 悬停联动:鼠标移到左侧某一行,右侧对应任务条同步高亮(反之亦然)
- 拖拽改期:中间整体移动,左右两端调整起止日期 (两端手柄在鼠标悬停时出现;时间轴太密或任务条过窄时会自动隐藏,避免误触)
- 任务条 tooltip:任务名称 / 任务内容 / 起止日期与工期 / 进度 / 是否关键工作
滚动同步:四路联动 —— 左表头↔左行、右表头↔右图体、左行↔右图体(横向 + 纵向)。
时间轴缩放滑块(右下角悬浮胶囊)
缩小 ─── 滑块 ─── 放大 | 150% | 重置- 范围 40% ~ 300%,实时缩放整条时间轴(刻度格、任务条、依赖连线、今天线全部同步)
- 缩放时锁定视口中点,画面不会跑到别处
- 切换 日 / 周 / 月 时倍率自动复位到 100%
- 该控件位于导出区域之外,不会出现在导出的图片里
- 整屏替换进度计划图,数据完全同步
- 节点内容:任务名称 + 起止日期 + 进度条与百分比 + 工期 / 前置后置数量 + 关键标记 (节点内不显示任务内容,任务名称最多 3 行)
- 依赖连线:连线收在箭头尾部,箭尖刚好落在下游节点的左边缘上(不会直接连到任务块)
- 配色:非关键线路为黑色、关键线路为红色(
#dc2626), 箭头与连线同色(fill="currentColor",跟随同一个<svg>的文字色) - 深色主题下纯黑连线在深底上看不见,故非关键线自动转为浅灰; 关键线的红色在深浅主题下都保留
- 箭头为固定
12 × 8(markerUnits="userSpaceOnUse"),不随线宽缩放, 因此关键线(2.2px 红实线)与普通线(1.5px 黑虚线)的箭头大小完全一致 - 普通依赖为虚线
6 4并带圆头端点,使虚线断口在收口处自动收缩、紧贴箭头尾部
- 配色:非关键线路为黑色、关键线路为红色(
- 工具条:
自动布局/适应视图/缩小 · 百分比 · 放大/重置视图 - 交互:双击节点编辑、拖动节点移动、悬停节点右侧圆点拖到另一节点建立依赖、 空白处拖动平移画布、Ctrl(⌘)+ 滚轮缩放
- 左下角图例:依赖关系 / 关键线路 / 里程碑
- 进入该视图时自动适应视图,保证所有节点一进来就在画面内
| 字段 | 说明 |
|---|---|
| 任务名称 | 必填 |
| 任务内容 / 施工要点 | 可写施工内容、验收标准、责任人等 |
| 开始 / 结束日期 | 勾选里程碑时自动禁用 |
| 设为里程碑 | 单日节点,自动令 start = end |
| 设为关键工作 | 唯一的关键工作勾选入口(主界面任务表已无「关键」列) |
| 进度完成度 | 滑块 0 ~ 100,步长 5 |
| 紧前任务(前置依赖) | 多选列表;会自动检测循环依赖并把不可选项置灰禁用;标注为「完成–开始(FS)关系」 |
底部操作:删除任务(仅编辑时出现)/ 取消 / 保存修改或创建任务。
按 Esc 可关闭弹窗。
双代号时标网络计划(按时间坐标画的网络图),和甘特图那套是完全独立的工具。
| 部分 | 内容 |
|---|---|
| 页头 | 工程名称、开工 / 计划完工 / 总工期(含折算日历天)、工序数、关键线路串 |
| 工具条 | 时刻度(日 / 周 / 月)、显示开关(关键线路 / 波形线 / 今日线)、专业筛选 chips、编辑工序数据、适配窗口、打印 / 导出 |
| 图区 | 实箭线(长度即工期)、波形线(自由时差 FF)、虚箭线(逻辑关系,不占时间)、里程碑菱形、事件节点圆圈编号、今日竖虚线 |
| 图例 | 实箭线 / 关键线路 / 波形线 / 虚箭线 / 里程碑 / 事件节点 / 今日线 |
| 时间参数表 | 节点、工作名称、专业、班组、开始 / 结束、工期、ES / EF / LS / LF、总时差 TF、自由时差 FF、关键 |
几个特点:
- 关键线路自动标红(按 CPM 算 TF≈0),不需要人工勾选——这一点和甘特图相反(那边是人工标)
- 单击任一工作可高亮它的紧前、紧后链
- 滚轮缩放、横向拖拽查看
- 「编辑工序数据」弹窗里可改工程名、开工日期、日历(工作日 / 日历天)、逐条工序的起止与紧前工序,
还支持批量粘贴(
名称<tab>工期<tab>紧前)与 JSON 导入 / 导出 - 改动实时重算并自动保存到本机(键名
tsnd.timescale.v2) - 打印为 A3 横向(
@page{size:A3 landscape})
这套工具是从
时标网络图_v1.0.html原样搬进来的:它的 CSS 全部加了#ts-app作用域前缀, DOM 放在<template>里、点进工具时才克隆并初始化,所以和甘特图的 Tailwind 互不干扰。
| 想做什么 | 怎么做 |
|---|---|
| 改工程名称 | 直接点顶部标题输入 |
| 新建任务 | 工具栏「新建任务」按钮,或左侧表底部「新建任务」行 |
| 编辑任务 | 单击左侧任务行 / 双击网络节点 / 右键菜单「编辑任务」 |
| 改期(整体移动) | 拖动甘特条中间部分 |
| 改期(只动开始或结束) | 拖动甘特条左端或右端(手柄在悬停时出现) |
| 调整工期 | 同上(拖动两端即改变工期) |
| 建依赖关系 | 网络图:悬停节点 → 拖动右侧圆点连到目标节点;或弹窗里勾选「紧前任务」 |
| 标关键工作 | 打开任务弹窗,勾「设为关键工作」(主界面已无「关键」列) |
| 设里程碑 | 弹窗里勾「设为里程碑」 |
| 任务排序 | 抓住任务行的「序」号拖动 |
| 列宽 / 表宽 | 拖动列头缝隙 / 拖动左侧分隔条(双击复位) |
| 横向找时间段 | 拖右下角滑块,或直接拖时间轴下方滚动条 |
| 只看某些任务 | 用顶部搜索框(支持名称与内容) |
| 全屏出图 | 导出 PNG / PDF |
三种视图都是按时间单位切格(不是单纯按天拉伸),因此每格都有完整文字:
| 视图 | 每格代表 | 格内显示 | 基准密度 |
|---|---|---|---|
| 日 | 1 天 | 日期数字 + 星期简称(如 9 / 五) |
40 px / 天 |
| 周 | 1 周(周一起算) | 起止区间 10/05 ~ 10/11(格窄时退化为 10/05) |
22 px / 天 |
| 月 | 1 个月 | 2026年10月 |
10 px / 天 |
实际密度 = 基准密度 × 滑块倍率(40% ~ 300%)。
时间轴在项目起止日期前后各留了少量空白(开工前约 4 天、完工后约 8 天), 方便把任务往两头拖。
本项目不内置 CPM 自动计算(不自动算 ES/EF/LS/LF 与总时差), 关键工作完全由人工勾选,入口只有一个:
- 任务编辑弹窗里勾「设为关键工作」
主界面任务表原有的「关键」复选框列已于 2026-10-12 按要求取消, 避免在表里误点;任务名称列的红点仍然表示"这是一项关键工作"。
勾选后,该任务在甘特图与网络图中统一标红(红条 / 红环 / 红色实线连线), 顶部概览同步显示「关键工作 N 项」。
这么设计是刻意的:施工现场的「关键线路」往往由交底人依据经验与工期盘子判断, 半自动的时差算法一旦与现场认知不一致,反而更容易误导。 示例数据中预设了 6 项关键工作(施工准备 → 预埋桥架 → BAS 设备安装 → 单机调试 → 系统联调 → 竣工验收)。
甘特网络图:点工具栏「导出」:
| 菜单项 | 说明 |
|---|---|
| 进度计划图 → 导出 PNG 长图 | 会自动把内部滚动容器「撑开」后整图截图,长图不会被裁切 |
| 进度计划图 → 导出 PDF(自动分页) | A4 横向,按页高自动分页,不会把长表压成一页糊掉 |
| 网络计划图 → 导出 PNG | 若当前不在网络视图,会先自动切换再截图 |
导出区域为 #gantt-export-area / #network-export-area;
时间轴缩放滑块、网络图工具条等辅助控件都在导出区域之外,不会被截进去。
实测:在 1400px 宽的窗口下导出的长图是 4922 × 1424 px(约 450 KB), 时间轴被完整展开到最右侧任务结束为止,没有被视口宽度截断。
时标网络图:工具条上的「打印 / 导出」调浏览器打印(A3 横向), 「编辑工序数据」弹窗里有「导出 JSON / 导入 JSON」。
| 项 | 说明 |
|---|---|
| 运行时 | React 18 UMD + ReactDOM 18 UMD(production 版) |
| 编译 | @babel/standalone 浏览器端编译 <script type="text/babel">,classic runtime(输出 React.createElement) |
| 样式 | Tailwind CSS(CDN)+ 内联自定义样式,自定义 notion 色板;darkMode: 'class' |
| 图标 | 内联 SVG 图标组件(Icons.*),不依赖图标库 |
| 导出 | html2canvas 1.4.1 + jsPDF 2.5.1 |
| 日期 | 统一本地时区(getFullYear / getMonth / getDate)构造与格式化,禁止 toISOString() |
几个关键实现细节:
- 时区安全:所有日期计算走本地时区,避免东八区下整体差一天。
- 长图导出:导出前把内部滚动容器改为
overflow: visible并按scrollWidth / scrollHeight自底向上撑开,再用整体外框尺寸截图,导出后还原。 - 网格高度约束:区分
gridHeight(= 任务行数 × 行高,网格与今天列只画到这里) 与contentHeight(= 网格 + 一行「新建任务」),保证右侧竖格线不会超出左侧任务行数。 - 状态提升:
sidebarWidth/colWidths保存在应用层,因此切换到网络图再切回来,调好的列宽不会丢。 - 条件渲染:两个视图互斥挂载(而非
display:none隐藏),因为隐藏元素尺寸为 0、无法截图。 - 反馈机制:操作提示统一走轻量 Toast,危险操作走自定义确认弹窗,不使用原生
alert/confirm。
- CSS 作用域:时标图那套 CSS 有 107 条顶层规则,原始写法里有
body{}、table{}、*{}, 直接拼进来会把甘特图的 Tailwind 全部打乱。做法是逐条加#ts-app前缀 (:root / html / body→#ts-app,*→#ts-app *),@page / @keyframes / @font-face原样保留,@media内部递归处理,构建时还会自检有没有漏网的裸选择器。 [hidden]会被作者样式盖掉:#app-view{display:flex}属于作者级display, 优先于浏览器默认的[hidden]{display:none}——结果hidden属性完全失效,应用区永远压在首页上。 必须自己补一条#app-view[hidden]{display:none}。jsdom 不做样式层叠,这类问题只能靠真浏览器发现。- 深色主题不要动
<html>:原来document.documentElement.classList.toggle('dark')会把整个文档带黑, 连带把浅色的时标图一起弄黑。改为只加在#gantt-dark-scope这个包裹层上。 - 按需挂载:两个工具都改成
window.__mountGanttApp()/window.__mountTimescaleApp(), 点进去才渲染;已经挂过的不重复挂(否则会重建 DOM、丢状态、动画重放)。
- 网格项上写
margin-left:auto会把元素缩没。align-items:center的网格里, 给.hx-sheet加max-width:482px; margin-left:auto想让样张靠右—— 结果触发「auto 外边距不拉伸」规则,宽度塌成内容宽度,实测只剩 300px。 正确做法是把宽度上限加在 SVG 上(.hx-sheet svg{max-width:492px; margin-left:auto}), 让<figure>保持网格列宽。 - 矮屏要连样张一起缩。样张是
520:340的定比 SVG,宽度一下来高度自然就矮了, 这是@media (max-height:820px)能把 1366×768 压进一屏的关键; 只压内边距是不够的。
CRSC-main/
├── index.html ← ★ 合并后的唯一运行入口(首页 + 两个工具,全部内联)
├── 甘特网络图.html ← 合并前的原始甘特图单文件(备份 + 构建源文件,别删)
├── 时标网络图_v1.0.html ← 原始时标网络图单文件(构建源文件,别删)
├── 图片1.jpg ← LOGO 原图(构建时由 mkdlogo.mjs 加工,不直接进页面)
├── README.md ← 本文档
├── .gitignore
└── .workbuddy/
├── build/ ← 构建脚本与片段(改首页/外壳时改这里再重新构建)
│ ├── build.mjs ← 把两个源文件拼成 index.html
│ ├── home.html ← 首页片段(顶栏、主视觉、两个入口、作者、页脚)
│ ├── home.css ← 首页样式(深色制图台,全部限定在 #home-view 内)
│ ├── shell.css ← 外壳样式(路由容器、顶部返回条)
│ ├── mkdlogo.mjs ← 把 LOGO 加工成「透明底 + 浅色字」版
│ └── assets/
│ ├── logo-dark.png ← 首页用的深色版 LOGO(构建时内联)
│ └── logo-plain.png ← 透明底的原始配色版(浅色场景备用)
└── memory/ ← 开发过程记录,与应用运行无关
只有 index.html 一个文件参与运行,它引用的全部是 CDN 资源(Tailwind、React 18 UMD、
Babel standalone、html2canvas、jsPDF、Google Fonts),不依赖任何本地文件
(LOGO 已内联成 base64)。
| 文件 | 角色 |
|---|---|
index.html |
产物。由 build.mjs 生成,别直接手改(下次构建会被覆盖) |
甘特网络图.html |
甘特图的源文件(合并前的原 index.html)。改甘特图逻辑改这个 |
时标网络图_v1.0.html |
时标图的源文件。改时标图逻辑改这个 |
改完源文件后重新构建:
node .workbuddy/build/build.mjs
⚠️ build.mjs读的是甘特网络图.html,不是index.html。 如果让它读index.html(也就是自己的产物)当输入,第一次能构建,第二次补丁就 0 命中—— 这是之前踩过的坑,脚本里已经写死并加了防呆断言。
早期从 AI Studio 导出时,目录里还带过一套
App.tsx/components/*.tsx/index.tsx/types.ts/utils.ts/package.json/vite.config.ts/tsconfig.json/metadata.json的 Vite 工程代码(React 19 + lucide-react)。那套代码从未被index.html引用 (页面里没有<script type="module" src="/index.tsx">),且功能明显落后于index.html(没有关键工作勾选、整屏视图切换、时间轴缩放滑块等),属死代码,已清理并移入回收站。因此:若今后要走
npm + Vite工程化路线,需要重新搭建脚手架,而不是恢复那套旧代码。
| 限制 | 说明 |
|---|---|
| 数据只存在本机这个浏览器里 | 用 localStorage 保存,换浏览器 / 换电脑 / 清理浏览器数据都会看不到。要跨设备或长期留存,请用「导出备份(.json)」 |
| 两个工具的数据不互通 | 甘特图的任务不会自动变成时标图的工序,反之亦然。两边要各录一遍(确实需要时,甘特图导出 JSON、时标图导入 JSON,但字段格式不同,需手工整理) |
| 多标签页会互相覆盖 | 同一文件开两个标签页分别修改,后保存的那份会覆盖另一份(没有做跨标签页同步) |
| 隐私模式下无法保存 | 无痕 / 隐私模式禁用本地存储,此时工具栏的保存按钮会变成黄色的「未保存」,请改用导出备份 |
| 工程文件需手动备份 | 支持 JSON 备份与导入,但不会自动上传云端 |
| 仅支持 FS 关系 | 前置依赖只有「完成–开始」,不支持 SS / FF / SF 与滞后时间 |
| 关键工作需人工标注 | 甘特图不自动计算关键线路(见上文说明);时标图是按 CPM 自动算的 |
| 需联网 | React / Babel / Tailwind / html2canvas / jsPDF 与字体均从 CDN 加载;离线打开会白屏 |
| 视图切换不记忆滚动位置 | 两个视图互斥挂载,切回甘特图后滚动位置回到左上角(列宽、表宽等设置会保留) |
| 浏览器打印一律 A3 横向 | 时标图的 @page{size:A3 landscape} 是文档级规则、无法作用域化,因此在甘特图里按 Ctrl+P 也会是 A3 横向。甘特图的 PDF 请用工具栏「导出 → PDF」,那条走 jsPDF、不受影响 |
两个工具各存各的,互不覆盖:
| 工具 | localStorage 键 | 什么时候写 |
|---|---|---|
| 甘特网络图 | crsc:progress-plan:v1 |
挂载时写一次基线,之后改动防抖 400ms 落盘 |
| 时标网络图 | tsnd.timescale.v2 |
只有真的改过工序数据才写(没改动就不占空间) |
关掉页面、重启电脑后再打开,任务、工程名称、主题、日/周/月、视图与列宽都会原样恢复。
工具栏上的绿色 「已保存」 按钮显示保存状态:
| 菜单项 | 作用 |
|---|---|
| 立即保存 | 跳过 400ms 防抖,立刻落盘(平时不用点) |
| 导出备份(.json) | 把当前全部数据存成一个 json 文件,用于换电脑 / 归档 / 多方案并存 |
| 导入备份(.json) | 读取备份文件并覆盖当前数据 |
| 恢复示例数据 | 清空任务、回到最初的 8 项示例(主题与列宽等显示习惯保留) |
几点实现上的取舍:
- 防抖 400ms:拖动任务条、拖列宽时状态高频变化,不能每帧都写盘。
- 关页面前补存:监听
beforeunload与visibilitychange,避免最后 400ms 内的改动丢掉。 - 日期存成
YYYY-MM-DD,读回时走parseDateInput按本地时区建Date。 不能用 ISO/UTC 字符串,否则东八区会整体差一天。 - 空任务列表会被尊重:把任务全删光后重开仍是 0 项,不会被示例数据"复活"。
- 存档损坏不白屏:JSON 解析失败时静默退回示例数据,页面照常可用。
改动后可以做两层自检。第一层必须有,第二层在有浏览器的机器上尽量跑——
因为 [hidden] 被作者样式盖掉、justify-content 没生效这类布局问题 jsdom 完全发现不了,
本轮就踩到过:jsdom 全绿,真浏览器一打开首页直接被应用区盖住。
- 抽取
index.html中<script type="text/babel">的内容; - 用
@babel/standalone按 classic runtime 编译:Babel.transform(code, { presets: [['react', { runtime: 'classic' }]] }); - 在
jsdom里先后window.evalReact UMD、ReactDOM UMD、上一步的编译产物, 合并版还要显式调用window.__mountGanttApp()(它不再自动挂载); - 用
dispatchEvent派发真实的mousedown / mousemove / mouseup / input / click事件, 再断言 DOM(行数、样式高度、勾选状态、文字内容等)。
npm install jsdom
node verify8.mjs # 甘特图布局 / 字号 / 列宽 / 关键列是否已删
node verify9.mjs # 依赖连线几何(S 曲线、反向绕道不压条)
node verify10.mjs # 本地保存 / 重开恢复 / 坏数据兜底
node verify11.mjs # 合并版:首页 / 路由 / 两个工具挂载 / 样式作用域 / ID 唯一关键陷阱:
@babel/standalone对text/babel默认使用 classic runtime (输出React.createElement),因此校验时也必须用 classic,否则会因找不到react/jsx-runtime而直接白屏。另一个:jsdom 没实现
window.scrollTo(),会以Not implemented的形式走console.error。 这是噪音不是 bug,校验脚本里已单独白名单,别把它当成报错。
node behave.mjs # 首页 / 进两个工具 / Esc 返回 / hash 直接进入 / 独立存档 / 窄屏不破版
node shots.mjs # 输出 r_home.png / r_gantt.png / r_ts.png 三张实拍图这一步能抓到的正是 jsdom 抓不到的:display 层叠、[hidden] 是否真的隐藏、
真浏览器下的实际排版与配色、以及 PNG 长图导出是否真能产出文件。
示例数据为地铁车站 BAS / 门禁系统施工顺序,仅用于演示;实际使用时请直接改标题与任务内容。