gis-view-webgl2 0.1.7 → 0.1.8

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.
Files changed (54) hide show
  1. package/README.md +9 -8
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.ts +232 -228
  4. package/dist/index.js +1 -1
  5. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +49 -0
  6. package/docs/GisView.md +1 -1
  7. package/docs/ai/06-/344/274/232/350/257/235/347/264/242/345/274/225/344/270/216/345/244/215/346/237/245/346/226/271/346/263/225.md +48 -0
  8. package/docs/demos/BasicDemo.vue +2 -2
  9. package/docs/demos/BatchAddDemo.vue +6 -6
  10. package/docs/demos/ConnectDemo.vue +164 -33
  11. package/docs/demos/CopyMergeDemo.vue +27 -14
  12. package/docs/demos/EventDemo.vue +17 -20
  13. package/docs/demos/LabelDemo.vue +5 -5
  14. package/docs/demos/api-index.js +31 -29
  15. package/docs/demos/gis-view-demo/GisViewDemoCardHeader.vue +1 -1
  16. package/docs/demos/gis-view-demo/GisViewDemoStage.vue +1 -1
  17. package/docs/demos/gis-view-demo/panels/DataPanels.vue +1 -1
  18. package/docs/demos/gis-view-demo/useGisViewDemo.js +31 -33
  19. package/docs/format/07-/344/272/213/344/273/266/350/275/275/350/215/267/344/270/216/347/244/272/344/276/213/346/225/260/346/215/256.md +8 -7
  20. package/docs/from0/05-/347/233/270/346/234/272/344/270/216/344/272/244/344/272/222.md +1 -1
  21. package/docs/from0/07-/351/200/232/351/201/223/345/210/206/347/273/204/344/270/216/345/244/232/345/233/236/347/272/277.md +26 -10
  22. package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +1 -1
  23. package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +21 -16
  24. package/docs/manual/06-/346/225/260/346/215/256/344/270/216/345/234/272/346/231/257/346/233/264/346/226/260.md +9 -10
  25. package/docs/manual/07-/346/214/207/351/222/210/344/270/216/350/246/201/347/264/240/347/274/226/350/276/221.md +54 -31
  26. package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +3 -3
  27. package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +9 -6
  28. package/docs/manual/10-/351/200/232/351/201/223/347/274/226/350/276/221/344/270/216/346/226/260/345/242/236.md +226 -493
  29. package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +2 -2
  30. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +12 -13
  31. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +23 -21
  32. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +19 -17
  33. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +125 -78
  34. package/docs/manual/19-/350/257/273/346/225/260/344/270/216/346/213/226/346/213/275/351/242/204/350/247/210.md +1 -1
  35. package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +2 -2
  36. package/docs/manual/21-/350/257/264/346/230/216/344/270/216/345/270/270/350/247/201/351/227/256/351/242/230.md +4 -4
  37. package/docs/steps/README.md +9 -7
  38. package/docs/steps/bench-scale.html +1 -1
  39. package/docs/steps/index.html +6 -6
  40. package/docs/steps/selftest-actions.html +26 -25
  41. package/docs/steps/selftest-data.html +1 -1
  42. package/docs/steps/selftest-demos.html +102 -6
  43. package/docs/steps/selftest-docs.html +3 -3
  44. package/docs/steps/selftest-features.html +283 -51
  45. package/docs/steps/selftest-full.html +3 -3
  46. package/docs/steps/selftest-labels.html +1 -1
  47. package/docs/steps/selftest-perf.html +31 -4
  48. package/docs/steps/selftest-select.html +20 -5
  49. package/docs/steps/step-05-pick.js +4 -8
  50. package/docs/steps/step-07-channels.js +3 -5
  51. package/docs/steps/step-08-labels.js +3 -3
  52. package/docs/steps/step-10-full.js +3 -3
  53. package/docs/steps/step-12-events.js +41 -24
  54. package/package.json +1 -1
@@ -61,7 +61,7 @@ view.updateOptions({ channelWaypointDragMode: 'vertex' })
61
61
 
62
62
  ```js
63
63
  view.updateOptions({
64
- onContextMenu: (payload, event) => {
64
+ onContextMenu: payload => {
65
65
  const context = payload.channelContext
66
66
  if (!context) {
67
67
  return closeMenu() // 点空白或节点:没有 channelContext,用它把框收掉
@@ -94,287 +94,218 @@ view.getChannelContext('corridor-1', 'corridor-1::2')
94
94
 
95
95
  ### 新增通道
96
96
 
97
- `beginAddChannel()` 进入新增流程(**草稿缺省关**,写 `features: { channelDraft: true }` 或 `features: 'edit'` 才进得去;程序化的 `addChannel()` 不受限):缺省点第一个节点当起点,一条草稿线跟着光标走(颜色 / 线宽 / 两端圆点由 `channelDraftStyle` 配),点第二个节点出方案表。**`options.from` 可以预指定起点**(节点 id / 下标 / `{ id }` / `{ index }`),调用后直接进入 `phase: 'from'`,只需再点终点;不写 `from` 就保持原来的两次点击。开始事件的 `from` 相位、`select: true` 时预置起点的选中状态都会同步给出;`from` 找不到时草稿不开始并报 warning。**同一套流程用 `kind` 分两种**:`'channel'`(缺省)是**新建通道**,两个节点之间已经有通道就加一组 / 并进某一组;`'line'` 是**新建线路**,只加不挂任何通道的独立线路(`channels` 一个字节都不动,两个节点相同才不允许)。`circuits` 决定这一次加几条(通道回线 / 独立线路都一样)。草稿期间点空白处继续等(不会退出)、点起点同一个节点不算数、按住拖动仍然平移画布,Esc / 右键 / `cancelAddChannel()` 退出。
97
+ 新增流程按语义拆成两个入口,草稿缺省关闭;写 `features: { channelDraft: true }` 或 `features: 'edit'` 才进得去。程序化 `addChannel()` / `addLine()` 不受这个开关限制。两个入口共用拾取、预览、连续新增与提交状态机,但**入口语义固定,不再通过 `kind` 切换**:
98
98
 
99
- ```js
100
- view.beginAddChannel({ from: 'station-a' }) // 起点已经指定:接下来只点 station-b
101
- view.beginAddChannel({ from: { id: 'station-a' }, select: true })
102
- view.continueAddChannel({ from: 'station-c', kind: 'line' }) // 连续新增的下一段也能换指定起点
103
- ```
99
+ - `beginAddChannel(options?)`:新建通道。两个节点之间已经有通道时,可按 `existingChannel` 失败、新加一组或并进指定分组。
100
+ - `beginAddLine(options?)`:新建线路。缺省只新建独立线路,不碰 `channels`;显式写 `existingChannel: 'newGroup' / 'joinGroup' / 'auto'` 且两端已有通道时,才把这次新建的回线挂进已有通道。
101
+ - `continueAddChannel(options?)`:只续当前草稿的配置,不能切换这一段是通道还是独立线路。传入旧 `kind` 不生效;当前没有草稿时按 `beginAddChannel` 的通道语义开新一段。
102
+ - 旧写法 `beginAddChannel({ kind: 'line' })` 已不再切换成线路;需要线路请明确调用 `beginAddLine()`。
104
103
 
105
- > `beginAddChannel` / `continueAddChannel` 只支持预指定 **起点** `from`,没有 `to`。`beginAddChannel({ from: 'station-a', to: 'station-b' })` 里的 `to` 会被忽略:起点已经选好,仍要再点一次终点才能进入 `ready`。想完全程序化地指定两端并落地,用 `addChannel` / `addLine`,不要先开草稿。
104
+ 缺省点第一个节点当起点,一条草稿线跟随光标;点第二个节点进入 `ready` 并给出方案表。`options.from` 可以预指定起点(节点 id / 下标 / `{ id }` / `{ index }`),调用后直接进入 `phase: 'from'`,只需再点终点;找不到起点时草稿不开始并报 warning。草稿期间点空白处继续等待,点回起点不算数,按住拖动仍然平移画布;`Esc`、右键或 `cancelAddChannel()` 退出。
106
105
 
107
- **直线 / 正交折线(`routing` / `path`)**:`beginAddChannel()` / `continueAddChannel()` 新增 `routing: 'straight' | 'orthogonal'` 与 `path`。`routing` 缺省 `'straight'`;写 `'orthogonal'` 且没有有效中间拐点时,组件自动补一个「先横后纵」的拐点 `[to.x, from.y]`。`path` 是**中间拐点**(不含两端),写了有效拐点就按它走,此时不会再追加自动拐点;草稿与 `commitAddChannel` / `addLine` / `addChannel` 共用同一份路由解析,预览和落地的折线完全一致。`onChannelDraft` / `onChannelDraftClick` 载荷里也带 `routing` / `path`,两端选好后 `path` 是实际会落地的中间拐点。
106
+ **草稿里的 `select` 不是“落地后选中新线”**:`beginAddChannel({ select: true })` 只让点中的节点同步进选中集(走 `onSelect` / `onSelectChange`);真正落地的新回线是否选中由 `commitAddChannel(..., { select: true })` 或程序化新增方法的 `options.select` 决定,缺省都不改选中集。
108
107
 
109
- | 字段 | 类型 | 缺省 | 说明 |
110
- | --- | --- | --- | --- |
111
- | `color` | `string` | 蓝色 | 预览线颜色 |
112
- | `width` | `number` | `2` | 预览线宽(CSS 像素) |
113
- | `dot` | `number` | `4` | 两端圆点半径(CSS 像素,`0` 不画) |
114
- | `arrow` | `boolean` | `false` | 末端是否画方向箭头 |
115
- | `arrowSize` | `number` | `12` | 箭头大小(CSS 像素) |
108
+ #### 单一通道与已有通道策略
116
109
 
117
- `channelDraftStyle` 在草稿进行中也能改,`updateOptions({ channelDraftStyle })` 会立即重拼预览。这里的箭头只属于预览;真实线路箭头仍由 `lines[].direction` / `lineProps.direction` 决定。完整示例见 `?demo=connect`。
110
+ | 配置 | 两端已有通道 | 两端没有通道 |
111
+ | --- | --- | --- |
112
+ | `beginAddChannel()` | 推荐新分组 / 并进可用分组 | 新建通道 |
113
+ | `beginAddChannel({ singleChannel: true })` | 直接阻断,`plan.blocked: true`、`preferred: null` | 仍新建通道 |
114
+ | `beginAddChannel({ existingChannel: 'block' })` | 直接阻断 | 新建通道 |
115
+ | `beginAddChannel({ existingChannel: 'newGroup' })` | 在已有通道里新加一组 | 新建通道 |
116
+ | `beginAddChannel({ existingChannel: 'joinGroup', groupId, groupIndex })` | 并进指定分组;都不写时取当前分组 / 第一个可用分组 | 新建通道 |
117
+ | `beginAddChannel({ existingChannel: 'auto' })` | 有新分组余量就新分组,否则并进可用分组 | 新建通道 |
118
+ | `beginAddLine()` | 独立线路 | 独立线路 |
119
+ | `beginAddLine({ existingChannel: 'newGroup' / 'joinGroup' / 'auto' })` | 把这次新建的回线挂进已有通道的新分组 / 指定分组 / 自动分组 | 独立线路 |
120
+ | `beginAddLine({ existingChannel: 'block' })` | 直接阻断 | 独立线路 |
121
+
122
+ `singleChannel: true` 只给 `beginAddChannel` 补一个缺省的 `existingChannel: 'block'`;显式写了 `existingChannel` 时以显式策略为准。`groupId` 认分组 id,`groupIndex` 从 `0` 起按声明顺序认;两者都写时以 `groupId` 为准。找不到分组、分组已满或余量不足时同样进入阻断,`plan.reason` / `payload.reason` 会写明原因。
118
123
 
119
- **真正的新建走 `addChannel`(新签名)**:`beginAddChannel()` 只负责那条跟随光标的**示例假通道** —— 选完两个节点发结束事件(`phase: 'ready'`),落地必须自己调(`addChannel` / `addChannelGroupLines` / `joinChannelGroupLines` / `addLine`)。新签名是「通道信息 + 线路数组 line_data + 可选的分组信息」:
124
+ 阻断后草稿仍停在 `ready`(`blocked: true`、`preferred: null`),不会自动改道偷偷新建:`autoCommit: true` 或手动 `commitAddChannel()` 在缺省 `resolution` 下返回 `ok: false`,并补一条 `phase: 'blocked'`;显式传一个可用的 `resolution` 仍可改道。
120
125
 
121
126
  ```js
