gis-view-webgl2 0.1.0 → 0.1.2
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 +40 -8
- package/dist/gis-view.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1036 -249
- package/dist/index.js +1 -1
- package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +194 -0
- package/docs/GisView.md +9 -8
- package/docs/GisView/345/256/236/347/216/260/345/216/237/347/220/206.md +2 -2
- 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 +172 -0
- package/docs/api/index.html +545 -59
- package/docs/demos/BackendSyncDemo.vue +10 -8
- package/docs/demos/BasicDemo.vue +101 -101
- package/docs/demos/BatchAddDemo.vue +5 -5
- package/docs/demos/ChannelDemo.vue +617 -10
- package/docs/demos/ConnectDemo.vue +392 -0
- package/docs/demos/CopyMergeDemo.vue +980 -929
- package/docs/demos/DecorationDraftDemo.vue +1300 -0
- package/docs/demos/DemoShell.vue +569 -240
- package/docs/demos/DragDropDemo.vue +11 -11
- package/docs/demos/EffectsDemo.vue +6 -6
- package/docs/demos/EventDemo.vue +327 -327
- package/docs/demos/ExtensionDemo.vue +458 -0
- package/docs/demos/GisViewDemo.vue +61 -48
- package/docs/demos/GraphicDemo.vue +338 -0
- package/docs/demos/LabelDemo.vue +553 -497
- package/docs/demos/MinimapDemo.vue +358 -0
- package/docs/demos/PerfDemo.vue +29 -0
- package/docs/demos/SearchFilterDemo.vue +412 -0
- package/docs/demos/StyleDemo.vue +121 -121
- package/docs/demos/api-index.js +98 -77
- package/docs/demos/demos.js +159 -135
- package/docs/demos/shared/demoData.js +107 -107
- package/docs/demos/shared/demoStyle.css +249 -249
- package/docs/demos/shared/fakeBackend.js +93 -6
- package/docs/format/03-/346/213/220/347/202/271/350/243/205/351/245/260/344/270/216/346/240/267/345/274/217.md +28 -8
- package/docs/format/04-/351/200/232/351/201/223/344/270/216/346/240/207/347/255/276.md +5 -4
- package/docs/format/06-/345/257/274/345/205/245/345/257/274/345/207/272/344/270/216/346/240/207/345/207/206/346/226/207/346/241/243.md +8 -6
- 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 +6 -6
- package/docs/guide/sample-document.html +4 -4
- package/docs/index.html +180 -9
- package/docs/internals/11-/350/246/206/347/233/226/345/261/202/346/240/207/347/255/276/344/270/216/346/217/220/347/244/272.md +1 -1
- package/docs/internals/13-/351/271/260/347/234/274/345/233/276.md +3 -2
- package/docs/internals/15-03-/351/200/220/350/275/256/350/256/260/345/275/225-/346/200/247/350/203/275/344/270/216/344/277/256/345/244/215-/344/270/213.md +16 -0
- package/docs/internals/16-03-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/213.md +11 -0
- package/docs/internals/17-/350/260/203/350/257/225/344/270/216/346/211/251/345/261/225/345/273/272/350/256/256.md +1 -1
- package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +18 -4
- package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +110 -25
- package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +13 -8
- 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 +111 -10
- 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 +47 -9
- package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +17 -15
- package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +61 -11
- 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 +74 -7
- package/docs/manual/11-/346/240/267/345/274/217/344/270/216/350/247/204/345/210/231.md +3 -2
- package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +260 -3
- package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +6 -5
- package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +27 -14
- package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +142 -49
- package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +31 -29
- package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +55 -39
- package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +189 -64
- 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 +4 -4
- package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +42 -2
- 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 +22 -6
- package/docs/playground/index.html +5 -5
- package/docs/steps/README.md +14 -10
- package/docs/steps/check-doc-truth.mjs +1 -1
- package/docs/steps/selftest-actions.html +10 -10
- package/docs/steps/selftest-data.html +96 -14
- package/docs/steps/selftest-decoration.html +577 -0
- package/docs/steps/selftest-demo.html +6 -5
- package/docs/steps/selftest-demos.html +2201 -1612
- package/docs/steps/selftest-docs.html +89 -16
- package/docs/steps/selftest-effects.html +5 -5
- package/docs/steps/selftest-features.html +757 -86
- package/docs/steps/selftest-full.html +583 -583
- package/docs/steps/selftest-labels.html +1034 -652
- package/docs/steps/selftest-perf.html +1899 -1844
- package/docs/steps/selftest-scale.html +117 -7
- package/docs/steps/selftest-select.html +24 -17
- package/docs/steps/step-10-full.js +1 -1
- package/docs//346/216/245/345/217/243/345/215/217/350/256/256.md +167 -17
- package/docs//346/225/260/346/215/256/346/240/274/345/274/217.md +1 -1
- package/package.json +43 -43
- package/docs/demos/HistoryDemo.vue +0 -230
- package/docs/demos/InterfaceDemo.vue +0 -309
- package/docs/demos/SampleDemo.vue +0 -283
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
一个实例可以反复换数据:切场景(场站与通道整批都变了)、从别的接口再拉一份、按批把通道补进来,
|
|
8
8
|
都不必 `destroy()` 再重建(重建会丢掉相机、筛选、选中与标签状态)。
|
|
9
9
|
|
|
10
|
-
### 整份换:updateData
|
|
10
|
+
### 整份换:updateData(含通道声明)
|
|
11
11
|
|
|
12
12
|
```js
|
|
13
13
|
// 换点与线(通道不动)——与从前一致
|
|
@@ -19,14 +19,14 @@ view.updateData(points, lines, channels)
|
|
|
19
19
|
// 单对象写法:只换给出的那几列(没给的不动)
|
|
20
20
|
view.updateData({ lines: nextLines, channels: nextChannels })
|
|
21
21
|
|
|
22
|
-
//
|
|
22
|
+
// 接口返回的数据先按自己的字段映射成这三列(见下面「接口列名不一样」)
|
|
23
23
|
view.updateData(res.points, res.lines, res.channels)
|
|
24
24
|
|
|
25
25
|
// 只想换通道声明(回线怎么分组、骨架拐点在哪):场站与回线数据一个都不动
|
|
26
|
-
view.
|
|
26
|
+
view.updateData({ channels: nextChannels })
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
`
|
|
29
|
+
`updateData({ channels })` 与 `updateChannel` 的分工:后者是按 id 给**一条**通道打补丁(`updateChannel('c1', { lineIds: [...] })` —— 补丁里的 `lineIds` 是这条通道成员的**整表**,增删线路就是改它:写回声明前与 `groups` 调和,不在表里的成员从各组剔掉、空组去掉,新成员补进第一组,组剔完回「整条通道一组」);前者是**整份换**、点不出某一条。两个入口写完都会把引擎算出的最终分组(分组 id、拐点路径)写回数据副本 —— 声明里没写分组 id 时引擎按「单组用通道 id、多组 `通道id::1` / `通道id::2`…」补(走 `splitChannelGroups` 这类分组编辑接口时第一组沿用通道 id、新组从 `::2` 起),写回后 `exportData` 再回灌不会换一组新 id 把分组骨架丢掉。声明里认不出的成员 id 会报一句 `onWarning`(引擎那边是静默忽略,不问就查不出);`updateChannel` 找不到通道、没给补丁也各报一句。`updateChannel` 还有第四段 `lineIds`(单独成参数):传了就是**整表对齐** —— 与第三段的线路数组一起,表里有、数据里没有的当场建,表里有的逐条改,表里没有了的旧成员**直接从数据里删掉**(视图跟着少);不传就只改成员回线自己的字段、不动数量。第二段补丁里的 `lineIds` 是「换声明」的老写法:少了只把成员移出通道(数据留着 + 提示一句,与 `removeChannelLines` 同一口径),要少了删就用第四段。 这一份调和不止打补丁这一条路:**整份提交**的几个入口(建视图 / `updateOptions({ channels })` / `updateData({ channels })` / `importData`)逐条走同一份规则 —— 写了非空 `lineIds` 的声明以它为准、`groups` 按它收窄(整份提交不再「给什么原样照收」);内部写回(`splitChannelGroups` / `mergeChannelGroups` / `updateChannel`)也会把 `lineIds` 对齐到各组,拆 / 并 / 挪组之后把声明原样整份重提交一遍,成员一个都不会被收窄剔掉。
|
|
30
30
|
|
|
31
31
|
`updateData` 与 `importData` 的分工:`updateData` 换的是数据那几列;`importData` 还认 `labelStates` /
|
|
32
32
|
`view` / `filter`(整份文档回灌,见「标准数据文档(full):接收 == 导出」)。两者换完都会按同一个筛选
|
|
@@ -114,13 +114,13 @@ loading.value = false
|
|
|
114
114
|
- 反过来的写法(先 `await` 接口、再建实例)也能用,只是首屏是「白屏 / 骨架」而不是「空场景 + 网格」;这样写时记得在组件卸载 / 路由离开时 `destroy()`,并且接口回调里对实例做一次存在性判断(窗口期内组件可能已经没了)。
|
|
115
115
|
- 首屏的主线程开销不在建实例(毫秒级),而在数据到货那一次的建场景 / 重排:大屏可以先把实例摆好、`whenDataRendered` 收 loading,两件事天然并行。
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
运行中的写法可以直接照上面两段:在 `onMounted` 里先建实例,接口回来再 `updateData` + `whenDataRendered()`;需要模拟延迟时在调用方加一个 `await new Promise(resolve => setTimeout(resolve, 900))`。
|
|
118
118
|
|
|
119
119
|
### 接口列名不一样:映射放在调用方
|
|
120
120
|
|
|
121
121
|
组件只认 `points` / `lines` / `channels` 三列 —— 后端返回别的列名(比如 `station_data` / `channel_data`)
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
时,在调用方映射一次再喂进来,组件一个字节都不用改。下面就是一份完整映射:
|
|
123
|
+
切场景走 `updateData`、通道分批到货走 `addChannels`、
|
|
124
124
|
收 loading 走 `whenDataRendered()`。
|
|
125
125
|
|
|
126
126
|
```js
|
|
@@ -209,10 +209,110 @@ if (moved.length) {
|
|
|
209
209
|
- **哪些算「位置变过」**:拖动落地、`updatePoint` / `patchPoints` 改坐标、`movePoint`,以及撤销 / 重做退回别的坐标都算;只改名字 / 颜色这种不动坐标的改动不在里面。同一个节点改两次只有一条(按 id 去重、取最新坐标)。
|
|
210
210
|
- **删掉的节点不留记录**:`removePoints` 之后 `getMovedPoints()` 里没有它(账本顺手清掉),所以「取差异 → 发接口 → 对账」这条回路不会往已经删掉的节点上发请求。
|
|
211
211
|
- **新加进来的节点先记不报**:`addPoints` / `addPoint` 以加入时的坐标入账 —— 刚加不算「变过」,之后被挪才算。
|
|
212
|
-
- **整份换数据 = 重新对账**:`updateData` / `
|
|
212
|
+
- **整份换数据 = 重新对账**:`updateData` / `importData` / `updateOptions` 里换 `points` 都会以新数据为基准对齐,换完读是空表。
|
|
213
213
|
- `clearMovedPoints()` 不传引用 = 全部对账;传一个或一批只清这些(写法与 `removePoints` 一致:id / 下标 / `{ id }` / `{ index }`,认不出来的跳过并记一条提醒);空数组是「没有目标」,什么都不做;返回实例本身,可以链式调。
|
|
214
214
|
- 这本账是**只读差异**,不落历史、不发事件:`getMovedPoints()` 本身不改数据,读到的记录会一直留着,直到 `clearMovedPoints` 或整份换数据。
|
|
215
215
|
|
|
216
|
+
## 要素显隐与变化对账
|
|
217
|
+
|
|
218
|
+
### 要素隐藏与恢复(setFeatureVisible / hideFeatures / showFeatures)
|
|
219
|
+
|
|
220
|
+
临时收起一个节点、一条线路或整条通道时,**不要删数据再重建**:`visible` 本来就是一个数据字段,组件提供三个入口把「翻字段 + 收集结果 + 合成一步历史」做成一件事:
|
|
221
|
+
|
|
222
|
+
```js
|
|
223
|
+
view.hideFeatures('channel', 'c1') // 整条通道与成员回线一起退场
|
|
224
|
+
view.showFeatures('channel', 'c1') // 数据 / 分组 / 拐点原样回来
|
|
225
|
+
view.setFeatureVisible('point', ['p-1', 'p-2'], false)
|
|
226
|
+
view.setFeatureVisible('line', { index: 3 }, true)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
| `kind` | `visible: false` 时发生什么 | 恢复时 |
|
|
230
|
+
| --- | --- | --- |
|
|
231
|
+
| `'point'` | 节点本身不画;绑在它身上的线路不再跟随它(线路有 `path` / `start` / `end` 时按自己的几何照画),以它为端点的锚定通道整条收起 | 节点回来,绑定线与通道重新接上 |
|
|
232
|
+
| `'line'` | 只收起这一条线路;通道声明、分组、骨架与其它回线都不动(要连整条通道一起收用 `'channel'`) | 这一条回线按原声明回来 |
|
|
233
|
+
| `'channel'` | 整条通道与全部成员回线一起退场;数据、分组、拐点与编辑状态都保留 | 成员回线、分组与编辑过的拐点原样恢复 |
|
|
234
|
+
|
|
235
|
+
- `refs` 收单个或一批:id / 下标 / `{ id }` / `{ index }`,也认 `'selection'`;通道的 `'selection'` 会按选中回线反查它们所属的整条通道。重复引用会去重。
|
|
236
|
+
- 返回 `{ ok, reason, kind, visible, indexes, ids, items, skipped }`:`indexes` / `ids` / `items` 按实际改到的数据顺序给出;认不出的引用逐条进 `skipped`,不影响其余条目。类型 / `visible` 写错或一个目标都没找到时 `ok: false`,其余情况是部分成功也返回 `true`。
|
|
237
|
+
- 整次调用收在一个 `batch` 里:只记一条历史、只发一条 `batch` 数据变更。`hideFeatures` / `showFeatures` 只是 `setFeatureVisible(..., false / true)` 的快捷写法。
|
|
238
|
+
- 也可以继续用 `updatePoint` / `updateLine` / `updateChannel` 写 `visible`;三个统一入口负责批量收集、一步历史与统一读数,适合做「隐藏选中 / 显示选中」这类按钮。
|
|
239
|
+
- **和删除的区别**:隐藏只改 `visible`,要素仍在数据里,`getPoint` / `getLine` / `getChannel` 仍查得到,`remove*` 才会真的删数据并清掉相关引用。
|
|
240
|
+
|
|
241
|
+
### 通道变化对账(getChangedChannels / clearChangedChannels)
|
|
242
|
+
|
|
243
|
+
和节点位置对账同一套回路:建视图(或整份换通道声明)时记一份基准,之后 `getChangedChannels()` 一趟扫出「相对上次对账变过什么」,发完接口再 `clearChangedChannels()` 把当前声明记成新基准:
|
|
244
|
+
|
|
245
|
+
```js
|
|
246
|
+
const changedChannels = view.getChangedChannels()
|
|
247
|
+
if (changedChannels.length) {
|
|
248
|
+
api.patch('/gis/channels/changes', { channels: changedChannels.map(one => ({ id: one.id, groups: one.groups, reasons: one.reasons })) })
|
|
249
|
+
.then(() => view.clearChangedChannels(changedChannels.map(one => one.id)))
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
每一项的读数是 `{ index, id, item, groups, groupsChanged, waypointsChanged, membersChanged, endpointsChanged, visibilityChanged, fieldsChanged, fields, reasons }`:
|
|
254
|
+
|
|
255
|
+
| 标记 / 原因 | 什么变了 |
|
|
256
|
+
| --- | --- |
|
|
257
|
+
| `membersChanged` / `members` | 通道的成员池变了(顶层 `lineIds` 与各分组里的回线一起算) |
|
|
258
|
+
| `groupsChanged` / `groups` | 分组结构变了:显式 `groups` 的开 / 关、分组数量、分组 id 或各组 `lineIds` 变了 |
|
|
259
|
+
| `waypointsChanged` / `waypoints` | 某个分组的骨架 `path` 变了:加 / 拖 / 删分组拐点都算 |
|
|
260
|
+
| `endpointsChanged` / `endpoints` | 解析后的通道两端 `from` / `to` 变了(包括从第一条回线继承出来的端点) |
|
|
261
|
+
| `visibilityChanged` / `visibility` | `visible` 在 `true` / `false` 之间翻过;缺省与 `true` 视为同一档 |
|
|
262
|
+
| `fieldsChanged` / `fields` | 除 `id` / `lineIds` / `groups` / `path` / `from` / `to` / `visible` 之外的声明字段变了,`fields` 列出字段名(名称、宽度、取宽系数、内收、`offsetMode`、提示等) |
|
|
263
|
+
|
|
264
|
+
`groups` 是当前生效分组的快照(没写 `groups` 的通道按「整组」折算,`id` 是通道 id,`lineIds` 是成员、`path` 是骨架)。几个口径:
|
|
265
|
+
|
|
266
|
+
- **新加通道只补账、不报变化**:基准里没有的通道读一次账本会登记进去,下一次再改才算;**删掉的通道从账本清掉**,不会留在返回里。
|
|
267
|
+
- **整份换通道声明 = 重新对账**:`updateData({ channels })`、`updateOptions` 里换 `channels`、`importData` 等整份替换之后,以新声明为基准,读出来是空表。
|
|
268
|
+
- **读本身不改基准**:`getChangedChannels()` 可以轮询 / 反复读,同一条变化会一直留着,直到 `clearChangedChannels` 或整份换通道声明。
|
|
269
|
+
- **空数组是「没有目标」**:`clearChangedChannels([])` 什么都不做;不传 / `null` 才是全部对账。传一个或一批引用只清这些,写法与 `removePoints` 一致(id / 下标 / `{ id }` / `{ index }`),认不出来的跳过并记一条提醒;返回实例本身可链式调用。
|
|
270
|
+
- **它看的是声明,不是实时几何**:拖动节点只改坐标、不改 `from` / `to` 的 id,所以不进通道账本;拖 / 加 / 删分组拐点改的是组 `path`,会进 `waypoints`。节点位置变化仍用 `getMovedPoints` / `clearMovedPoints`。
|
|
271
|
+
- **删除同步清账**:`removeChannel` 与 `removeLines` 连带删掉的通道会当场从账本摘掉,下一次 `getChangedChannels()` 不会再返回它们;没删的通道若因摘成员而变了,仍按 `members` / `groups` / `waypoints` 正常报。
|
|
272
|
+
|
|
273
|
+
#### 只看某一类变化:`getChangedChannels(options?)`
|
|
274
|
+
|
|
275
|
+
```js
|
|
276
|
+
// 只关心拐点:分组结构的新增 / 删除 / 重排不入账,但已有分组的 path 真变了仍报 waypoints
|
|
277
|
+
const waypointChanges = view.getChangedChannels({ reasons: ['waypoints'], ignoreGroups: true })
|
|
278
|
+
|
|
279
|
+
// 只要分组增删,不要成员和字段变化
|
|
280
|
+
const groupChanges = view.getChangedChannels({ reasons: ['groups'] })
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
- `reasons` 是白名单,可写 `groups` / `members` / `waypoints` / `endpoints` / `visibility` / `fields` 的任意组合。返回项的 `reasons` 与各个 `*Changed` 标记会一起收窄,未选中的原因对应标记为 `false`、`fields` 清空。
|
|
284
|
+
- `reasons: []` 表示什么都不取,直接返回空数组,不扫数据;传非数组会遵守「提醒一次、忽略这次筛选」的约定,按全部原因查询。
|
|
285
|
+
- `ignoreGroups: true` 时不报 `groups`,也不把整组新增 / 删除 / 重排折算成 `waypoints`。只有基准与当前都存在的同一分组(按 `id` 配对)`path` 真变了,才报 `waypoints`;缺省 `path` 与空数组归一到同一档,不会因为分组声明刚落数据而误报。
|
|
286
|
+
- 这两个选项只影响这一次查询,不修改基准;之后照常 `clearChangedChannels()` 对账。
|
|
287
|
+
|
|
288
|
+
### 线路变化对账(getChangedLines / clearChangedLines)
|
|
289
|
+
|
|
290
|
+
只有节点和线路、没有通道的场景用这一对方法。口径与通道账本相同:建视图 / 整份换线路时记基准,`getChangedLines(options?)` 返回最近变化,发完接口再 `clearChangedLines(refs?)` 对账。
|
|
291
|
+
|
|
292
|
+
```js
|
|
293
|
+
const changedLines = view.getChangedLines()
|
|
294
|
+
if (changedLines.length) {
|
|
295
|
+
api.patch('/gis/lines/changes', { lines: changedLines.map(one => ({ id: one.id, reasons: one.reasons })) })
|
|
296
|
+
.then(() => view.clearChangedLines(changedLines.map(one => one.id)))
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const geometryOnly = view.getChangedLines({ reasons: ['path'] })
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
每项返回 `{ index, id, item, path, start, end, from, to, visible, pathChanged, endpointsChanged, visibilityChanged, fieldsChanged, fields, reasons }`:
|
|
303
|
+
|
|
304
|
+
| 标记 / 原因 | 什么变了 |
|
|
305
|
+
| --- | --- |
|
|
306
|
+
| `pathChanged` / `path` | 线路自己的 `path` / `start` / `end` 变了;通道骨架变化不走这里 |
|
|
307
|
+
| `endpointsChanged` / `endpoints` | 线路的 `from` / `to` 绑定变了 |
|
|
308
|
+
| `visibilityChanged` / `visibility` | `visible` 在 `true` / `false` 之间翻过;缺省与 `true` 视为同一档 |
|
|
309
|
+
| `fieldsChanged` / `fields` | 除 `id` / `start` / `end` / `path` / `from` / `to` / `visible` 之外的声明字段变了,`fields` 列出字段名 |
|
|
310
|
+
|
|
311
|
+
- **新加线路只补账、不报变化**;`removeLine` / `removeLines` 删掉的线路同步清账,下一次查询不再返回。`removeChannel` 连带删除的成员回线也一起清账。
|
|
312
|
+
- **整份换线路 = 重新对账**:`updateData({ lines })`、`updateOptions` 里换 `lines`、`importData` 等整份替换之后以新声明为基准;读取本身不重置,`clearChangedLines()` 全部对账,传一个 / 一批引用只清这些,空数组无操作。
|
|
313
|
+
- `reasons` 白名单的规则与 `getChangedChannels` 相同:只写 `path` / `endpoints` / `visibility` / `fields`,空数组直接返回空表,非数组忽略并提醒一次。
|
|
314
|
+
- **点和线路各管一段**:拖动节点只改节点坐标,仍用 `getMovedPoints` / `clearMovedPoints`;线路数组里的 `path` / `start` / `end` / 端点绑定由 `getChangedLines` 负责。通道里的回线只比较自己的声明,分组骨架的增删与拐点变化仍由 `getChangedChannels` 负责。
|
|
315
|
+
|
|
216
316
|
## 按方法新增要素(节点 / 线路 / 通道 / 分组 / 拐点)
|
|
217
317
|
|
|
218
318
|
都能加,**全是内置方法** —— 不用自己拼好整份数据再 `updateData`。新增类的方法都往数组末尾追加(老要素的下标一个都不动),返回值说明这一次落地了什么,同时照常发一条 `dataChange`(`options.emit: false` 只落地不通知),开着历史时记一步(可撤销)。
|
|
@@ -279,7 +379,8 @@ view.getChannelGroupWaypoints(added.channelId, split.ok ? split.groupId : undefi
|
|
|
279
379
|
view.getParallelGroups()
|
|
280
380
|
```
|
|
281
381
|
|
|
282
|
-
|
|
382
|
+
四条口径:
|
|
383
|
+
- **新增时的线路 / 通道 `path` 口径**:`addLine` / `addChannel(..., kind: 'line')` 的 `routing: 'orthogonal'` 会自动补一个先横后纵的 90° 拐点;输入里的 `path` 一律按**中间拐点**解释,独立线路落地成 `[起点, ...中间拐点, 终点]` 的完整 `path`,新建通道 / 分组只把中间拐点写进骨架。`beginAddChannel` 草稿用同一份解析,预览与落地一致。
|
|
283
384
|
|
|
284
385
|
- **返回里带下标**:`addPoint` / `addPoints` / `addLines` 的 `indexes` 是追加后在数组里的位置(老数据一个都不动),后续 `updatePoint` / `updateLine` 直接拿它当 id 用;`addChannel` 给的是 `lineIndex` / `channelIndex` / `groupId`。
|
|
285
386
|
- **事件与历史**:新增发 `dataChange`(`addPoints` / `addLines` / `addLine` / `addChannel` / `addChannels` / `addChannelGroups`),分组改动另发 `channelGroups`(`onChannelGroupChange`,见「通道分组」);`addPoints` / `addLines` / `addChannels` / `addChannelGroups` / `addWaypoints` 的批量写法都只记一步历史、只发一条数据变更,`options.emit: false` 只落地不通知。开着历史时每一步都可撤销。
|
|
@@ -311,7 +412,7 @@ view.updateOptions({ history: { limit: 100 } }) // 换上限(0 表示不限
|
|
|
311
412
|
view.updateOptions({ history: false }) // 关掉,两份栈一起清
|
|
312
413
|
```
|
|
313
414
|
|
|
314
|
-
打开之后**每一次真的改了数据的操作都会记一条**:拖动节点、复制、合并、拐点增删、新增通道 / 线路、通道分组、删除,以及 `updateData` / `
|
|
415
|
+
打开之后**每一次真的改了数据的操作都会记一条**:拖动节点、复制、合并、拐点增删、新增通道 / 线路、通道分组、删除,以及 `updateData` / `patchPoints` / `patchLines` / `updatePoint` / `updateLine` / `updateChannel` / `updateOptions` 这些接口调用。快捷键也接上了:`Ctrl+Z` 撤销、`Ctrl+Shift+Z`(再认 `Ctrl+Y`)重做,写法可以在 `shortcuts` 里改(动作名 `undo` / `redo`),没得撤销 / 重做时 `onShortcut` 的 `handled` 为 `false`。
|
|
315
416
|
|
|
316
417
|
记的是**改动本身**(哪几个下标、改前是什么、改后是什么),不是整份数据的快照:拖一次节点与拖十万个节点都只按实际改动量付费,一次操作的连续过程(按下 → 拖动 → 停手)只留一条记录,撤销时回到起点的那些「空拖动」不会进历史。撤销 / 重做会走一遍完整的「换数据」流程 —— 筛选重算、引擎重同步、标签与小图重画,并按 id 重新选中(下标平移之后选中的还是原来那几个)。
|
|
317
418
|
|
|
@@ -40,7 +40,7 @@ createGisView(container, {
|
|
|
40
40
|
|
|
41
41
|
| 字段 | 取值 | 说明 |
|
|
42
42
|
| --- | --- | --- |
|
|
43
|
-
| `action.type` | 动作名 | 按下时是**意图**(`dragPoint` 拖节点 / `dragWaypoint` 拖拐点 / `boxSelect` 框选 / `pan` 平移 / `copy` 复制 / `channelDraft` 点两次加通道 / `contextMenu` 右键 / `click` 普通左键),松开时是**刚做完的那件事**(没拖就是 `click`),点击时是**真正发生的那一件**(`select` 选中 / `clearSelection` 点空白 / `click` 没开选中时的点击);拐点是 `addWaypoint` / `removeWaypoint` / `selectWaypoint` / `dragWaypoint`,标签是 `selectLabel` / `dragLabel` / `contextMenu` / `dblClick`,选中集是 `select` / `toggleSelect` / `boxSelect` / `clearSelection`,悬停固定是 `hover` |
|
|
43
|
+
| `action.type` | 动作名 | 按下时是**意图**(`dragPoint` 拖节点 / `dragWaypoint` 拖拐点 / `boxSelect` 框选 / `pan` 平移 / `copy` 复制 / `channelDraft` 点两次加通道 / `decorationDraft` 动态装饰器草稿 / `contextMenu` 右键 / `click` 普通左键),松开时是**刚做完的那件事**(没拖就是 `click`),点击时是**真正发生的那一件**(`select` 选中 / `clearSelection` 点空白 / `click` 没开选中时的点击);拐点是 `addWaypoint` / `removeWaypoint` / `selectWaypoint` / `dragWaypoint`,标签是 `selectLabel` / `dragLabel` / `contextMenu` / `dblClick`,选中集是 `select` / `toggleSelect` / `boxSelect` / `clearSelection`,悬停固定是 `hover` |
|
|
44
44
|
| `action.target` | `point` / `line` / `channel` / `waypoint` / `label` / `selection` / `empty` | 点在了什么上面:通道回线的 `type` 也是 `line`,但要按通道处理,所以 `target` 单独给 `channel`;点空白是 `empty`;拐点与标签各有自己的 `waypoint` / `label` |
|
|
45
45
|
| `action.data` | 对象 / `null` | 按 `target` 直接取到的那份数据:节点 / 线路是数据里那个对象(与 `getData()` 同一份引用)、通道回线是通道定义、选中集是这一批 `items`、拐点是 `{ lineIndex, pathIndex, coordinate, line }`、标签是 `{ label, parent }`、空白处是 `null` |
|
|
46
46
|
| `action.button` | `left` / `middle` / `right` / `none` | 鼠标键名,与载荷里的 `button` 数字一一对应;悬停没有按键,固定是 `'none'` |
|
|
@@ -119,7 +119,7 @@ createGisView(container, {
|
|
|
119
119
|
event.preventDefault() // 自己弹菜单
|
|
120
120
|
if (payload.type === 'point' && !payload.wasSelected) {
|
|
121
121
|
// 右键落在没选中的节点上:先把它选上,再按「选中集」弹面板(落在已选中的节点上就保留整份多选)
|
|
122
|
-
view.
|
|
122
|
+
view.selectFeatures(payload.item.id)
|
|
123
123
|
}
|
|
124
124
|
openMenu({ target: payload.item, selection: payload.selection, coordinate: payload.world })
|
|
125
125
|
}
|
|
@@ -232,7 +232,7 @@ onPointDrag: payload => {
|
|
|
232
232
|
}
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
-
组内节点之间有连线时不会互相拉扯:连线的两端都在组里,两个端点各按自己的位移量移动。Shift 框选与 Ctrl 点击得到的选中集合完全等价,`getSelectionData().ids` 读回的结果可以再喂给 `
|
|
235
|
+
组内节点之间有连线时不会互相拉扯:连线的两端都在组里,两个端点各按自己的位移量移动。Shift 框选与 Ctrl 点击得到的选中集合完全等价,`getSelectionData().ids` 读回的结果可以再喂给 `selectFeatures()`。
|
|
236
236
|
|
|
237
237
|
关闭拖拽:
|
|
238
238
|
|
|
@@ -430,11 +430,11 @@ view.on('pointCopy', async payload => {
|
|
|
430
430
|
|
|
431
431
|
## 节点合并
|
|
432
432
|
|
|
433
|
-
|
|
433
|
+
把一个节点拖到另一个节点身上,松手时不会直接合并,而是进入**待确认**:目标节点按悬停样式亮一下、并抬到最上层,同时派发 `onPointMerge` 的 `phase: 'pending'`。触发时机由 `merge.trigger` 决定:`'collision'`(缺省)在两个节点画在屏幕上的半径之和进入范围时触发;`'center'` 要求拖动的节点中心点进入目标节点的屏幕区域,更晚、更不容易误触;两种都再叠加 `merge.distance` 的像素容差。载荷里的 `trigger` 就是这一次命中的档位。确认之后两个节点的数据并成一个:**缺省保留碰撞目标**(结果留在目标节点上,拖动的来源节点从数据里消失),也可以在确认时写 `keep: 'source'` 反过来保留正在拖动的节点。取消则被拖动的节点连同跟随它的线路一起回到原位,并补发一条 `onPointDrag` 的 `end` 载荷。
|
|
434
434
|
|
|
435
435
|
### 对话框自己写
|
|
436
436
|
|
|
437
|
-
组件**不带任何对话框**:它只提供数据(事件载荷)与方法,确认界面自己写。`pending` 停在那里等使用方裁决 —— 在回调里直接调 `payload.confirm(options)` / `payload.cancel()`,或者把载荷存下来、用自己的弹窗按钮触发;用 `getPointMerge()` / `resolvePointMerge(true | false, options?)`
|
|
437
|
+
组件**不带任何对话框**:它只提供数据(事件载荷)与方法,确认界面自己写。`pending` 停在那里等使用方裁决 —— 在回调里直接调 `payload.confirm(options)` / `payload.cancel()`,或者把载荷存下来、用自己的弹窗按钮触发;用 `getPointMerge()` / `resolvePointMerge(true | false, options?)` 也能从外面读和裁决。确认参数是 `{ keep?: 'target' | 'source', item?: 节点字段补丁 }`:`keep` 选保留碰撞目标还是拖动的来源,`item` 改写最终节点的名称 / 分区 / 颜色 / 尺寸 / 图标 / 描边等字段;位置与 id 始终取保留端。
|
|
438
438
|
|
|
439
439
|
**确认前先看影响多少条线路 / 通道、再按 id 查详情**:`pending`、`confirm` 与 `getPointMerge()` 返回的 `state` 载荷都带 `impact`(`GisViewMergeImpact | null`)。线路侧:`impact.lineCount` 是这次合并会改写或删除的线路并集(同一条线只算一次),`linesChanged` / `removedLines` 分别与落地结果里的同名字段同一口径,`lineIds` / `removedLineIds` 给出具体线路 id(没有 id 时是 lines 下标)。通道侧:`channelsChanged` / `removedChannels` / `mergedChannels` 是三个读数,`channelIds` / `channelsChangedIds` / `removedChannelIds` / `mergedChannelIds` 是对应的 id 明细;`mergedChannelIds` 是被并进保留通道、之后不再单独存在的那些通道。`pending` 阶段按缺省 `keep: 'target'` 预演;在 `confirm` 阶段按最终 `keep` 重算,所以切换保留方向之后读到的才是最终影响。
|
|
440
440
|
|
|
@@ -443,7 +443,7 @@ view.on('pointCopy', async payload => {
|
|
|
443
443
|
```js
|
|
444
444
|
let pending = null
|
|
445
445
|
const view = createGisView(container, {
|
|
446
|
-
merge: { distance: 6 }, //
|
|
446
|
+
merge: { trigger: 'center', distance: 6 }, // 中心进入目标区域;再留 6px 额外容差
|
|
447
447
|
onPointMerge: payload => {
|
|
448
448
|
if (payload.phase === 'pending') {
|
|
449
449
|
pending = payload // 自己的确认框:确认按钮里调 payload.confirm({ keep, item })
|
|
@@ -473,7 +473,7 @@ pending.confirm({
|
|
|
473
473
|
|
|
474
474
|
// 从外面裁决与 payload.confirm 同一份参数
|
|
475
475
|
view.resolvePointMerge(true, { keep: 'source', item: { name: '合并后的站点' } })
|
|
476
|
-
view.
|
|
476
|
+
view.resolvePointMerge(true, { keep: 'target', item: { size: 16 } })
|
|
477
477
|
```
|
|
478
478
|
|
|
479
479
|
三种确认方式由 `merge.confirm` 决定(不写就是「什么都不做,停在 pending」):
|
|
@@ -587,12 +587,50 @@ view.mergeSelectedPoints({
|
|
|
587
587
|
|
|
588
588
|
// 拖动合并的裁决:pending / confirm 载荷都带 source / target / item
|
|
589
589
|
view.getPointMerge()?.confirm()
|
|
590
|
-
view.
|
|
591
|
-
view.
|
|
590
|
+
view.resolvePointMerge(true) // 确认
|
|
591
|
+
view.resolvePointMerge(false) // 取消:拖动的节点回原位,数据不动
|
|
592
592
|
```
|
|
593
593
|
|
|
594
594
|
删节点用 `view.removePoints('a')` / `view.removePoints({ index: 2 })` / 数组:绑在它上面的线路一并删掉,声明了它当端点的整条通道(含成员回线)跟着删。
|
|
595
595
|
|
|
596
|
+
## 节点拆分(splitPoint)
|
|
597
|
+
|
|
598
|
+
`splitPoint` 是 `mergePoints` 的逆操作:把一个节点按业务需要拆成两份或更多,原来连在它身上的线路与通道两端按分配重新挂到新节点上。它不弹对话框,直接按规则落地;整次调用只记一条历史、只发一条 `splitPoint` 数据变更。
|
|
599
|
+
|
|
600
|
+
```js
|
|
601
|
+
const split = view.splitPoint('hub', [
|
|
602
|
+
{ name: '枢纽(主)' }, // 第一份:沿用原下标与 id
|
|
603
|
+
{ x: 116.42, y: 39.91, name: '枢纽(东)', lines: ['line-east'] },
|
|
604
|
+
{ x: 116.38, y: 39.89, name: '枢纽(西)', lines: ['line-west'], channelTo: ['corridor-1'] }
|
|
605
|
+
], { label: '拆分枢纽' })
|
|
606
|
+
|
|
607
|
+
if (split.ok) {
|
|
608
|
+
console.log(split.pointIds, split.linesChanged, split.channelsChanged)
|
|
609
|
+
api.post('/gis/points/split', split.changes) // 拆分后的节点、受影响的线路与通道端点,以及删除项
|
|
610
|
+
}
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
每份节点定义可以直接写节点字段(`id` / `x` / `y` / `name` / 样式 / 业务字段),也可以放进 `item`;另外三项是从节点字段里摘出来的分配指令,不会写进节点数据:
|
|
614
|
+
|
|
615
|
+
| 字段 | 作用 |
|
|
616
|
+
| --- | --- |
|
|
617
|
+
| `lines` | 分到这一份的线路(id / 下标 / `{ id }` / `{ index }`,单个或一批)。只能分配真的连在来源节点上的线路(`from` / `to` 等于来源 id,或 `start` / `end` / `path` 顶点与来源坐标重合) |
|
|
618
|
+
| `channels` | 两端都分到这一份的受影响通道;把同一条通道写进两份时,按出现顺序当作 `from` 端、`to` 端各去一份 |
|
|
619
|
+
| `channelFrom` / `channelTo` | 只把通道的起点 / 终点分到这一份;同一条通道的同一端不能分配给两份 |
|
|
620
|
+
|
|
621
|
+
落地规则:
|
|
622
|
+
|
|
623
|
+
- **第一份接在原下标上**,缺省沿用来源节点的 id;其余几份追加到 `points` 末尾。`options.keepSourceId: false` 时第一份也生成 `${来源 id}-split-1`。没写 id 的其余份依次生成 `${来源 id}-split-2` / `-3`…,撞上现有 id 时自动加序号;显式写了重复 id 会整次失败。
|
|
624
|
+
- **节点字段从来源继承,再按这一份的字段覆盖**;坐标缺省继承来源。至少给两份,坐标必须是有效数字,否则 `ok: false` 且 `reason` 说明原因。
|
|
625
|
+
- **线路默认跟第一份**:没写进任何一份 `lines`、但连在来源节点上的线路,全部改挂到第一份;同一条线路显式分给两份会整次失败。认不出 / 没连在来源上的引用进 `skipped.lines`,不影响其余条目。
|
|
626
|
+
- **通道端点默认跟第一条被分配过的成员回线**:例如通道里唯一一条回线分给了第二份,通道的 `from` 会自动改到第二份节点,而不是留在第一份;成员都没显式分配时才回落到第一份。显式写了 `channels` / `channelFrom` / `channelTo` 的按显式分配来。
|
|
627
|
+
- **几何一起改**:线路的 `from` / `to` 改到对应新节点;没有 id 绑定、但 `start` / `end` / `path` 顶点与来源坐标重合的端点 / 顶点也改到新坐标。通道端点按新声明重新锚定,成员回线由通道骨架推导,不用逐条改。
|
|
628
|
+
- **拆完清理退化数据**:两端落到同一份、变自环或零长度的线路删掉;两端因此相同的通道删掉;成员回线全被删空的通道也删掉。删除项分别进 `removedLineIds` / `removedLineIndexes` 与 `removedChannelIds`。
|
|
629
|
+
- **返回的 `channelsChanged` 只数端点被改写的通道**;被删掉的通道单列在 `removedChannels` / `removedChannelIds`。`changes` 给 `{ removedPoint, points, lines, removedLines, channels, removedChannels }`:`points` 是拆分后的全部节点,`lines` / `channels` 是仍存在且端点被改写的线路 / 通道,删除项单列;可直接作为后端事务输入。`skipped` 说明哪些线路 / 通道引用没认出来。
|
|
630
|
+
- **撤销**:不用逐条回滚,`view.undo()` 一步回到拆分前;重做一步恢复拆分结果。
|
|
631
|
+
|
|
632
|
+
`CopyMergeDemo.vue` 的「拆分节点」一节可以直接点按钮看效果:选中一个节点,按两份拆开,读数会列出新节点 id、改绑的线路与通道端点,以及这一步的变更集。
|
|
633
|
+
|
|
596
634
|
## 删除(remove)
|
|
597
635
|
|
|
598
636
|
删除走的是「**先看清楚要删什么,确认之后才动数据**」这条路:Delete 键(`shortcuts.remove`,默认 `['delete', 'backspace']`)与 `requestRemove()` 都先发一份载荷(`onRemove` 回调 / `'remove'` 事件,`phase: 'pending'`),载荷里把这次要删的拐点 / 线路 / 节点、各自的连带影响与可选的删除范围列全;使用方拿着它弹自己的确认框,确认才删、取消数据一个字节都不动。
|
|
@@ -57,13 +57,13 @@ view.updateOptions({ selectionKinds: false }) // 两类入口都
|
|
|
57
57
|
view.selectInBox({ x: 40, y: 40, width: 320, height: 200 }, { selectionKinds: ['channel'] })
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
**不写 `selectionKinds` 时保留旧行为**:框选只收节点,Ctrl / 多选点击仍收节点与线路(通道回线按 `channelSelectMode` 摊开)。显式写了之后,框选与 Ctrl / 多选点击统一按这一份过滤;普通单选、`
|
|
60
|
+
**不写 `selectionKinds` 时保留旧行为**:框选只收节点,Ctrl / 多选点击仍收节点与线路(通道回线按 `channelSelectMode` 摊开)。显式写了之后,框选与 Ctrl / 多选点击统一按这一份过滤;普通单选、`selectFeatures` 这些接口入口不受它限制。运行中用 `updateOptions({ selectionKinds })` 切换即可,不需要重排场景。
|
|
61
61
|
|
|
62
|
-
## 接口选中(
|
|
62
|
+
## 接口选中(selectFeatures)与读回选中
|
|
63
63
|
|
|
64
64
|
点选、Ctrl 多选、框选与接口选中落在**同一份选中 state** 上:任何一种选上,`getSelections()` / `getSelectionData()` 与 `onSelectChange` 读到的都是它(接口选中会补一条 `cause: 'api'` 的选中集变更,所以一个回调就能覆盖所有来源)。
|
|
65
65
|
|
|
66
|
-
`selectFeatures(refs, options?)`
|
|
66
|
+
`selectFeatures(refs, options?)` 认这些写法(传 `null` / 空数组等于清空):
|
|
67
67
|
|
|
68
68
|
```js
|
|
69
69
|
// ① 节点:id / 下标 / 一批(id 与对象可以混着写)
|
|
@@ -108,11 +108,11 @@ view.focusOn(selection.items) // 把视角挪到这一批上
|
|
|
108
108
|
view.focusOn(selection.items, { select: true }) // 反过来也行:聚焦的同时把它们选上
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
`mode` 只决定「这一批怎么并进当前选中集」,落地之后与手动选中完全一样(通道回线照旧按通道抬层,见上一节)。认不出来的条目跳过(与 `patchPoints` 的 `skipped` 一个态度)。按 id 定位用的是与 `
|
|
111
|
+
`mode` 只决定「这一批怎么并进当前选中集」,落地之后与手动选中完全一样(通道回线照旧按通道抬层,见上一节)。认不出来的条目跳过(与 `patchPoints` 的 `skipped` 一个态度)。按 id 定位用的是与 `patchPoints` 同一张 id 表(按数据版本缓存),`isSelected` 与 `selectFeatures` 的去重也都走常数级的键集合 —— 十万级数据下「按 id 选一批」是 O(选中数),不会每个 id 扫一遍全量。
|
|
112
112
|
|
|
113
|
-
### 取消选中(deselect /
|
|
113
|
+
### 取消选中(deselect / selectFeatures(null))
|
|
114
114
|
|
|
115
|
-
取消某一个 / 某一类 / 全部都是**一句话**(`
|
|
115
|
+
取消某一个 / 某一类 / 全部都是**一句话**(`deselect()` 与 `selectFeatures(null)` 都表示全部取消):与选中落在同一份 state 上,清完 `getSelections()` / `getSelectionData()` 立刻读得到,`onSelectChange` 补一条 `cause: 'api'` 的选中集变更。
|
|
116
116
|
|
|
117
117
|
```js
|
|
118
118
|
view.deselect('station-1024') // 取消一个节点(id,或 { type: 'point', index: 3 })
|
|
@@ -120,18 +120,20 @@ view.deselect('line-3') // 取消一条线
|
|
|
120
120
|
view.deselect({ type: 'channel', channelId: 'channel-a' }) // 取消整条通道:它名下**已选中**的回线一起去掉
|
|
121
121
|
view.deselect({ channelId: 'channel-a', groupId: 'g-2' }) // 只取消这条通道的某一组
|
|
122
122
|
view.deselect(['station-1024', { type: 'line', index: 3 }]) // 一次取消几个(id 与对象可以混着写)
|
|
123
|
-
view.deselect() // 取消全部(=
|
|
124
|
-
view.
|
|
123
|
+
view.deselect() // 取消全部(= selectFeatures(null))
|
|
124
|
+
view.selectFeatures('station-7', { mode: 'remove' }) // 与 deselect('station-7') 同一件事
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
- `deselect(refs)` 的写法与 `selectFeatures` **完全一样**(节点 / 通道 / 线路 / 混合一批,拾取载荷与搜索命中也能直接喂进来),只做「去掉这几条」:其余选中原样留着,认不出来的条目跳过(与 `patchPoints` 的 `skipped` 一个态度)。不传(或传 `null`)等于取消全部(`
|
|
128
|
-
- 「加 / 减 / 取反」三种并入选中集的方式还是 `options.mode`:`selectFeatures(refs, { mode: 'remove' })` 与 `deselect(refs)` 等价,`'add'` / `'toggle'`
|
|
129
|
-
- **画布上的手动取消**:单击空白处与 Esc 都是清空全部(Esc 先逐层退出复制预览 → 待确认合并 → 通道草稿,最后一层才是清空);**Ctrl / ⌘ + 单击**已经选中的要素是取反取消(节点一直是单个取反;回线在 `channelSelectMode` 为 `'group'` / `'channel'` 时整组 / 整条通道一起取反,`'line'` 模式下 Ctrl + 单击留给加点与换选中);通道右键交互框里的「取消该分组选中」就是 `deselect({ channelId, groupId })
|
|
127
|
+
- `deselect(refs)` 的写法与 `selectFeatures` **完全一样**(节点 / 通道 / 线路 / 混合一批,拾取载荷与搜索命中也能直接喂进来),只做「去掉这几条」:其余选中原样留着,认不出来的条目跳过(与 `patchPoints` 的 `skipped` 一个态度)。不传(或传 `null`)等于取消全部(`selectFeatures(null)` 也是这个意思);传空数组是「没有目标」,什么都不做。返回实例本身,可以接着链式调。
|
|
128
|
+
- 「加 / 减 / 取反」三种并入选中集的方式还是 `options.mode`:`selectFeatures(refs, { mode: 'remove' })` 与 `deselect(refs)` 等价,`'add'` / `'toggle'` 见上一节。
|
|
129
|
+
- **画布上的手动取消**:单击空白处与 Esc 都是清空全部(Esc 先逐层退出复制预览 → 待确认合并 → 通道草稿,最后一层才是清空);**Ctrl / ⌘ + 单击**已经选中的要素是取反取消(节点一直是单个取反;回线在 `channelSelectMode` 为 `'group'` / `'channel'` 时整组 / 整条通道一起取反,`'line'` 模式下 Ctrl + 单击留给加点与换选中);通道右键交互框里的「取消该分组选中」就是 `deselect({ channelId, groupId })`。要「取消全部」时这三种写法等价:`selectFeatures(null)` / `selectFeatures([])` / `deselect()`。按住加点快捷键点击通道回线时,新增拐点后的选中与高亮也按 `channelSelectMode` 摊成一条 / 一组 / 整条通道。
|
|
130
130
|
- 想先确认再取消:`isSelected(ref)` 问单个、`getSelections()` 取原始条目、`getSelectionData()` 一次取全(`count` / `ids` / `bounds`)。
|
|
131
131
|
|
|
132
132
|
## 线路拐点
|
|
133
133
|
|
|
134
|
-
写 `waypointEditable: true`(缺省关)后,线路的中间顶点会画出可交互的拐点:按住 Ctrl 点击线路在点击位置插入拐点,单击选中,拖动即移动顶点、线路随之变成折线,双击或选中后按 Delete 删除。节点很密时 Ctrl 点击会先比较点到节点与点到线路的屏幕距离:**线路更近就按加点处理**,比节点远才走 Ctrl
|
|
134
|
+
写 `waypointEditable: true`(缺省关)后,线路的中间顶点会画出可交互的拐点:按住 Ctrl 点击线路在点击位置插入拐点,单击选中,拖动即移动顶点、线路随之变成折线,双击或选中后按 Delete 删除。节点很密时 Ctrl 点击会先比较点到节点与点到线路的屏幕距离:**线路更近就按加点处理**,比节点远才走 Ctrl 多选节点,所以贴着节点云也能给线路补拐点。通道回线加点后的高亮范围与普通点选共用 `channelSelectMode`:`'line'` 只高亮点中的一条、`'group'` 连这一组、`'channel'` 整条通道。
|
|
135
|
+
|
|
136
|
+
需要完全停掉拐点编辑时写 `features: { waypoint: false }` 或 `waypointEditable: false`:Ctrl + 点击、`addWaypoint` / `addWaypoints` / `addChannelGroupWaypoint`、通道右键框「加拐点」、拖动已有拐点以及双击 / Delete 删除都会一起变成无操作,数据与历史都不动。拐点只有这一道总闸,没有 `waypointAdd` 之类的细分开关;`shortcuts.addWaypoint` 只决定加点用哪个组合键,不改变总闸。运行时也可以 `view.setFeatures({ waypoint: false })`,`getFeatures().waypoint` 读当前有效值。
|
|
135
137
|
|
|
136
138
|
拐点默认只在需要的时候出现:`waypointVisibility: 'selected'`(默认)下,只有被选中的线路(以及刚用 `addWaypoint` 加过拐点的线路)才画出拐点,点击空白处清空选中后拐点一起隐藏;改成 `'always'` 可以回到常显。`getWaypoints()` 返回当前显示中的拐点,`getWaypoints(lineIndex)` 只取某条线路的,拿到的是 `{ lineIndex, pathIndex, coordinate, selected }`。
|
|
137
139
|
|
|
@@ -153,11 +155,11 @@ const view = createGisView(container, {
|
|
|
153
155
|
拐点就是 `path` 的中间顶点:首尾两端始终是线的端点(`start` / `end` 或绑定的 `from` / `to`),所以顶点数不足 3 的线没有拐点;首次加点按离点击位置最近的线段插入进 `path`。`addWaypoint(lineId, [x, y])` 与 `removeWaypoint(lineId, pathIndex)` 可以在代码里增删,删到只剩两个顶点时写法还原为 `start` / `end`。拖动中的 `phase` 依次是 `start`、`move`、`end`,每次载荷里的 `line` 都是该线路的最新数据。
|
|
154
156
|
|
|
155
157
|
整份换一条线的拐点用 `setLineWaypoints(id, [[x, y], …])`(**只写中间拐点**、不含首尾端点:写进数据时两端按节点 / `start` / `end` 补齐,传单个坐标 = 只留一个拐点、空数组等于去掉全部拐点回到两端直连;通道里的回线改的是它所在分组的骨架),通道骨架用 `setChannelPath(channelId, [[x, y], …])`(同一套写法,只写中间拐点,两端由节点坐标补齐)—— 分过组的通道写的是**第一组**的骨架(`groups[].path` 优先于顶层 `path`;「第一组」按引擎的过滤口径算 —— `lineIds` 空的分组声明会被整组丢掉,跳过它们取第一个真在用的组),要改别的组用 `updateChannel(id, { groups })` 整份声明覆盖。
|
|
156
|
-
`onWaypointChange` 的载荷给 `phase` / `lineIndex` / `pathIndex` / `coordinate` / `group_id` / `lineIndexes` 与最新的 `line` —— `line.path` 可以直接写回业务数据,`lineIndexes`
|
|
158
|
+
`onWaypointChange` 的载荷给 `phase` / `lineIndex` / `pathIndex` / `coordinate` / `group_id` / `lineIndexes` 与最新的 `line` —— `line.path` 可以直接写回业务数据,`lineIndexes` 说明这一个拐点动了哪些回线(通道里是整组的成员)。通道分组用 `'vertex'` 拖动时,`line.path` 只是被操作那条回线的派生几何,后端还要同时保存 `getChannelGroupWaypoints()` 给出的 `path` 与 `waypointOffsets`。导出侧见 [数据格式](../数据格式.md) 的「拐点」一节(`path` / `group_route` / `parallel_groups` 三处写法)。
|
|
157
159
|
|
|
158
160
|
拖动中间态(`start` / `move`)只更新线路数据与引擎读数,**不再逐帧写** `getOptions().channels` 里那份声明副本:`add` / `remove` / `end` 才是落地,声明与导出文档在那一刻跟上(一次拖动因此在历史里只留一条记录);拖动中要看实时骨架用 `getChannelGroups()` / `getChannelGroupWaypoints()`,它们读的本来就是实时几何。
|
|
159
161
|
|
|
160
|
-
通道内的回线按**分组**各记一份拐点:给某条回线加点 / 拖点 /
|
|
162
|
+
通道内的回线按**分组**各记一份拐点:给某条回线加点 / 拖点 / 删点只会改它所在分组的骨架,同一通道里其它分组的线路一根拐点都不动(见「通道分组」)。拖动分组的拐点还有两种模式(`channelWaypointDragMode`):缺省 `'parallel'` 改共用骨架、组内回线保持平行;`'vertex'` 只平移同组同一 `pathIndex` 的拐点,相邻顶点与其它分组不动,结果记在 `groups[].waypointOffsets`。
|
|
161
163
|
|
|
162
164
|
### 拐点样式(waypointStyle / waypointSelectStyle)
|
|
163
165
|
|
|
@@ -204,7 +206,7 @@ view.getParallelGroups()
|
|
|
204
206
|
// ]
|
|
205
207
|
```
|
|
206
208
|
|
|
207
|
-
一个通道的每个**分组**各成一组:`group_id` 取分组 id(没拆分过就是通道的 `id`,拆分出来的分组是 `通道 id::2` 这样的写法;没写 `id` 时用内容签名),`member_line_ids` 是这一分组里的回线,`group_route` 是它们共用的骨架(两端节点 +
|
|
209
|
+
一个通道的每个**分组**各成一组:`group_id` 取分组 id(没拆分过就是通道的 `id`,拆分出来的分组是 `通道 id::2` 这样的写法;没写 `id` 时用内容签名),`member_line_ids` 是这一分组里的回线,`group_route` 是它们共用的骨架(两端节点 + 每个拐点),同一分组的回线只记一份。分组写了 `waypointOffsets` 时,`group_route` 是已经叠上刚性偏移的实际路由。通道外的线路自己成组,组内只有它一条。集合只列**当前还有拐点**的组:删掉最后一个拐点后线路回到两端点,这条组也跟着退出去;换一批线路数据时通道外的组一起作废,通道的组跟着骨架一起保留。`onWaypointChange` 载荷里的 `group_id` 就是这个组号,拐点属于哪个集合一眼可见(分组见「通道分组」)。
|
|
208
210
|
|
|
209
211
|
---
|
|
210
212
|
|
package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md
CHANGED
|
@@ -6,6 +6,26 @@
|
|
|
6
6
|
|
|
7
7
|
一个通道表示一条走廊里并行敷设的多条回线(二 / 三 / 四 / 五回线都行)。回线本身照常写进 `lines`,通道只描述它们怎么排布。通道两端可以绑在节点上:声明 `from` / `to` 之后,**两端必须有节点,通道才显示**——骨架的首尾顶点就是这两个节点的坐标,拖动节点整条通道跟着走,节点被删掉或设成 `visible: false` 时通道与它的回线一起消失。
|
|
8
8
|
|
|
9
|
+
### 通道显隐(visible)
|
|
10
|
+
|
|
11
|
+
通道声明上可以直接写 `visible`(缺省 `true`):
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
const channels = [
|
|
15
|
+
{ id: 'c1', lineIds: ['l1', 'l2'], visible: false }, // 数据里先收起
|
|
16
|
+
{ id: 'c2', lineIds: ['l3', 'l4'] }
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
view.updateChannel('c2', { visible: false }) // 只改这一条:整条通道与成员回线退场
|
|
20
|
+
view.updateChannel('c2', { visible: true }) // 原样恢复
|
|
21
|
+
view.hideFeatures('channel', ['c1', 'c2']) // 批量隐藏:一条历史、一条 batch 数据变更
|
|
22
|
+
view.showFeatures('channel', 'c1')
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`visible: false` 是「整条通道退场」,不是删通道:通道声明、成员表、分组、骨架拐点、编辑标记都留在数据里,成员回线跟着通道一起不画;改回 `true` 时按原声明重新接上。它和两端节点缺失走同一条隐藏通路 —— 端点节点写了 `visible: false` 时,以它为端点的通道也会整条隐藏,节点恢复后自动回来。要真正删掉通道与成员回线用 `removeChannel`;要隐藏整条通道用 `updateChannel` / `hideFeatures`。
|
|
26
|
+
|
|
27
|
+
逐条回线也可以写 `lines[].visible: false`:那只收起这一条回线,通道声明、分组、骨架与其它回线都不变;整条通道一起收用 `channel` 上的 `visible` 或 `hideFeatures('channel', ...)`。
|
|
28
|
+
|
|
9
29
|
### 通道选中模式(channelSelectMode)
|
|
10
30
|
|
|
11
31
|
点中通道里的一条回线时选中什么由 `channelSelectMode` 决定(缺省 `'line'`):
|
|
@@ -20,11 +40,40 @@
|
|
|
20
40
|
view.updateOptions({ channelSelectMode: 'channel' }) // 点一下回线 = 选中整条通道
|
|
21
41
|
```
|
|
22
42
|
|
|
23
|
-
Ctrl / ⌘ + 点击在 `'group'` / `'channel'` 下是**整批取反**:这一组 /
|
|
43
|
+
Ctrl / ⌘ + 点击在 `'group'` / `'channel'` 下是**整批取反**:这一组 / 这一条通道都在选中集里时一起取消,否则一起选上(与单击走同一套摊开规则)。按住 `shortcuts.addWaypoint`(默认也是 Ctrl / ⌘)点击通道回线加点时,加完由这一档决定选中并高亮一条、一组还是整条通道。框选、接口选中(`select` / `selectFeatures`)与「按通道写法的接口选中」不受影响。改这一项只重画一帧、不重排场景(`updateOptions({ channelSelectMode })`)。
|
|
44
|
+
|
|
45
|
+
### 分组拐点拖动模式(channelWaypointDragMode)
|
|
46
|
+
|
|
47
|
+
拖动通道分组里的一个拐点时,`channelWaypointDragMode` 决定这一组的骨架怎么变(缺省 `'parallel'`):
|
|
48
|
+
|
|
49
|
+
| 值 | 拖动效果 |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| `'parallel'` | 沿用共用骨架 + 平行偏移。骨架顶点跟着鼠标走,拐角处的相邻顶点可能随 miter 一起微调,组内回线保持平行等距 |
|
|
52
|
+
| `'vertex'` | 只平移当前分组同一 `pathIndex` 的拐点:组内每条回线都按相同的世界坐标增量走,相邻拐点、其它顶点与其它分组都不动 |
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
view.updateOptions({ channelWaypointDragMode: 'vertex' }) // 下一次拖动生效
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`'vertex'` 的结果不直接改写分组公共的 `path`,而是写进分组的刚性偏移 `groups[].waypointOffsets`:
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
{
|
|
62
|
+
id: 'corridor-1-b',
|
|
63
|
+
lineIds: ['c-3', 'c-4'],
|
|
64
|
+
path: [[..., ...], [..., ...]], // 原骨架(中间拐点)
|
|
65
|
+
waypointOffsets: { '1': [12.5, -8] } // 骨架第 1 个顶点的世界坐标增量
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- **序号按骨架算**:键是分组骨架的顶点序号(含首尾端点,从 0 起),但只接受中间拐点;`'1'` 表示含端点后的第二个顶点。值是 `[dx, dy]` 或 `{ x, y }`,组内所有成员回线都叠在各自平行偏移之后。
|
|
70
|
+
- **只属于这一组**:偏移不改其它分组,也不影响通道外的普通线路;普通线路拖动永远只动自己的 `path`。
|
|
71
|
+
- **不会自动清空**:切回 `'parallel'` 或再次 `updateOptions` 只换后续拖动的行为,已有 `waypointOffsets` 仍按数据继续生效。要把一组恢复成普通平行骨架,可把该组偏移拖回零,或重写分组声明(例如 `updateChannel(channelId, { groups })`,组项不写 `waypointOffsets` / 写空对象)后重新落数据。
|
|
72
|
+
- **读数与导出**:`getChannelGroups()` / `getChannelGroupWaypoints()` / `getChannels()` 都带 `waypointOffsets`;`getParallelGroups()` 与 `exportData({ format: 'test' | 'view', parts: ['waypoint'] })` 的 `group_route` 已经把偏移叠到骨架上,导出的是实际路由,不是旧 `path`。
|
|
24
73
|
|
|
25
74
|
### 由数组批量生成:createChannels
|
|
26
75
|
|
|
27
|
-
回线数量多的时候不用手写 `lines` 再手动拼 `channels`:给一份「通道声明数组」,`createChannels` 会按 `circuits` 展开回线、生成 id / 名称 /
|
|
76
|
+
回线数量多的时候不用手写 `lines` 再手动拼 `channels`:给一份「通道声明数组」,`createChannels` 会按 `circuits` 展开回线、生成 id / 名称 / 颜色,并组装出共用同一条骨架的通道对象。回线只声明两端节点;需要中间拐点时写在通道的 `path` 上(`path` **可选**,不写就是两端直连),所以通道内多回线永远是等距平行的阶梯。
|
|
28
77
|
|
|
29
78
|
```js
|
|
30
79
|
import { createChannels, createGisView } from 'gis-view'
|
|
@@ -92,7 +141,7 @@ console.log(view.getChannels()) // 骨架、每条回线的偏移与推导出来
|
|
|
92
141
|
| `id` | `string \| number` | 通道标识,同 id 的通道在数据刷新之间保留已编辑过的骨架 |
|
|
93
142
|
| `lineIds` | `Array<string \| number>` | 通道内的线路 id,**顺序即回线顺序**(沿骨架左法线方向依次排开) |
|
|
94
143
|
| `from` / `to` | `string \| number` | 两端绑定的节点 id;**声明了任意一端就要求两端都有节点**,缺一个整条通道不显示。缺省时继承第一条回线自己的 `from` / `to`(只作便利,不强制) |
|
|
95
|
-
| `path` | `GisViewCoordinate[]` |
|
|
144
|
+
| `path` | `GisViewCoordinate[]` | **可选**,骨架折线:两端锚定时只写**中间的拐点**(首尾顶点由节点坐标决定;整段省略就是两端直连);没锚定时写完整折线,完全缺省时取第一条回线自身的路径。配了 `groups` 时,第一组没写自己的 `path` 会回落到这里 |
|
|
96
145
|
| `width` | `number` | 通道宽度的世界单位写法,缺省用 `options.channelWidth`;`nodeWidth` 不为 0 且两端绑了节点时按节点尺寸算,这个值只作兜底(改了看不到变化) |
|
|
97
146
|
| `nodeWidth` | `number` | 节点宽度倍数:通道宽度按两端节点尺寸取的倍数,`1`(默认)表示回线正好铺在节点两边、节点放大回线跟着变宽,`0` 表示退回 `width` 的世界单位写法 |
|
|
98
147
|
| `offsetMode` | `'tangent' \| 'inset'` | `tangent`(默认)切点均分:最外侧回线正好落在通道宽度的两端;`inset` 切点内收:先按 `inset` 从两端各收 / 放一段,再在剩下的区间里均分 |
|
|
@@ -112,9 +161,9 @@ console.log(view.getChannels()) // 骨架、每条回线的偏移与推导出来
|
|
|
112
161
|
|
|
113
162
|
两端锚定的通道还会把切点往节点里收一段:缺省按成员回线里最粗那条的线宽收(切点从节点边缘再往节点里挪一个线宽),所以线不会从节点边缘探出去;`options.channelTangentInset` 或单个通道的 `tangentInset` 可以改成固定的 CSS 像素(`0` 表示切点正好落在节点边上),和线宽一样会随相机缩放折算回世界单位。内收两侧合计最多吃掉节点宽度的 40%,小节点配粗线时不会把回线挤成一条;写成负数就是**外收**:切点从节点边缘往外让一段,整组回线比节点还宽,单侧最多让出节点宽度的 50%。
|
|
114
163
|
|
|
115
|
-
通道的配置项没有重复的,成对出现的都是「**全局默认 + 逐通道覆盖**」:`channelWidth` / `width`、`channelNodeWidth` / `nodeWidth`、`channelInset` / `inset`、`channelTangentInset` / `tangentInset`,单个通道不写就取 `options` 上的同名值;`groups` / `path` / `from` / `to`
|
|
164
|
+
通道的配置项没有重复的,成对出现的都是「**全局默认 + 逐通道覆盖**」:`channelWidth` / `width`、`channelNodeWidth` / `nodeWidth`、`channelInset` / `inset`、`channelTangentInset` / `tangentInset`,单个通道不写就取 `options` 上的同名值;`groups` / `path` / `from` / `to` 分别管分组、骨架与两端锚定,各管一件事(`path` 是可选骨架,不写时由锚点或第一条回线补出)。要留意的是各自的**生效条件**(不是冗余,而是只在特定组合下才有意义):`offsetMode: 'tangent'`(默认)时 `inset` / `channelInset` 不参与计算;`nodeWidth` 不为 0 且两端都锚在节点上时 `width` 只作兜底(宽度按节点尺寸算,改它看不到变化);`tangentInset` 只对两端锚定的通道有效(没锚定时切点就是骨架端点,无所谓往哪收);只有一条回线的通道不排偏移(回线就是骨架),`nodeWidth` 与 `tangentInset` 对它的回线位置没有影响。
|
|
116
165
|
|
|
117
|
-
|
|
166
|
+
拐点挂在骨架上:`'parallel'` 模式下拖动通道内任一回线的拐点,整条通道的所有回线一起折线,回线之间保持平行等距;`'vertex'` 模式下只平移当前分组同一序号的拐点(见「分组拐点拖动模式」)。`insertWaypoint` / `removeWaypoint` 增删的也是骨架顶点(首尾两端由节点或原点决定,删不掉)。拐点只显示选中线路的规则对通道一样适用:通道内**任一回线被选中,整条通道的拐点都显示**,这样拖动时能看清整条走廊。拖动不做任何限制:拐点可以拖到两端节点之外、折回去、拐到任意一侧,拖动端点节点时中间拐点也保持原样,骨架怎么走回线就怎么平行偏移(`channelPathFor` 就是骨架的纯平行偏移)。`onWaypointChange` 载荷里的 `coordinate` 与 `line` 都是被操作的那条回线的最新几何(`line.path` 是推导后的折线);通道分组还要一起保存 `waypointOffsets`,只写 `line.path` 或只写公共 `path` 会丢掉 `'vertex'` 拖动结果。`getChannels()` 返回运行时状态:`{ id, channelId, channelIndex, lineIndexes, skeleton, waypointOffsets, offsets, paths, from, to, anchored, spacing }`——`anchored` 为 `true` 表示两端都落在节点上,`spacing` 是按两端节点尺寸算出来的相邻回线中心间距(分组之后 `offsets` 是这一组在整条通道里的那几条车道,`spacing` 仍是整条通道的间距),需要自有覆盖层或回写业务数据时用它。
|
|
118
167
|
|
|
119
168
|
两端锚定的通道与节点是联动的:拖动端点节点,骨架首尾跟着走(中间拐点保留),所有回线一起重算;节点被删掉或设成不可见时,通道与它的回线都从这一帧开始不参与渲染,节点回来后通道恢复,之前编辑过的拐点还在。
|
|
120
169
|
|
|
@@ -130,7 +179,8 @@ view.getChannelGroups('corridor-1')
|
|
|
130
179
|
// lineIndexes: [0, 1, 2, 3], lineIds: ['c-1', 'c-2', 'c-3', 'c-4'],
|
|
131
180
|
// anchored: true, edited: true,
|
|
132
181
|
// skeleton: [[116.2, 39.3], [116.7, 39.6], [117.2, 39.15]],
|
|
133
|
-
// path: [[116.7, 39.6]] // 可以直接写回 groups[].path
|
|
182
|
+
// path: [[116.7, 39.6]], // 可以直接写回 groups[].path
|
|
183
|
+
// waypointOffsets: {} // 'vertex' 拖出来的刚性偏移
|
|
134
184
|
// }]
|
|
135
185
|
|
|
136
186
|
// 拆成两组:每组最少一条;裸数组也行(分组 id 自动取 通道 id / 通道 id::2)
|
|
@@ -172,23 +222,23 @@ view.updateChannel('corridor-1', { name: '东干线', lineIds: ['c-1', 'c-2', 'c
|
|
|
172
222
|
- **一次拆几条走 `addChannelGroup(channelId, lineIds, options?)`**:`moveChannelLine` 一次只挪一条,要把两三回一起单独成组用这一条 —— **数据里没写 `groups` 的通道照样能拆**(没写就是「整条通道一组」,这几条从第一组里挪出去,被挪空的分组整组消失),新分组缺省照抄来源组当前的骨架(拆出来那一组画面不会跳),`options.path` 自己给这一组的中间拐点、`path: []` 要一条直连;组数 / 回线数到上限返回 `{ ok: false, reason }`,认不出的回线进 `skipped`、不影响其余条目。
|
|
173
223
|
|
|
174
224
|
- **合并时拐点跟随目标分组**:被并入的线路读的是目标分组的骨架,目标分组没有拐点就一条都不带(它们原来的拐点随之作废),目标分组自己的拐点原样保留;没参与合并的分组不受影响(拐点与编辑标记都留着)。
|
|
175
|
-
- 分组项可以写成 `{ id, lineIds, path }` 显式给这一组一份声明:给了 `path` 就按它重建(想主动清掉某组的拐点就写 `path: []`),只写 `id` + `lineIds` 则沿用同 id
|
|
225
|
+
- 分组项可以写成 `{ id, lineIds, path, waypointOffsets? }` 显式给这一组一份声明:给了 `path` 就按它重建(想主动清掉某组的拐点就写 `path: []`),只写 `id` + `lineIds` 则沿用同 id 分组当前的骨架与偏移;要清掉 `'vertex'` 偏移而不动骨架,重写声明时省略 `waypointOffsets`(空对象同义),只写 `id` + `lineIds` 会把当前偏移一起沿用。
|
|
176
226
|
- **分组变化另有专用事件 `channelGroupChange` / `onChannelGroupChange`**:拆分 / 合并 / 挪线 / 新建组与整份换声明除了照旧发 `dataChange('channelGroups')`(说「数据变了」),还会发这一条(说「变了什么」):`action.type` 为 `splitGroups` / `mergeGroups` / `moveLine` / `addGroup` / `setGroups`,`action.groupId` / `groupIds` / `lineIds` 指到具体分组与回线,`item` / `groups` 与 `dataChange` 那份是**同一个引用**(不想区分动作就照旧只接 `onDataChange`)。分组拐点写回声明那条路不发这一条 —— 它属于 `onWaypointChange`。
|
|
177
227
|
#### 拐点 / 分组同步后端(推荐)
|
|
178
228
|
|
|
179
229
|
这两类事件都不要逐帧提交,也不要把事件里的 action 当成完整数据:
|
|
180
230
|
|
|
181
231
|
- **拐点**:`onWaypointChange` 的 `phase` 是 `add` / `select` / `remove` / `start` / `move` / `end`。只有 `add`、`remove`、`end` 会改变并落地数据;`start` / `move` 是拖动中间态,`select` 只改选中,三者都不应发写请求。拖动一次会在松手时落成一批可撤销的历史,同步也只在 `end` 发一条。
|
|
182
|
-
- **通道拐点**:事件载荷里的 `line`
|
|
183
|
-
- **分组**:`onChannelGroupChange` 的 `action.type` 可能是 `splitGroups` / `mergeGroups` / `moveLine` / `addGroup` / `setGroups`;动作只是审计信息,声明以 `getChannelGroups(channelId)` 返回的整份 `groups` 为准(每项含 `id` / `lineIds` / `path`)。不要根据这个 action 自行猜「删了哪一组」:被挪空的分组会整组消失,按整份快照覆盖最稳。
|
|
232
|
+
- **通道拐点**:事件载荷里的 `line` 是**被操作那条回线**经过平行与刚性偏移后的几何,不能拿它覆盖整组骨架。要整份覆盖时,用 `channelId` + `groupId`(载荷里也可能是 `group_id`)调 `getChannelGroupWaypoints(channelId, groupId)`,提交返回的 `path` 与 `waypointOffsets`。只提交 `path` 会把 `'vertex'` 拖动结果丢掉。独立线路没有通道字段,才用 `PATCH /gis/lines/{id}/path`。
|
|
233
|
+
- **分组**:`onChannelGroupChange` 的 `action.type` 可能是 `splitGroups` / `mergeGroups` / `moveLine` / `addGroup` / `setGroups`;动作只是审计信息,声明以 `getChannelGroups(channelId)` 返回的整份 `groups` 为准(每项含 `id` / `lineIds` / `path` / `waypointOffsets`)。不要根据这个 action 自行猜「删了哪一组」:被挪空的分组会整组消失,按整份快照覆盖最稳。
|
|
184
234
|
- **空 path 合法**:删掉分组最后一个拐点时 `path` 就是 `[]`。这正是「回到直连」的完整声明,必须照常发请求;不要用 `if (!path.length) return` 把删除动作吞掉。
|
|
185
235
|
|
|
186
|
-
Demo 的「通道与数据」页直接接了两类假接口:通道分组拐点走 `PATCH /gis/channels/{id}/groups/waypoints`,独立线路走 `PATCH /gis/lines/{id}/path`,分组声明走 `PATCH /gis/channels/{id}/groups`。请求头带 `Idempotency-Key` / `If-Match`,body 带 `channelId` / `groupId` / 整份 `path` 或整份 `groups`,外层再补 `baseVersion` 与 `opId`;假后端延迟 420ms,按 `opId` 幂等,并模拟 200 / 网络失败 / 500 / 409。成功响应统一为 `{ code, message, requestId, traceId, data: { operation, version, idempotent, transactionId, result } }`:分组接口的 `result` 回最新 `groups` / `groupCount` / `lineCount`,拐点接口回 `path` / `pointCount`。失败时本地编辑仍在,面板给出「重试最近失败请求」,同 `opId` 重试会命中幂等缓存;409 后用新版本 rebase,再生成新的 `opId` 重新提交;「重放最近成功请求」复用原 `opId`,版本不会前进。完整请求 / 响应字段见[编辑接口协议](../接口协议.md)。
|
|
236
|
+
Demo 的「通道与数据」页直接接了两类假接口:通道分组拐点走 `PATCH /gis/channels/{id}/groups/waypoints`,独立线路走 `PATCH /gis/lines/{id}/path`,分组声明走 `PATCH /gis/channels/{id}/groups`。请求头带 `Idempotency-Key` / `If-Match`,body 带 `channelId` / `groupId` / 整份 `path` + `waypointOffsets` 或整份 `groups`,外层再补 `baseVersion` 与 `opId`;假后端延迟 420ms,按 `opId` 幂等,并模拟 200 / 网络失败 / 500 / 409。成功响应统一为 `{ code, message, requestId, traceId, data: { operation, version, idempotent, transactionId, result } }`:分组接口的 `result` 回最新 `groups` / `groupCount` / `lineCount`,拐点接口回 `path` / `pointCount`。失败时本地编辑仍在,面板给出「重试最近失败请求」,同 `opId` 重试会命中幂等缓存;409 后用新版本 rebase,再生成新的 `opId` 重新提交;「重放最近成功请求」复用原 `opId`,版本不会前进。完整请求 / 响应字段见[编辑接口协议](../接口协议.md)。
|
|
187
237
|
|
|
188
238
|
生产环境还要加两件事:**按通道串行**(同一条通道的请求不能并发覆盖,拖动期间只保留最后一个 `end`),以及**服务端版本校验**。整份覆盖接口应返回新版本号;客户端收到 409 时先拉取该通道最新声明,再让用户决定 rebase 还是放弃本地这次编辑。稳定 id 优先于数组下标,`groupId` 与 `lineIds` 都按声明里的 id 提交。
|
|
189
239
|
|
|
190
240
|
- **加点 / 删点 / 拖点只作用于同一分组**:同一分组共用一条骨架,其余分组的线路不动。
|
|
191
|
-
- 分组结果会写回 `channels[].groups`(`{ id?, lineIds, path? }`),下次 `updateOptions` / 换数据按它重建;也可以一开始就手写 `groups` 而不是 `lineIds` + `path`。
|
|
241
|
+
- 分组结果会写回 `channels[].groups`(`{ id?, lineIds, path?, waypointOffsets? }`),下次 `updateOptions` / 换数据按它重建;也可以一开始就手写 `groups` 而不是 `lineIds` + `path`。
|
|
192
242
|
- `getChannelMembers()` 与鹰眼图都按分组各成一份:`channelCircuits: false` 时每个分组各画偏移最靠中间的那一根回线。
|
|
193
243
|
|
|
194
244
|
---
|