ct-gantt-core 1.0.13 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,83 +1,98 @@
1
- # ct-gantt-core
2
-
3
- 框架无关的甘特图数据、布局、依赖排程和命令式引擎。该包不包含界面,可用于 Vue、React、原生 JavaScript、服务端计算或自定义渲染器。
4
-
5
- ## 安装
6
-
7
- ```bash
8
- pnpm add ct-gantt-core
9
- ```
10
-
11
- 也可以使用 `npm install ct-gantt-core` 或 `yarn add ct-gantt-core`。
12
-
13
- ## 最小示例
14
-
15
- ```ts
16
- import { GanttEngine, type GanttTask } from "ct-gantt-core"
17
-
18
- const tasks: GanttTask[] = [
19
- {
20
- id: "task-1",
21
- name: "需求分析",
22
- type: "task",
23
- plan: { start: "2026-07-01", end: "2026-07-05" },
24
- actual: { start: "2026-07-01", end: "2026-07-06", progress: 60 }
25
- }
26
- ]
27
-
28
- const engine = new GanttEngine({
29
- tasks,
30
- config: { viewMode: "day", columnWidth: 30 }
31
- })
32
-
33
- const layout = engine.getLayout()
34
- if (layout.ok) {
35
- console.log(layout.data)
36
- }
37
-
38
- engine.setTask("task-1", { progress: 80 })
39
- engine.destroy()
40
- ```
41
-
42
- ## 依赖排程
43
-
44
- ```ts
45
- import { scheduleByDependencies, type GanttLink } from "ct-gantt-core"
46
-
47
- const links: GanttLink[] = [
48
- { id: "design-to-dev", sourceId: "design", targetId: "dev", type: "FS" }
49
- ]
50
-
51
- const result = scheduleByDependencies(tasks, links)
52
- if (result.ok) {
53
- console.log(result.data)
54
- }
55
- ```
56
-
57
- 支持 `FS`、`SS`、`FF`、`SF` 四种依赖类型,以及自然日或工作日间隔。
58
-
59
- ## 主要导出
60
-
61
- | API | 用途 |
62
- | --- | --- |
63
- | `GanttEngine` | 管理任务、依赖、折叠、预览、布局和视口命令 |
64
- | `computeLayout` | 计算任务条位置和尺寸 |
65
- | `computeTimeScale` | 计算时间刻度(day/week/month/quarter/year) |
66
- | `computeHourScale` | 计算小时级时间刻度(每天 24 个小时列,顶行日期 + 底部整点小时) |
67
- | `computeResourceLayout` | 资源视图布局:资源行 + 占用色条(支持 day/hour 粒度与车道堆叠) |
68
- | `resolveUsageTimes` | 将资源占用解析为布局用起止时间(hour 粒度下纯日期占用含结束日整天) |
69
- | `scheduleByDependencies` | 按依赖关系调整任务日期 |
70
- | `computeImpact` | 分析任务变更的影响和约束冲突 |
71
- | `checkCyclicDependency` | 检查循环依赖 |
72
- | `normalizeLinks` | 统一任务内依赖与独立依赖数据 |
73
- | `flattenTasks` | 将阶段树转换为可渲染行 |
74
- | `toDate`、`toDateTime`、`addDays`、`addHours`、`diffDays`、`diffHours`、`formatDate`、`formatDateTime` | 日期工具(`toDateTime`/`diffHours` 保留时间,用于小时级计算) |
75
-
76
- ## 数据说明
77
-
78
- - `plan` 表示计划日期。
79
- - `actual` 表示实际日期和完成进度。
80
- - `summary` 表示阶段,`task` 表示普通任务,`milestone` 表示任务型里程碑。
81
- - Core 不负责绘制界面;需要现成 Vue 界面时请安装 `ct-gantt-vue`。
82
-
83
- 完整类型、配置和示例请查看[项目文档](https://github.com/moonlight-219/ganttu#readme)。
1
+ # ct-gantt-core
2
+
3
+ 框架无关的甘特图数据、布局、依赖排程和命令式引擎。该包不包含界面,可用于 Vue、React、原生 JavaScript、服务端计算或自定义渲染器。
4
+
5
+ ## 安装
6
+
7
+ 支持 npm / pnpm / yarn 三种包管理器,任选其一:
8
+
9
+ ```bash
10
+ # npm
11
+ npm install ct-gantt-core
12
+
13
+ # pnpm
14
+ pnpm add ct-gantt-core
15
+
16
+ # yarn
17
+ yarn add ct-gantt-core
18
+ ```
19
+
20
+ `ct-gantt-core` 框架无关、无运行时依赖,Vue / React / 原生 JavaScript / 服务端环境均可直接使用。
21
+
22
+ ## 最小示例
23
+
24
+ ```ts
25
+ import { GanttEngine, type GanttTask } from "ct-gantt-core"
26
+
27
+ // 任务:type 区分 task / milestone / summary;
28
+ // plan 是计划日期,actual 是实际日期与进度。
29
+ const tasks: GanttTask[] = [
30
+ {
31
+ id: "task-1",
32
+ name: "需求分析",
33
+ type: "task",
34
+ plan: { start: "2026-07-01", end: "2026-07-05" },
35
+ actual: { start: "2026-07-01", end: "2026-07-06", progress: 60 }
36
+ }
37
+ ]
38
+
39
+ // 创建引擎:持有任务、依赖与视口配置,负责全部数据计算。
40
+ const engine = new GanttEngine({
41
+ tasks,
42
+ config: { viewMode: "day", columnWidth: 30 }
43
+ })
44
+
45
+ // 计算布局:ok true data 含每个任务条的像素位置与尺寸。
46
+ const layout = engine.getLayout()
47
+ if (layout.ok) {
48
+ console.log(layout.data)
49
+ }
50
+
51
+ engine.setTask("task-1", { progress: 80 }) // 命令式更新任务并触发重算
52
+ engine.destroy() // 释放引擎内部资源
53
+ ```
54
+
55
+ ## 依赖排程
56
+
57
+ ```ts
58
+ import { scheduleByDependencies, type GanttLink } from "ct-gantt-core"
59
+
60
+ // 依赖:FS 表示 design 完成后 dev 才能开始。
61
+ const links: GanttLink[] = [
62
+ { id: "design-to-dev", sourceId: "design", targetId: "dev", type: "FS" }
63
+ ]
64
+
65
+ // 按依赖推算任务日期;ok false 时 error 含原因(如循环依赖)。
66
+ const result = scheduleByDependencies(tasks, links)
67
+ if (result.ok) {
68
+ console.log(result.data)
69
+ }
70
+ ```
71
+
72
+ 支持 `FS`、`SS`、`FF`、`SF` 四种依赖类型,以及自然日或工作日间隔。
73
+
74
+ ## 主要导出
75
+
76
+ | API | 用途 |
77
+ | --- | --- |
78
+ | `GanttEngine` | 管理任务、依赖、折叠、预览、布局和视口命令 |
79
+ | `computeLayout` | 计算任务条位置和尺寸 |
80
+ | `computeTimeScale` | 计算时间刻度(day/week/month/quarter/year) |
81
+ | `computeHourScale` | 计算小时级时间刻度(每天 24 个小时列,顶行日期 + 底部整点小时) |
82
+ | `computeResourceLayout` | 资源视图布局:资源行 + 占用色条(支持 day/hour 粒度与车道堆叠) |
83
+ | `resolveUsageTimes` | 将资源占用解析为布局用起止时间(hour 粒度下纯日期占用含结束日整天) |
84
+ | `scheduleByDependencies` | 按依赖关系调整任务日期 |
85
+ | `computeImpact` | 分析任务变更的影响和约束冲突 |
86
+ | `checkCyclicDependency` | 检查循环依赖 |
87
+ | `normalizeLinks` | 统一任务内依赖与独立依赖数据 |
88
+ | `flattenTasks` | 将阶段树转换为可渲染行 |
89
+ | `toDate`、`toDateTime`、`addDays`、`addHours`、`diffDays`、`diffHours`、`formatDate`、`formatDateTime` | 日期工具(`toDateTime`/`diffHours` 保留时间,用于小时级计算) |
90
+
91
+ ## 数据说明
92
+
93
+ - `plan` 表示计划日期。
94
+ - `actual` 表示实际日期和完成进度。
95
+ - `summary` 表示阶段,`task` 表示普通任务,`milestone` 表示任务型里程碑。
96
+ - Core 不负责绘制界面;需要现成 Vue 界面时请安装 `ct-gantt-vue`。
97
+
98
+ 完整类型、配置和示例请查看[项目文档](https://github.com/moonlight-219/ganttu#readme)。
package/dist/index.cjs CHANGED
@@ -24,6 +24,7 @@ __export(index_exports, {
24
24
  DEFAULT_HOUR_COLORS: () => DEFAULT_HOUR_COLORS,
25
25
  DEFAULT_RESOURCE_COLORS: () => DEFAULT_RESOURCE_COLORS,
26
26
  DEFAULT_TASK_COLORS: () => DEFAULT_TASK_COLORS,
27
+ DEFAULT_WORKLOAD_COLORS: () => DEFAULT_WORKLOAD_COLORS,
27
28
  EDITOR_COLOR_OPTIONS: () => EDITOR_COLOR_OPTIONS,
28
29
  GanttEngine: () => GanttEngine,
29
30
  HOUR_MS: () => HOUR_MS,
@@ -57,6 +58,7 @@ __export(index_exports, {
57
58
  mergeTaskPatch: () => mergeTaskPatch,
58
59
  normalizeLinks: () => normalizeLinks,
59
60
  plannedDurationBetween: () => plannedDurationBetween,
61
+ resolveColumnWidth: () => resolveColumnWidth,
60
62
  resolveGanttConfig: () => resolveGanttConfig,
61
63
  resolveResourceColor: () => resolveResourceColor,
62
64
  resolveUsageColor: () => resolveUsageColor,
@@ -97,6 +99,14 @@ var DEFAULT_RESOURCE_COLORS = {
97
99
  booking: "#409EFF",
98
100
  assignment: "#909399"
99
101
  };
102
+ var DEFAULT_WORKLOAD_COLORS = {
103
+ used: "#67C23A",
104
+ // 已排工时徽章 · Element Success
105
+ over: "#F56C6C",
106
+ // 超负荷徽章 / 超载面积条 · Element Danger
107
+ area: "#409EFF"
108
+ // capacity 模式容量面积条 · Element 主色
109
+ };
100
110
  var RESOURCE_COLOR_PALETTE = [DEFAULT_RESOURCE_COLORS.booking];
101
111
  var RESOURCE_EDITOR_COLOR_OPTIONS = [
102
112
  "#409eff",
@@ -128,6 +138,7 @@ var defaultConfig = {
128
138
  showPlanBar: true,
129
139
  showActualBar: true,
130
140
  showTimelineWhenEmpty: false,
141
+ highlightWeekend: true,
131
142
  builtInTaskEditor: false,
132
143
  builtInMarkerEditor: true,
133
144
  editablePlan: false,
@@ -136,13 +147,17 @@ var defaultConfig = {
136
147
  showLinkRejectionNotice: true,
137
148
  virtualScroll: true,
138
149
  taskColors: { ...DEFAULT_TASK_COLORS },
150
+ resourceUsageColors: { ...DEFAULT_RESOURCE_COLORS },
151
+ workloadColors: { ...DEFAULT_WORKLOAD_COLORS },
152
+ workloadDefaultCapacity: 8,
139
153
  timeUnit: "day",
140
154
  hourWidth: 12,
141
155
  autoSchedule: true,
142
156
  editable: true,
143
157
  enableSelection: false,
144
158
  showAddTaskColumn: true,
145
- addTaskAs: "child"
159
+ addTaskAs: "child",
160
+ showAddUsageColumn: true
146
161
  };
147
162
 
148
163
  // src/services/checkCyclicDependency.ts
@@ -544,12 +559,17 @@ function mergeGanttConfig(base, patch = {}) {
544
559
  ...patch,
545
560
  taskColors: { ...base.taskColors, ...patch.taskColors },
546
561
  columnWidths: { ...base.columnWidths, ...patch.columnWidths },
547
- resourceColors: { ...base.resourceColors, ...patch.resourceColors }
562
+ resourceColors: { ...base.resourceColors, ...patch.resourceColors },
563
+ resourceUsageColors: { ...base.resourceUsageColors, ...patch.resourceUsageColors },
564
+ workloadColors: { ...base.workloadColors, ...patch.workloadColors }
548
565
  };
549
566
  }
550
567
  function resolveGanttConfig(patch = {}) {
551
568
  return mergeGanttConfig(defaultConfig, patch);
552
569
  }
570
+ function resolveColumnWidth(config, viewMode = config.viewMode ?? defaultConfig.viewMode) {
571
+ return config.columnWidths?.[viewMode] ?? config.columnWidth ?? defaultConfig.columnWidth;
572
+ }
553
573
 
554
574
  // src/engines/scheduling.ts
555
575
  function scheduleByDependencies(tasks, links) {
@@ -821,14 +841,14 @@ function resolveResourceColor(resource, resourceColors) {
821
841
  const source = resource.name || resource.id;
822
842
  return RESOURCE_COLOR_PALETTE[colorHash(source) % RESOURCE_COLOR_PALETTE.length];
823
843
  }
824
- function resolveUsageColor(usage, resource, resourceColors) {
844
+ function resolveUsageColor(usage, resource, resourceColors, resourceUsageColors) {
825
845
  if (usage.color) {
826
846
  return usage.color;
827
847
  }
828
848
  if (resource.color || resourceColors?.[resource.id]) {
829
849
  return resolveResourceColor(resource, resourceColors);
830
850
  }
831
- return usage.type === "assignment" ? DEFAULT_RESOURCE_COLORS.assignment : DEFAULT_RESOURCE_COLORS.booking;
851
+ return usage.type === "assignment" ? resourceUsageColors?.assignment ?? DEFAULT_RESOURCE_COLORS.assignment : resourceUsageColors?.booking ?? DEFAULT_RESOURCE_COLORS.booking;
832
852
  }
833
853
  function resolveUsageTimes(usage, timeUnit) {
834
854
  if (timeUnit === "hour") {
@@ -842,7 +862,7 @@ function computeResourceLayout(resources, usages, config, viewport) {
842
862
  const mergedConfig = resolveGanttConfig(config);
843
863
  const timeUnit = mergedConfig.timeUnit ?? "day";
844
864
  const hourMode = timeUnit === "hour";
845
- const hourW = mergedConfig.hourWidth ?? 12;
865
+ const hourW = mergedConfig.hourWidth ?? mergedConfig.columnWidth ?? 12;
846
866
  for (const usage of usages) {
847
867
  const { start, end } = resolveUsageTimes(usage, timeUnit);
848
868
  if (!isValidDate(start) || !isValidDate(end)) {
@@ -962,17 +982,33 @@ function isLaneFree(laneEnd, start, hourMode) {
962
982
  }
963
983
 
964
984
  // src/engines/computeWorkload.ts
965
- function computeWorkload(tasks, people, departments = [], range) {
985
+ function computeWorkload(tasks, people, departments = [], range, groupBy, defaultCapacityPerDay, includeIdle = false, includeWeekends) {
966
986
  const dated = range ?? inferRange(tasks);
967
987
  if (!dated) return { dates: [], departments: [], ungrouped: [] };
968
988
  const dates = datesBetween(dated.start, dated.end);
969
989
  const dateIndex = new Map(dates.map((date, index) => [date, index]));
970
- const rows = new Map(people.map((person) => [person.id, createPerson(person, dates)]));
990
+ const activeIds = /* @__PURE__ */ new Set();
991
+ for (const task of tasks) {
992
+ if (task.type === "summary" || task.type === "milestone") continue;
993
+ for (const id of task.resources ?? []) activeIds.add(id);
994
+ }
995
+ const candidates = mergePeopleWithTaskResources(people, tasks);
996
+ const members = includeIdle ? candidates : candidates.filter((person) => activeIds.has(person.id));
997
+ const rows = new Map(members.map((person) => [person.id, createPerson(person, dates, defaultCapacityPerDay)]));
998
+ const tasksByPerson = /* @__PURE__ */ new Map();
999
+ for (const task of tasks) {
1000
+ if (task.type === "summary" || task.type === "milestone") continue;
1001
+ for (const id of task.resources ?? []) {
1002
+ const list = tasksByPerson.get(id);
1003
+ if (list) list.push(task);
1004
+ else tasksByPerson.set(id, [task]);
1005
+ }
1006
+ }
971
1007
  for (const task of tasks) {
972
1008
  if (task.type === "summary" || task.type === "milestone") continue;
973
1009
  const ids = task.resources ?? [];
974
1010
  if (!ids.length) continue;
975
- const values = task.dailyWorkloads ?? averageTaskWorkload(task);
1011
+ const values = task.dailyWorkloads ?? averageTaskWorkload(task, includeWeekends);
976
1012
  for (const personId of ids) {
977
1013
  const row = rows.get(personId);
978
1014
  if (!row) continue;
@@ -985,19 +1021,44 @@ function computeWorkload(tasks, people, departments = [], range) {
985
1021
  for (const row of rows.values()) finalize(row);
986
1022
  const grouped = /* @__PURE__ */ new Map();
987
1023
  const ungrouped = [];
1024
+ const keyOf = groupBy ?? ((person) => person.departmentId);
988
1025
  for (const row of rows.values()) {
989
- if (row.person.departmentId) {
990
- const members = grouped.get(row.person.departmentId);
991
- if (members) members.push(row);
992
- else grouped.set(row.person.departmentId, [row]);
1026
+ const key = keyOf(row.person, members, tasksByPerson.get(row.person.id) ?? []);
1027
+ if (key) {
1028
+ const list = grouped.get(key);
1029
+ if (list) list.push(row);
1030
+ else grouped.set(key, [row]);
993
1031
  } else {
994
1032
  ungrouped.push(row);
995
1033
  }
996
1034
  }
997
1035
  const departmentMap = new Map(departments.map((item) => [item.id, item]));
998
- const result = [...grouped].map(([id, members]) => aggregateDepartment(departmentMap.get(id) ?? { id, name: id }, members, dates));
1036
+ const result = [...grouped].map(([id, list]) => aggregateDepartment(departmentMap.get(id) ?? { id, name: id }, list, dates));
999
1037
  return { dates, departments: result, ungrouped };
1000
1038
  }
1039
+ function derivePeople(tasks) {
1040
+ const map = /* @__PURE__ */ new Map();
1041
+ for (const task of tasks) {
1042
+ for (const id of task.resources ?? []) {
1043
+ if (!map.has(id)) map.set(id, { id, name: id });
1044
+ }
1045
+ }
1046
+ return [...map.values()];
1047
+ }
1048
+ function mergePeopleWithTaskResources(people, tasks) {
1049
+ const list = people?.length ? [...people] : derivePeople(tasks);
1050
+ const known = new Set(list.map((person) => person.id));
1051
+ for (const task of tasks) {
1052
+ if (task.type === "summary" || task.type === "milestone") continue;
1053
+ for (const id of task.resources ?? []) {
1054
+ if (!known.has(id)) {
1055
+ known.add(id);
1056
+ list.push({ id, name: id });
1057
+ }
1058
+ }
1059
+ }
1060
+ return list;
1061
+ }
1001
1062
  function inferRange(tasks) {
1002
1063
  if (!tasks.length) return void 0;
1003
1064
  const extent = dateExtent(tasks.flatMap((task) => [toDate(task.plan.start), toDate(task.plan.end)]));
@@ -1008,18 +1069,20 @@ function datesBetween(start, end) {
1008
1069
  for (let date = toDate(start); date <= toDate(end); date = addDays(date, 1)) values.push(formatDate(date));
1009
1070
  return values;
1010
1071
  }
1011
- function averageTaskWorkload(task) {
1072
+ function averageTaskWorkload(task, includeWeekends) {
1012
1073
  const total = task.workload ?? (task.duration ?? inclusiveDays(task.plan.start, task.plan.end)) * 8;
1013
1074
  const allDates = datesBetween(task.plan.start, task.plan.end);
1014
- const dates = task.calendarId === "delivery" ? allDates : allDates.filter((date) => {
1075
+ const workdays = allDates.filter((date) => {
1015
1076
  const day = toDate(date).getDay();
1016
1077
  return day !== 0 && day !== 6;
1017
1078
  });
1079
+ const withWeekends = includeWeekends ?? task.calendarId === "delivery";
1080
+ const dates = withWeekends ? allDates : workdays;
1018
1081
  const hours = dates.length ? total / dates.length : 0;
1019
1082
  return Object.fromEntries(dates.map((date) => [date, hours]));
1020
1083
  }
1021
- function createPerson(person, dates) {
1022
- return { person, days: dates.map((date) => ({ date, hours: 0, capacity: person.capacityPerDay ?? 8 })), workload: 0, capacity: 0, utilization: 0, progress: 0 };
1084
+ function createPerson(person, dates, defaultCapacityPerDay) {
1085
+ return { person, days: dates.map((date) => ({ date, hours: 0, capacity: person.capacityPerDay ?? defaultCapacityPerDay ?? 8 })), workload: 0, capacity: 0, utilization: 0, progress: 0 };
1023
1086
  }
1024
1087
  function finalize(row) {
1025
1088
  row.workload = row.days.reduce((sum, day) => sum + day.hours, 0);
@@ -1062,15 +1125,25 @@ function mergeTaskPatch(task, patch) {
1062
1125
  };
1063
1126
  }
1064
1127
  var GanttEngine = class {
1128
+ /** 任务列表(内部以浅拷贝存储,避免外部引用篡改)。 */
1065
1129
  tasks;
1130
+ /** 链路列表(内部以浅拷贝存储)。 */
1066
1131
  links;
1132
+ /** 合并后的完整配置。 */
1067
1133
  config;
1134
+ /** 滚动容器元素;可为 null(纯计算场景)。 */
1068
1135
  container;
1136
+ /** 当前折叠的任务 id 集合。 */
1069
1137
  collapsedIds;
1138
+ /** 拖拽预览状态;null 表示无预览。 */
1070
1139
  dragPreview = null;
1140
+ /** 里程碑标记列表。 */
1071
1141
  markers;
1142
+ /** 事件名 → 监听器集合。 */
1072
1143
  listeners;
1144
+ /** 是否已销毁。 */
1073
1145
  destroyed = false;
1146
+ /** 构造引擎:初始化数据、合并配置,并异步发出 ready 事件。 */
1074
1147
  constructor(options = {}) {
1075
1148
  this.tasks = options.tasks ? options.tasks.map((task) => ({ ...task })) : [];
1076
1149
  this.links = options.links ? options.links.map((link) => ({ ...link })) : [];
@@ -1081,40 +1154,50 @@ var GanttEngine = class {
1081
1154
  this.listeners = /* @__PURE__ */ new Map();
1082
1155
  queueMicrotask(() => this.emit("ready"));
1083
1156
  }
1084
- // ── 状态查询 ──
1157
+ // ── 状态查询(均返回副本,避免外部直接篡改内部状态) ──
1158
+ /** 获取全部任务的浅拷贝数组。 */
1085
1159
  getTasks() {
1086
1160
  return this.tasks.map((task) => ({ ...task }));
1087
1161
  }
1162
+ /** 获取全部链路的浅拷贝数组。 */
1088
1163
  getLinks() {
1089
1164
  return this.links.map((link) => ({ ...link }));
1090
1165
  }
1166
+ /** 获取全部里程碑标记的浅拷贝数组。 */
1091
1167
  getMarkers() {
1092
1168
  return this.markers.map((marker) => ({ ...marker }));
1093
1169
  }
1170
+ /** 获取当前配置的浅拷贝。 */
1094
1171
  getConfig() {
1095
1172
  return { ...this.config };
1096
1173
  }
1174
+ /** 按 id 查询单个任务,不存在时返回 undefined。 */
1097
1175
  getTask(id) {
1098
1176
  const task = this.tasks.find((item) => item.id === id);
1099
1177
  return task ? { ...task } : void 0;
1100
1178
  }
1179
+ /** 获取当前折叠的任务 id 列表。 */
1101
1180
  getCollapsedIds() {
1102
1181
  return Array.from(this.collapsedIds);
1103
1182
  }
1183
+ /** 引擎是否已被销毁(destroy 之后为 true)。 */
1104
1184
  isDestroyed() {
1105
1185
  return this.destroyed;
1106
1186
  }
1107
1187
  // ── 命令式变更(均触发对应事件) ──
1188
+ /** 整体替换任务列表,并触发 taskschange 事件。 */
1108
1189
  setTasks(tasks) {
1109
1190
  this.assertNotDestroyed();
1110
1191
  this.tasks = tasks.map((task) => ({ ...task }));
1111
1192
  this.emit("taskschange", this.tasks);
1112
1193
  }
1194
+ /** 整体替换链路列表,并触发 linkschange 事件。 */
1113
1195
  setLinks(links) {
1114
1196
  this.assertNotDestroyed();
1115
1197
  this.links = links.map((link) => ({ ...link }));
1116
1198
  this.emit("linkschange", this.links);
1117
1199
  }
1200
+ /** 整体替换里程碑标记列表,并触发 markerschange 事件。 */
1118
1201
  setMarkers(markers) {
1119
1202
  this.assertNotDestroyed();
1120
1203
  this.markers = markers.map((marker) => ({ ...marker }));
@@ -1126,6 +1209,7 @@ var GanttEngine = class {
1126
1209
  this.config = mergeGanttConfig(this.config, patch);
1127
1210
  this.emit("configchange", this.getConfig());
1128
1211
  }
1212
+ /** 以合并方式设置配置,等价于 setConfig(mergeConfig(patch)) 的便捷封装。 */
1129
1213
  setConfig(config) {
1130
1214
  this.mergeConfig(config);
1131
1215
  }
@@ -1140,11 +1224,13 @@ var GanttEngine = class {
1140
1224
  this.tasks = this.tasks.map((task) => task.id === id ? updated : task);
1141
1225
  this.emit("taskchange", id, patch, { ...updated });
1142
1226
  }
1227
+ /** 追加一个新任务,并触发 taskcreate 事件。 */
1143
1228
  addTask(task) {
1144
1229
  this.assertNotDestroyed();
1145
1230
  this.tasks = [...this.tasks, { ...task }];
1146
1231
  this.emit("taskcreate", { ...task });
1147
1232
  }
1233
+ /** 按 id 删除任务(不存在时静默忽略),并触发 taskdelete 事件。 */
1148
1234
  removeTask(id) {
1149
1235
  this.assertNotDestroyed();
1150
1236
  const removed = this.tasks.find((task) => task.id === id);
@@ -1153,12 +1239,14 @@ var GanttEngine = class {
1153
1239
  this.collapsedIds.delete(id);
1154
1240
  this.emit("taskdelete", id);
1155
1241
  }
1242
+ /** 折叠/展开单个任务,并触发 collapsechange 事件。 */
1156
1243
  collapse(id, collapsed = true) {
1157
1244
  this.assertNotDestroyed();
1158
1245
  if (collapsed) this.collapsedIds.add(id);
1159
1246
  else this.collapsedIds.delete(id);
1160
1247
  this.emit("collapsechange", this.getCollapsedIds());
1161
1248
  }
1249
+ /** 切换单个任务的折叠状态。 */
1162
1250
  toggleCollapse(id) {
1163
1251
  this.collapse(id, !this.collapsedIds.has(id));
1164
1252
  }
@@ -1177,6 +1265,7 @@ var GanttEngine = class {
1177
1265
  this.emit("collapsechange", this.getCollapsedIds());
1178
1266
  }
1179
1267
  // ── 拖拽预览(交互态源;组件写入迁移到 engine,dateRange 可据此扩展) ──
1268
+ /** 记录一次拖拽预览状态(含受影响的联动任务),并触发 previewchange 事件。 */
1180
1269
  setPreview(preview) {
1181
1270
  this.assertNotDestroyed();
1182
1271
  this.dragPreview = {
@@ -1186,6 +1275,7 @@ var GanttEngine = class {
1186
1275
  };
1187
1276
  this.emit("previewchange", this.getPreview());
1188
1277
  }
1278
+ /** 清空拖拽预览(无预览时忽略),并触发 previewchange 事件。 */
1189
1279
  clearPreview() {
1190
1280
  if (!this.dragPreview) {
1191
1281
  return;
@@ -1193,6 +1283,7 @@ var GanttEngine = class {
1193
1283
  this.dragPreview = null;
1194
1284
  this.emit("previewchange", null);
1195
1285
  }
1286
+ /** 获取当前拖拽预览的副本,无预览时返回 null。 */
1196
1287
  getPreview() {
1197
1288
  if (!this.dragPreview) {
1198
1289
  return null;
@@ -1246,6 +1337,7 @@ var GanttEngine = class {
1246
1337
  const extent = dateExtent(dates);
1247
1338
  return extent ?? { start: dates[0], end: dates[0] };
1248
1339
  }
1340
+ /** 计算时间刻度(委托给 computeTimeScale 纯函数)。 */
1249
1341
  getTimeScale() {
1250
1342
  const range = this.getDateRange();
1251
1343
  return computeTimeScale(
@@ -1256,6 +1348,7 @@ var GanttEngine = class {
1256
1348
  this.config.firstDayOfWeek
1257
1349
  );
1258
1350
  }
1351
+ /** 计算任务行布局(委托给 computeLayout 纯函数)。 */
1259
1352
  getLayout() {
1260
1353
  return computeLayout(
1261
1354
  this.tasks,
@@ -1264,20 +1357,25 @@ var GanttEngine = class {
1264
1357
  this.collapsedIds
1265
1358
  );
1266
1359
  }
1360
+ /** 计算扁平化任务列表(委托给 flattenTasks 纯函数)。 */
1267
1361
  getFlatTasks() {
1268
1362
  return flattenTasks(this.tasks, this.collapsedIds);
1269
1363
  }
1364
+ /** timeline 总宽度(所有刻度宽度之和)。 */
1270
1365
  getTotalWidth() {
1271
1366
  return this.getTimeScale().reduce((sum, tick) => sum + tick.width, 0);
1272
1367
  }
1368
+ /** 整体高度(表头 + 行数 × 行高)。 */
1273
1369
  getTotalHeight() {
1274
1370
  const rows = this.getFlatTasks().length;
1275
1371
  return this.config.headerHeight + rows * this.config.rowHeight;
1276
1372
  }
1277
1373
  // ── 视口命令(需要 container) ──
1374
+ /** 当前横向滚动位置(无容器时为 0)。 */
1278
1375
  getScrollLeft() {
1279
1376
  return this.container?.scrollLeft ?? 0;
1280
1377
  }
1378
+ /** 设置横向滚动位置(自动夹取到 [0, maxScrollLeft])。 */
1281
1379
  setScrollLeft(px) {
1282
1380
  if (!this.container) return;
1283
1381
  const max = Math.max(0, this.container.scrollWidth - this.container.clientWidth);
@@ -1293,9 +1391,11 @@ var GanttEngine = class {
1293
1391
  this.container.scrollLeft = max;
1294
1392
  }
1295
1393
  }
1394
+ /** 当前纵向滚动位置(无容器时为 0)。 */
1296
1395
  getScrollTop() {
1297
1396
  return this.container?.scrollTop ?? 0;
1298
1397
  }
1398
+ /** 设置纵向滚动位置(自动夹取到 [0, maxScrollTop])。 */
1299
1399
  setScrollTop(px) {
1300
1400
  if (!this.container) return;
1301
1401
  const max = Math.max(0, this.container.scrollHeight - this.container.clientHeight);
@@ -1310,15 +1410,18 @@ var GanttEngine = class {
1310
1410
  const left = tick ? tick.left : diffDays(this.getDateRange().start, target) * this.config.columnWidth;
1311
1411
  this.setScrollLeft(left);
1312
1412
  }
1413
+ /** 滚动到某个任务的所在列(按布局 left 定位)。 */
1313
1414
  scrollToTask(id) {
1314
1415
  const layout = this.getLayout();
1315
1416
  if (!layout.ok) return;
1316
1417
  const item = layout.data.find((row) => row.taskId === id);
1317
1418
  if (item) this.setScrollLeft(item.left);
1318
1419
  }
1420
+ /** 滚动到最左侧。 */
1319
1421
  scrollToStart() {
1320
1422
  this.setScrollLeft(0);
1321
1423
  }
1424
+ /** 滚动到最右侧(按 timeline 总宽度)。 */
1322
1425
  scrollToEnd() {
1323
1426
  if (!this.container) return;
1324
1427
  this.setScrollLeft(this.getTotalWidth());
@@ -1337,10 +1440,12 @@ var GanttEngine = class {
1337
1440
  const target = this.container.clientWidth / span;
1338
1441
  this.mergeConfig({ columnWidth: this.clampColumnWidth(target) });
1339
1442
  }
1443
+ /** 把列宽限制到 [4, 400] 并取整。 */
1340
1444
  clampColumnWidth(value) {
1341
1445
  return Math.max(4, Math.min(400, Math.round(value)));
1342
1446
  }
1343
1447
  // ── 事件订阅 ──
1448
+ /** 订阅事件,返回取消订阅函数(同一事件可多次订阅)。 */
1344
1449
  on(event, fn) {
1345
1450
  this.assertNotDestroyed();
1346
1451
  let set = this.listeners.get(event);
@@ -1357,6 +1462,7 @@ var GanttEngine = class {
1357
1462
  }
1358
1463
  };
1359
1464
  }
1465
+ /** 订阅单次事件:触发一次后自动取消订阅,返回取消订阅函数。 */
1360
1466
  once(event, fn) {
1361
1467
  const off = this.on(event, (...args) => {
1362
1468
  off();
@@ -1364,6 +1470,7 @@ var GanttEngine = class {
1364
1470
  });
1365
1471
  return off;
1366
1472
  }
1473
+ /** 取消订阅;不传 fn 时移除该事件的全部监听器。 */
1367
1474
  off(event, fn) {
1368
1475
  const set = this.listeners.get(event);
1369
1476
  if (!set) return;
@@ -1374,6 +1481,7 @@ var GanttEngine = class {
1374
1481
  this.listeners.delete(event);
1375
1482
  }
1376
1483
  }
1484
+ /** 触发事件,逐个调用监听器;单个监听器抛错不影响其余监听器。 */
1377
1485
  emit(event, ...args) {
1378
1486
  const set = this.listeners.get(event);
1379
1487
  if (!set) return;
@@ -1388,6 +1496,7 @@ var GanttEngine = class {
1388
1496
  }
1389
1497
  }
1390
1498
  // ── 生命周期 ──
1499
+ /** 销毁引擎:置位 destroyed、触发 destroy 事件并清空监听器与数据。 */
1391
1500
  destroy() {
1392
1501
  if (this.destroyed) return;
1393
1502
  this.destroyed = true;
@@ -1396,6 +1505,7 @@ var GanttEngine = class {
1396
1505
  this.tasks = [];
1397
1506
  this.links = [];
1398
1507
  }
1508
+ /** 断言引擎未被销毁,否则抛出异常(防止对已销毁实例操作)。 */
1399
1509
  assertNotDestroyed() {
1400
1510
  if (this.destroyed) {
1401
1511
  throw new Error("GanttEngine: operation on destroyed instance");
@@ -1411,6 +1521,7 @@ function createGanttEngine(options = {}) {
1411
1521
  DEFAULT_HOUR_COLORS,
1412
1522
  DEFAULT_RESOURCE_COLORS,
1413
1523
  DEFAULT_TASK_COLORS,
1524
+ DEFAULT_WORKLOAD_COLORS,
1414
1525
  EDITOR_COLOR_OPTIONS,
1415
1526
  GanttEngine,
1416
1527
  HOUR_MS,
@@ -1444,6 +1555,7 @@ function createGanttEngine(options = {}) {
1444
1555
  mergeTaskPatch,
1445
1556
  normalizeLinks,
1446
1557
  plannedDurationBetween,
1558
+ resolveColumnWidth,
1447
1559
  resolveGanttConfig,
1448
1560
  resolveResourceColor,
1449
1561
  resolveUsageColor,