122
- // ① 通道信息:{ from, to, name, id }(其余字段原样写进通道声明)
123
- // ② 线路数组 line_data:每条按自己的 id 建一条回线 —— 这些 id 自动就是新通道的 lineIds
124
- // (写对象 / 直接写一个 id 都行,id 从哪来见下面那条;没写 id 的自动编号,撞车往后排)
125
- // ③ 分组信息(可选):一组或整份声明;缺省「整条通道一组」
126
- const result = view.addChannel(
127
- { from: 'station-a', to: 'station-b', name: '甲通道', id: 'channel-1' },
128
- [{ id: 'line-1', color: '#dd4b2e' }, { id: 'line-2' }],
129
- { id: 'group-1', path: [[116.5, 39.9]] }
130
- )
131
- // → result.lineIds = ['line-1', 'line-2'];channel-1 的 groups = [{ id: 'group-1', lineIds: [...], path: [...] }]
127
+ view.beginAddChannel({ from: 'station-a' })
128
+ view.beginAddChannel({ from: { id: 'station-a' }, select: true })
129
+ view.continueAddChannel({ from: 'station-c' })
130
+
131
+ view.beginAddChannel({ from: 'station-a', singleChannel: true })
132
+ view.beginAddChannel({ from: 'station-a', existingChannel: 'newGroup' })
133
+ view.beginAddChannel({ from: 'station-a', existingChannel: 'joinGroup', groupId: 'corridor-1::2' })
134
+
135
+ view.beginAddLine({ from: 'station-a' })
136
+ view.beginAddLine({ from: 'station-a', existingChannel: 'newGroup' })
132
137
  ```
133
138
 
134
- - 第三段的分组信息认 `id` / `lineIds` / `path`(与通道声明里的 `groups[]` 同一套写法):`lineIds` 缺省是这次新建的全部回线,`id` 缺省单组用通道 id(多组 `通道 id::序号`);写进了认不出的成员会像 `updateChannel` 那样报一句 warning。
135
- - **回线 id 从哪来(`addChannel` / `addLine` 的第二段是同一套归一)**:每条写一个对象时 `id` 就是这条回线的 id(其余字段原样落在这条回线上),只写一个 id 也行 —— 裸字符串 / 数字等价于 `{ id }`,所以 `[{ id: 'line-1' }, 'line-2', 3]` 是三条回线。**没写 id 就自动编号**:`addChannel` 新签名按 `通道 id-序号`,`addLine` 的独立线路按 `from-to-序号`;显式给的 id 撞上数据里已有的由 `uniqueLineId` 往 `-1` / `-2` 排(一次加多回逐条都查)。新签名第一段的 `id` 是**通道 id**(不再当回线 id 的种子);旧写法(`addLine(input, lineItems?)` / `addChannel(input, lineItems?)`)顶层 `id` / `lineId` 与 `linePropsByMember[i].id` 是**种子**(一次多回时自动带 `-1` / `-2`)。
136
- - **`path` / `routing` 的落地口径**:`applyChannelAdd` 对 `kind: 'line'` 写完整 `path`(首尾仍是 `from` / `to` 节点坐标),对新建通道 / 新分组只写中间拐点;逐条线路自己写了 `path` 时以它为准。预览、提交与程序化新增因此不会画出两套几何。
137
- - **只负责新建一个通道**:两个节点之间已经有通道时整条失败(`ok: false`),`reason` 指路下面两条方法,返回里的 `plan` 带着 `hasChannel` / `hasGroup` / `groups` / `channel`(`channel.groups` 就是已有分组的读数)—— 界面按它决定「新建」还是「并进」。
138
- - 旧写法(入参里带 `circuits` / `resolution` / `channelId` / `kind` 里的任何一个)**原样保留**:按方案表挑新通道 / 新分组 / 并入某组。它原来的三件事现在各有专门的方法(新代码建议直接用):
139
+ > `beginAddChannel` / `beginAddLine` / `continueAddChannel` 的草稿配置只支持预指定起点 `from`,没有 `to`;如果要换终点,重新开一段或在地图上点选。
140
+
141
+ #### 草稿配置
142
+
143
+ `beginAddChannel()` / `beginAddLine()` / `continueAddChannel()` 的 `GisViewChannelDraftOptions`:
144
+
145
+ | 字段 | 类型 | 缺省 | 说明 |
146
+ | --- | --- | --- | --- |
147
+ | `from` | `GisViewPointRef` | 无 | 预指定起点,直接进入 `from` 相位 |
148
+ | `autoCommit` | `boolean` | `false` | 两个节点一选完就按推荐方案落地 |
149
+ | `continuous` | `boolean \| 'chain'` | `false` | 每段落地后自动开下一段;`'chain'` 同时打开 `chain` |
150
+ | `chain` | `boolean` | `false` | 连续新增时以上一段终点当下一段起点;显式 `from` 优先 |
151
+ | `singleChannel` | `boolean` | `false` | 只约束 `beginAddChannel`:已有通道时等价于 `existingChannel: 'block'` |
152
+ | `existingChannel` | `'block' \| 'newGroup' \| 'joinGroup' \| 'auto'` | 无 | 已有通道时的处理策略;`beginAddLine` 用它决定是否挂已有通道 |
153
+ | `groupId` | `string \| number` | 无 | `joinGroup` 的目标分组 id |
154
+ | `groupIndex` | `number` | 无 | `joinGroup` 的目标分组序号(从 `0` 起) |
155
+ | `circuits` | `number` | `1` | 这次新建几回;有效 `lineData` 的条数优先 |
156
+ | `name` | `string` | 按两端节点生成 | 新线路名 |
157
+ | `lineId` | `string \| number` | 按通道 id 生成 | 新回线 id 的旧别名;与 `id` 同时给时以 `id` 为准 |
158
+ | `id` | `string \| number` | 无 | 新回线 id;一次多条时作为种子 |
159
+ | `lineProps` | `Partial<GisViewLine>` | 无 | 所有新回线共用的业务字段 |
160
+ | `linePropsByMember` | `Array<Partial<GisViewLine> \| undefined>` | 无 | 按下标逐条覆盖的线路属性 |
161
+ | `lineData` | `GisViewChannelLineData` | 无 | 第二段线路数据;给了几条这一段就按几条对齐(`circuits` 按它算) |
162
+ | `routing` | `'straight' \| 'orthogonal'` | `'straight'` | 直连或自动正交折线 |
163
+ | `path` | `GisViewCoordinate[]` | 无 | 中间拐点;有效时优先于 `routing` 的自动拐点 |
164
+ | `channelDraftStyle` | `GisViewChannelDraftStyle` | 全局样式 | 本次草稿的预览样式,草稿结束即丢弃 |
165
+ | `handoff` | `boolean` | `false` | `ready` 发完自动收掉草稿,由使用方自己落地 |
166
+ | `select` | `boolean` | `false` | 只控制点中的节点是否同步选中,不控制新回线落地后的选中 |
167
+ | `channelProps` | `Partial<GisViewChannel>` | 无 | 新建通道透传的字段 |
168
+ | `originalEvent` | `Event \| null` | `null` | 原样带进回调载荷 |
169
+
170
+ 旧字段 `lineItems` 已移除,不再作为草稿的第二段;请统一写 `lineData`。草稿载荷里的第二段同样叫 `lineData`。
171
+
172
+ `lineData` 与程序化接口的第二段是同一份写法:一条或一批均可,裸字符串 / 数字等价于 `{ id }`,对象里的 `id` 就是该回线 id。未写 id 时,`beginAddChannel` 按通道 id 自动编号,`beginAddLine` 按 `from-to-序号` 自动编号;与已有数据撞 id 时自动加后缀。
139
173
 
140
174
  ```js
141
- // 在已有通道里新加一组回线(原 newGroup):第二段这次新建的线路数组,第三段可选 { id?, path? }
142
- view.addChannelGroupLines('channel-1', [{ id: 'line-3' }], { id: 'group-2' })
143
- // 把新回线并进已有分组(原 joinGroup):groupId 认分组 id,也认序号(从 0 起、按声明顺序)
144
- view.joinChannelGroupLines('channel-1', 'group-1', [{ id: 'line-4' }])
145
- // 独立线路(原 kind: 'line'):不挂任何通道
146
- view.addLine({ from: 'station-a', to: 'station-b' }, [{ id: 'solo-1' }])
175
+ view.beginAddChannel({
176
+ from: 'station-a',
177
+ lineData: [
178
+ { id: 'line-1', color: '#dd4b2e' },
179
+ { id: 'line-2', color: '#2ea36b' },
180
+ 'line-3'
181
+ ]
182
+ })
147
183
  ```
148
184
 
149
- **每一次点击都有回调**:`onChannelDraftClick` 在草稿里每点一次都发一条,`step` 为 `1` 是刚拿到起点(`payload.from`)、`2` 是刚拿到终点(`payload.to`,线路到此结束)、`0` 是点在空白或点回起点(`phase: 'pick'`,草稿继续等)。载荷与 `onChannelDraft` 完全一致,因此「第一次点击拿起点、第二次点击拿终点、之后自己决定取消还是落地」这条流程只守一个回调就够:
185
+ #### 每一次点击与收尾
186
+
187
+ `onChannelDraftClick` 在草稿里每点一次都发一条:`step === 1` 是刚拿到起点,`step === 2` 是刚拿到终点,`0` 是点在空白或点回起点(`phase: 'pick'`,草稿继续等待)。载荷与 `onChannelDraft` 一致。
150
188
 
151
189
  ```js
152
190
  view.beginAddChannel()
153
191
  .updateOptions({
154
192
  onChannelDraftClick: payload => {
155
- if (payload.step === 1) console.log('起点', payload.from.id, payload.from.coordinate)
193
+ if (payload.step === 1) console.log('起点', payload.from.id)
156
194
  if (payload.step === 2) {
157
- console.log('终点', payload.to.id, '线路结束', payload.options) // 可以取消,也可以落地
158
- view.commitAddChannel() // 落地成真实通道;不想加就 view.cancelAddChannel()
195
+ console.log('终点', payload.to.id, payload.options)
196
+ view.commitAddChannel(undefined, { select: true })
159
197
  }
160
198
  }
161
199
  })
162
200
  ```
163
201
 
164
- **取消、默认添加,或自行落地**:选完两个节点后草稿只是预览,除非 `autoCommit: true`,否则数据一个字节都不动。三种收尾方式:
202
+ 选完两个节点后草稿只是预览,除非 `autoCommit: true`,否则数据一个字节都不动。收尾方式:
165
203
 
166
204
  - `cancelAddChannel()`:收起草稿并补一条 `phase: 'cancel'`;`endAddChannel()` 静默收起。两者都不写数据。
167
- - `commitAddChannel()`:按推荐方案(或传入的 `resolution` / 新增线路 / 通道对象)走内置落地,草稿里的 `routing` / `path` 会一起交给 `applyChannelAdd`;对象字段优先,见下文「对象直传」。
168
- - `addLines()` / `addChannels()`:绕开提交,用草稿读数自己落地。独立线路写完整 `path`;通道只把 `path` 当中间拐点。落地后调用 `endAddChannel()` 收起假通道。
205
+ - `commitAddChannel(input?, options?)`:按推荐方案或容器对象落地;草稿里的 `routing` / `path` 会一起带上。
206
+ - `addChannel()` / `addChannelGroupLines()` / `joinChannelGroupLines()` / `addLine()`:绕开草稿提交,自己按草稿读数落地;落地后记得 `endAddChannel()` 收起假通道。
169
207
 
170
- ```js
171
- const draft = view.getAddChannelDraft()
172
- if (draft?.phase === 'ready' && draft.from && draft.to) {
173
- const middle = draft.path ?? []
174
- const fullPath = middle.length ? [draft.from.coordinate, ...middle, draft.to.coordinate] : null
175
-
176
- // ① 独立线路:addLines 收现成数据对象,path 要完整(含两端)
177
- view.addLines([{
178
- id: 'visual-line-1',
179
- from: draft.from.id,
180
- to: draft.to.id,
181
- ...(fullPath ? { path: fullPath } : {}),
182
- color: '#2f6fd0',
183
- direction: { count: 1, size: 12 }
184
- }])
185
- view.endAddChannel()
186
-
187
- // ② 通道:addChannels 的 path 只写中间拐点(与 ① 二选一)
188
- // view.addChannels([{ from: draft.from.id, to: draft.to.id, kind: 'channel', circuits: 2, path: middle }])
189
- // view.endAddChannel()
190
- }
191
- ```
208
+ #### 连续新增
209
+
210
+ `beginAddChannel({ continuous: true })` 之后,一段落地(或提交失败)会原地重开并发送 `phase: 'restart'`;继续点下一对节点即可。`continuous: 'chain'`(或 `chain: true`)会把上一段终点作为下一段起点。
192
211
 
193
- **连续新增**:`beginAddChannel({ continuous: true })` 之后草稿不再是一次性的 —— 一条落地(或提交失败)之后草稿原地重开(发一条 `phase: 'restart'`),接着点下一对节点就是下一条线,不用再点一次「新增通道」。再给 `continuous: 'chain'`(或 `chain: true`)就是「接着上一段的终点画」:重开的草稿直接拿上一段的终点当起点(`restart` 载荷里的 `from`),一路点下去就是首尾相接的一条条线路。中途想换起点 / 退出,`cancelAddChannel()` 或 `continueAddChannel()` / `getAddChannelDraft()` 都按这套跑。`autoCommit: true` 与连续新增是正交的两件事,叠起来就是「点两个节点 → 落地 → 草稿重开」,全程不需要界面按钮。
212
+ `autoCommit` 与连续新增正交,可以同时使用。`continueAddChannel()` 可手动开下一段;传 `lineData` 会按新条数更新这一段,传 `from` 可改起点,但不会再切换入口语义。
194
213
 
195
214
  ```js
196
- view.beginAddChannel({ autoCommit: true, continuous: 'chain' }) // 点、点、点… 一路加
197
- view.continueAddChannel({ continuous: true }) // 手动开下一段(连续模式下这一步是自动的)
198
- view.cancelAddChannel() // 退出草稿
215
+ view.beginAddChannel({ autoCommit: true, continuous: 'chain' })
216
+ view.continueAddChannel({ from: 'station-c' })
217
+ view.cancelAddChannel()
199
218
  ```
200
219
 
201
- **结束事件(`phase: 'ready'`)里有什么**:两个节点的信息在 `payload.from` / `payload.to`(`index` / `id` / `item` / `coordinate`);**两个节点之间有没有分组**看 `payload.hasChannel` / `payload.hasGroup`(没有通道时都是 `false`),分组读数在 `payload.groups`(与 `getChannelGroups()` 同一份口径)与 `payload.group`(当前分组),已有通道的成员 / 分组在 `payload.channel`(`id` / `index` / `name` / `lineIds` / `groupCount` / `currentGroupId` / `groups`),已有通道 id 另有 `payload.existingChannelId`(没有就是 `null`)。事件里可以:
220
+ #### `ready` 载荷与交接
202
221
 
203
- - **直接落地**:`view.addChannel(...)`(新签名,见上)或 `view.addChannelGroupLines` / `view.joinChannelGroupLines` —— 按 `payload.hasChannel` 自己分流,`beginAddChannel` 一个字节的数据都不改。
204
- - **删掉那条示例假通道**:`view.endAddChannel()`(静默收掉、不补事件)/ `view.cancelAddChannel()`(收掉并补一条 `phase: 'cancel'`)—— 提前结束不新建就是这两条;`beginAddChannel({ handoff: true })` 是「ready 发完自动收」的自动版。
205
- - **让点中的节点看起来是选中的**:`beginAddChannel({ select: true })` —— 起点先选上、第二个节点之后两个都在,走的是与普通点击同一份选中状态与回调(`onSelect` / `onSelectChange` 照发,`getSelections()` 立刻能读回来);它与 `features.select` 开关无关(和 `selectFeatures()` 接口一样是显式调用)。
222
+ `phase: 'ready'` 时:两个节点在 `payload.from` / `payload.to`;`payload.hasChannel` / `payload.hasGroup` 表示两端是否已有通道 / 分组;`payload.groups`、`payload.group`、`payload.channel` 与 `getChannelGroups()` / `getChannelBetween()` 同一份读数;已有通道 id 在 `payload.existingChannelId`。
206
223
 
207
- **交接模式(`handoff: true`)**:如果真实的新建逻辑在使用方自己手里(先弹表单、先过一遍校验、先调接口拿 id…),用交接模式最顺手 —— 两个节点选完、`phase: 'ready'` 的载荷发完,草稿(那条假通道)当场收掉(静默结束,**不补 `phase: 'cancel'`**),使用方在事件里拿着 `payload.from` / `payload.to` 走自己的 `addChannel`:
224
+ `handoff: true` 时,`ready` 发完立即收掉假通道(静默结束,不补 `cancel`),使用方在回调里自己落地:
208
225
 
209
226
  ```js
210
- view.beginAddChannel({ handoff: true, select: true }) // ready 发完假通道自动收掉;点中的节点也选中
227
+ view.beginAddChannel({ handoff: true, select: true })
211
228
  view.updateOptions({
212
229
  onChannelDraft: payload => {
213
- if (payload.phase !== 'ready') {
214
- return
215
- }
216
- // 两个节点之间已经有分组:并进已有通道(新加一组 / 并进某一组由界面自己选)
230
+ if (payload.phase !== 'ready') return
217
231
  if (payload.hasGroup) {
218
232
  const target = payload.group?.id ?? payload.channel?.groups?.[0]?.id
219
233
  view.joinChannelGroupLines(payload.channel.id, target, [{ id: 'line-9' }])
220
234
  return
221
235
  }
222
- // 假通道已经收掉了,这里走自己的真实新建(dataChange / 撤销都以这次 addChannel 为准)
223
236
  const result = view.addChannel(
224
237
  { from: payload.from.id, to: payload.to.id, name: '手填通道', id: 'channel-9' },
225
238
  [{ id: 'line-1', color: '#dd4b2e' }, { id: 'line-2', color: '#2ea36b' }],
226
- { id: 'group-1' } // 第三段:顺带声明分组(缺省整条通道一组)
239
+ { id: 'group-1' },
240
+ { select: true }
227
241
  )
228
- if (result.ok) {
229
- console.log('落地:', result.channelId, result.lineIds, result.groupId)
230
- }
231
- if (!result.ok) {
232
- console.warn(result.reason) // 失败原因与 commitAddChannel 同一套口径
233
- }
242
+ if (!result.ok) console.warn(result.reason)
234
243
  }
235
244
  })
236
245
  ```
237
246
 
238
- - 交接模式的那一条 `ready` 载荷里 `handoff: true`(其余字段见上一段:`from` / `to` / `hasChannel` / `hasGroup` / `groups` / `channel` / `plan` / `options`…),照着它落地即可;**不交接就不填 `handoff`**,草稿照旧留着 —— 自己收用 `endAddChannel()`(静默)/ `cancelAddChannel()`(补一条 `cancel`),也可以接着 `commitAddChannel` 走内置落地。
239
- - 与 `autoCommit` 同时给时 `autoCommit` 先落地(草稿已经被 `commitAddChannel` 收掉 / 重开,交接就不再触发);交接模式也不会自动开下一段,想连着加就每段各自 `beginAddChannel`。
240
- - 交接模式下草稿直接结束,所以 `getAddChannelDraft()` 在事件回调之后就是 `null`;要拿两个节点请只认载荷(`payload.from` / `payload.to`)。
247
+ 交接模式不会自动开下一段;与 `autoCommit` 同时给时先自动落地,交接不再触发。交接后 `getAddChannelDraft()` 已是 `null`,两个节点只认回调载荷。
241
248
 
242
- **新建通道还是新建线路**:`kind` 只认两个值 —— `'channel'`(缺省,新建通道 / 回线)与 `'line'`(新建不挂通道的独立线路);`circuits` 是这一次加几条(缺省 1)。草稿载荷(`onChannelDraft` / `onChannelDraftClick` / `getAddChannelDraft()`)与落地回执里都带 `kind` / `circuits`,界面照着它就能说清「这一段在加什么」:
249
+ #### 程序化新增
243
250
 
244
- ```js
245
- view.beginAddChannel({ kind: 'line' }) // 新建线路:不挂通道
246
- view.beginAddChannel({ kind: 'channel', circuits: 3 }) // 新建通道:一次给这条通道加 3 回
247
- view.continueAddChannel({ kind: 'line', circuits: 2 }) // 下一段换成加两条独立线路
248
-
249
- // 程序化:addLine 等价于 addChannel 传 kind: 'line'(resolution: 'newLine' 也一样认)
250
- view.addChannel({ from: 'station-a', to: 'station-b', circuits: 3 }) // 一条新通道 + 3 回
251
- view.addLine({ from: 'station-a', to: 'station-b', circuits: 2 }) // 两条独立线路
252
- ```
253
-
254
- 一次加多条时结果里用 `lines` / `lineIds`(`line` / `lineId` 是第一条):多回的名字按 `<名字> #1 / #2…` 排,id 逐条查重(撞车自动往后排),落地之后整批选中(`getSelectionData().ids` 读回)。
251
+ | 方法 | 入参 | 说明 |
252
+ | --- | --- | --- |
253
+ | `addChannel(spec, lineData?, group?, options?)` | `spec: GisViewChannelAddSpec`;`lineData?: GisViewChannelLineData`;`group?: GisViewChannelAddGroup \| GisViewChannelAddGroup[]`;`options?: { emit?: boolean; select?: boolean }` | **只新建一个通道**。`spec` 为 `{ from, to, name?, id?, channelProps?, path?, routing?, lineData?, group? }`;对象上的 `lineData` / `group` 是第二 / 第三段别名。两端已有通道时整条失败,`reason` 指路下面两条方法 |
254
+ | `addChannelGroupLines(channelId, lineData?, groupInfo?, options?)` | `groupInfo?: { id?, groupId?, path?, ... }`;`options?: { emit?: boolean; select?: boolean }` | 在已有通道里新加一组;缺省组 id 为 `${通道 id}::N`,缺省不带拐点 |
255
+ | `joinChannelGroupLines(channelId, groupId?, lineData?, options?)` | `groupId` 认分组 id,也认从 `0` 起的序号;`options?: { emit?: boolean; select?: boolean }` | 把新回线并进已有分组;不传时选第一个还有余量的分组 |
256
+ | `addChannelGroups(channelId, groups, options?)` | 每项 `{ id?, groupId?, path?, lineData? \| lines?, ... }`;`options?: { emit?: boolean; label?: string; select?: boolean }` | 一次新加多组;每项只认 `lineData` / `lines`,旧 `lineItems` 会让整批明确失败且不写数据 |
257
+ | `addChannels(input, options?)` | 一条或一批 `GisViewChannelAddSpec`(每项写法同 `addChannel`);`options?: { emit?: boolean; select?: boolean }` | 批量只新建通道;逐条落地、只重排一次、只记一步历史 |
258
+ | `addLine(spec, lineData?, options?)` | `spec: GisViewLineAddSpec` 为 `{ from, to, name?, id?, lineProps?, path?, routing?, lineData? }`;`options?: { emit?: boolean; select?: boolean }` | 独立线路,不挂任何通道;落地走追加式引擎路径,可在已有通道声明时增线 |
259
+ | `addPoint(item, options?)` / `addPoints(items, options?)` / `addLines(items, options?)` | `options?: { emit?: boolean; select?: boolean }` | 直接追加现成数据对象;不改通道声明 |
255
260
 
256
- **逐条给属性(`linePropsByMember`)**:一次加多条时 `linePropsByMember[i]` 只作用于第 i 条,合并顺序是 `lineProps < linePropsByMember[i]`,与 `createChannels` 的 `linePropsByMember` 同名同义 —— 批量生成与交互新增共用同一份写法。三条路都认它:草稿(`beginAddChannel` / `continueAddChannel`,载荷里带 `linePropsByMember`,连续新增重开后这一段仍然带着)、程序化(`addChannel` / `addLine`)。每一回可以单独换 `name`(给了就用它,没给按 `<名字> #序号`)、`color` / `width` / `opacity`、`id`(当种子用,撞车仍然自动往后排)以及任意业务字段(比如热力图取的 `max_loading_rate`);某项不写就跟着 `lineProps`,整份不写就是原来的行为。
261
+ 所有程序化新增方法缺省都**不选中**新要素。要选中时显式传 `options.select: true`;批量方法只触发一次选中。
257
262
 
258
263
  ```js
259
- view.addChannel({
260
- from: 'station-a', to: 'station-b', circuits: 3, name: '甲通道',
261
- lineProps: { color: '#f4a259', width: 3 }, // 三条回线的共同配置
262
- linePropsByMember: [
263
- { name: '第一回', color: '#dd4b2e', width: 5 }, // 只盖第 1 回
264
- null, // 第 2 回走 lineProps
265
- { id: 'third', color: '#2ea36b' } // 第 3 回:换色 + 指定 id
266
- ]
267
- })
268
- // → lines:[ { id: '…-0', name: '第一回', color: '#dd4b2e', width: 5 },
269
- // { id: '…-1', name: '甲通道 #2', color: '#f4a259', width: 3 },
270
- // { id: 'third', name: '甲通道 #3', color: '#2ea36b', width: 3 } ]
271
- ```
272
-
273
- `linePropsByMember[i].id` 只是**种子**:真的撞上已有 id 时仍然由 `uniqueLineId` 往后排(不写就按通道 id 自动编号)。`from` / `to` 恒为这一次的两个节点,逐条属性改不了它们。
274
-
275
- **连同线路一起新建(第二段 `lineItems`)**:`addChannel(input, lineItems)` / `addLine(input, lineItems)` 与 `updateChannel(id, patch, linePatches, lineIds?)` 同一个直觉 —— 第二段是一条或一批逐条属性(每项可以只写一个 id:裸字符串 / 数字按 `{ id }` 收,与新签名的线路数组同一份归一),落地时逐条对应到新建的回线上;**给了几条就建几条**(`circuits` 忽略)。它与 `linePropsByMember` 是同一份写法(两者同下标都写时第二段优先),区别只在放哪儿:`linePropsByMember` 是入参里的字段(`addChannels` 的每一项也能写),第二段是方法签名上的那一格。草稿侧对应 `beginAddChannel({ lineItems })` / `continueAddChannel({ lineItems })`(载荷里带 `lineItems`,重开后这一段仍按它的条数对齐)。
264
+ const channelResult = view.addChannel(
265
+ { from: 'station-a', to: 'station-b', name: '甲通道', id: 'channel-9' },
266
+ [{ id: 'line-1' }, { id: 'line-2' }],
267
+ { id: 'group-1' },
268
+ { select: true }
269
+ )
276
270
 
277
- ```js
278
- // 一条或一批都认:写了几条就建几条,每条自己的颜色 / 线宽 / 名字 / id 一起落地
279
- view.addChannel({ from: 'station-a', to: 'station-b', name: '甲通道' }, [
280
- { id: 'l-1', name: '第一回', color: '#dd4b2e', width: 5 },
281
- { id: 'l-2', color: '#2ea36b' } // 没写的字段跟着 lineProps / 自动命名
282
- ])
283
- view.addChannel({ from: 'station-a', to: 'station-b', circuits: 1 }, { id: 'l-3', color: '#3b82f6' }) // 单条也认(已有通道时走旧写法的 newGroup)
284
- view.addLine({ from: 'station-a', to: 'station-b' }, [{ id: 'n-1' }, { id: 'n-2' }]) // 独立线路同理
271
+ view.addChannelGroupLines('corridor-1', [{ id: 'c-7' }], { id: 'group-2' }, { select: true })
272
+ view.joinChannelGroupLines('corridor-1', 'corridor-1::2', [{ id: 'c-8' }])
273
+ view.addChannelGroups('corridor-1', [
274
+ { id: 'group-3', lineData: [{ id: 'c-9' }] },
275
+ { lines: ['c-10'] }
276
+ ], { select: true })
277
+
278
+ view.addLine({ from: 'station-a', to: 'station-b', name: '三回' }, [
279
+ { id: 'solo-1' },
280
+ { id: 'solo-2' },
281
+ { id: 'solo-3' }
282
+ ], { select: true })
283
+
284
+ view.addPoints([{ id: 'p-new', x: 10, y: 20 }], { select: true })
285
+ view.addLines([{ id: 'l-new', from: 'p-new', to: 'station-b' }], { select: true })
285
286
  ```
286
287
 
287
- **余量怎么算**:`getChannelAddOptions(from, to, count?, kind?)`(实例层按节点 id,引擎层同名方法按下标)里的 `count` 就是这一次要加的回数,每个方案的 `enabled` 都按它判 —— `newGroup` 要求「还能新分组」并且 `count <= channelGroupMaxLines`,`joinGroup` 要求目标分组的余量 `>= count`(`reason` 会写清「只剩 R 回余量,本次要加 M 回」),全都不可用才 `blocked`。`kind: 'line'` 时方案表是空的、`preferred` 为 `null`(独立线路不挑方案),只有两个节点相同才 `blocked`。
288
+ **`addChannel` 只负责新建**:两个节点之间已经有通道时返回 `ok: false`,`plan` 里带 `hasChannel` / `hasGroup` / `groups` / `channel`。要新加一组用 `addChannelGroupLines`,并进已有分组用 `joinChannelGroupLines`。
288
289
 
289
- `beginAddChannel()` / `continueAddChannel()` 的 `GisViewChannelDraftOptions` 逐项如下:
290
+ **回线 id 的来历**:`addChannel` 第一段的 `id` 是**通道 id**,不是回线 id 种子;回线 id 来自第二段每项的 `id`,没写就按通道 id 自动编号。`addLine` 第一段的 `id` 是回线 id 种子;一次多条时自动展开,第二段每项自己的 `id` 优先。
290
291
 
291
- | 字段 | 类型 | 缺省 | 说明 |
292
- | --- | --- | --- | --- |
293
- | `from` | `GisViewPointRef` | 无 | 预指定起点(id / 下标 / `{ id }` / `{ index }`),直接进 `from` 相位;找不到时报 warning 且不开始 |
294
- | `autoCommit` | `boolean` | `false` | 两个节点一选完就按推荐方案自动落地,不用再调 `commitAddChannel()` |
295
- | `continuous` | `boolean \| 'chain'` | `false` | 每段落地后自动开下一段;`'chain'` 等价于同时 `chain: true` |
296
- | `chain` | `boolean` | `false` | 连续新增时以上一段终点当下一段起点;显式 `from` 优先于它 |
297
- | `kind` | `'channel' \| 'line'` | `'channel'` | 新建通道 / 回线,或新建不挂通道的独立线路 |
298
- | `circuits` | `number` | `1` | 一次新建几回;`lineItems` 给了有效项时按它的条数对齐 |
299
- | `name` | `string` | 按两端节点生成 | 新线路名 |
300
- | `lineId` | `string \| number` | 按通道 id 生成 | 新回线 id 的兼容别名;与 `id` 二选一 |
301
- | `id` | `string \| number` | 无 | 新回线 id;与 `lineId` 同时给时优先 |
302
- | `lineProps` | `Partial<GisViewLine>` | 无 | 透传给每条新回线的业务字段(颜色、宽度、电压等级…) |
303
- | `linePropsByMember` | `Array<Partial<GisViewLine> \| undefined>` | 无 | 逐回属性,第 i 项只盖在第 i 条上;与 `createChannels` 同名同义 |
304
- | `lineItems` | `GisViewChannelLineItems` | 无 | 连同线路一起新建的一条 / 一批;给了几条就建几条 |
305
- | `routing` | `'straight' \| 'orthogonal'` | `'straight'` | 直连,或自动补一个先横后纵的拐点 |
306
- | `path` | `GisViewCoordinate[]` | 无 | 中间拐点(不含两端);有效时优先于 `routing` 的自动拐点 |
307
- | `channelDraftStyle` | `GisViewChannelDraftStyle` | 全局 `channelDraftStyle` | 本次草稿的预览样式,草稿结束即丢弃;含两端示例点 `dots` 与示例线路层级 `layer`,见下文「示例点与层级」 |
308
- | `handoff` | `boolean` | `false` | `ready` 发完就收掉假通道,由使用方自己落地;与 `autoCommit` 同给时先自动落地 |
309
- | `select` | `boolean` | `false` | 点节点时同步进选中集(走 `onSelect` / `onSelectChange`);预指定 `from` 时起点当场选中 |
310
- | `channelProps` | `Partial<GisViewChannel>` | 无 | 新通道透传字段(宽度、节点宽度、偏移方式…) |
311
- | `originalEvent` | `Event \| null` | `null` | 触发这次草稿的原始事件,原样带进回调载荷 |
292
+ **`linePropsByMember` 与 `lineData`**:二者可以同时写,按下标合并,`lineData[i]` 优先。`linePropsByMember[i]` 与 `lineData[i]` 都只作用于第 i 条回线;单条自己的 `name`、`color`、`width`、`opacity`、`id` 与任意业务字段都会原样落地。
312
293
 
313
294
  ```js
314
- view.beginAddChannel({
315
- from: 'station-a', // 预指定起点:接下来只点终点;不写则点两次
316
- autoCommit: false, // true:两个节点一选完就按推荐方案自动落地,不用再调 commitAddChannel
317
- name: '新增通道', // 新线路名,缺省按两端节点拼
318
- lineId: 'new-line-1', // 新回线 id,缺省按通道 id 生成(撞车自动加后缀)
319
- lineProps: { color: '#ff7a45', width: 3 }, // 新回线透传的业务字段
320
- // 逐条回线的属性:第 i 项只管第 i 回,盖在 lineProps 之上(与 createChannels 同名同义)
321
- linePropsByMember: [{ name: '第一回', color: '#dd4b2e' }, { color: '#2ea36b', width: 4 }],
322
- channelProps: { nodeWidth: 1 } // 新建通道时透传的通道字段
323
- })
324
-
325
- view.updateOptions({
326
- // 草稿的每一步都走这支回调:start / from / ready / cancel / commit / blocked
327
- onChannelDraft: payload => {
328
- if (payload.phase === 'from') {
329
- // 两个节点的读数:index(points 里的下标)、id、item(节点数据对象)、coordinate
330
- console.log('起点', payload.from.id, payload.from.item?.name, payload.from.coordinate)
331
- }
332
- if (payload.phase === 'ready') {
333
- // 选好两个节点:options 是全部方案、preferred 是推荐方案
334
- console.log(payload.from.id, payload.to.id, payload.options, payload.preferred)
335
- // blocked 为 true 时只能放弃,reason 说明原因(同一个节点 / 已有通道的每个方案都不可用)
336
- // 手动提交:resolution 缺省用 preferred,也可以给 { type: 'joinGroup', groupId }
337
- const chosen = payload.options.find(option => option.type === 'joinGroup' && option.enabled)
338
- if (chosen) {
339
- // 方案读数是 { type, enabled, count, lineIds, ... },不能整条回传;只取 resolution 认识的四个键
340
- view.commitAddChannel({
341
- type: chosen.type,
342
- groupId: chosen.groupId,
343
- groupIndex: chosen.groupIndex,
344
- channelId: chosen.channelId
345
- })
346
- }
347
- }
348
- if (payload.phase === 'commit') {
349
- // 落地结果:line 是写进 lines 的新回线(已自动选中)
350
- console.log(payload.lineId, payload.channelId, payload.groupId, payload.lineIndex)
351
- }
352
- }
353
- })
354
-
355
- // 程序化新增(新签名):不用点节点,按 from / to 直接落到数据上(onChannelDraft 一样收到 phase: 'commit')
356
295
  view.addChannel(
357
- { from: 'station-a', to: 'station-b', name: '甲通道', id: 'channel-9' },
358
- [{ id: 'line-1' }, { id: 'line-2' }],
359
- { id: 'group-1' }
296
+ { from: 'station-a', to: 'station-b', name: '甲通道' },
297
+ [
298
+ { id: 'line-a', name: '第一回', color: '#dd4b2e' },
299
+ { id: 'line-b', color: '#2ea36b' }
300
+ ]
360
301
  )
361
- // 并进已有通道 / 独立线路:走拆出来的专门方法(或旧写法)
362
- view.addChannelGroupLines('corridor-1', [{ id: 'c-7' }]) // 新加一组
363
- view.joinChannelGroupLines('corridor-1', 'corridor-1::2', [{ id: 'c-8' }]) // 并进某一组
364
- view.addChannel({ from: 'station-a', to: 'station-b', circuits: 3 }) // 旧写法照旧(按方案表)
365
-
366
- // 只想问「这两个节点能怎么加」:拿到的是与草稿里同一份方案表(节点找不到时返回 null)
367
- const plan = view.getChannelAddOptions('station-a', 'station-b')
368
302
  ```
369
303
 
370
- - **新签名(推荐)**:`addChannel({ from, to, name, id }, 线路数组, group?)` 只新建通道 —— 第二段 / 第三段显式给了才走这条(对象上写 `lineData` / `group` 别名也行);两个节点之间已经有通道时整条失败(`reason` 指路 `addChannelGroupLines` / `joinChannelGroupLines`,`plan` 里带 `hasChannel` / `hasGroup` / `groups` / `channel`)。旧写法(`circuits` / `resolution` / `channelId` …,以及只写 `{ from, to }` 的)保持原样,下面这几条方案表口径说的就是它。
371
- - 两个节点之间**还没有通道**:只有 `newChannel`(新建一条通道,这一次加的回线都是它的成员,`circuits` 大于 1 就是一次建一条多回线通道),`channelId` / `name` 可以自己给,缺省按两端节点拼。
372
- - 已经有通道:`newGroup`(变成新分组加入原通道,新分组不带拐点)与 `joinGroup`(并进某一组,方案里带 `groupId` / `groupIndex` / `lineCount` / `lineIds`)。上限由 `channelMaxGroups`(一条通道最多几组)与 `channelGroupMaxLines`(一组最多几条回线)决定,`0` 表示不限;超限的方案 `enabled: false` 并给出 `reason`。
373
- - **并进已有通道的简写**:`addChannel` 入参顶层直接写 `channelId`(并进哪条通道,按这条通道自己的推荐顺序挑:新分组 → 第一个能并的分组)/ `groupId` 或 `groupIndex`(并进哪一组)—— 与 `resolution: { type: 'joinGroup', groupId }` 是同一件事的两种写法(与 `updateLine` 的分组指令同一套)。指定了却挑不到可用方案时 `ok: false`、`reason` 写明「指定的通道 / 分组没有可用方案」,对照返回里的 `plan.options` 看每条为什么不可用;两个节点之间还没有通道时顶层 `channelId` 仍然是「新建通道的 id」。
374
- - `blocked: true` 只有两种情况:起点与终点是同一个节点,或者已有通道的每个方案都不可用(组数满了、每个分组也满了);这时 `reason` 给出原因,`options` 里仍然逐条列出不可用的方案。
375
- - 提交(`commitAddChannel`)与程序化新增(`addChannel` / `addLine`)返回同一份结果(草稿里的 `routing` / `path` 会自动带上):`{ ok, reason?, plan, resolution, kind, circuits, line, lineId, lines, lineIds, lineIndex, channelId, groupId, channelLineIds, channelIndex }`(`kind: 'line'` 时 `channelId` 为 `null`、`resolution.type` 为 `'newLine'`;`channelLineIds` 是落地后这条通道的**完整成员表** —— 并进已有通道时就是「更新过的 `lineIds` 数组」,独立线路为 `null`);落地之后草稿自动结束,`onChannelDraft` 再发一条 `phase: 'commit'`(失败时发 `blocked`,载荷里同样带 `channelLineIds`),新回线自动选中,之后用 `getData()` / `onWaypointChange` / `updateLine()` 像普通线路一样改。
376
- - **移除新增**:`removeLine(lineId)` 把刚落地的这条回线从数据里摘掉,通道声明(`lineIds` 与 `groups`)一起改,通道一条回线都不剩就整条删掉;`removeChannel(channelId)` 整条通道一起删。两个方法都返回结果对象(`{ ok, line / lineIds, channelId, removedChannel, reason? }`),并且已经处理好选中下标 —— 删掉线路之后 `getSelections()` 不会指到别的线上。
377
- - `getAddChannelDraft()` 读当前草稿(没有草稿时为 `null`);方案表也可以直接问:`getChannelAddOptions(from, to, count?, kind?)`,实例层按节点 id、引擎层同名方法按下标;只要「这两个点之间到底有没有通道 / 有几组」的读数(不做方案判断)用 `getChannelBetween(from, to)` —— 有通道时 `hasChannel: true`、`channel` / `groups` / `groupCount` / `currentGroupId` 与方案表同一份,没有通道时 `hasChannel: false` / `channel: null` / `groups: []`,端点认不出来返回 `null`。
304
+ **路径口径**:`addLine` 的 `routing: 'orthogonal'` 会补一个先横后纵的 90° 拐点;`path` 一律按中间拐点解释,独立线路落地成 `[起点, ...中间拐点, 终点]` 的完整折线。`addChannel` / `addChannelGroupLines` 的新建通道 / 新分组只把中间拐点写进通道骨架,首尾仍由两端节点坐标给出。逐条回线自己写了 `path` 时以它为准。
305
+
306
+ **删除的旧入参**:`addChannel` 不再接受 `circuits`、`count`、`resolution`、`kind`、`channelId`、`groupId`、`groupIndex`、`lineId`、`lineName`、`channelName`、`lineProps`、`linePropsByMember`、`lineItems`、`newGroup`;`addLine` 不再接受 `circuits`、`count`、`resolution`、`kind`、通道字段、`lineId`、`lineName`、`lineItems`、`linePropsByMember`。`commitAddChannel` 的旧顶层分派字段同样移除。命中这些字段时方法明确失败且不写数据;请改用第二段 `lineData`、第三段 `group` / `channelGroups`,或换用拆分后的方法。
307
+
308
+ **余量计算**:`getChannelAddOptions(from, to, count?, kind?)` 的 `count` 是本次回线数;`newGroup` 要求还能新分组且 `count <= channelGroupMaxLines`,`joinGroup` 要求目标分组余量足够。`kind: 'line'` 时方案表为空、`preferred: null`,只有两端相同才 `blocked`。
378
309
 
379
310
  **示例点与层级(`channelDraftStyle`)**
380
311
 
@@ -382,302 +313,118 @@ const plan = view.getChannelAddOptions('station-a', 'station-b')
382
313
 
383
314
  `channelDraftStyle.layer` 决定示例线路画在真实节点的哪一侧:`'above'`(默认)连同箭头 / `customDraw` 一起压在真实节点上;`'below-points'` 先画示例线、箭头与 `customDraw`,再把真实节点整层盖上去,示例线因此位于实际节点层级之下。示例点不受这个开关影响,始终在真实节点之后绘制,保证两端仍看得见。
384
315
 
385
- #### `commitAddChannel`:签名、全部传参与五种写法
316
+ #### `commitAddChannel`:唯一对象契约
386
317
 
387
318
  ```ts
388
- commitAddChannel(input?, options?): GisViewChannelAddResult
389
-
390
- input?:
391
- | 'auto' | 'newChannel' | 'newGroup' | 'joinGroup' | 'newLine'
392
- | { type?: 'newChannel' | 'newGroup' | 'joinGroup' | 'newLine'; groupId?: string | number; groupIndex?: number; channelId?: string | number }
393
- | GisViewLine // 直接线路对象
394
- | GisViewChannel // 直接通道对象(必须有 lineIds)
395
- | GisViewChannelAddCommitInput // 容器写法
396
- | undefined | null
397
-
398
- options?: {
399
- auto?: boolean
400
- continuous?: boolean
401
- originalEvent?: Event | null
402
- }
319
+ commitAddChannel(
320
+ input?: GisViewChannelAddCommitInput,
321
+ options?: { auto?: boolean; continuous?: boolean; originalEvent?: Event | null; select?: boolean }
322
+ ): GisViewChannelAddResult
403
323
  ```
404
324
 
405
- **调用前提**:它只负责把当前草稿落地,不能替代 `addChannel` / `addLine` 做无草稿的程序化新增。调用前必须已经按下面的顺序走到 `ready`:
406
-
407
- 1. `beginAddChannel(options?)` 进入草稿,或由 `continueAddChannel(options?)` 开出下一段。
408
- 2. 通过点击选好起点 / 终点;写了 `options.from` 时起点已预选,只需再点终点。草稿配置只支持 `from`,没有 `to`;写 `{ from, to }` 时 `to` 会被忽略。
409
- 3. `getAddChannelDraft()` 返回 `phase: 'ready'`,且 `plan`、`from`、`to` 都有值;`onChannelDraft` 也会收到同一次 `ready`。
410
- 4. 调 `commitAddChannel(...)`。成功后草稿结束、新回线自动选中;连续模式则在原地重开下一段。
411
-
412
- 以下两条任一不满足时不会写数据:
413
-
414
- | 情况 | 返回 | 草稿 |
415
- | --- | --- | --- |
416
- | 功能开关 `channelDraft` 关着 | `{ ok: false, reason: '新增通道已被功能开关关闭' }` | 保留 |
417
- | 没有草稿、仍在 `start` / `from` / `pick`,或没有可选方案 | `{ ok: false, reason: '还没有选好两个节点' }` | 保留 |
418
-
419
- **五种写法一览**。识别顺序是:先认无参 / 旧 resolution;其余对象先看有没有容器键,有就按容器处理;没有容器键再按 `lineIds` 或线路形状识别直接对象;实在认不出的对象最后仍交回 `resolution`。所以只要写成下列任一形状,就不需要先调 `addLine` / `addChannel`:
420
-
421
- | 写法 | 识别条件 | 缺省落地方式 | 典型用途 |
422
- | --- | --- | --- | --- |
423
- | 无参 | `input` 为 `undefined` / `null` | 用草稿的 `preferred` 方案 | 接受界面推荐的「新通道 / 新分组 / 并入某组」 |
424
- | 旧 resolution | 字符串;或只含非 `undefined` 的 `type` / `groupId` / `groupIndex` / `channelId` 的对象 | 按方案表挑一条;`'auto'` 的优先级是 `newChannel → newGroup → joinGroup` | 让用户明确选「新通道 / 新分组 / 并入哪组」 |
425
- | 直接线路对象 | 普通对象、没有 `lineIds` 数组,且至少有一个:`kind: 'line'`、`path` 数组、`from`、`to`、`start`、`end` | 按独立线路落地,不走通道方案表 | 最后一步直接把业务线路对象交给组件 |
426
- | 直接通道对象 | 普通对象且 `Array.isArray(lineIds)`;同时不能再出现容器键 `channel` / `line` / `lines` / `lineData` / `lineItems` / `channelGroups` / `group` / `channelProps` / `resolution` | 缺省 `resolution: 'newChannel'`;`lineIds` 是回线 id 种子,`groups` 是分组声明 | 一次写入一条新通道及其全部成员 |
427
- | 容器对象 | 出现任一容器键:`channel` / `line` / `lines` / `lineData` / `lineItems` / `channelGroups` / `group` / `channelProps` / `resolution`;只写 `lineProps` 也会按容器处理 | 各段分别覆盖草稿;`resolution` 不写时沿用草稿推荐方案 | 需要逐条回线字段、显式覆盖 `routing`,或把通道提交到已有分组 |
428
-
429
- > 空对象 `{}` 不是「新建通道」,会按旧 resolution 交给方案表(通常得到「指定的通道 / 分组没有可用方案」)。想接受推荐方案请直接不传参。直接线路对象里的 `start` / `end` 只参与形状识别,**不会**替代 `from` / `to`;要让落地点与草稿不同,请写 `from` / `to`。
430
-
431
- **旧 resolution 的字符串与对象**
325
+ 调用前草稿必须已经 `ready`。第一个参数只接受**一个对象**(`undefined` / `null` 表示全部沿用草稿);字符串、数组或旧版直接线路 / 直接通道对象都会明确失败,不写数据。通道与线路对象分别放在 `channel` / `line` / `lines` / `lineData` 里。
432
326
 
433
- | `resolution` | 行为 |
327
+ | 字段 | 含义 |
434
328
  | --- | --- |
435
- | 不写 / `null` / `'auto'` | 依次找可用的 `newChannel` → `newGroup` → `joinGroup`;前一个不可用就退下一个。独立线路草稿直接按 `newLine` 落地 |
436
- | `'newChannel'` | 新建通道;两个节点之间已经有通道时找不到同类型可用方案,返回 `ok: false` |
437
- | `'newGroup'` | 在已有通道里新加一组;没有通道、组数已满或回数余量不够时失败 |
438
- | `'joinGroup'` | 并入一个可用的已有分组;不带 `groupId` 时沿用方案表挑中的那一组 |
439
- | `'newLine'` | 只在 `kind: 'line'`(独立线路草稿)时有效;普通通道草稿传它会失败 |
440
- | `{ type, groupId?, groupIndex?, channelId? }` | 按字段逐项收窄;`type` 缺省时,只写 `groupId` / `groupIndex` 会自动当作 `joinGroup` |
441
- | `{ groupId }` / `{ groupIndex }` | `joinGroup` 的简写。二者都写时以 `groupId` 为准 |
442
- | `{ channelId }` | 在指定通道内按「新分组 → 第一个能并的分组」挑;没有已有通道时 `channelId` 只是新通道 id,不会当成并组 |
443
- | `payload.options.find(option => option.enabled)` | **不能整条回传**。方案读数还带 `enabled` / `count` / `lineCount` / `lineIds`,不满足旧 resolution 的「只含四个键」条件;只取 `{ type, groupId, groupIndex, channelId }` 这个子集 |
444
-
445
- ```js
446
- // 1. 接受推荐方案
447
- const accepted = view.commitAddChannel()
448
-
449
- // 2. 明确要「新建通道」或「并进某个分组」
450
- view.commitAddChannel('newChannel')
451
- view.commitAddChannel({ type: 'joinGroup', groupId: 'corridor-1::2' })
452
-
453
- // 3. 让界面把用户点中的那条方案交回来(只取 resolution 认识的字段)
454
- view.updateOptions({
455
- onChannelDraft: payload => {
456
- if (payload.phase === 'ready' && !payload.blocked) {
457
- const chosen = payload.options.find(option => option.enabled && option.type === 'joinGroup')
458
- if (chosen) {
459
- const result = view.commitAddChannel({
460
- type: chosen.type,
461
- groupId: chosen.groupId,
462
- groupIndex: chosen.groupIndex,
463
- channelId: chosen.channelId
464
- })
465
- console.log(result.ok, result.lineIds, result.channelLineIds)
466
- }
467
- }
468
- }
469
- })
470
- ```
471
-
472
- > `ready` 里的 `option` 是**方案读数**,不是可直接回传的 `resolution`。建议固定只取四个定位字段;整条传进来时,带 `lineIds` 的方案可能被识别成直接通道对象,其余带容器键的对象也可能走成容器,而不是按方案表选组。
473
-
474
- #### 直接线路对象:字段怎么分、`path` 怎么算
475
-
476
- 直接线路对象面向「最后一步已经有一份线路业务数据」的场景。它不要求先建独立线路草稿;只要当前草稿已经 `ready`,传入对象就会按 `kind: 'line'` 落地一条不挂通道的线路。
477
-
478
- | 对象字段 | 去向 | 说明 |
479
- | --- | --- | --- |
480
- | `from` / `to` | 新线路两端 | 覆盖草稿两端;必须是数据里真实存在的节点 id。两端相同会按方案表给出 `blocked` 原因 |
481
- | `id` | 新回线 id 种子 | 与 `options.lineId` 同义;与已有线路撞车时自动改成 `-1` / `-2` |
482
- | `name` | 新线路名 | 不写时沿用草稿名(草稿缺省按两端节点生成) |
483
- | `path` | **中间拐点** | 和草稿的 `path` 同一口径:首尾仍由 `from` / `to` 覆盖;要整条现成折线请用容器里的 `lines: [{ id, path: [首, …, 尾] }]` |
484
- | `lineProps` | 额外线路字段 | 最后合并,优先级高于对象上的普通业务字段 |
485
- | 其余字段(`color` / `width` / `lineType` / `voltageLevel`…) | `lineProps` | 原样透传到每一条新回线,可参与热力图、提示、筛选 |
486
- | `kind` | 只用于识别 | 写 `'line'` 可让一个没有 `from` / `to` / `path` 的对象也按独立线路解释 |
487
-
488
- ```js
489
- // 直接把业务对象交给草稿提交:虚线、颜色、线宽和自定义字段都会写进新线路
490
- const result = view.commitAddChannel({
491
- id: 'drop-line-1',
492
- name: '备用联络线',
493
- from: 'station-a',
494
- to: 'station-b',
495
- color: '#ff7a45',
496
- width: 3,
497
- lineType: 'dashed',
498
- voltageLevel: '10kV',
499
- // 中间拐点,不含首尾;首尾仍是 station-a / station-b 的坐标
500
- path: [[116.72, 39.91], [116.86, 39.95]]
501
- })
502
-
503
- console.log(result.ok) // true
504
- console.log(result.kind) // 'line'
505
- console.log(result.lineIds) // ['drop-line-1']
506
- console.log(result.channelId) // null
507
- ```
508
-
509
- #### 直接通道对象:`lineIds`、`groups` 与样式怎么写
510
-
511
- 直接通道对象适合「一次给全通道 + 成员」。识别它的硬条件是 `lineIds` 必须是数组;没有 `lineIds` 的对象即使写了 `groups`,也只会被当成旧 resolution 或无法识别的对象。
512
-
513
- | 对象字段 | 去向 | 说明 |
514
- | --- | --- | --- |
515
- | `lineIds` | 新回线 id 种子 | 每个 id 生成一条新回线;`lineIds` 为空数组时回线数量回落到草稿的 `circuits` / `lineItems`,不会清空草稿 |
516
- | `groups` | 分组声明 | 等价于容器的 `channelGroups`;组的 `lineIds` 应使用这次生成的成员 id。第一组不写成员时拿本次全部回线,其余空组会被丢掉 |
517
- | `id` | 新通道 id | 只决定新通道的身份;回线 id 一律来自 `lineIds`,这个 `id` 不参与逐条回线的 id 或前缀 |
518
- | `name` | 通道名与新线路名 | 新线路没有逐条 `name` 时一起使用 |
519
- | `from` / `to` | 新通道两端 | 覆盖草稿;实际回线端点也按它落 |
520
- | `path` | 通道骨架的中间拐点 | 不含两端;分组各自写了 `path` 时以分组那一份为准 |
521
- | `lineProps` | 全部新回线的公共字段 | 直接通道对象自己的 `lineProps` 会生效,优先级低于每一条逐条字段;容器写法里的 `channel.lineProps` **不合并**,见下文容器优先级 |
522
- | 其余字段(`width` / `nodeWidth` / `inset` / `tooltip` / `customCode`…) | `channelProps` | 原样写到新通道上 |
523
-
524
- ```js
525
- // 通道对象:两个 lineIds 各生成一条回线;groups 把它们分成两组
526
- view.commitAddChannel({
527
- id: 'object-channel-1',
528
- name: '东侧双回',
529
- from: 'station-a',
530
- to: 'station-b',
531
- lineIds: ['object-line-1', 'object-line-2'],
532
- groups: [
533
- { id: 'object-channel-1::1', lineIds: ['object-line-1'], path: [[116.75, 39.86]] },
534
- { id: 'object-channel-1::2', lineIds: ['object-line-2'], path: [[116.75, 39.98]] }
535
- ],
536
- width: 28,
537
- nodeWidth: 1,
538
- offsetMode: 'tangent',
539
- lineProps: { color: '#2f6fd0', width: 2.4, lineType: 'dashed' },
540
- owner: '运维一班'
541
- })
542
- ```
543
-
544
- > 直接通道对象的回数只由 `lineIds` 决定;`lineIds: []` 时回落到草稿的 `lineItems` / `circuits`。直接通道对象上另外写的 `circuits` 不会改回数,它会作为普通通道字段收进 `channelProps`;要覆盖本次回数,请用容器顶层 `circuits`,或在 `beginAddChannel` 时配置。
545
- >
546
- > `groups[].lineIds` 应写本次 `lineIds` / `lines` 真正生成的回线 id。写了不存在的 id 不会让整次提交失败:组件会报 warning,并在声明落地前按本次成员表调和(剔掉不属于本次的回线、把没落组的成员补进第一组)。
547
-
548
- > **直接通道对象缺省按新通道提交**。要在提交对象的同时并进已有分组,必须换成容器写法并写 `resolution`,例如 `{ resolution: { type: 'joinGroup', groupId: 'corridor-1::2' }, lines: [...] }`。直接把 `resolution` 塞进「带 `lineIds` 的通道对象」会把它识别成容器,顶层 `lineIds` 不再作为通道成员使用。
549
-
550
- #### 容器对象:逐段覆盖与精确优先级
329
+ | `from` / `to` | 覆盖草稿两端;必须是数据里真实存在的节点 id |
330
+ | `name` | 通道名 / 新线路名的统一覆盖值 |
331
+ | `id` | 新回线 id 种子;一次多条时自动展开。通道 id 写 `channel.id` |
332
+ | `resolution` | 落点:新建通道 / 新分组 / 并进分组 / 独立线路,见下文 |
333
+ | `channel` | 本次新建的通道对象:`{ id?, name?, from?, to?, path?, lineIds?, groups?, ...通道字段 }` |
334
+ | `line` | 本次新建的**独立线路**对象;对象上的普通业务字段进 `lineProps` |
335
+ | `lines` / `lineData` | 本次新建的回线数据,二者同义且 `lines` 优先;一条或一批,裸 id 等价于 `{ id }` |
336
+ | `channelGroups` / `group` | 新通道的分组声明,二者同义且 `channelGroups` 优先 |
337
+ | `lineProps` | 所有新回线共用的业务字段 |
338
+ | `linePropsByMember` | 按下标覆盖的逐条线路字段 |
339
+ | `channelProps` | 新通道的其余字段 |
340
+ | `path` / `routing` | 中间拐点与走线方式;`path` 不含两端 |
341
+
342
+ `channel.lineIds` 只在没有 `line` / `lines` / `lineData` 时当回线 id 种子;每个 id 建一条回线。`channel.groups` 与 `channelGroups` 同一份写法。传给 `channel` 的 `id` / `name` / `lineIds` / `groups` / `from` / `to` / `path` 以外的字段都进 `channelProps`。
343
+
344
+ `line` 决定这次是**独立线路**;`lines` / `lineData` 才是本次新建的成员回线表。两者不要混用来表达同一批线路。`lines: []` / `lineData: []` 不会清空草稿,而是沿用草稿的 `lineData`;想放弃草稿里的线路,重开一段不要带 `lineData` 的草稿。
345
+
346
+ #### 字段优先级
347
+
348
+ | 字段 | 优先级(左高右低) |
349
+ | --- | --- |
350
+ | `from` / `to` | 顶层 → `channel` → `line` → `lines` / `lineData` 里第一个对象项 → 草稿 |
351
+ | 成员回线 | `lines` → `lineData` → `line` → `channel.lineIds.map(id => ({ id }))` → 草稿 `lineData` |
352
+ | 分组声明 | `channelGroups` → `group` → `channel.groups` → 草稿 |
353
+ | 名称 | 顶层 `name` → `channel.name` → `line.name` → 草稿 |
354
+ | 回线 id 种子 | `line.id` → 顶层 `id` |
355
+ | `path` | 顶层 → `line.path` → `channel.path` → 草稿 |
356
+ | `routing` | 顶层 → 草稿 |
357
+ | `lineProps` | 草稿 → `line` 上除保留键外的业务字段 → 顶层 `lineProps` → `line.lineProps` |
358
+ | 逐条字段 | 同下标的 `lines` / `lineData` 项覆盖公共 `lineProps`;`linePropsByMember` 也按下标合并 |
359
+ | `channelProps` | 草稿 → `channel` 上除保留键外的业务字段 → 顶层 `channelProps` |
551
360
 
552
- 容器是表达能力最完整的一种写法。所有段都可省略,写了哪一段就覆盖哪一段,没写的继续沿用当前草稿:
361
+ `lineIds`、`channelId`、`groupId`、`groupIndex`、`lineId`、`kind`、`circuits`、`count`、`lineItems`、`newGroup`、`start`、`end` 都不是合法的顶层字段;命中时整个调用失败且不写数据。并组用 `resolution: { type: 'joinGroup', groupId }`,成员用 `lines` / `lineData`,通道 id 用 `channel.id`。
553
362
 
554
- | 容器键 | 类型 | 作用 |
555
- | --- | --- | --- |
556
- | `channel` | `GisViewChannel` | 新通道声明:`id` / `name` / `from` / `to` / `path` / `lineIds` / `groups` 与业务字段 |
557
- | `line` | `GisViewLine` | 一条独立线路;缺省把本次 `kind` 定成 `'line'`,不是「只含一条的 `lines`」。只有同时显式写 `kind: 'channel'` 时,它才作为一条成员回线挂进通道 |
558
- | `lines` | 一条或一批线路数据 | 逐条回线字段,支持对象、裸字符串 / 数字 id;**给了几条就建几条** |
559
- | `lineData` / `lineItems` | 同上 | `lines` 的兼容别名;三者同时写时的优先级是 `lines > lineData > lineItems` |
560
- | `channelGroups` / `group` | 一组或整份分组声明 | `channelGroups` 优先于 `group`;都写时以 `channelGroups` 为准 |
561
- | `channelProps` | `Partial<GisViewChannel>` | 最后覆盖新通道字段,优先级高于 `channel` 上的普通字段 |
562
- | `lineProps` | `Partial<GisViewLine>` | 公共回线字段;合并顺序是草稿 `lineProps` → 容器顶层 `lineProps` → `line.lineProps`,最后被逐条 `lines[i]` 覆盖。`channel.lineProps` 在容器里不参与合并 |
563
- | `resolution` | 旧 resolution 的字符串 / 对象 | 不写时:有 `channel` 按 `newChannel`,否则沿用草稿推荐方案 |
564
- | `kind` | `'channel' \| 'line'` | 显式改这一条是通道还是独立线路 |
565
- | `from` / `to` / `path` / `routing` | 同草稿 | 顶层端点、中间拐点与走线方式;`routing` 只认容器这一层 |
566
- | `circuits` | `number` | 没有有效 `lines` 时一次建几回;有 `lines` 时按条数(`circuits` 被忽略) |
567
- | `id` / `lineId` | 线路 id 种子 | `lineId` 优先;通道 id 请写在 `channel.id` 或 `channelId` |
568
- | `channelId` / `channelName` | 通道 id / 名称 | `channelId` 在无已有通道时是新通道 id;已有通道时是并组筛选条件 |
569
-
570
- > 容器顶层只有 `channelGroups` / `group` 是分组键;裸写 `groups` 不会被读取。分组直接写在 `channel.groups`,或用 `channelGroups` / `group`。
571
-
572
- **逐字段优先级(从高到低)**:
573
-
574
- | 目标字段 | 优先级 |
575
- | --- | --- |
576
- | `from` / `to` | 容器顶层 → `channel` → `line` → `lineItems` 里第一个对象项 → 草稿。前面的裸字符串 / 数字 id 跳过,不贡献端点 |
577
- | `path`(中间拐点) | 容器顶层 → `line` → `channel` → 草稿 |
578
- | `routing` | 容器顶层 → 草稿(直接线路 / 直接通道对象没有这一层) |
579
- | 分组 | `channelGroups` → `group` → `channel.groups` → 不声明(新通道只建一组,组 id 用通道 id) |
580
- | 逐条回线 | `lines` → `lineData` → `lineItems` → `line` → `channel.lineIds.map(id => ({ id }))` → 草稿 |
581
- | 线路公共字段(直接线路对象) | 对象上的普通业务字段 → 对象自己的 `lineProps` |
582
- | 线路公共字段(容器) | 草稿 `lineProps` → 容器顶层 `lineProps` → `line.lineProps` → 逐条 `lines[i]`;**不读 `channel.lineProps`** |
583
- | 通道字段 | 草稿 `channelProps` → `channel` 上的业务字段 → 容器 `channelProps` |
584
- | 名称 | 容器 `name` → `channelName` / `channel.name` → `line.name` → 草稿名称 |
585
- | `resolution` | 容器 `resolution` → 直接通道对象固定 `newChannel` → 草稿 `preferred` |
586
-
587
- **空数组的规则**:`lines: []`、`lineData: []`、`lineItems: []` **不会清空**草稿里已有的逐条回线,而是沿用草稿。想放弃草稿里的线路,重新 `beginAddChannel()`(不要传 `lineItems`)或改用 `addLine` / `addChannel` 自行落地。
363
+ 样例:
588
364
 
589
365
  ```js
590
- // 容器写法:通道声明、两条回线、分组、样式、走线方式一次给全
591
366
  view.commitAddChannel({
592
- resolution: 'newChannel',
593
- from: 'station-a',
594
- to: 'station-b',
595
367
  channel: {
596
368
  id: 'container-channel-1',
597
369
  name: '容器通道',
598
370
  nodeWidth: 1,
599
- width: 32,
600
- customCode: 'CH-001' // 不在通道保留键里的字段会进 channelProps
371
+ width: 32
601
372
  },
602
373
  lines: [
603
- { id: 'container-line-1', color: '#dd4b2e', width: 2.2, name: '一回' },
604
- { id: 'container-line-2', color: '#2ea36b', width: 2.2, name: '二回', voltageLevel: '10kV' }
374
+ { id: 'line-1', color: '#dd4b2e', width: 2.2 },
375
+ { id: 'line-2', color: '#2ea36b', width: 2.2 }
605
376
  ],
606
377
  channelGroups: [
607
- { id: 'container-channel-1::1', lineIds: ['container-line-1'], path: [[116.78, 39.88]] },
608
- { id: 'container-channel-1::2', lineIds: ['container-line-2'], path: [[116.78, 39.96]] }
378
+ { id: 'container-channel-1::1', lineIds: ['line-1'], path: [[116.78, 39.88]] },
379
+ { id: 'container-channel-1::2', lineIds: ['line-2'], path: [[116.78, 39.96]] }
609
380
  ],
610
- routing: 'straight',
611
381
  lineProps: { opacity: 0.9 },
612
382
  channelProps: { tooltip: false }
613
- })
383
+ }, { select: true })
614
384
  ```
615
385
 
616
- **只给种子、逐条字段另写**:`channel.lineIds` 不会被 `lines` 覆盖成通道成员表 —— 容器里的 `lines` 才是本次新建的回线;`channel.lineIds` 只在没有 `lines` 时提供 id 种子。下面的结果是两条回线、一条新通道:
617
-
618
386
  ```js
619
387
  view.commitAddChannel({
620
- channel: {
621
- id: 'seed-channel',
388
+ line: {
389
+ id: 'solo-line-1',
622
390
  from: 'station-a',
623
391
  to: 'station-b',
624
- lineIds: ['seed-line-1', 'seed-line-2']
625
- },
626
- lineProps: { color: '#2f6fd0', width: 2 }
392
+ color: '#ff7a45',
393
+ width: 3,
394
+ lineType: 'dashed',
395
+ path: [[116.72, 39.91]]
396
+ }
627
397
  })
628
398
  ```
629
399
 
630
- **逐条回线用 `lines` / `lineData` / `lineItems`**:它们接受单对象、对象数组,也接受裸 id。每个对象里的 `id` 就是回线 id;不写 id 时按「通道 id-序号」自动编号,撞车自动加后缀:
400
+ `line.path` 与顶层 `path` 都按中间拐点解释,首尾仍取 `from` / `to` 节点坐标;要整条折线也通过节点坐标与中间拐点一起表达。
631
401
 
632
- ```js
633
- view.commitAddChannel({
634
- channel: { id: 'mixed-channel', from: 'station-a', to: 'station-b' },
635
- lines: [
636
- { id: 'mixed-1', color: '#dd4b2e', width: 2 },
637
- 'mixed-2',
638
- { id: 'mixed-3', lineType: 'dotted', label: '三回' }
639
- ],
640
- group: { id: 'mixed-group', path: [[116.8, 39.9]] }
641
- })
642
- ```
402
+ #### resolution 与落点
643
403
 
644
- #### 覆盖草稿的端点、路由、名称与 id
404
+ | `resolution` | 行为 |
405
+ | --- | --- |
406
+ | 不写 / `null` / `'auto'` | 有 `line` 时按 `newLine`,有 `channel` 时按 `newChannel`,否则用草稿的推荐方案 |
407
+ | `'newChannel'` | 新建通道;两端已有通道时失败 |
408
+ | `'newGroup'` | 在已有通道里新加一组;没有通道或余量不足时失败 |
409
+ | `'joinGroup'` | 并进已有分组;不带 `groupId` 时沿用方案表挑中的分组 |
410
+ | `'newLine'` | 新建独立线路;普通通道草稿也可显式用它改道 |
411
+ | `{ type, groupId?, groupIndex?, channelId? }` | 按字段收窄方案;只写 `groupId` / `groupIndex` 时自动视为 `joinGroup`,只写 `channelId` 时在该通道里按「新分组 → 可并分组」挑 |
412
+ | `payload.options.find(option => option.enabled)` | 不能整条回传;只取 `{ type, groupId, groupIndex, channelId }` 子集 |
645
413
 
646
- 对象字段覆盖草稿,未写的继续用草稿。最常改的是这四组:
414
+ 草稿被 `singleChannel` / `existingChannel: 'block'` 阻断时,缺省提交(不传 `resolution` 或写 `'auto'`)直接失败并发 `phase: 'blocked'`;要改道必须显式传一个可用方案。方案表里的 `enabled: false` 条目不能用;`reason` 会说明余量或分组上限问题。
647
415
 
648
416
  ```js
649
- // 端点 / 名称 / 线路 id 种子全部由容器覆盖
650
- view.commitAddChannel({
651
- from: 'station-c',
652
- to: 'station-d',
653
- name: '改道后的联络线',
654
- lineId: 'rerouted-line',
655
- lines: [{ id: 'rerouted-line', color: '#7c4dff' }],
656
- routing: 'orthogonal'
657
- })
658
-
659
- // 只改公共线路字段,其余(含推荐方案、circuits、端点、名称)全部沿用草稿
660
- view.commitAddChannel({ lineProps: { width: 3.5, opacity: 0.8 } })
661
-
662
- // 只改通道字段:通道对象的 lineIds 仍按当前草稿回线数生成
663
- view.commitAddChannel({
664
- channel: { id: 'styled-channel', from: 'station-a', to: 'station-b', nodeWidth: 0.8 },
665
- channelProps: { width: 40, offsetMode: 'inset', inset: 0.2 }
666
- })
417
+ view.commitAddChannel() // 接受当前推荐方案
418
+ view.commitAddChannel({ resolution: 'newChannel' })
419
+ view.commitAddChannel({ resolution: { type: 'joinGroup', groupId: 'corridor-1::2' } })
420
+ view.commitAddChannel({ channel: { id: 'new-channel' } }) // 新建通道
421
+ view.commitAddChannel({ line: { from: 'a', to: 'b' } }) // 新建独立线路
667
422
  ```
668
423
 
669
- - `routing: 'orthogonal'` 只在**容器顶层**生效;直接线路 / 直接通道对象上的 `routing` 会进业务字段,不会改变走线。
670
- - 顶层 `path` 会盖过 `line.path` / `channel.path`;没有顶层 `path` 时才依次看线路、通道和草稿。
671
- - `lineId` 只是 id 种子:一次多条时自动展开成 `lineId-1` / `lineId-2`;每条自己的 `lines[i].id` 优先级更高。
672
- - `from` / `to` 可以覆盖草稿端点,但必须能在当前数据里找到;找不到时返回 `{ ok: false, reason: '找不到这两个节点' }`,草稿保留。
673
-
674
- #### 提交已有通道 / 已有分组:必须显式写 `resolution`
675
-
676
- 直接通道对象默认新建通道。要可预期地并进已有通道 / 指定分组,换成容器写法并在顶层显式写 `resolution`;容器不写时只是沿用草稿推荐方案,不能保证一定是并组。并组时成员由 `lines` 给出,`channel` 段不再负责新建:
424
+ 并进已有通道 / 分组时,使用方要在容器里写 `lines` / `lineData` 并显式给 `resolution`(或依赖 `'auto'` 的推荐方案):
677
425
 
678
426
  ```js
679
- // 并进已有通道的指定分组
680
- const joined = view.commitAddChannel({
427
+ view.commitAddChannel({
681
428
  resolution: { type: 'joinGroup', groupId: 'corridor-1::2' },
682
429
  lines: [
683
430
  { id: 'join-line-1', color: '#dd4b2e' },
@@ -685,25 +432,9 @@ const joined = view.commitAddChannel({
685
432
  ],
686
433
  lineProps: { width: 2.4 }
687
434
  })
688
-
689
- console.log(joined.ok) // true
690
- console.log(joined.channelId) // 'corridor-1'
691
- console.log(joined.groupId) // 'corridor-1::2'
692
- console.log(joined.channelLineIds) // 并完之后这条通道的完整成员表
693
435
  ```
694
436
 
695
- - `resolution: 'newGroup'` 必须在已有通道的前提下才可用;新组的 id / 拐点用 `channelGroups` 或 `group` 给。
696
- - 只写 `channelId` / `groupId` / `groupIndex` 也能作为旧简写,但它们只有在两个节点之间已经有通道时才按并组解释。
697
- - `channelProps` 只在新建通道时写进新通道;并进已有分组时不会改已有通道的字段,需要改通道请另调 `updateChannel`。
698
-
699
- ```js
700
- // 旧简写:两个对象字段等价
701
- view.commitAddChannel({ type: 'joinGroup', groupId: 'corridor-1::2' })
702
- view.commitAddChannel({ groupId: 'corridor-1::2' })
703
-
704
- // 只指定通道,让组件按「新分组 → 能并的分组」挑
705
- view.commitAddChannel({ channelId: 'corridor-1' })
706
- ```
437
+ `channelProps` 只作用于新建通道;并进已有通道时不会修改原通道字段,需要改名 / 改样式请另调 `updateChannel`。
707
438
 
708
439
  #### 连续新增、自动提交与事件
709
440
 
@@ -711,13 +442,14 @@ view.commitAddChannel({ channelId: 'corridor-1' })
711
442
 
712
443
  | 字段 | 类型 | 缺省 | 说明 |
713
444
  | --- | --- | --- | --- |
714
- | `auto` | `boolean` | `false` | 只标记本次事件是自动提交;不等于 `beginAddChannel({ autoCommit: true })`。`autoCommit` 由组件内部用 `commitAddChannel('auto', { auto: true })` 提交 |
445
+ | `auto` | `boolean` | `false` | 只标记本次事件是自动提交;不等于 `beginAddChannel({ autoCommit: true })`。`autoCommit` 由组件内部用 `commitAddChannel(undefined, { auto: true })` 提交 |
715
446
  | `continuous` | `boolean` | 跟草稿的 `continuous` | 显式 `false` 强制本次落地后结束草稿;`true` 只在草稿本来就是连续模式时有效 |
716
447
  | `originalEvent` | `Event \| null` | `null` | 连续模式重开草稿时带回去,供下一次草稿回调使用 |
448
+ | `select` | `boolean` | `false` | 落地后选中这次新建的全部回线;缺省不动选中集 |
717
449
 
718
450
  ```js
719
451
  // 连续新增:本次落地后直接开下一段,并以上一段终点为下一段起点
720
- view.beginAddChannel({ continuous: true, chain: true, kind: 'channel' })
452
+ view.beginAddChannel({ continuous: true, chain: true })
721
453
 
722
454
  view.updateOptions({
723
455
  onChannelDraft: payload => {
@@ -770,7 +502,7 @@ view.commitAddChannel(undefined, { continuous: false })
770
502
  | `channelIndex` | 新通道或并进通道的下标;独立线路为 `-1` | 通常不返回;事件载荷失败时按 `-1` 兜底 |
771
503
 
772
504
  ```js
773
- const result = view.commitAddChannel({ groupId: 'not-exist' })
505
+ const result = view.commitAddChannel({ resolution: { type: 'joinGroup', groupId: 'not-exist' } })
774
506
  if (!result.ok) {
775
507
  console.warn(result.reason) // 例如:找不到通道 / 指定分组没有可用方案
776
508
  console.table(result.plan?.options ?? []) // 每条方案为什么不可用
@@ -784,19 +516,20 @@ if (!result.ok) {
784
516
  - **功能关闭**:`features: { channelDraft: false }` 时提交直接拒绝。
785
517
  - **节点找不到 / 端点相同**:`from` / `to` 覆盖成不存在的 id,或两端指到同一个节点。
786
518
  - **方案不可用**:已有通道组数已满、目标分组余量不足,或者 `resolution` 指向不存在的通道 / 分组。
519
+ - **策略阻断**:`beginAddChannel({ singleChannel: true })` 或 `existingChannel: 'block'` 遇到已有通道时,`ready` 载荷已经带 `blocked: true` / `reason`;`commitAddChannel()` 无参(或 `resolution: 'auto'`)返回失败并发 `phase: 'blocked'`,不会改走新分组。要让用户改道,显式传 `resolution: 'newGroup'` 或 `resolution: { type: 'joinGroup', groupId }` 等可用方案。
787
520
 
788
521
  **给已有通道加线路 / 删除线路**
789
522
 
790
523
  | 想做什么 | 方法 | 要点 |
791
524
  | --- | --- | --- |
792
- | 往某条通道**新建**回线(可选分组) | `addChannelGroupLines(channelId, lineData, groupInfo?)` | 第二段 line_data 一条 / 一批:每项就是「线路的属性对象」(裸字符串 / 数字按 `{ id }` 收、不写 id 自动编号),**传几条建几条**;`groupInfo`(`{ id?, path? }`)给新分组的 id 与骨架,缺省 `${通道 id}::N`、不带拐点 |
793
- | 把新回线并进**指定分组** | `joinChannelGroupLines(channelId, groupId, lineData)` | `groupId` 认分组 id 也认序号(从 0 起、按声明顺序),不传挑第一个还有余量的分组;第二段同上 |
525
+ | 往某条通道**新建**回线(可选分组) | `addChannelGroupLines(channelId, lineData, groupInfo?, options?)` | 第二段 line_data 一条 / 一批:每项就是「线路的属性对象」(裸字符串 / 数字按 `{ id }` 收、不写 id 自动编号),**传几条建几条**;`groupInfo`(`{ id?, path? }`)给新分组的 id 与骨架,缺省 `${通道 id}::N`、不带拐点;`options.select: true` 选中新回线 |
526
+ | 把新回线并进**指定分组** | `joinChannelGroupLines(channelId, groupId, lineData, options?)` | `groupId` 认分组 id 也认序号(从 0 起、按声明顺序),不传挑第一个还有余量的分组;第二段同上;`options.select: true` 选中新回线 |
794
527
  | 把**已经在数据里**的线路挂进通道 | `addChannelLines(channelId, lineIds, options?)` | 只改成员表与分组声明(回线本身早就在数据里);`options.groupId` 指定进哪一组(缺省第一组),认不出 / 已在别的通道的进 `skipped` |
795
528
  | 从通道里摘回线 | `removeChannelLines(channelId, lineIds)` | 回线留在数据里(回到自己的 `path` / `from` / `to`);摘完一条不剩时 `ok: false`(要连通道一起删用 `removeChannel`) |
796
529
  | 删除线路 | `removeLine(lineId)` / `removeLines(lineIds)` | 把线路从数据里删掉:挂着通道的会一起从通道声明里摘(空掉的组整组去掉),这条通道一条回线都不剩时整条通道一起删。`removeLine` 返回 `{ ok, reason?, line, lineId, index, channelId, removedChannel }`(`removedChannel` 是布尔:这条通道跟着整条走了);`removeLines` 返回 `{ ok, reason?, entries, lines, lineIds, indexes, removedChannels }`(`removedChannels` 是跟着走掉的通道数组) |
797
530
  | 删除整条通道 | `removeChannel(channelId)` | 通道声明与它的成员回线一起删 |
798
531
 
799
- 三个新增入口(`addChannelGroupLines` / `joinChannelGroupLines` / `addChannelLines`)与 `addChannel` 返回同一份结果,`channelLineIds` 都是**更新后这条通道的完整成员表**;`addChannelLines` 的 `skipped` 逐条写清哪一条为什么没进。
532
+ 三个新增入口(`addChannelGroupLines` / `joinChannelGroupLines` / `addChannelLines`)与 `addChannel` 返回同一份结果,`channelLineIds` 都是**更新后这条通道的完整成员表**;`addChannelLines` 的 `skipped` 逐条写清哪一条为什么没进。`addChannelGroupLines` / `joinChannelGroupLines` 缺省同样不选中新回线,需要时传 `options.select: true`。
800
533
 
801
534
  ### 通道改名 / 通道下增减回线 / 删除分组
802
535