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
@@ -58,23 +58,22 @@ const describePayload = (payload) => {
58
58
  if (payload.index !== undefined) parts.push(`index=${payload.index}`)
59
59
  if (payload.labelId !== undefined) parts.push(`labelId=${payload.labelId}`)
60
60
  if (payload.count !== undefined) parts.push(`count=${payload.count}`)
61
- if (payload.selection?.count !== undefined) parts.push(`selection=${payload.selection.count}`)
62
- if (payload.channelId !== undefined) parts.push(`channel=${payload.channelId}`)
61
+ if (payload.channelId !== undefined) parts.push(`channel=${payload.channelId}`)
63
62
  if (payload.camera !== undefined) parts.push('camera=…')
64
63
  if (payload.screen !== undefined) parts.push(`screen=[${payload.screen.map(value => Number(value).toFixed(0)).join(', ')}]`)
65
64
  if (payload.action) {
66
65
  const action = payload.action
67
- parts.push(`action=${action.type}/${action.target}${action.moved ? '+moved' : ''}${action.double ? '+double' : ''}@${action.button ?? '-'}`)
68
- parts.push(`data=${dataLabel(action.data)}`)
66
+ parts.push(`action=${action.type}/${action.target}${action.moved ? '+moved' : ''}${action.double ? '+double' : ''}@${payload.originalEvent?.button ?? '-'}`)
67
+ parts.push(`data=${dataLabel(hitDataOf(payload))}`)
69
68
  }
70
69
  if (!parts.length) parts.push(`keys=[${Object.keys(payload).slice(0, 6).join(', ')}]`)
71
70
  return parts.join(' · ')
72
71
  }
73
72
 
74
73
  /**
75
- * action.data 给的是哪一份数据:节点 / 线路 / 通道看 id,选中集报条数,
76
- * 标签事件(labelClick / labelDrag / labelContextMenu / labelDblClick)给的是 { label, parent } —— 报内容与它挂在哪个要素上。
77
- */
74
+ * 命中的数据在载荷顶层:节点 / 线路看 item,通道看 channel,选中集报 items 条数,
75
+ * 标签(labelClick / labelDrag / labelContextMenu / labelDblClick)看 label / parent。
76
+ */
78
77
  const dataLabel = (value) => {
79
78
  if (Array.isArray(value)) return `${value.length} 项`
80
79
  if (!value || typeof value !== 'object') return String(value)
@@ -88,12 +87,10 @@ const dataLabel = (value) => {
88
87
  return value.id ?? value.channelId ?? value.name ?? `对象 keys=[${Object.keys(value).slice(0, 4).join(', ')}]`
89
88
  }
90
89
 
91
- /**
92
- * 要 DOM 事件的三个事件(pointerDown / pointerUp / contextMenu)在总线上
93
- * 载荷是 `{ payload, event }`(与回调的两个参数一一对应),其余事件载荷就是回调里那一份 —— 这里统一拆开。
94
- */
95
- const EVENT_PAYLOAD_EVENTS = new Set(['pointerDown', 'pointerUp', 'contextMenu'])
96
- const unwrapPayload = (raw, name) => (EVENT_PAYLOAD_EVENTS.has(name) && raw && typeof raw === 'object' && 'payload' in raw ? raw.payload : raw)
90
+ /** 一次事件里可读的那份顶层数据:不把通道 / 分组上下文再打成一个 action.data */
91
+ const hitDataOf = payload => payload.item ?? payload.channel ?? payload.label ?? payload.line
92
+ ?? (payload.type === 'cluster' ? { id: payload.id, count: payload.count } : null)
93
+ ?? (Array.isArray(payload.items) ? payload.items : null)
97
94
 
98
95
  /** 会带 action 的事件名:* 订阅里从载荷上认出「这一次是什么操作」 */
99
96
  const ACTION_EVENTS = new Set(['click', 'dblClick', 'pointerDown', 'pointerUp', 'contextMenu', 'labelClick', 'labelDrag', 'labelContextMenu', 'labelDblClick', 'selectChange', 'waypointChange'])
@@ -257,10 +254,8 @@ const dismissCopy = () => {
257
254
 
258
255
  onMounted(() => {
259
256
  view = createGisView(container.value, buildOptions())
260
- // 一条 '*' 订阅全部事件:回调拿到的第二参就是事件名(与 options 里的 onXxx 同一份载荷)
261
- offAll = view.on('*', (raw, name) => {
262
- // 四个要 DOM 事件的事件先拆包,后面按「载荷就是回调里那一份」统一读
263
- const payload = unwrapPayload(raw, name)
257
+ // 一条 '*' 订阅全部事件:回调拿到的第二参就是事件名;载荷与 options 里的 onXxx 完全同一份
258
+ offAll = view.on('*', (payload, name) => {
264
259
  counts.value = { ...counts.value, [name]: (counts.value[name] ?? 0) + 1 }
265
260
  // 合并 / 复制:同一条 * 订阅里收中间态,确认条与日志读的是同一份载荷
266
261
  if (name === 'pointMerge') {
@@ -275,8 +270,10 @@ onMounted(() => {
275
270
  if (ACTION_EVENTS.has(name) && payload?.action) {
276
271
  // 一次操作的判读:什么操作 / 点到了什么 / 拿到了什么数据 —— 左键与右键都走这一套
277
272
  const action = payload.action
278
- const held = Object.entries(action.modifiers ?? {}).filter(([, on]) => on).map(([key]) => key).join('+')
279
- lastAction.value = `${name}:${action.type} → ${action.target}${action.data === undefined || action.data === null ? '' : ` · ${dataLabel(action.data)}`}${action.button ? ` · ${action.button}` : ''}${held ? ` · ${held}` : ''}${action.moved ? ' · 拖动过' : ''}${action.double ? ' · 双击第二下' : ''}`
273
+ const event = payload.originalEvent
274
+ const held = [event?.ctrlKey && 'ctrl', event?.metaKey && 'meta', event?.shiftKey && 'shift', event?.altKey && 'alt'].filter(Boolean).join('+')
275
+ const data = hitDataOf(payload)
276
+ lastAction.value = `${name}:${action.type} → ${action.target}${data === null || data === undefined ? '' : ` · ${dataLabel(data)}`}${event ? ` · button ${event.button}` : ''}${held ? ` · ${held}` : ''}${action.moved ? ' · 拖动过' : ''}${action.double ? ' · 双击第二下' : ''}`
280
277
  }
281
278
  if (!showHighFrequency.value && HIGH_FREQUENCY.has(name)) {
282
279
  return
@@ -350,7 +347,7 @@ onBeforeUnmount(() => {
350
347
  <div class="dv-group">
351
348
  <h3>动作判读(action)</h3>
352
349
  <p class="dv-readout">{{ lastAction }}</p>
353
- <p class="dv-hint">在画布上点一下 / 右键 / 拖一段 / 框选一下:这里按 action.type 说清「是什么操作」、action.target 说清「点到了什么」、action.data 就是那一份数据(点 / 线是数据里的对象、通道回线是通道定义)。</p>
350
+ <p class="dv-hint">在画布上点一下 / 右键 / 拖一段 / 框选一下:action.type 说清「是什么操作」、action.target 说清「点到了什么」,命中的数据直接读载荷顶层的 item / channel / label 等字段;按键与修饰键读 originalEvent。</p>
354
351
  </div>
355
352
  <div class="dv-group">
356
353
  <h3>事件计数(* 订阅)</h3>
@@ -338,11 +338,11 @@ const onLabelDrag = (payload) => {
338
338
  const onLabelContextMenu = (payload) => {
339
339
  hit.value = payload
340
340
  syncFormFromHit()
341
- // 用载荷里的 clientX / clientY 把自绘菜单贴在指针位置(系统菜单已被 preventContextMenu 收掉)
342
- menu.open = true
343
- menu.x = payload.clientX
344
- menu.y = payload.clientY
345
- pushLog('labelContextMenu', `右键 ${describeLabel(payload)} @ ${payload.clientX}, ${payload.clientY}`)
341
+ // 用原始指针事件的 clientX / clientY 把自绘菜单贴在指针位置(系统菜单已被 preventContextMenu 收掉)
342
+ menu.open = true
343
+ menu.x = payload.originalEvent?.clientX ?? 0
344
+ menu.y = payload.originalEvent?.clientY ?? 0
345
+ pushLog('labelContextMenu', `右键 ${describeLabel(payload)} @ ${menu.x}, ${menu.y}`)
346
346
  }
347
347
 
348
348
  const onLabelDblClick = (payload) => {
@@ -7,8 +7,8 @@
7
7
  * 源码或文档改过之后重跑一遍;`node docs/steps/check-doc-coverage.mjs` 会对账。
8
8
  */
9
9
 
10
- /** { members: 265, methods: 260, properties: 5, options: 96, sections: 16 } */
11
- export const API_SUMMARY = {"members":265,"methods":260,"properties":5,"options":96,"sections":16}
10
+ /** { members: 267, methods: 262, properties: 5, options: 96, sections: 16 } */
11
+ export const API_SUMMARY = {"members":267,"methods":262,"properties":5,"options":96,"sections":16}
12
12
 
13
13
  /** 实例成员,按 docs/GisView.md「参数速查」的分组排 */
14
14
  export const API_SECTIONS = [
@@ -50,8 +50,8 @@ export const API_SECTIONS = [
50
50
  { name: "zoomBy", kind: "method", params: "factor: number 缩放倍数(> 1 放大、< 1 缩小);origin?: [number, number] 缩放中心(画布 CSS 像素,缺省画布中心)", returns: "this", doc: "相对缩放,origin 为画布 CSS 像素锚点(缺省视口中心)", examples: [{"group":"相机与视图","code":"// zoomBy(factor, origin?):按倍数缩放;origin 是画布 CSS 像素的缩放中心\nview.zoomBy(1.5)\nview.zoomBy(0.5, [320, 200])\n","from":"header"}] },
51
51
  { name: "zoomTo", kind: "method", params: "scale: number 目标缩放值(世界单位 → CSS 像素的比例);origin?: [x, y] 缩放中心", returns: "this", doc: "直接设成指定比例(像素 / 世界单位)", examples: [{"group":"相机与视图","code":"// zoomTo(scale, origin?):直接给目标比例(世界单位 → CSS 像素)\nview.zoomTo(12)\n","from":"header"}] },
52
52
  { name: "zoomToBounds", kind: "method", params: "bounds: GisViewBounds 要装进画面的世界矩形 [minX, minY, maxX, maxY](也认 { minX, minY, maxX, maxY });padding?: number 四周留白", returns: "this", doc: "把一段世界坐标矩形适应到视图内,退化成一点时只居中不改比例", examples: [{"group":"相机与视图","code":"// zoomToBounds(bounds, padding?):把世界矩形装进画面(数组与对象写法都认)\nview.zoomToBounds([0, 0, 400, 200], 40)\nview.zoomToBounds({ minX: 116, minY: 39, maxX: 117, maxY: 40 })\n","from":"header"}] },
53
- { name: "focusOn", kind: "method", params: "target: GisViewFocusTargets 要对准的东西:id / { type: 'point' | 'line' | 'channel' | 'group', id | index } / { channelId }(整条通道)/ { type: 'group', channelId?, groupId } / [x, y] 坐标 / 矩形 / { lngLat } / { center },或者一批(数组取整体包围盒)。字符串按「先节点、再线路」,裸字符串不能表示通道;同 id 用显式 type 消歧,分组必须用 type: 'group'(groupId 全局唯一时可省略 channelId)。缺省只居中、比例一点不动;options.zoom: true 才按解析出的真实范围适配,四类目标共用这一条、没有额外放大倍率;scale 优先级高于 zoom,maxZoom 只给单点 / 单线这类零面积目标补最小范围。options?: GisViewFocusOptions:zoom: true 显式适配、scale 直接给比例、center: true 显式只居中(与缺省一致)、padding 要四周留白(CSS 像素,缺省 40)、maxZoom 限制这次适配的最大比例、select 顺带选中(true 选这次目标,也可另给一批写法)、mode 决定怎么并进当前选中集。目标认不出来或参数非法时什么都不做", returns: "this(相机更新后发 onCameraChange;select 生效时另发 onSelect / onSelectChange)", doc: "把视角移到目标上:单条(搜索命中 / 要素 id / { type: 'point' | 'line' | 'channel', id | index } / { channelId } 整条通道 / { type: 'group', channelId?, groupId } / 坐标 / 矩形 / { lngLat })或一批(数组取整体包围盒 —— 搜到三个节点就是这三个点的中心当屏幕中心);字符串先节点后线路,裸字符串不表示通道,同 id 用 type 消歧,分组必须显式写 type: 'group'(全局唯一 groupId 可省略通道)。缺省只居中、比例一点不动;options.zoom: true 才按解析出的真实范围适配(节点 / 线路 / 通道 / 分组共用这一条,不额外放大),scale 优先级高于 zoom。options 还认 center: true(显式只居中,与缺省一致)、padding、maxZoom、select(顺带选中这批,mode 决定怎么并进当前选中集)", examples: [{"group":"相机与视图","code":"// focusOn(target, options?):对准一个或多个目标(id / 显式类型 / 坐标 / 矩形 / 搜索命中 / 一批)\nview.focusOn('a') // 缺省只居中,比例不动\nview.focusOn('a', { zoom: true, maxZoom: 8 }) // 显式按范围适配\nview.focusOn(['a', 'b'], { zoom: true, padding: 60, select: true })\nview.focusOn({ type: 'point', id: 'a' }) // 节点与线路同 id 时显式消歧\nview.focusOn({ type: 'line', id: 'a' })\nview.focusOn({ type: 'channel', channelId: 'c1' }) // 通道也可以直接写 { channelId: 'c1' }\nview.focusOn({ type: 'group', channelId: 'c1', groupId: 'c1::2' })\nview.focusOn({ type: 'group', groupId: 'global-group' }) // groupId 全局唯一时可省略通道\nview.focusOn([120, 80], { center: true }) // 显式只居中(与缺省一致)\nview.focusOn({ lngLat: [116.4, 39.9] })\n","from":"header"}] },
54
- { name: "flyTo", kind: "method", params: "目标与世界坐标解析、zoom / scale / center / padding / maxZoom / select / mode 均与 focusOn 同义。options?: GisViewFlyOptions 另认 duration(毫秒,缺省 450;<= 0 立即落位)与 easing('linear' / 'ease-in' / 'ease-out' / 'ease-in-out',缺省 'ease-in-out',也可给自定义 (progress) => eased)。中心线性、比例按对数插值;系统 prefers-reduced-motion: reduce 时立即落位。再次 flyTo、用户拖拽 / 滚轮、setCamera / centerOn / zoom* / fitView / resetView / applyInitialView 都会停在当前帧", returns: "this(相机变化时发 onCameraChange;select 生效时另发选中事件)", doc: "平滑飞到与 focusOn 相同的目标:目标写法与 zoom / scale / center / padding / maxZoom / select / mode 完全同义,另认 duration(毫秒,缺省 450)与 easing(linear / ease-in / ease-out / ease-in-out 或 (progress) => progress)。中心线性插值、比例按对数插值;duration <= 0 或系统要求减少动态效果时立即落位。再次飞行、用户拖拽 / 滚轮或任何程序化改相机都会停在当前帧,不补跳到终点", examples: [{"group":"相机与视图","code":"// flyTo(target, options?):与 focusOn 同一套目标解析,平滑飞过去\nview.flyTo(hit, { zoom: true, duration: 600, easing: 'ease-in-out' })\nview.flyTo(['station-1', 'station-2'], { select: true, duration: 0 }) // 立即落位\nview.flyTo({ center: [120, 80], scale: 12, easing: progress => progress * progress })\n","from":"header"}] },
53
+ { name: "focusOn", kind: "method", params: "target: GisViewFocusTargets 要对准的东西:id / { type: 'point' | 'line' | 'channel' | 'group', id | index } / { channelId }(整条通道)/ { type: 'group', channelId?, groupId } / [x, y] 坐标 / 矩形 / { lngLat } / { center },或者一批(数组取整体包围盒)。字符串按「先节点、再线路」,裸字符串不能表示通道;同 id 用显式 type 消歧,分组必须用 type: 'group'(groupId 全局唯一时可省略 channelId)。缺省只居中、比例一点不动;options.zoom: true 才按解析出的真实范围适配,四类目标共用这一条、没有额外放大倍率;scale 优先级高于 zoom,maxZoom 只给单点 / 单线这类零面积目标补最小范围。options?: GisViewFocusOptions:zoom: true 显式适配、scale 直接给比例、padding 要四周留白(CSS 像素,缺省 40)、maxZoom 限制这次适配的最大比例、select 顺带选中(true 选这次目标,也可另给一批写法)、mode 决定怎么并进当前选中集。目标认不出来或参数非法时什么都不做", returns: "this(相机更新后发 onCameraChange;select 生效时另发 onSelect / onSelectChange)", doc: "把视角移到目标上:单条(搜索命中 / 要素 id / { type: 'point' | 'line' | 'channel', id | index } / { channelId } 整条通道 / { type: 'group', channelId?, groupId } / 坐标 / 矩形 / { lngLat })或一批(数组取整体包围盒 —— 搜到三个节点就是这三个点的中心当屏幕中心);字符串先节点后线路,裸字符串不表示通道,同 id 用 type 消歧,分组必须显式写 type: 'group'(全局唯一 groupId 可省略通道)。缺省只居中、比例一点不动;options.zoom: true 才按解析出的真实范围适配(节点 / 线路 / 通道 / 分组共用这一条,不额外放大),scale 优先级高于 zoom。options 还认 padding、maxZoom、select(顺带选中这批,mode 决定怎么并进当前选中集)", examples: [{"group":"相机与视图","code":"// focusOn(target, options?):对准一个或多个目标(id / 显式类型 / 坐标 / 矩形 / 搜索命中 / 一批)\nview.focusOn('a') // 缺省只居中,比例不动\nview.focusOn('a', { zoom: true, maxZoom: 8 }) // 显式按范围适配\nview.focusOn(['a', 'b'], { zoom: true, padding: 60, select: true })\nview.focusOn({ type: 'point', id: 'a' }) // 节点与线路同 id 时显式消歧\nview.focusOn({ type: 'line', id: 'a' })\nview.focusOn({ type: 'channel', channelId: 'c1' }) // 通道也可以直接写 { channelId: 'c1' }\nview.focusOn({ type: 'group', channelId: 'c1', groupId: 'c1::2' })\nview.focusOn({ type: 'group', groupId: 'global-group' }) // groupId 全局唯一时可省略通道\nview.focusOn([120, 80]) // 缺省只居中、比例不动\nview.focusOn({ lngLat: [116.4, 39.9] })\n","from":"header"}] },
54
+ { name: "flyTo", kind: "method", params: "目标与世界坐标解析、zoom / scale / padding / maxZoom / select / mode 均与 focusOn 同义。options?: GisViewFlyOptions 另认 duration(毫秒,缺省 450;<= 0 立即落位)与 easing('linear' / 'ease-in' / 'ease-out' / 'ease-in-out',缺省 'ease-in-out',也可给自定义 (progress) => eased)。中心线性、比例按对数插值;系统 prefers-reduced-motion: reduce 时立即落位。再次 flyTo、用户拖拽 / 滚轮、setCamera / centerOn / zoom* / fitView / resetView / applyInitialView 都会停在当前帧", returns: "this(相机变化时发 onCameraChange;select 生效时另发选中事件)", doc: "平滑飞到与 focusOn 相同的目标:目标写法与 zoom / scale / padding / maxZoom / select / mode 完全同义,另认 duration(毫秒,缺省 450)与 easing(linear / ease-in / ease-out / ease-in-out 或 (progress) => progress)。中心线性插值、比例按对数插值;duration <= 0 或系统要求减少动态效果时立即落位。再次飞行、用户拖拽 / 滚轮或任何程序化改相机都会停在当前帧,不补跳到终点", examples: [{"group":"相机与视图","code":"// flyTo(target, options?):与 focusOn 同一套目标解析,平滑飞过去\nview.flyTo(hit, { zoom: true, duration: 600, easing: 'ease-in-out' })\nview.flyTo(['station-1', 'station-2'], { select: true, duration: 0 }) // 立即落位\nview.flyTo({ center: [120, 80], scale: 12, easing: progress => progress * progress })\n","from":"header"}] },
55
55
  { name: "flyToCamera", kind: "method", params: "camera: GisViewCameraTarget 直接给相机目标 { center?, scale? }:没写 center / scale 的那一项沿用当前相机,不经过要素解析,也不改选中与数据;options?: GisViewFlyCameraOptions 的 duration / easing 与 flyTo 同义。中心 / 比例插值、减少动态效果、用户手势取消都走同一条相机动画", returns: "this(相机变化时发 onCameraChange;目标非法时保持当前相机)", doc: "直接飞到相机目标 { center?, scale? }:不写的那一项沿用当前相机,不经过要素解析、也不改选中与数据;duration / easing 与 flyTo 同义,参数非法时保持当前相机", examples: [{"group":"相机与视图","code":"// flyToCamera(camera, options?):直接飞相机目标;没写的 center / scale 沿用当前相机\nview.flyToCamera({ center: [120, 80], scale: 3 }, { duration: 600, easing: 'ease-out' })\nview.flyToCamera({ scale: 5 }, { duration: 0 }) // 中心不动,比例立即落到 5\nview.flyToCamera({ center: [116.4, 39.9] }) // 比例沿用当前相机\n","from":"header"}] },
56
56
  { name: "search", kind: "method", params: "input: string | GisViewSearchOptions 关键字,或 { keyword, scope, fields, limit } 这类选项。scope 可选 'points' / 'lines' / 'channels' / 'groups',也认与 focusOn 一致的单数别名 'point' / 'line' / 'channel' / 'group' 及数组;省略或写错时四类全搜。limit 对每一类分别计数。返回的 groups / hits 带分组命中(channelIndex / groupId / groupIndex,item 是轻量快照)—— 只返回结果、不动视角,要定位就自己调 focusOn(result.hits, …)", returns: "GisViewSearchResult:keyword / scope / points / lines / channels / groups / hits / total / truncated / durationMs", doc: "按关键字搜索节点 / 线路 / 通道 / 通道分组并返回命中:scope 认复数的 points / lines / channels / groups,也认与 focusOn 一致的单数别名 point / line / channel / group;结果分 points / lines / channels / groups 四类,分组命中带 channelIndex / groupId / groupIndex,可直接交给 focusOn / selectFeatures。不动视角:要不要聚焦由调用方自己调 focusOn,见「搜索与定位」", examples: [{"group":"相机与视图","code":"// search(input) / searchAsync(input):搜索(只返回结果、不动视角)\nconst hits = view.search('甲站')\nconst scoped = view.search({ keyword: '甲', scope: ['points', 'channels', 'groups'], limit: 10 })\nconst groupHits = view.search({ keyword: 'c1::2', scope: 'group' }) // 单数别名与 focusOn 一致\nview.focusOn(groupHits.groups[0], { zoom: true, select: true }) // 按整组范围适配并选中成员回线\nview.focusOn(hits.hits, { zoom: true, maxZoom: 8 }) // 命中集直接丢给 focusOn\nconst asyncHits = await view.searchAsync('甲站') // 大表走 Worker;stale 说明这一份不作数了\n","from":"header"}] },
57
57
  { name: "searchAsync", kind: "method", params: "同上;分组范围的当前轻量快照会随请求交给 Worker", returns: "Promise<GisViewSearchResult>:多一个 worker(是不是在 Worker 里算的)与 stale(回来时数据已经改过、或又搜过一次,这一份不作数了)", doc: "同一份口径的异步版:数据量大时把扫描放进 Worker(10 万点 / 10 万线下同步要占主线程 40 ms 上下的那一段),返回 Promise<GisViewSearchResult>;结果带 worker 与 stale 两个字段,见「异步搜索」", examples: [{"group":"相机与视图","code":"// search(input) / searchAsync(input):搜索(只返回结果、不动视角)\nconst hits = view.search('甲站')\nconst scoped = view.search({ keyword: '甲', scope: ['points', 'channels', 'groups'], limit: 10 })\nconst groupHits = view.search({ keyword: 'c1::2', scope: 'group' }) // 单数别名与 focusOn 一致\nview.focusOn(groupHits.groups[0], { zoom: true, select: true }) // 按整组范围适配并选中成员回线\nview.focusOn(hits.hits, { zoom: true, maxZoom: 8 }) // 命中集直接丢给 focusOn\nconst asyncHits = await view.searchAsync('甲站') // 大表走 Worker;stale 说明这一份不作数了\n","from":"header"}] },
@@ -100,7 +100,7 @@ export const API_SECTIONS = [
100
100
  { name: "getPointer", kind: "method", params: "无", returns: "GisViewPointerState", doc: "当前光标状态 { inside, screen, world, lngLat }(onPointer 回调的同一份数据)", examples: [{"group":"坐标、拾取与读数","code":"// getPointer():光标位置读数 { inside, screen, world, lngLat }\nconst pointer = view.getPointer()\n","from":"header"}] },
101
101
  { name: "pick", kind: "method", params: "x / y 画布 CSS 像素", returns: "GisViewPickResult | null", doc: "拾取指定画布位置上的要素(clickable: false 的要素不会返回)", examples: [{"group":"坐标、拾取与读数","code":"//(pick / getContextAt / nearestPointsAt 这些吃画布像素的方法先用这一对转;自动处理 canvas rect 与宿主缩放)\nconst [sx, sy] = view.clientToScreen(event.clientX, event.clientY)\nconst [fixedX, fixedY] = view.screenToClient(320, 200) // 摆 position: fixed 的浮层 / 菜单\n","from":"header"},{"group":"坐标、拾取与读数","code":"// pick(x, y) / pickAt(clientX, clientY):按画布像素 / 客户端坐标拾取\nconst hit = view.pick(320, 200)\nconst hitAt = view.pickAt(event.clientX, event.clientY)\nhitAt?.groups // 命中通道回线:同一通道的全部分组;hitAt.group 是当前这一组\n","from":"header"}] },
102
102
  { name: "pickAt", kind: "method", params: "clientX / clientY 浏览器客户端坐标(PointerEvent 原样给);命中通道回线时返回的 group 是当前组、groups 是整条通道的全部分组", returns: "GisViewPickAtResult | null", doc: "按浏览器客户端坐标拾取(就是鼠标事件里的 clientX / clientY):与指针事件载荷同一份结果(type / index / item / distance / screen / coordinate 与通道字段),另加 target('point' / 'line' / 'channel')与 lngLat;命中通道回线时 group 是当前组、groups 是整条通道的全部分组;没命中返回 null。只读,不改选中也不动光标", examples: [{"group":"坐标、拾取与读数","code":"// pick(x, y) / pickAt(clientX, clientY):按画布像素 / 客户端坐标拾取\nconst hit = view.pick(320, 200)\nconst hitAt = view.pickAt(event.clientX, event.clientY)\nhitAt?.groups // 命中通道回线:同一通道的全部分组;hitAt.group 是当前这一组\n","from":"header"}] },
103
- { name: "selectAt", kind: "method", params: "clientX / clientY 浏览器客户端坐标;按普通单击的命中与通道摊开规则立即选中,空白 / 聚合圆返回 null、不改选中,不派发 onClick / onDblClick。每次成功命中都发 onSelect,选中集实际变化时再发 onSelectChange(cause: 'api',顺序 onSelect → onSelectChange);通道回线载荷带当前 group / 完整 groups,多通道看 channelContexts。已知 id 时优先 selectFeatures", returns: "GisViewSelectPayload | null", doc: "只有客户端坐标时用它像普通单击一样立即选中命中要素:节点 / 线路直接选中,通道回线按 channelSelectMode 摊开;空白或聚合圆返回 null 且不改选中。不等待 selectTrigger: 'dblClick',也不派发 onClick / onDblClick;每次成功命中都发 onSelect,选中集实际变化时再发 onSelectChange,顺序 onSelect → onSelectChange(cause: 'api')。选中通道回线时载荷带当前 group / 完整 groups,多通道看 channelContexts。已知节点 / 线路 id 时直接用 selectFeatures,见「按坐标单击选中(selectAt)」", examples: [{"group":"选中、复制与合并","code":"// selectAt(clientX, clientY):每次命中发 onSelect,集合变化时再发 onSelectChange(不动指针、不派发 onClick);已知 id 直接用 selectFeatures\nview.selectAt(event.clientX, event.clientY) // 节点 / 线路命中即选中;空白返回 null\nview.updateOptions({ channelSelectMode: 'channel' })\nview.selectAt(event.clientX, event.clientY) // 命中任一回线 = 选中整条通道\n","from":"header"}] },
103
+ { name: "selectAt", kind: "method", params: "clientX / clientY 浏览器客户端坐标;按普通单击的命中与通道摊开规则立即选中,空白 / 聚合圆返回 null、不改选中,不派发 onClick / onDblClick。每次成功命中都发 onSelect,选中集实际变化时再发 onSelectChange(cause: 'api',顺序 onSelect → onSelectChange);通道回线载荷带当前 group / 完整 groups,多通道看 channelContexts。已知 id 时优先 selectFeatures", returns: "GisViewSelectPayload | null", doc: "只有客户端坐标时用它像普通单击一样立即选中命中要素:节点 / 线路直接选中,通道回线按 channelSelectMode 摊开;空白或聚合圆返回 null 且不改选中。不等待 selectTrigger: 'dblClick',也不派发 onClick / onDblClick;每次成功命中都发 onSelect,选中集实际变化时再发 onSelectChange,顺序 onSelect → onSelectChange(cause: 'api')。选中通道回线时外层带当前 group / 完整 groups / 本次选中的 lineIndexes / lineIds,多通道看 channelContexts;items[] 不挂通道字段。已知节点 / 线路 id 时直接用 selectFeatures,见「按坐标单击选中(selectAt)」", examples: [{"group":"选中、复制与合并","code":"// selectAt(clientX, clientY):每次命中发 onSelect,集合变化时再发 onSelectChange(不动指针、不派发 onClick);已知 id 直接用 selectFeatures\nview.selectAt(event.clientX, event.clientY) // 节点 / 线路命中即选中;空白返回 null\nview.updateOptions({ channelSelectMode: 'channel' })\nview.selectAt(event.clientX, event.clientY) // 命中任一回线 = 选中整条通道\n","from":"header"}] },
104
104
  { name: "hoverAt", kind: "method", params: "clientX / clientY 客户端坐标", returns: "GisViewHoverPayload | null", doc: "程序化悬停:hoverAt 让指针「停」到某个客户端坐标上(悬停高亮、光标与 onHover 一起更新,返回与 onHover 载荷同一份读数,空白处 null;不走悬停阈值,也不用先订阅事件)、clearHover() 撤掉并补发一条 onHover(null)、getHover() 随时读当前这一份 —— 外部列表 / 表格 hover 到某一行时联动高亮图上的要素用它", examples: [{"group":"坐标、拾取与读数","code":"// hoverAt(clientX, clientY) / clearHover() / getHover():程序化悬停 / 撤掉 / 读当前那一份\nview.hoverAt(event.clientX, event.clientY)\nview.clearHover()\nconst hover = view.getHover()\n","from":"header"}] },
105
105
  { name: "clearHover", kind: "method", params: "无", returns: "null", doc: "程序化悬停:hoverAt 让指针「停」到某个客户端坐标上(悬停高亮、光标与 onHover 一起更新,返回与 onHover 载荷同一份读数,空白处 null;不走悬停阈值,也不用先订阅事件)、clearHover() 撤掉并补发一条 onHover(null)、getHover() 随时读当前这一份 —— 外部列表 / 表格 hover 到某一行时联动高亮图上的要素用它", examples: [{"group":"坐标、拾取与读数","code":"// hoverAt(clientX, clientY) / clearHover() / getHover():程序化悬停 / 撤掉 / 读当前那一份\nview.hoverAt(event.clientX, event.clientY)\nview.clearHover()\nconst hover = view.getHover()\n","from":"header"}] },
106
106
  { name: "getHover", kind: "method", params: "无", returns: "GisViewHoverPayload | null", doc: "程序化悬停:hoverAt 让指针「停」到某个客户端坐标上(悬停高亮、光标与 onHover 一起更新,返回与 onHover 载荷同一份读数,空白处 null;不走悬停阈值,也不用先订阅事件)、clearHover() 撤掉并补发一条 onHover(null)、getHover() 随时读当前这一份 —— 外部列表 / 表格 hover 到某一行时联动高亮图上的要素用它", examples: [{"group":"坐标、拾取与读数","code":"// hoverAt(clientX, clientY) / clearHover() / getHover():程序化悬停 / 撤掉 / 读当前那一份\nview.hoverAt(event.clientX, event.clientY)\nview.clearHover()\nconst hover = view.getHover()\n","from":"header"}] },
@@ -138,18 +138,19 @@ export const API_SECTIONS = [
138
138
  {
139
139
  title: "选中、复制与合并",
140
140
  items: [
141
- { name: "selectFeatures", kind: "method", params: "refs: GisViewSelectionRefs 要选中的东西:节点 / 线路 id、id 数组、{ type, index }、通道写法({ type: 'channel', channelId | channelIndex },可再给 groupIds / lineIds / lineIndexes 取子集)、分组写法({ type: 'group', channelId?, groupId },groupId 全局唯一时可省略通道)、拾取载荷 / 搜索命中,传 null / 空数组等于清空一份新的选中集。认不出来的条目逐条跳过;options?: GisViewSelectOptions 只有 mode:'replace' 缺省换一份、'add' 追加、'toggle' 在就取消 / 不在就选上、'remove' 去掉", returns: "this(选中真的变了才按 onSelect → onSelectChange 发,cause 为 api;载荷带通道上下文)", doc: "接口选中(换一份选中集;selectFeatures(null) / 空数组就是清空):refs 认节点 / 线路 id、id 数组、{ type, index }、通道写法({ type: 'channel', channelId } / { channelId, groupId } / { channelId, lineIds })、分组写法({ type: 'group', channelId?, groupId },全局唯一 groupId 可省略通道)以及拾取载荷与搜索命中;options.mode 给 'replace'(缺省,换一份)/ 'add' / 'toggle'(在就取消、不在就选上)/ 'remove'。会同步选中样式与读数;选中集实际变化时按 onSelect → onSelectChange 派发(cause: 'api'),载荷带同一份通道上下文(单通道摊平,多通道看 channelContexts),不发 onClick / onDblClick,见「接口选中(selectFeatures)与读回选中」", examples: [{"group":"选中、复制与合并","code":"// selectFeatures(refs, options?):接口选中(与点击选中走同一份 state;实际变化时按 onSelect → onSelectChange 发,载荷带通道上下文)\nview.selectFeatures('a') // 选中一个\nview.selectFeatures(['a', 'b']) // 一批\nview.selectFeatures('a', { mode: 'add' }) // mode:replace(缺省)/ add / toggle / remove\nview.selectFeatures([{ type: 'line', index: 0 }], { mode: 'toggle' })\nview.selectFeatures({ type: 'channel', channelId: 'c1' }) // 整条通道的全部回线\nview.selectFeatures({ type: 'group', channelId: 'c1', groupId: 'c1::2' })\nview.selectFeatures({ type: 'group', groupId: 'global-group' }) // groupId 全局唯一时可省略通道\nview.selectFeatures(null) // 清空(空数组同义)\n","from":"header"}] },
141
+ { name: "selectFeatures", kind: "method", params: "refs: GisViewSelectionRefs 要选中的东西:节点 / 线路 id、id 数组、{ type, index }、通道写法({ type: 'channel', channelId | channelIndex },可再给 groupIds / lineIds / lineIndexes 取子集)、分组写法({ type: 'group', channelId?, groupId },groupId 全局唯一时可省略通道)、拾取载荷 / 搜索命中,传 null / 空数组等于清空一份新的选中集。认不出来的条目逐条跳过;options?: GisViewSelectOptions 只有 mode:'replace' 缺省换一份、'add' 追加、'toggle' 在就取消 / 不在就选上、'remove' 去掉", returns: "this(选中真的变了才按 onSelect → onSelectChange 发,cause 为 api;载荷带通道上下文)", doc: "接口选中(换一份选中集;selectFeatures(null) / 空数组就是清空):refs 认节点 / 线路 id、id 数组、{ type, index }、通道写法({ type: 'channel', channelId } / { channelId, groupId } / { channelId, lineIds })、分组写法({ type: 'group', channelId?, groupId },全局唯一 groupId 可省略通道)以及拾取载荷与搜索命中;options.mode 给 'replace'(缺省,换一份)/ 'add' / 'toggle'(在就取消、不在就选上)/ 'remove'。会同步选中样式与读数;选中集实际变化时按 onSelect → onSelectChange 派发(cause: 'api'),载荷外层带同一份通道上下文(单通道摊平,多通道看 channelContexts),items[] 不挂通道字段,不发 onClick / onDblClick,见「接口选中(selectFeatures)与读回选中」", examples: [{"group":"选中、复制与合并","code":"// selectFeatures(refs, options?):接口选中(与点击选中走同一份 state;实际变化时按 onSelect → onSelectChange 发,载荷带通道上下文)\nview.selectFeatures('a') // 选中一个\nview.selectFeatures(['a', 'b']) // 一批\nview.selectFeatures('a', { mode: 'add' }) // mode:replace(缺省)/ add / toggle / remove\nview.selectFeatures([{ type: 'line', index: 0 }], { mode: 'toggle' })\nview.selectFeatures({ type: 'channel', channelId: 'c1' }) // 整条通道的全部回线\nview.selectFeatures({ type: 'group', channelId: 'c1', groupId: 'c1::2' })\nview.selectFeatures({ type: 'group', groupId: 'global-group' }) // groupId 全局唯一时可省略通道\nview.selectFeatures(null) // 清空(空数组同义)\n","from":"header"}] },
142
142
  { name: "selectWaypoint", kind: "method", params: "lineRef 线路 id、lines 下标或 { lineIndex, pathIndex } / { lineId, pathIndex } 对象;pathIndex?: number 必须是 getWaypoints() 给出的可编辑中间拐点下标(首尾端点不算,通常直接从读数里取);options?: GisViewSelectOptions 的 mode 决定所在线路怎么并进当前选中集(缺省 replace)。有效拐点会同时选中线路;线路不存在 / 下标非法 / 拐点不可见时只清拐点选中,不猜位置;传 null 也只清拐点,不改要素选中集", returns: "this", doc: "代码选中一个拐点:selectWaypoint('line-1', 2) / { lineIndex, pathIndex } / { lineId, pathIndex },并同时选中所在线路;有效下标从 getWaypoints() 取。传 null 只取消拐点选中、不改要素选中集,见「代码选中拐点(selectWaypoint)」", examples: [{"group":"选中、复制与合并","code":"// selectWaypoint(lineRef, pathIndex? / { lineIndex | lineId, pathIndex }, options?):选中拐点并带选线路\nview.selectWaypoint('l1', 2) // pathIndex 从 getWaypoints() 取\nview.selectWaypoint({ lineIndex: 0, pathIndex: 1 }, { mode: 'add' })\nview.selectWaypoint({ lineId: 'l1', pathIndex: 2 })\nview.selectWaypoint(null) // 只取消拐点选中,不改要素选中集\n","from":"header"}] },
143
143
  { name: "deselect", kind: "method", params: "refs?: GisViewSelectionRefs 要取消的东西(写法与 selectFeatures 一致:节点 / 通道 / 线路 / 混合一批);不传 / null 等于取消全部(= selectFeatures(null)),空数组是「没有目标」什么都不做", returns: "this(选中真的变了才按 onSelect → onSelectChange 发,cause 为 api;清空时 onSelect 载荷为 null)", doc: "取消选中:写法与 selectFeatures 完全一样,把这些从当前选中集里去掉、其余选中原样留着;不传 / null 等于取消全部(也就是 selectFeatures(null)),空数组是「没有目标」什么都不做。改变选中集时按 onSelect → onSelectChange 派发(cause: 'api';清空时 onSelect 载荷为 null),见「取消选中(deselect / selectFeatures(null))」", examples: [{"group":"选中、复制与合并","code":"// deselect(refs?):取消选中(写法与 selectFeatures 一致;不传 / null 全清,空数组什么都不做)\nview.deselect('a')\nview.deselect(['a', 'b'])\nview.deselect()\n","from":"header"}] },
144
- { name: "getSelections", kind: "method", params: "无", returns: "GisViewPickResult[]", doc: "读回选中结果 [{ type, index, item, … }](按选中顺序):要 id 列表 / 个数 / 包围盒看 getSelectionData()", examples: [{"group":"选中、复制与合并","code":"// getSelections() / getSelectionData() / isSelected(ref):读回选中\nconst picks = view.getSelections()\nconst selection = view.getSelectionData() // { ids, count, points, lines, channels, bounds, … }\nconst selected = view.isSelected('a')\n","from":"header"}] },
145
- { name: "isSelected", kind: "method", params: "ref: GisViewFeatureRef | GisViewFeatureKey | GisViewSelectionRefObject 要素定位(id / 下标 / { id } / { index } / { type, index } / { item })", returns: "boolean", doc: "这个要素在不在当前选中集里:isSelected('node-0')(id)/ isSelected({ type: 'line', index: 12 })(也可以只写下标,按 id 认不出来时当下标),返回 true / false。面板上高亮「当前这一行」时不必自己比对 getSelections();选中集变化时缓存一份键集合(见「接口选中」),十万级选中下这一问也是常数级", examples: [{"group":"选中、复制与合并","code":"// getSelections() / getSelectionData() / isSelected(ref):读回选中\nconst picks = view.getSelections()\nconst selection = view.getSelectionData() // { ids, count, points, lines, channels, bounds, … }\nconst selected = view.isSelected('a')\n","from":"header"}] },
146
- { name: "getSelectionData", kind: "method", params: "无", returns: "GisViewSelectionData:{ ids, count, pointCount, lineCount, channelCount, points, lines, channels, items, pointIndexes, lineIndexes, bounds }(bounds 是世界包围盒,空选中时 null)", doc: "选中集一次取全:读数(ids / count / pointCount / lineCount / channelCount)加要素本身(points / lines / channels(按通道去重,与 getData() 同一批对象)),另有 items(与 getSelections() 同一份)、pointIndexes / lineIndexes 与 bounds(世界包围盒,空选中时 null),见「接口选中(selectFeatures)与读回选中」", examples: [{"group":"选中、复制与合并","code":"// getSelections() / getSelectionData() / isSelected(ref):读回选中\nconst picks = view.getSelections()\nconst selection = view.getSelectionData() // { ids, count, points, lines, channels, bounds, … }\nconst selected = view.isSelected('a')\n","from":"header"}] },
144
+ { name: "getSelections", kind: "method", params: "无", returns: "GisViewPickResult[]:选中项原始读数;通道回线不带 channelId / channelIndex / groupId / groups,单条归属用 getLineChannel(index)", doc: "读回选中结果 [{ type, index, item, … }](按选中顺序):通道回线不带通道字段,单条归属用 getLineChannel(index);要 id 列表 / 个数 / 包围盒看 getSelectionData()", examples: [{"group":"选中、复制与合并","code":"// getSelections() / getSelectionInfo() / getSelectionData() / isSelected(ref):读回选中\nconst picks = view.getSelections()\nconst info = view.getSelectionInfo() // { ids, count, points, lines, channels }\nconst selection = view.getSelectionData() // { ids, count, points, lines, channels, bounds, … }\nconst selected = view.isSelected('a')\n","from":"header"}] },
145
+ { name: "getSelectionInfo", kind: "method", params: "无", returns: "GisViewSelectionInfo:{ ids, count, points, lines, channels },通道回线按通道算一个;与 onSelectChange 顶层同口径并按键集合引用缓存", doc: "选中集轻量计数 { ids, count, points, lines, channels }(通道回线按通道算一个),与 onSelectChange 顶层同名字段同口径;按选中集引用缓存,十万级选中下也只是一次读取。指针 / 点击 / 悬停载荷不再重复挂 selection,状态栏这类只要计数的读数用它;要要素对象或包围盒再用 getSelectionData()", examples: [{"group":"选中、复制与合并","code":"// getSelections() / getSelectionInfo() / getSelectionData() / isSelected(ref):读回选中\nconst picks = view.getSelections()\nconst info = view.getSelectionInfo() // { ids, count, points, lines, channels }\nconst selection = view.getSelectionData() // { ids, count, points, lines, channels, bounds, … }\nconst selected = view.isSelected('a')\n","from":"header"}] },
146
+ { name: "isSelected", kind: "method", params: "ref: GisViewFeatureRef | GisViewFeatureKey | GisViewSelectionRefObject 要素定位(id / 下标 / { id } / { index } / { type, index } / { item })", returns: "boolean", doc: "这个要素在不在当前选中集里:isSelected('node-0')(id)/ isSelected({ type: 'line', index: 12 })(也可以只写下标,按 id 认不出来时当下标),返回 true / false。面板上高亮「当前这一行」时不必自己比对 getSelections();选中集变化时缓存一份键集合(见「接口选中」),十万级选中下这一问也是常数级", examples: [{"group":"选中、复制与合并","code":"// getSelections() / getSelectionInfo() / getSelectionData() / isSelected(ref):读回选中\nconst picks = view.getSelections()\nconst info = view.getSelectionInfo() // { ids, count, points, lines, channels }\nconst selection = view.getSelectionData() // { ids, count, points, lines, channels, bounds, … }\nconst selected = view.isSelected('a')\n","from":"header"}] },
147
+ { name: "getSelectionData", kind: "method", params: "无", returns: "GisViewSelectionData:{ ids, count, pointCount, lineCount, channelCount, points, lines, channels, items, pointIndexes, lineIndexes, bounds }(bounds 是世界包围盒,空选中时 null;items 的线路不带通道字段,channels 是去重后的所属通道)", doc: "选中集一次取全:读数(ids / count / pointCount / lineCount / channelCount)加要素本身(points / lines / channels(按通道去重,与 getData() 同一批对象)),另有 items(与 getSelections() 同一份,通道回线不带通道字段)、pointIndexes / lineIndexes 与 bounds(世界包围盒,空选中时 null),见「接口选中(selectFeatures)与读回选中」", examples: [{"group":"选中、复制与合并","code":"// getSelections() / getSelectionInfo() / getSelectionData() / isSelected(ref):读回选中\nconst picks = view.getSelections()\nconst info = view.getSelectionInfo() // { ids, count, points, lines, channels }\nconst selection = view.getSelectionData() // { ids, count, points, lines, channels, bounds, … }\nconst selected = view.isSelected('a')\n","from":"header"}] },
147
148
  { name: "getCascadeHighlight", kind: "method", params: "无", returns: "GisViewCascadeHighlightInfo | null:当前点击级联高亮的来源、范围与节点 / 线路读数 { origin, scope, points, lines, pointIndexes, lineIndexes },不改变选中集", doc: "当前点击级联高亮的读数:{ origin, scope, points, lines, pointIndexes, lineIndexes };没有高亮或 cascadeHighlight 关着时返回 null。它只读、不改选中集,见「点击级联高亮」", examples: [{"group":"选中、复制与合并","code":"// getCascadeHighlight():读点击级联高亮(与选中集分开;没有高亮时为 null)\nconst cascade = view.getCascadeHighlight() // { origin, scope, points, lines, pointIndexes, lineIndexes }\nview.updateOptions({\n cascadeHighlight: {\n scope: 'points',\n triggerOnClick: true,\n triggerOnMultiSelect: false,\n triggerOnBoxSelect: true,\n clearOnEmptyClick: true\n }\n})\nview.on('cascadeHighlight', payload => console.log(payload?.pointIndexes, payload?.lineIndexes))\n","from":"header"}] },
148
- { name: "selectInBox", kind: "method", params: "box: GisViewRect 画布 CSS 像素矩形 { x, y, width, height }(与手动框选同一套坐标);options?: GisViewSelectInBoxOptions:selectionKinds 临时覆盖这一次保留哪几类('point' / 'line' / 'channel'、数组、'all' / true / false / null,不写沿用全局 selectionKinds),originalEvent 原样带进 onSelect;第二段也兼容旧的 Event 写法。空框不会选中任何要素", returns: "this(每次框选都发 onSelect,空框为 null;集合变化时再发 onSelectChange,顺序 onSelect → onSelectChange,cause 为 boxSelect)", doc: "按画布 CSS 像素矩形框选,等价于 Shift + 拖动(跟随 shortcuts.boxSelect);options.selectionKinds 可只覆盖这一次的节点 / 线路 / 通道类型过滤。每次框选操作都发 onSelect(空框的载荷为 null),选中集实际变化时再发 onSelectChange,顺序 onSelect → onSelectChange(cause: 'boxSelect');通道回线的分组字段与鼠标框选同一份", examples: [{"group":"选中、复制与合并","code":"// selectInBox(box, options?):用矩形框选(画布 CSS 像素,与手动框选同一套判定)\nview.selectInBox({ x: 40, y: 40, width: 320, height: 200 })\nview.selectInBox({ x: 40, y: 40, width: 320, height: 200 }, { selectionKinds: ['line', 'channel'] })\n","from":"header"}] },
149
+ { name: "selectInBox", kind: "method", params: "box: GisViewRect 画布 CSS 像素矩形 { x, y, width, height }(与手动框选同一套坐标);options?: GisViewSelectInBoxOptions:selectionKinds 临时覆盖这一次保留哪几类('point' / 'line' / 'channel'、数组、'all' / true / false / null,不写沿用全局 selectionKinds),originalEvent 原样带进 onSelect。空框不会选中任何要素", returns: "this(每次框选都发 onSelect,空框为 null;集合变化时再发 onSelectChange,顺序 onSelect → onSelectChange,cause 为 boxSelect)", doc: "按画布 CSS 像素矩形框选,等价于 Shift + 拖动(跟随 shortcuts.boxSelect);options.selectionKinds 可只覆盖这一次的节点 / 线路 / 通道类型过滤。每次框选操作都发 onSelect(空框的载荷为 null),选中集实际变化时再发 onSelectChange,顺序 onSelect → onSelectChange(cause: 'boxSelect');通道回线的分组字段与鼠标框选同一份", examples: [{"group":"选中、复制与合并","code":"// selectInBox(box, options?):用矩形框选(画布 CSS 像素,与手动框选同一套判定)\nview.selectInBox({ x: 40, y: 40, width: 320, height: 200 })\nview.selectInBox({ x: 40, y: 40, width: 320, height: 200 }, { selectionKinds: ['line', 'channel'] })\n","from":"header"}] },
149
150
  { name: "beginCopyPoint", kind: "method", params: "options: GisViewCopyStartOptions:id / index / item 三选一给来源,都不给就进入「下一次点选节点」模式;coordinate / screen 是落点(缺省按 copy.offset);follow 让预览跟指针;autoCommit 覆盖 copy.autoCommit;commit: true 出预览即落地;via / originalEvent 原样带回。必须配置 copy(enabled 不为 false),否则返回 null。预览节点插在 points 末尾且不带 id,不会把绑定源 id 的线路 / 通道带过去", returns: "GisViewPointCopyPayload | null:phase: 'start' 的预览载荷;来源认不出来时返回 null", doc: "开始复制一个节点:给 id / index 直接出预览(commit: true 出预览即落地,follow: true 跟随鼠标,autoCommit: false 停在预览态),不给来源就是「先点按钮再选节点」模式,见「节点复制」", examples: [{"group":"选中、复制与合并","code":"// beginCopyPoint(options) / getPointCopy() / updateCopyPoint(patch) / commitPointCopy() / cancelPointCopy()\nview.updateOptions({ copy: { enabled: true, autoCommit: false } }) // API 复制入口要先开 copy 配置\nview.beginCopyPoint({ id: 'a', coordinate: [40, 30] }) // 出预览并钉在世界坐标 [40, 30]\nview.beginCopyPoint({ id: 'a', follow: true }) // 预览跟着指针走(autoCommit 缺省停住)\nview.beginCopyPoint() // 先点按钮再选节点(点中下一个节点当来源)\nview.updateCopyPoint({ name: '新节点' }) // 字段级改预览节点(落点仍由 coordinate / 指针决定)\nconst copyEnd = view.commitPointCopy() // 落地:返回 end 载荷;没有预览时为 null\nview.cancelPointCopy() // 取消:预览撤掉,数据与历史不动\nconst copyState = view.getPointCopy() // 当前预览 / 选点态(没有就是 null)\n","from":"header"}] },
150
151
  { name: "commitPointCopy", kind: "method", params: "无:把当前复制预览落地,先发 commit、落地后发 end;commit 阶段载荷里的 cancel() 可以拦住这次复制", returns: "GisViewPointCopyPayload | null:成功的 end 载荷;没有预览或已被取消时为 null", doc: "落地 / 取消预览中的复制(返回 end / cancel 载荷)", examples: [{"group":"选中、复制与合并","code":"// beginCopyPoint(options) / getPointCopy() / updateCopyPoint(patch) / commitPointCopy() / cancelPointCopy()\nview.updateOptions({ copy: { enabled: true, autoCommit: false } }) // API 复制入口要先开 copy 配置\nview.beginCopyPoint({ id: 'a', coordinate: [40, 30] }) // 出预览并钉在世界坐标 [40, 30]\nview.beginCopyPoint({ id: 'a', follow: true }) // 预览跟着指针走(autoCommit 缺省停住)\nview.beginCopyPoint() // 先点按钮再选节点(点中下一个节点当来源)\nview.updateCopyPoint({ name: '新节点' }) // 字段级改预览节点(落点仍由 coordinate / 指针决定)\nconst copyEnd = view.commitPointCopy() // 落地:返回 end 载荷;没有预览时为 null\nview.cancelPointCopy() // 取消:预览撤掉,数据与历史不动\nconst copyState = view.getPointCopy() // 当前预览 / 选点态(没有就是 null)\n","from":"header"}] },
151
152
  { name: "cancelPointCopy", kind: "method", params: "无:撤掉预览并补一条 cancel,数据与历史不动", returns: "GisViewPointCopyPayload | null:取消载荷;没有预览时为 null", doc: "落地 / 取消预览中的复制(返回 end / cancel 载荷)", examples: [{"group":"选中、复制与合并","code":"// beginCopyPoint(options) / getPointCopy() / updateCopyPoint(patch) / commitPointCopy() / cancelPointCopy()\nview.updateOptions({ copy: { enabled: true, autoCommit: false } }) // API 复制入口要先开 copy 配置\nview.beginCopyPoint({ id: 'a', coordinate: [40, 30] }) // 出预览并钉在世界坐标 [40, 30]\nview.beginCopyPoint({ id: 'a', follow: true }) // 预览跟着指针走(autoCommit 缺省停住)\nview.beginCopyPoint() // 先点按钮再选节点(点中下一个节点当来源)\nview.updateCopyPoint({ name: '新节点' }) // 字段级改预览节点(落点仍由 coordinate / 指针决定)\nconst copyEnd = view.commitPointCopy() // 落地:返回 end 载荷;没有预览时为 null\nview.cancelPointCopy() // 取消:预览撤掉,数据与历史不动\nconst copyState = view.getPointCopy() // 当前预览 / 选点态(没有就是 null)\n","from":"header"}] },
152
- { name: "updateCopyPoint", kind: "method", params: "patch: Partial<GisViewPoint> 预览节点的字段级合并(名称 / 样式等;x / y 仍以当前落点为准),只重解析这一个预览节点", returns: "GisViewPoint | null:改完的预览节点;没有预览或 patch 不是对象时 null", doc: "改预览节点的数据(等价于回调里的 payload.update / setItem),返回改完的节点", examples: [{"group":"选中、复制与合并","code":"// beginCopyPoint(options) / getPointCopy() / updateCopyPoint(patch) / commitPointCopy() / cancelPointCopy()\nview.updateOptions({ copy: { enabled: true, autoCommit: false } }) // API 复制入口要先开 copy 配置\nview.beginCopyPoint({ id: 'a', coordinate: [40, 30] }) // 出预览并钉在世界坐标 [40, 30]\nview.beginCopyPoint({ id: 'a', follow: true }) // 预览跟着指针走(autoCommit 缺省停住)\nview.beginCopyPoint() // 先点按钮再选节点(点中下一个节点当来源)\nview.updateCopyPoint({ name: '新节点' }) // 字段级改预览节点(落点仍由 coordinate / 指针决定)\nconst copyEnd = view.commitPointCopy() // 落地:返回 end 载荷;没有预览时为 null\nview.cancelPointCopy() // 取消:预览撤掉,数据与历史不动\nconst copyState = view.getPointCopy() // 当前预览 / 选点态(没有就是 null)\n","from":"header"}] },
153
+ { name: "updateCopyPoint", kind: "method", params: "patch: Partial<GisViewPoint> 预览节点的字段级合并(名称 / 样式等;x / y 仍以当前落点为准),只重解析这一个预览节点", returns: "GisViewPoint | null:改完的预览节点;没有预览或 patch 不是对象时 null", doc: "改预览节点的数据(等价于回调里的 payload.update),返回改完的节点", examples: [{"group":"选中、复制与合并","code":"// beginCopyPoint(options) / getPointCopy() / updateCopyPoint(patch) / commitPointCopy() / cancelPointCopy()\nview.updateOptions({ copy: { enabled: true, autoCommit: false } }) // API 复制入口要先开 copy 配置\nview.beginCopyPoint({ id: 'a', coordinate: [40, 30] }) // 出预览并钉在世界坐标 [40, 30]\nview.beginCopyPoint({ id: 'a', follow: true }) // 预览跟着指针走(autoCommit 缺省停住)\nview.beginCopyPoint() // 先点按钮再选节点(点中下一个节点当来源)\nview.updateCopyPoint({ name: '新节点' }) // 字段级改预览节点(落点仍由 coordinate / 指针决定)\nconst copyEnd = view.commitPointCopy() // 落地:返回 end 载荷;没有预览时为 null\nview.cancelPointCopy() // 取消:预览撤掉,数据与历史不动\nconst copyState = view.getPointCopy() // 当前预览 / 选点态(没有就是 null)\n","from":"header"}] },
153
154
  { name: "getPointCopy", kind: "method", params: "无", returns: "GisViewPointCopyState | null", doc: "当前复制状态:预览中是 { phase: 'preview', index, item, source, … },等选点时是 { phase: 'pick' },没有就 null", examples: [{"group":"选中、复制与合并","code":"// beginCopyPoint(options) / getPointCopy() / updateCopyPoint(patch) / commitPointCopy() / cancelPointCopy()\nview.updateOptions({ copy: { enabled: true, autoCommit: false } }) // API 复制入口要先开 copy 配置\nview.beginCopyPoint({ id: 'a', coordinate: [40, 30] }) // 出预览并钉在世界坐标 [40, 30]\nview.beginCopyPoint({ id: 'a', follow: true }) // 预览跟着指针走(autoCommit 缺省停住)\nview.beginCopyPoint() // 先点按钮再选节点(点中下一个节点当来源)\nview.updateCopyPoint({ name: '新节点' }) // 字段级改预览节点(落点仍由 coordinate / 指针决定)\nconst copyEnd = view.commitPointCopy() // 落地:返回 end 载荷;没有预览时为 null\nview.cancelPointCopy() // 取消:预览撤掉,数据与历史不动\nconst copyState = view.getPointCopy() // 当前预览 / 选点态(没有就是 null)\n","from":"header"}] },
154
155
  { name: "copyPoint", kind: "method", params: "source?: string | number | GisViewCopyStartOptions 复制哪个节点(不给就用选中的 / 光标下的,都没有就进「点一个节点」模式)", returns: "GisViewCopyItem | null", doc: "复制一个节点到它旁边(一步到位):来源给 id / 下标 / 一份节点数据,不给就用选中的节点(再没有用光标下的);返回新节点 { index, item }", examples: [{"group":"选中、复制与合并","code":"// copyPoint(source?) / copyPoints(sources?, options?) / copySelectedPoints(options?) / copyPointToClipboard(sources?, options?) / copyPointsToClipboard(sources?, options?) / pastePoint(options?) / pastePoints(options?) / getPointClipboard()\nconst copyItem = view.copyPoint('a') // 一步到位,落在原位置旁边\nconst batchCopy = view.copyPoints(['a', 'b'], { select: true }) // 只复制这两个节点(不带未选中的线路 / 通道)\nconst selectedCopy = view.copySelectedPoints({ coordinate: [120, 80] }) // 复制当前选中集:节点 + 选中线路 / 通道\nview.copySelectedPoints({ withLines: true, withChannels: true }) // 恢复旧口径:来源内部线路 / 通道全带\nview.copyPoints(['a', 'b'], { lineIndexes: [0], channelIndexes: [1] }) // 再显式点名线路 / 通道\nview.copyPointToClipboard('a') // 收进剪贴板(默认整个选中集,Ctrl+C 的接口写法)\nview.copyPointsToClipboard(['a', 'b', 'c']) // 只把给定的一批收进剪贴板\nview.pastePoint({ screen: [320, 200] }) // 粘到光标处(Ctrl+V 的接口写法)\nview.pastePoint({ coordinate: [120, 80], offset: [40, 0] })\nconst pastedBatch = view.pastePoints({ coordinate: [160, 100] }) // 强制整组落地(单节点也走批量路)\nconsole.log(batchCopy.count, batchCopy.lines.length, selectedCopy.indexes, pastedBatch?.count)\nconst clipboard = view.getPointClipboard()\n","from":"header"}] },
155
156
  { name: "copyPoints", kind: "method", params: "sources?: GisViewPointRef | GisViewPointRef[] 要复制的节点(不给就取当前选中集;copySelectedPoints 不回落到光标);显式给 sources 时只复制这批节点,不再顺带选中的线路 / 通道。options: GisViewCopyPointsOptions:coordinate / screen(落点,缺省按 copy.offset)、offset(屏幕像素偏移)、lineIndexes / channelIndexes(显式点名选中的线路 / 通道)、withLines / withChannels(缺省 false:只复制选中要素;写 true 恢复「来源内部全带」的旧口径,写 false 连选中线路 / 通道也不带)、select(复制后选中新节点)、label / via / originalEvent", returns: "GisViewBatchCopyPayload", doc: "批量复制(不经过剪贴板):不给 sources 时复制当前选中集(框选之后一次复制整组);默认只带选中要素 —— 选中线路复制线路、选中通道复制整条通道及回线,并自动补所需端点(supportSources / supportItems / supportIndexes)。显式 sources 时只复制这批节点;lineIndexes / channelIndexes 可再点名,withLines: true / withChannels: true 才恢复「来源内部全带」的旧口径(写 false 则连选中线路 / 通道也不带)。落点按 coordinate / screen 或 copy.offset,select: true 复制后选中新节点。返回 phase: 'batch' 载荷(sources / items / indexes / selectedPointCount / lines / channels / count / revert()),复制不了时 ok: false + reason", examples: [{"group":"选中、复制与合并","code":"// copyPoint(source?) / copyPoints(sources?, options?) / copySelectedPoints(options?) / copyPointToClipboard(sources?, options?) / copyPointsToClipboard(sources?, options?) / pastePoint(options?) / pastePoints(options?) / getPointClipboard()\nconst copyItem = view.copyPoint('a') // 一步到位,落在原位置旁边\nconst batchCopy = view.copyPoints(['a', 'b'], { select: true }) // 只复制这两个节点(不带未选中的线路 / 通道)\nconst selectedCopy = view.copySelectedPoints({ coordinate: [120, 80] }) // 复制当前选中集:节点 + 选中线路 / 通道\nview.copySelectedPoints({ withLines: true, withChannels: true }) // 恢复旧口径:来源内部线路 / 通道全带\nview.copyPoints(['a', 'b'], { lineIndexes: [0], channelIndexes: [1] }) // 再显式点名线路 / 通道\nview.copyPointToClipboard('a') // 收进剪贴板(默认整个选中集,Ctrl+C 的接口写法)\nview.copyPointsToClipboard(['a', 'b', 'c']) // 只把给定的一批收进剪贴板\nview.pastePoint({ screen: [320, 200] }) // 粘到光标处(Ctrl+V 的接口写法)\nview.pastePoint({ coordinate: [120, 80], offset: [40, 0] })\nconst pastedBatch = view.pastePoints({ coordinate: [160, 100] }) // 强制整组落地(单节点也走批量路)\nconsole.log(batchCopy.count, batchCopy.lines.length, selectedCopy.indexes, pastedBatch?.count)\nconst clipboard = view.getPointClipboard()\n","from":"header"}] },
@@ -161,7 +162,7 @@ export const API_SECTIONS = [
161
162
  { name: "getPointClipboard", kind: "method", params: "无", returns: "GisViewClipboardItem | null", doc: "剪贴板:copyPointToClipboard 默认收当前选中的全部节点(只有没选中才回落到光标下的那个),copyPointsToClipboard 只认给定 / 选中的一批;Ctrl+V 时单节点且没有内部线路 / 通道仍走原预览,多节点或带线路 / 通道走整组落地,pastePoints 强制整组(单节点也走批量载荷)。getPointClipboard() 读 count / sources / items / lines / channels / anchor 快照,见「节点复制」", examples: [{"group":"选中、复制与合并","code":"// copyPoint(source?) / copyPoints(sources?, options?) / copySelectedPoints(options?) / copyPointToClipboard(sources?, options?) / copyPointsToClipboard(sources?, options?) / pastePoint(options?) / pastePoints(options?) / getPointClipboard()\nconst copyItem = view.copyPoint('a') // 一步到位,落在原位置旁边\nconst batchCopy = view.copyPoints(['a', 'b'], { select: true }) // 只复制这两个节点(不带未选中的线路 / 通道)\nconst selectedCopy = view.copySelectedPoints({ coordinate: [120, 80] }) // 复制当前选中集:节点 + 选中线路 / 通道\nview.copySelectedPoints({ withLines: true, withChannels: true }) // 恢复旧口径:来源内部线路 / 通道全带\nview.copyPoints(['a', 'b'], { lineIndexes: [0], channelIndexes: [1] }) // 再显式点名线路 / 通道\nview.copyPointToClipboard('a') // 收进剪贴板(默认整个选中集,Ctrl+C 的接口写法)\nview.copyPointsToClipboard(['a', 'b', 'c']) // 只把给定的一批收进剪贴板\nview.pastePoint({ screen: [320, 200] }) // 粘到光标处(Ctrl+V 的接口写法)\nview.pastePoint({ coordinate: [120, 80], offset: [40, 0] })\nconst pastedBatch = view.pastePoints({ coordinate: [160, 100] }) // 强制整组落地(单节点也走批量路)\nconsole.log(batchCopy.count, batchCopy.lines.length, selectedCopy.indexes, pastedBatch?.count)\nconst clipboard = view.getPointClipboard()\n","from":"header"}] },
162
163
  { name: "mergePoints", kind: "method", params: "source: GisViewPointRef 被并入的节点(并完就没了)、target: GisViewPointRef 接收的节点、options?: { originalEvent?: Event | null; item?: GisViewPoint }:item 可以给一份已经改好的合并结果(确认阶段改过 payload.item 之后就是它)", returns: "GisViewMergeOutcome", doc: "把来源节点并进目标节点(接口调用,不做确认):来源 / 目标收 id、下标或 { id } / { index },节点数据按 merge.createItem 合并(缺省目标优先、尺寸取双方最大、图标优先目标),改线路绑定、删自环 / 零长线 / 自环通道、把撞成同一条的通道合成一条,返回 { ok, item, linesChanged, removedLines, removedChannels, mergedChannels };并不了返回 ok: false 并另发一条失败事件,见「节点合并」", examples: [{"group":"选中、复制与合并","code":"// mergePoints(source, target, options?):把 source 并进 target(接口调用,不做确认)\nconst merged = view.mergePoints('a', 'b')\nconst mergedCustom = view.mergePoints('a', 'b', { item: { name: '合并结果', size: 12 } })\n","from":"header"}] },
163
164
  { name: "mergeSelectedPoints", kind: "method", params: "options: GisViewMergeSelectedOptions:target 指定并到哪一个(不给就自动挑绑定线路最多的);sources 直接给来源(不依赖选中集);item 是最终节点补丁或 (context) => 补丁(上下文含 item / merged / source / target / sourceIndex / targetIndex / merge / view;坐标与 id 仍按 target 保留);force: true 越过 mergeable: false;label / originalEvent", returns: "GisViewMergeSelectedOutcome", doc: "框选 / 多选之后一步把选中的节点并到一个目标上(接口调用):target 不指定就自动挑绑定线路最多的那一个(一样多取画得大的、再取下标小的),来源默认是除目标之外的选中节点、也可以用 sources(id / 下标 / { id } / { index },单个或数组)直接给 —— 这时不用先选中;mergeable: false 默认跳过(force: true 越过)、label 改这一步历史的标签。多个来源合成一步历史(数据变更收成一条 batch),返回 { phase: 'mergeSelected', ok, reason, target, merged, skipped, sourcesMerged, linesChanged, removedLines, removedChannels, mergedChannels, channelsChanged },见「节点合并」", examples: [{"group":"选中、复制与合并","code":"// mergeSelectedPoints(options?):框选 / 多选之后一次并完(目标缺省挑绑定线路最多的那个)\nconst mergedAll = view.mergeSelectedPoints()\nview.mergeSelectedPoints({ target: 'b', sources: ['a', 'c'], force: true })\n","from":"header"}] },
164
- { name: "splitPoint", kind: "method", params: "source: GisViewPointRef 要拆的节点;parts: GisViewSplitPointPart[] 至少两份,每份可直接写节点字段或放进 item,并用 lines / channels / channelFrom / channelTo 分配线路与通道端点;options?: GisViewSplitPointOptions:keepSourceId(缺省 true,第一份沿用来源 id)/ label", returns: "GisViewSplitPointOutcome:{ ok, reason, source, items, indexes, pointIds, linesChanged, removedLines, removedLineIds, removedLineIndexes, channelsChanged, removedChannels, removedChannelIds, skipped, changes };changes 可直接作为后端事务输入", doc: "mergePoints 的逆操作:把一个节点拆成两份或更多(第一份接在原下标上、缺省沿用原 id,其余追加到末尾),按每份的 item / 顶层字段生成新节点,按 lines 分配线路、按 channels / channelFrom / channelTo 分配受影响通道的两端。没显式分配的线路跟第一份;没显式分配的通道端点跟它第一条被分配过的成员回线。线路的 from / to 与和来源坐标重合的 start / end / path 顶点改到对应新节点;拆完后的自环 / 零长度线路与两端落到同一份的通道删掉,成员全没了的通道也删掉。整次调用只记一条历史、只发一条 splitPoint 数据变更,返回的 changes 可直接作为后端事务输入;至少两份、坐标必须有效、线路不能重复分配,写错时 ok: false 并在 reason 说明", examples: [{"group":"选中、复制与合并","code":"// splitPoint(source, parts, options?):把一个节点拆成多份(mergePoints 的逆操作)\nconst splitResult = view.splitPoint('a', [\n { x: 0, y: 0, name: '甲站(主)' }, // 第一份沿用原下标 / id\n { x: 80, y: 40, name: '甲站(东)', lines: ['l1'] } // 显式分给第二份;未分配的线路跟第一份\n], { label: '拆分甲站' })\nconsole.log(splitResult.pointIds, splitResult.linesChanged, splitResult.changes)\n","from":"header"}] },
165
+ { name: "splitPoint", kind: "method", params: "source: GisViewPointRef 要拆的节点;parts: GisViewSplitPointPart[] 至少两份,每份可直接写节点字段或放进 item,并用 lines / channels / channelFrom / channelTo 分配线路与通道端点;options?: GisViewSplitPointOptions:keepSourceId(缺省 true,第一份沿用来源 id)/ label / autoLines?: 'first' | 'alternate'(缺省 first 跟第一份;alternate 按「通道成组、独立线路单条」轮流给负载最少的一份,解除合并推荐)", returns: "GisViewSplitPointOutcome:{ ok, reason, source, items, indexes, pointIds, linesChanged, removedLines, removedLineIds, removedLineIndexes, channelsChanged, removedChannels, removedChannelIds, autoLines, autoAssignedLines, lineAssignments, skipped, changes };lineAssignments 每项 { index, pointId, lineIds, lineCount };changes 可直接作为后端事务输入", doc: "mergePoints 的逆操作:把一个节点拆成两份或更多(第一份接在原下标上、缺省沿用原 id,其余追加到末尾),按每份的 item / 顶层字段生成新节点,按 lines 分配线路、按 channels / channelFrom / channelTo 分配受影响通道的两端。没显式分配的线路默认跟第一份;options.autoLines: 'alternate' 时按「同一条通道的成员成组、独立线路单条」为单位,轮流分给负载最少的一份(解除合并推荐,通道成员不会被拆散)。没显式分配的通道端点跟它第一条被分配过的成员回线。线路的 from / to 与和来源坐标重合的 start / end / path 顶点改到对应新节点;拆完后的自环 / 零长度线路与两端落到同一份的通道删掉,成员全没了的通道也删掉。整次调用只记一条历史、只发一条 splitPoint 数据变更,返回 { autoLines, autoAssignedLines, lineAssignments, changes, ... },changes 可直接作为后端事务输入;至少两份、坐标必须有效、线路不能重复分配,写错时 ok: false 并在 reason 说明", examples: [{"group":"选中、复制与合并","code":"// splitPoint(source, parts, options?):把一个节点拆成多份(mergePoints 的逆操作)\nconst splitResult = view.splitPoint('a', [\n { x: 0, y: 0, name: '甲站(主)' }, // 第一份沿用原下标 / id\n { x: 80, y: 40, name: '甲站(东)', lines: ['l1'] } // 显式分给第二份\n], { autoLines: 'alternate', label: '解除合并' })","from":"header"}] },
165
166
  { name: "getPointMerge", kind: "method", params: "无(载荷带 impact:lineCount / linesChanged / removedLines / lineIds / removedLineIds 与通道读数,确认前就能估算影响)", returns: "GisViewPointMergePayload | null", doc: "读 / 裁决 / 收尾当前待确认的合并(把一个节点拖到另一个节点上之后):resolvePointMerge(true, { keep, item }) 推进到 confirm 阶段 —— keep 选保留碰撞目标(缺省)还是拖动的来源,item 改写最终节点的信息与样式,位置 / id 始终取保留端;resolvePointMerge(false) 让拖动的节点回原位。pending / confirm / getPointMerge() 的载荷带 impact(受影响线路条数、具体 lineIds 与通道读数;lineCount 按线路去重,linesChanged / removedLines 与落地结果同口径)。自己接管数据落地时用 commitPointMerge() 收状态、revertPointMerge() 还原(silent: true 不补发 cancel 载荷)", examples: [{"group":"选中、复制与合并","code":"// getPointMerge() / resolvePointMerge(confirmed?, options?) / commitPointMerge() / revertPointMerge()\nconst pendingMerge = view.getPointMerge() // 待确认的那一份载荷(没有就是 null)\nconsole.log(pendingMerge?.impact?.lineCount) // 这次合并会影响几条线路(改写 + 删除,按线路去重)\nconsole.log(pendingMerge?.impact?.lineIds) // 具体是哪几条(没有 id 时是 lines 下标)\nview.resolvePointMerge(true) // 确认(false = 取消)\nview.resolvePointMerge(true, { keep: 'source', item: { name: '合并结果', size: 18 } })\nview.resolvePointMerge(true, { keep: 'target', item: { color: '#2f6fd0', strokeColor: '#ffffff' } })\nview.resolvePointMerge(false) // 取消(拖动的节点回原位,数据不动)\nview.commitPointMerge() // 自己接管数据时收尾\nview.revertPointMerge() // 反悔(silent: true 时不补发 cancel 载荷)\n","from":"header"}] },
166
167
  { name: "resolvePointMerge", kind: "method", params: "confirmed?: boolean(缺省 true = 确认,false = 取消);options?: GisViewPointMergeConfirmOptions:keep 选 'target'(缺省,保留碰撞目标)/ 'source'(保留拖动的来源),item 是最终节点字段补丁(名称 / 分区 / 颜色 / 尺寸 / 图标 / 描边等;位置与 id 始终取保留端)", returns: "GisViewPointMergePayload | null", doc: "读 / 裁决 / 收尾当前待确认的合并(把一个节点拖到另一个节点上之后):resolvePointMerge(true, { keep, item }) 推进到 confirm 阶段 —— keep 选保留碰撞目标(缺省)还是拖动的来源,item 改写最终节点的信息与样式,位置 / id 始终取保留端;resolvePointMerge(false) 让拖动的节点回原位。pending / confirm / getPointMerge() 的载荷带 impact(受影响线路条数、具体 lineIds 与通道读数;lineCount 按线路去重,linesChanged / removedLines 与落地结果同口径)。自己接管数据落地时用 commitPointMerge() 收状态、revertPointMerge() 还原(silent: true 不补发 cancel 载荷)", examples: [{"group":"选中、复制与合并","code":"// getPointMerge() / resolvePointMerge(confirmed?, options?) / commitPointMerge() / revertPointMerge()\nconst pendingMerge = view.getPointMerge() // 待确认的那一份载荷(没有就是 null)\nconsole.log(pendingMerge?.impact?.lineCount) // 这次合并会影响几条线路(改写 + 删除,按线路去重)\nconsole.log(pendingMerge?.impact?.lineIds) // 具体是哪几条(没有 id 时是 lines 下标)\nview.resolvePointMerge(true) // 确认(false = 取消)\nview.resolvePointMerge(true, { keep: 'source', item: { name: '合并结果', size: 18 } })\nview.resolvePointMerge(true, { keep: 'target', item: { color: '#2f6fd0', strokeColor: '#ffffff' } })\nview.resolvePointMerge(false) // 取消(拖动的节点回原位,数据不动)\nview.commitPointMerge() // 自己接管数据时收尾\nview.revertPointMerge() // 反悔(silent: true 时不补发 cancel 载荷)\n","from":"header"}] },
167
168
  { name: "commitPointMerge", kind: "method", params: "无:收掉「已确认、数据已由使用方并好」的合并状态;mergePoints / resolvePointMerge 的正常路径由组件自己调用,只有完全自建合并落地流程时才手动调", returns: "boolean:真的收掉了一个待确认合并为 true,没有可收的状态为 false", doc: "读 / 裁决 / 收尾当前待确认的合并(把一个节点拖到另一个节点上之后):resolvePointMerge(true, { keep, item }) 推进到 confirm 阶段 —— keep 选保留碰撞目标(缺省)还是拖动的来源,item 改写最终节点的信息与样式,位置 / id 始终取保留端;resolvePointMerge(false) 让拖动的节点回原位。pending / confirm / getPointMerge() 的载荷带 impact(受影响线路条数、具体 lineIds 与通道读数;lineCount 按线路去重,linesChanged / removedLines 与落地结果同口径)。自己接管数据落地时用 commitPointMerge() 收状态、revertPointMerge() 还原(silent: true 不补发 cancel 载荷)", examples: [{"group":"选中、复制与合并","code":"// getPointMerge() / resolvePointMerge(confirmed?, options?) / commitPointMerge() / revertPointMerge()\nconst pendingMerge = view.getPointMerge() // 待确认的那一份载荷(没有就是 null)\nconsole.log(pendingMerge?.impact?.lineCount) // 这次合并会影响几条线路(改写 + 删除,按线路去重)\nconsole.log(pendingMerge?.impact?.lineIds) // 具体是哪几条(没有 id 时是 lines 下标)\nview.resolvePointMerge(true) // 确认(false = 取消)\nview.resolvePointMerge(true, { keep: 'source', item: { name: '合并结果', size: 18 } })\nview.resolvePointMerge(true, { keep: 'target', item: { color: '#2f6fd0', strokeColor: '#ffffff' } })\nview.resolvePointMerge(false) // 取消(拖动的节点回原位,数据不动)\nview.commitPointMerge() // 自己接管数据时收尾\nview.revertPointMerge() // 反悔(silent: true 时不补发 cancel 载荷)\n","from":"header"}] },
@@ -200,21 +201,22 @@ export const API_SECTIONS = [
200
201
  { name: "addChannelGroupWaypoint", kind: "method", params: "channelId 通道 id;groupId 分组 id;coordinate 新拐点世界坐标", returns: "this", doc: "给某个分组加拐点、把这一组单独拉走:不给坐标就按默认位置放一个(第 n 组沿法线往外让 n 档),其余分组一根拐点都不动,见「分组拐点」", examples: [{"group":"拐点与通道","code":"// addChannelGroupWaypoint(channelId, groupId, coordinate) / removeChannelGroupWaypoint(channelId, groupId, pathIndex)\nview.addChannelGroupWaypoint('c1', 'c1::2', [200, 120])\nview.removeChannelGroupWaypoint('c1', 'c1::2', 1)\n","from":"header"}] },
201
202
  { name: "removeChannelGroupWaypoint", kind: "method", params: "channelId / groupId / pathIndex 要删的骨架顶点下标", returns: "this", doc: "删掉某个分组的拐点(缺省删最后一个)", examples: [{"group":"拐点与通道","code":"// addChannelGroupWaypoint(channelId, groupId, coordinate) / removeChannelGroupWaypoint(channelId, groupId, pathIndex)\nview.addChannelGroupWaypoint('c1', 'c1::2', [200, 120])\nview.removeChannelGroupWaypoint('c1', 'c1::2', 1)\n","from":"header"}] },
202
203
  { name: "runChannelAction", kind: "method", params: "actionId: GisViewChannelActionId 动作名('select-group' / 'deselect-group' / 'add-waypoint' / 'remove-waypoint' / 'split' / 'merge',见 getChannelContext().actions);options?: GisViewChannelActionOptions 动作自己的参数(groupId / lineId / coordinate…)", returns: "GisViewChannelContext | null", doc: "执行右键交互框里的动作:select-group / deselect-group / add-waypoint / remove-waypoint / split / merge(options 带 channelId / groupId / coordinate / pathIndex / size),返回执行之后的上下文", examples: [{"group":"拐点与通道","code":"// setCurrentChannelGroup(channelId, groupId) / runChannelAction(actionId, options?):当前分组与动作\nview.setCurrentChannelGroup('c1', 'c1::2')\nview.setCurrentChannelGroup('c1', null) // 不选中\nview.runChannelAction('select-group', { groupId: 'c1::2' })\nview.runChannelAction('split', { lineId: 'l2' })\n","from":"header"}] },
203
- { name: "beginAddChannel", kind: "method", params: "options?: GisViewChannelDraftOptions 草稿配置:from(预指定起点,下一步只选终点)/ kind / circuits / autoCommit / continuous / chain / name / lineId / id / lineProps / linePropsByMember / lineItems(第二段)/ routing / path(中间拐点)/ channelProps / channelDraftStyle(本次草稿的预览样式,含 dots 与 layer)/ handoff(交接模式)/ select(点中的节点顺手选中,走 onSelect 那一套)/ originalEvent", returns: "this(只负责那条示例假通道,真正新增走 commitAddChannel / addChannel)", doc: "进入「新增通道」:缺省点两个节点选起点 / 终点,或由 options.from 预指定起点、只需再点终点;options 还支持 kind('channel' 新建通道 / 'line' 新建独立线路,缺省 'channel')/ circuits(一次加几条,缺省 1)/ autoCommit / continuous / chain / name / lineId / id / lineProps / linePropsByMember / lineItems(第二段,连同线路一起新建)/ routing('straight' / 'orthogonal')/ path(中间拐点)/ channelProps / channelDraftStyle(本次草稿的预览样式,含 dots / layer)/ handoff(交接模式:ready 发完收起草稿,自己调 addChannel 落地)/ select(点中的节点顺手选中)/ originalEvent,见「新增通道」", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?)","from":"header"}] },
204
- { name: "getAddChannelDraft", kind: "method", params: "无", returns: "GisViewChannelDraftPayload | null", doc: "当前新增通道草稿(两个节点的信息与可选方案),没有草稿时为 null", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?)","from":"header"}] },
205
- { name: "updateAddChannelStyle", kind: "method", params: "patch?: GisViewChannelDraftStyle | null 字段级合并的预览样式(箭头位置 / 多回线间距 / 两端 dots / 层级 layer / 自绘等);传 null 回到全局 channelDraftStyle", returns: "this", doc: "字段级合并当前草稿的预览样式(color / width / dot / dotColor / dots / layer / arrow / arrowSize / arrowPositions / arrowReverse / arrowColor / circuitSpacing / opacity / customDraw);传 null 回到全局 channelDraftStyle,没有草稿时什么也不做", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?)","from":"header"}] },
206
- { name: "continueAddChannel", kind: "method", params: "options?: GisViewChannelDraftOptions 开下一段草稿(写法与 beginAddChannel 一致,不传就沿用这一段原来的)", returns: "this", doc: "开下一段草稿:连续模式落地时内部自动调(发 phase: 'restart'),也可以手动调;草稿已经退出时按 options 重新进来", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?)","from":"header"}] },
207
- { name: "cancelAddChannel", kind: "method", params: "无", returns: "this(收掉草稿并补一条 phase: 'cancel')", doc: "取消新增通道(草稿期间按 Esc 等价,会补一条 phase: 'cancel')", examples: [{"group":"拐点与通道","code":"// commitAddChannel(input?, options?) / cancelAddChannel() / endAddChannel()\nview.beginAddChannel({\n kind: 'channel',\n circuits: 3,\n channelDraftStyle: {\n color: '#2f6fd0', width: 2.5, arrow: true, arrowSize: 14, arrowPositions: [0, 0.5, 1],\n circuitSpacing: 12, opacity: 0.9,\n // 两端示例点:false 全关;对象可统一 size / color / shape,from / to 再分别覆盖\n dots: { size: 5, shape: 'square', from: { color: '#2f6fd0' }, to: { color: '#d9544d' } },\n layer: 'below-points' // 示例线 / 箭头 / customDraw 画在真实节点之下;示例点仍在节点之后\n }\n}) // 点两个节点:第一个起点、第二个终点;通道 3 回时预览也画 3 条平行回线\nview.beginAddChannel({ from: 'station-a' }) // 只预指定起点:接下来只点终点(没有 from + to 的写法)\nconst draft = view.getAddChannelDraft() // 草稿读数(跟随光标的预览、两个节点、方案表与实际 style)\nview.updateAddChannelStyle({ arrowPositions: [0.25, 0.75], arrowReverse: true }) // 字段级合并,只改箭头\nview.updateAddChannelStyle(null) // 回到全局 channelDraftStyle\n","from":"header"}] },
208
- { name: "endAddChannel", kind: "method", params: "无", returns: "this(静默收掉视图上的示例假通道 / 线路,数据不动、不补事件)", doc: "结束新增通道:静默收掉视图上的示例假通道 / 线路(不补 cancel 事件),数据一个字节都不改 —— 结束事件里决定「不新建了」就用它", examples: [{"group":"拐点与通道","code":"// commitAddChannel(input?, options?) / cancelAddChannel() / endAddChannel()\nview.beginAddChannel({\n kind: 'channel',\n circuits: 3,\n channelDraftStyle: {\n color: '#2f6fd0', width: 2.5, arrow: true, arrowSize: 14, arrowPositions: [0, 0.5, 1],\n circuitSpacing: 12, opacity: 0.9,\n // 两端示例点:false 全关;对象可统一 size / color / shape,from / to 再分别覆盖\n dots: { size: 5, shape: 'square', from: { color: '#2f6fd0' }, to: { color: '#d9544d' } },\n layer: 'below-points' // 示例线 / 箭头 / customDraw 画在真实节点之下;示例点仍在节点之后\n }\n}) // 点两个节点:第一个起点、第二个终点;通道 3 回时预览也画 3 条平行回线\nview.beginAddChannel({ from: 'station-a' }) // 只预指定起点:接下来只点终点(没有 from + to 的写法)\nconst draft = view.getAddChannelDraft() // 草稿读数(跟随光标的预览、两个节点、方案表与实际 style)\nview.updateAddChannelStyle({ arrowPositions: [0.25, 0.75], arrowReverse: true }) // 字段级合并,只改箭头\nview.updateAddChannelStyle(null) // 回到全局 channelDraftStyle\n","from":"header"}] },
209
- { name: "commitAddChannel", kind: "method", params: "input?: GisViewChannelAddResolution | GisViewChannel | GisViewLine | GisViewChannelAddCommitInput,五种写法:① 不传 / null = 用草稿 preferred;② 旧 resolution:字符串或只含非 undefined 的 type / groupId / groupIndex / channelId 的对象;③ 直接线路对象:没有 lineIds 数组,且写了 kind: 'line' / path / from / to / start / end 之一,业务字段与自己的 lineProps 透传到新线路;④ 直接通道对象:Array.isArray(lineIds) 且没有容器键,lineIds 逐条作新回线 id 种子、groups 作分组声明、自己的 lineProps 对所有新回线生效,其余字段进 channelProps,缺省 resolution: 'newChannel';⑤ 容器:出现 channel / line / lines / lineData / lineItems / channelGroups / group / channelProps / resolution 任一键(只写 lineProps 也算)。容器里 line 缺省是独立线路,lines 是成员表(给了几条建几条,空数组沿用草稿);channel.lineIds 只在没有 lines 时当种子。字段优先级:from / to = 顶层 → channel → line → lineItems 第一个对象项(裸 id 跳过)→ 草稿;path = 顶层 → line → channel → 草稿;分组 = channelGroups → group → channel.groups;容器 lineProps = 草稿 → 顶层 → line.lineProps → 逐条,不读 channel.lineProps;channelProps = 草稿 → channel 业务字段 → 顶层 channelProps。直接线路 / 直接通道对象默认新建,要并进已有分组必须用容器并显式写 resolution。options?: { auto?: boolean; continuous?: boolean; originalEvent?: Event | null }:auto 只标记自动提交,continuous: false 强制本次后结束,originalEvent 带回重开的草稿。ready 的方案对象不能整条回传,只取 { type, groupId, groupIndex, channelId } 子集。调用前草稿必须 ready,否则不写数据;没有草稿的程序化新增用 addChannel / addLine。完整示例见「10 通道编辑与新增」与「18 逐方法示例」", returns: "GisViewChannelAddResult", doc: "把已 ready 的草稿落到数据上。input 有五类:不传(用草稿 preferred)、旧 resolution(字符串,或只含 type / groupId / groupIndex / channelId 的对象)、直接线路对象(有 from / to / path / kind: 'line' 等线路形状)、直接通道对象(必须有 lineIds,缺省新建通道)、容器 { channel?, line?, lines?, lineData?, lineItems?, channelGroups?, group?, channelProps?, lineProps?, resolution?, ... }。对象字段优先、没写的沿用草稿;line 缺省表示独立线路,lines 才是成员回线表;直接通道对象缺省新建,直接线路对象缺省按独立线路落地,要并进已有分组必须用容器并显式写 resolution。options?: { auto?, continuous?, originalEvent? } 只影响本次事件与连续草稿。方案读数不能整条回传,只取 { type, groupId, groupIndex, channelId }。完整字段优先级与示例见「10 通道编辑与新增」的 commitAddChannel / 对象直传", examples: [{"group":"拐点与通道","code":"// commitAddChannel(input?, options?) / cancelAddChannel() / endAddChannel()\nview.beginAddChannel({\n kind: 'channel',\n circuits: 3,\n channelDraftStyle: {\n color: '#2f6fd0', width: 2.5, arrow: true, arrowSize: 14, arrowPositions: [0, 0.5, 1],\n circuitSpacing: 12, opacity: 0.9,\n // 两端示例点:false 全关;对象可统一 size / color / shape,from / to 再分别覆盖\n dots: { size: 5, shape: 'square', from: { color: '#2f6fd0' }, to: { color: '#d9544d' } },\n layer: 'below-points' // 示例线 / 箭头 / customDraw 画在真实节点之下;示例点仍在节点之后\n }\n}) // 点两个节点:第一个起点、第二个终点;通道 3 回时预览也画 3 条平行回线\nview.beginAddChannel({ from: 'station-a' }) // 只预指定起点:接下来只点终点(没有 from + to 的写法)\nconst draft = view.getAddChannelDraft() // 草稿读数(跟随光标的预览、两个节点、方案表与实际 style)\nview.updateAddChannelStyle({ arrowPositions: [0.25, 0.75], arrowReverse: true }) // 字段级合并,只改箭头\nview.updateAddChannelStyle(null) // 回到全局 channelDraftStyle\n","from":"header"},{"group":"拐点与通道","code":"// commitAddChannel 只负责把已 ready 的草稿落地;没有草稿的程序化新增用 addChannel / addLine。","from":"header"}] },
204
+ { name: "beginAddChannel", kind: "method", params: "options?: GisViewChannelDraftOptions 草稿配置:from(预指定起点,下一步只选终点)/ circuits / singleChannel(已有通道时缺省按 block 失败)/ existingChannel?: 'block' | 'newGroup' | 'joinGroup' | 'auto' / groupId / groupIndex(joinGroup 的目标分组,序号从 0 起,都写以 groupId 为准)/ autoCommit / continuous / chain / name / lineId / id / lineProps / linePropsByMember / lineData(第二段)/ routing / path(中间拐点)/ channelProps / channelDraftStyle(本次草稿的预览样式,含 dots 与 layer)/ handoff(交接模式)/ select(点中的节点顺手选中,走 onSelect 那一套)/ originalEvent;入口固定为通道语义,旧 kind / lineItems 已移除且不会生效", returns: "this(只负责那条示例假通道,真正新增走 commitAddChannel / addChannel)", doc: "进入「新增通道」:缺省点两个节点选起点 / 终点,或由 options.from 预指定起点、只需再点终点。options 支持 autoCommit / continuous / chain / singleChannel / existingChannel / groupId / groupIndex / circuits / name / lineId / id / lineProps / linePropsByMember / lineData(第二段)/ routing / path / channelProps / channelDraftStyle / handoff / select(只选中点中的节点)/ originalEvent。入口固定为通道语义;旧 kind 与 lineItems 不再生效。", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / beginAddLine(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?)","from":"header"},{"group":"拐点与通道","code":"// 旧 beginAddChannel({ kind: 'line' }) 已移除;线路入口只认 beginAddLine\n","from":"header"}] },
205
+ { name: "beginAddLine", kind: "method", params: "options?: GisViewChannelDraftOptions 与 beginAddChannel 同一套草稿配置,kind 恒为 line;不写 existingChannel 时只加独立线路,显式 existingChannel: 'newGroup' | 'joinGroup' | 'auto' 且两端已有通道时把新回线挂进通道;joinGroup 可写 groupId / groupIndex,singleChannel 不生效", returns: "this(真正新增走 commitAddChannel / addLine)", doc: "进入「新增线路」:与 beginAddChannel 共用拾取 / 预览 / 连续新增,入口固定为线路语义;不写 existingChannel 时只新建独立线路,显式写 existingChannel: 'newGroup' / 'joinGroup' / 'auto' 且两端已有通道时才把新回线挂进已有通道。旧 beginAddChannel({ kind: 'line' }) 已移除,见「10 通道编辑与新增」。", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / beginAddLine(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?)","from":"header"},{"group":"拐点与通道","code":"// beginAddLine:入口固定为线路语义;缺省独立线路,显式 existingChannel 且已有通道时才挂进通道\nview.beginAddLine({ from: 'station-a' })\nview.beginAddLine({ from: 'station-a', existingChannel: 'newGroup' })\nview.beginAddLine({ from: 'station-a', existingChannel: 'joinGroup', groupId: 'c1::2' })","from":"header"},{"group":"拐点与通道","code":"// 连续模式只续草稿配置,不能把这一段切换成线路;要线路请重新 beginAddLine\nview.continueAddChannel({ from: 'station-b' })\nview.cancelAddChannel() // 取消(Esc / 右键等价)\n","from":"header"}] },
206
+ { name: "getAddChannelDraft", kind: "method", params: "无", returns: "GisViewChannelDraftPayload | null", doc: "当前新增草稿(两个节点的信息、可选方案与 lineData),没有草稿时为 null", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / beginAddLine(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?)","from":"header"}] },
207
+ { name: "updateAddChannelStyle", kind: "method", params: "patch?: GisViewChannelDraftStyle | null 字段级合并的预览样式(箭头位置 / 多回线间距 / 两端 dots / 层级 layer / 自绘等);传 null 回到全局 channelDraftStyle", returns: "this", doc: "字段级合并当前草稿的预览样式(color / width / dot / dotColor / dots / layer / arrow / arrowSize / arrowPositions / arrowReverse / arrowColor / circuitSpacing / opacity / customDraw);传 null 回到全局 channelDraftStyle,没有草稿时什么也不做", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / beginAddLine(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?)","from":"header"}] },
208
+ { name: "continueAddChannel", kind: "method", params: "options?: GisViewChannelDraftOptions 开下一段草稿(写法与 beginAddChannel 一致,不传就沿用这一段原来的;可以改 singleChannel / existingChannel / groupId / groupIndex 等配置,但不能切换通道 / 线路入口)", returns: "this", doc: "开下一段草稿:连续模式落地时内部自动调(发 phase: 'restart'),也可以手动调;草稿已经退出时按通道入口重新进来。只续配置,不能把当前段切换成线路;旧 kind 不生效。", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / beginAddLine(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?)","from":"header"}] },
209
+ { name: "cancelAddChannel", kind: "method", params: "无", returns: "this(收掉草稿并补一条 phase: 'cancel')", doc: "取消新增通道(草稿期间按 Esc 等价,会补一条 phase: 'cancel')", examples: [{"group":"拐点与通道","code":"// commitAddChannel(input?, options?) / cancelAddChannel() / endAddChannel()\nview.beginAddChannel({\n circuits: 3,\n channelDraftStyle: {\n color: '#2f6fd0', width: 2.5, arrow: true, arrowSize: 14, arrowPositions: [0, 0.5, 1],\n circuitSpacing: 12, opacity: 0.9,\n // 两端示例点:false 全关;对象可统一 size / color / shape,from / to 再分别覆盖\n dots: { size: 5, shape: 'square', from: { color: '#2f6fd0' }, to: { color: '#d9544d' } },\n layer: 'below-points' // 示例线 / 箭头 / customDraw 画在真实节点之下;示例点仍在节点之后\n }\n}) // 点两个节点:第一个起点、第二个终点;通道 3 回时预览也画 3 条平行回线\nview.beginAddChannel({ from: 'station-a' }) // 只预指定起点:接下来只点终点(没有 from + to 的写法)\nconst draft = view.getAddChannelDraft() // 草稿读数(跟随光标的预览、两个节点、方案表与实际 style)\nview.updateAddChannelStyle({ arrowPositions: [0.25, 0.75], arrowReverse: true }) // 字段级合并,只改箭头\nview.updateAddChannelStyle(null) // 回到全局 channelDraftStyle\n","from":"header"}] },
210
+ { name: "endAddChannel", kind: "method", params: "无", returns: "this(静默收掉视图上的示例假通道 / 线路,数据不动、不补事件)", doc: "结束新增通道:静默收掉视图上的示例假通道 / 线路(不补 cancel 事件),数据一个字节都不改 —— 结束事件里决定「不新建了」就用它", examples: [{"group":"拐点与通道","code":"// commitAddChannel(input?, options?) / cancelAddChannel() / endAddChannel()\nview.beginAddChannel({\n circuits: 3,\n channelDraftStyle: {\n color: '#2f6fd0', width: 2.5, arrow: true, arrowSize: 14, arrowPositions: [0, 0.5, 1],\n circuitSpacing: 12, opacity: 0.9,\n // 两端示例点:false 全关;对象可统一 size / color / shape,from / to 再分别覆盖\n dots: { size: 5, shape: 'square', from: { color: '#2f6fd0' }, to: { color: '#d9544d' } },\n layer: 'below-points' // 示例线 / 箭头 / customDraw 画在真实节点之下;示例点仍在节点之后\n }\n}) // 点两个节点:第一个起点、第二个终点;通道 3 回时预览也画 3 条平行回线\nview.beginAddChannel({ from: 'station-a' }) // 只预指定起点:接下来只点终点(没有 from + to 的写法)\nconst draft = view.getAddChannelDraft() // 草稿读数(跟随光标的预览、两个节点、方案表与实际 style)\nview.updateAddChannelStyle({ arrowPositions: [0.25, 0.75], arrowReverse: true }) // 字段级合并,只改箭头\nview.updateAddChannelStyle(null) // 回到全局 channelDraftStyle\n","from":"header"}] },
211
+ { name: "commitAddChannel", kind: "method", params: "input?: GisViewChannelAddCommitInput,只接受一个容器对象;不传 / null 用草稿的推荐方案。字段:channel?(通道对象与 groups)/ line?(独立线路对象)/ lines? 或 lineData?(成员回线表)/ channelGroups? 或 group?(分组声明)/ lineProps? / linePropsByMember? / channelProps? / path? / routing? / resolution? / from? / to? / name? / id?。lines 与 lineData 同义且 lines 优先,空数组沿用草稿;channel.lineIds 只在线路表缺省时当回线 id 种子。字段优先级:from / to = 顶层 → channel → line → lines 第一个对象项 → 草稿;path = 顶层 → line → channel → 草稿;分组 = channelGroups → group → channel.groups;lineProps = 草稿 → 顶层 → line.lineProps → 逐条;channelProps = 草稿 → channel 业务字段 → 顶层 channelProps。resolution 收 'auto' / 'newChannel' / 'newGroup' / 'joinGroup' / 'newLine' 或 { type, groupId?, groupIndex?, channelId? }。旧顶层分派字段(kind / circuits / channelId / groupId / lineId / lineItems / lineIds / start / end 等)明确失败。options?: { auto?: boolean; continuous?: boolean; originalEvent?: Event | null; select?: boolean };select: true 才选中这次新建的全部回线,缺省不动选中集。调用前草稿必须 ready;没有草稿的程序化新增用 addChannel / addLine", returns: "GisViewChannelAddResult", doc: "把已 ready 的草稿落到数据上。第一个参数只接受一个容器对象:{ channel?, line?, lines? / lineData?, channelGroups? / group?, lineProps?, linePropsByMember?, channelProps?, path?, routing?, resolution?, from?, to?, name?, id? };字符串、数组和旧顶层分派字段(kind / circuits / channelId / groupId / lineId / lineItems / lineIds 等)都明确失败。options?: { auto?, continuous?, originalEvent?, select? };select: true 才选中落地的新回线。完整优先级见「10 通道编辑与新增」。", examples: [{"group":"拐点与通道","code":"// commitAddChannel(input?, options?) / cancelAddChannel() / endAddChannel()\nview.beginAddChannel({\n circuits: 3,\n channelDraftStyle: {\n color: '#2f6fd0', width: 2.5, arrow: true, arrowSize: 14, arrowPositions: [0, 0.5, 1],\n circuitSpacing: 12, opacity: 0.9,\n // 两端示例点:false 全关;对象可统一 size / color / shape,from / to 再分别覆盖\n dots: { size: 5, shape: 'square', from: { color: '#2f6fd0' }, to: { color: '#d9544d' } },\n layer: 'below-points' // 示例线 / 箭头 / customDraw 画在真实节点之下;示例点仍在节点之后\n }\n}) // 点两个节点:第一个起点、第二个终点;通道 3 回时预览也画 3 条平行回线\nview.beginAddChannel({ from: 'station-a' }) // 只预指定起点:接下来只点终点(没有 from + to 的写法)\nconst draft = view.getAddChannelDraft() // 草稿读数(跟随光标的预览、两个节点、方案表与实际 style)\nview.updateAddChannelStyle({ arrowPositions: [0.25, 0.75], arrowReverse: true }) // 字段级合并,只改箭头\nview.updateAddChannelStyle(null) // 回到全局 channelDraftStyle\n","from":"header"},{"group":"拐点与通道","code":"// 这一段最终落地几条回线由 circuits / lineData 决定;落地后是否选中新回线由 commitAddChannel 的 options.select 决定\nview.commitAddChannel({}, { select: true })\n","from":"header"},{"group":"拐点与通道","code":"// commitAddChannel 只负责把已 ready 的草稿落地;没有草稿的程序化新增用 addChannel / addLine。","from":"header"}] },
210
212
  { name: "getChannelAddOptions", kind: "method", params: "from / to 两端节点 id;count?: number 回数(缺省 1);kind?: 'channel' | 'line'(缺省 'channel')", returns: "GisViewChannelAddPlan | null", doc: "两个节点之间的新增方案:新通道 / 新分组 / 并进某一组 / 无法添加;count 是这一次要加的回数(余量按它判),kind: 'line' 时方案表为空(独立线路不挑方案)", examples: [{"group":"拐点与通道","code":"// getChannelAddOptions(from, to, count?, kind?):两个节点之间能怎么加(界面拿它让用户选)\nconst plan = view.getChannelAddOptions('a', 'b', 2)\nconst linePlan = view.getChannelAddOptions('a', 'b', 1, 'line')\n","from":"header"}] },
211
- { name: "getChannelBetween", kind: "method", params: "from / to 两端节点 id", returns: "GisViewChannelBetween | null", doc: "两个节点之间的通道读数(只读):有通道时 hasChannel: true,channelId / channelIndex / groupCount / currentGroupId 与 groups / group / channel(id / name / lineIds / 分组明细)都给全;没有通道时 hasChannel: false、channel: null、groups: [],端点认不出来返回 null —— 与 getChannelAddOptions 的读数同一份,界面做「这两个点之间已经有通道 / 有几组」的判断用它,不必算方案表", examples: [{"group":"拐点与通道","code":"// getChannelBetween(from, to):只要通道读数 —— 有没有通道 / 几组 / 当前哪组(不做方案判断,端点认不出返回 null)\nconst between = view.getChannelBetween('a', 'b')\nif (between?.hasChannel) {\n console.log(between.channelId, between.groupCount, between.groups.map(group => group.id), between.currentGroupId)\n}\n","from":"header"}] },
212
- { name: "addChannel", kind: "method", params: "channel: GisViewChannelAddSpec 通道信息 { from, to, name?, id?, lineData?, group? }(后两个是第二 / 第三段写在对象上的别名);lineData?: GisViewChannelLineData 线路数组(Partial<GisViewLine> / string / number 的一条或一批,按每项 id 建 lineIds);group?: GisViewChannelAddGroup | GisViewChannelAddGroup[] 可选分组信息 { id?, lineIds?, path? }。旧写法 addChannel(input, lineItems?)(GisViewChannelAddInput:circuits / resolution / channelId / groupId / groupIndex / kind / routing / path 那套)照样认;path 是中间拐点,独立线路落地成完整折线、通道只写骨架拐点", returns: "GisViewChannelAddResult(channelLineIds 是落地后通道的完整成员表,独立线路为 null)", doc: "程序化新增通道(新签名,只新建):第一段通道信息(from / to / name / id / path 等通道字段),第二段线路数组 line_data —— 一条或一批(字符串 / 数字按 id 认),按每项 id 建 lineIds,第三段可选分组信息({ id?, lineIds?, path? },单个 / 一组一个)—— 第二段 / 第三段显式给了才走这条新路;两个节点之间已经有通道时整条失败(reason 指路下面两条)。旧写法 addChannel(input, lineItems?)(带 circuits / resolution / channelId / groupId / groupIndex / kind,或只写 { from, to })照样认:按方案表新建或并进已有通道,第二段 lineItems 给了几条就建几条", examples: [{"group":"拐点与通道","code":"// commitAddChannel 只负责把已 ready 的草稿落地;没有草稿的程序化新增用 addChannel / addLine。","from":"header"},{"group":"拐点与通道","code":"// addChannel(spec, lineData?, group?) / addChannels(input, options?) / addChannelGroups(channelId, groups, options?) / addLine(input):程序化新增\nconst channelAdd = view.addChannel({ from: 'a', to: 'b', name: '二号通道', path: [[200, 120]] }, [{ id: 'm-1' }, { id: 'm-2' }])\nconst manyChannels = view.addChannels([{ from: 'a', to: 'b', circuits: 1 }, { from: 'b', to: 'a', circuits: 2, kind: 'line' }])\nconst manyGroups = view.addChannelGroups('c1', [\n { id: 'c1::4', path: [[130, 70]], lineData: [{ id: 'm-7' }, { id: 'm-8' }] },\n { path: [[150, 90]], lines: ['m-9'] }\n])\nconsole.log(manyGroups.count, manyGroups.lineIds, manyGroups.groupIds)\nconst lineAdd = view.addLine({ from: 'a', to: 'b', name: '三回' })","from":"header"}] },
213
- { name: "addChannelGroupLines", kind: "method", params: "channelId 目标通道 id;lineData?: GisViewChannelLineData 这次新建的线路数组(按每项 id 建回线);group?: GisViewChannelAddGroup 新分组信息 { id?, path? }(缺省 ${通道 id}::N、不带拐点)", returns: "GisViewChannelAddResult", doc: "给已有通道新加一组回线(原 addChannel 的 newGroup 那一支):第二段线路数组 line_data 按每项 id 建回线,groupInfo({ id?, path? })给新分组的 id 与中间拐点,缺省 ${通道 id}::N、不带拐点;通道 / 线路认不出来时 ok: false + reason", examples: [{"group":"拐点与通道","code":"// 已经有通道时用拆出来的两条(语义直说,不用自己拼 resolution)\nview.addChannelGroupLines('c1', [{ id: 'm-5' }], { id: 'c1::3' }) // 新加一组\nview.joinChannelGroupLines('c1', 'c1::2', [{ id: 'm-6' }]) // 并进某一组(序号 1 也认)\nconsole.log(joined.channelLineIds) // 更新后这条通道的完整成员表(lineIds)\n","from":"code"}] },
214
- { name: "addChannelGroups", kind: "method", params: "channelId 目标通道 id;groups: GisViewChannelAddGroupInput | GisViewChannelAddGroupInput[] 每项 { id?, path?, lineData? | lines? | lineItems? }(线路数据与 addChannel 第二段同一份写法);options?: { emit?: boolean; label?: string } emit: false 只落地不通知", returns: "GisViewAddChannelGroupsResult", doc: "给已有通道一次新加多组回线:groups 每项 { id?, path?, lineData | lines | lineItems },线路数据与 addChannel 第二段同一份写法;逐组落地但只记一步历史、只发一条 addChannelGroups 数据变更,返回 { ok, reason, count, results, lineIds, groupIds, groups },options.emit: false 只落地不通知", examples: [{"group":"拐点与通道","code":"// addChannel(spec, lineData?, group?) / addChannels(input, options?) / addChannelGroups(channelId, groups, options?) / addLine(input):程序化新增\nconst channelAdd = view.addChannel({ from: 'a', to: 'b', name: '二号通道', path: [[200, 120]] }, [{ id: 'm-1' }, { id: 'm-2' }])\nconst manyChannels = view.addChannels([{ from: 'a', to: 'b', circuits: 1 }, { from: 'b', to: 'a', circuits: 2, kind: 'line' }])\nconst manyGroups = view.addChannelGroups('c1', [\n { id: 'c1::4', path: [[130, 70]], lineData: [{ id: 'm-7' }, { id: 'm-8' }] },\n { path: [[150, 90]], lines: ['m-9'] }\n])\nconsole.log(manyGroups.count, manyGroups.lineIds, manyGroups.groupIds)\nconst lineAdd = view.addLine({ from: 'a', to: 'b', name: '三回' })","from":"header"}] },
215
- { name: "joinChannelGroupLines", kind: "method", params: "channelId 目标通道 id;groupId?: string | number 并进哪一组(认分组 id 也认序号,从 0 起;不传挑第一个还有余量的分组);lineData?: GisViewChannelLineData 这次新建的线路数组", returns: "GisViewChannelAddResult", doc: "把新回线并进已有通道的某一组(原 addChannel 的 joinGroup 那一支):第二段线路数组 line_data,groupId 认分组 id 也认序号(从 0 起、按声明顺序),不传就挑第一个还有余量的分组", examples: [{"group":"拐点与通道","code":"// 已经有通道时用拆出来的两条(语义直说,不用自己拼 resolution)\nview.addChannelGroupLines('c1', [{ id: 'm-5' }], { id: 'c1::3' }) // 新加一组\nview.joinChannelGroupLines('c1', 'c1::2', [{ id: 'm-6' }]) // 并进某一组(序号 1 也认)\nconsole.log(joined.channelLineIds) // 更新后这条通道的完整成员表(lineIds)\n","from":"code"}] },
216
- { name: "addChannels", kind: "method", params: "input: GisViewChannelAddInput | GisViewChannelAddInput[] 一条或一批声明(每条与 addChannel 同一套写法;routing: 'orthogonal' 自动正交折线,path 是中间拐点,独立线路落地成完整 path、通道只写骨架);options?: { emit?: boolean } emit: false 只改数据不发 dataChange", returns: "GisViewAddChannelsResult", doc: "addChannel 的批量版:一次新增多条通道 / 独立线路(每条是新签名或旧写法,逐条各自认;每项的 routing / path 走同一套解析,path 是中间拐点),逐条落地但只重排一次场景、只记一步历史、只发一条 addChannels 数据变更;返回 { ok, count, results, lineIds, channelIds, items },失败条目在各自的 reason 里说明原因、不影响其余条目,options.emit: false 时不发 onDataChange,见「动态换数据与加载完成」", examples: [{"group":"拐点与通道","code":"// addChannel(spec, lineData?, group?) / addChannels(input, options?) / addChannelGroups(channelId, groups, options?) / addLine(input):程序化新增\nconst channelAdd = view.addChannel({ from: 'a', to: 'b', name: '二号通道', path: [[200, 120]] }, [{ id: 'm-1' }, { id: 'm-2' }])\nconst manyChannels = view.addChannels([{ from: 'a', to: 'b', circuits: 1 }, { from: 'b', to: 'a', circuits: 2, kind: 'line' }])\nconst manyGroups = view.addChannelGroups('c1', [\n { id: 'c1::4', path: [[130, 70]], lineData: [{ id: 'm-7' }, { id: 'm-8' }] },\n { path: [[150, 90]], lines: ['m-9'] }\n])\nconsole.log(manyGroups.count, manyGroups.lineIds, manyGroups.groupIds)\nconst lineAdd = view.addLine({ from: 'a', to: 'b', name: '三回' })","from":"header"}] },
217
- { name: "addLine", kind: "method", params: "input: GisViewChannelAddInput 同上(等价 addChannel({ ...input, kind: 'line' });routing: 'orthogonal' 自动正交折线,path 是中间拐点,落地成含两端的完整折线);第二段 GisViewChannelLineData 与 addChannel 的线路数组同一份 —— 每项可以只写一个 id(裸字符串 / 数字按 { id } 收)、写对象给逐条字段,不写就按 from-to-序号 自动编号", returns: "GisViewChannelAddResult", doc: "/** * 程序化新增独立线路(不挂通道)。", examples: [{"group":"拐点与通道","code":"// commitAddChannel 只负责把已 ready 的草稿落地;没有草稿的程序化新增用 addChannel / addLine。","from":"header"},{"group":"拐点与通道","code":"// addChannel(spec, lineData?, group?) / addChannels(input, options?) / addChannelGroups(channelId, groups, options?) / addLine(input):程序化新增\nconst channelAdd = view.addChannel({ from: 'a', to: 'b', name: '二号通道', path: [[200, 120]] }, [{ id: 'm-1' }, { id: 'm-2' }])\nconst manyChannels = view.addChannels([{ from: 'a', to: 'b', circuits: 1 }, { from: 'b', to: 'a', circuits: 2, kind: 'line' }])\nconst manyGroups = view.addChannelGroups('c1', [\n { id: 'c1::4', path: [[130, 70]], lineData: [{ id: 'm-7' }, { id: 'm-8' }] },\n { path: [[150, 90]], lines: ['m-9'] }\n])\nconsole.log(manyGroups.count, manyGroups.lineIds, manyGroups.groupIds)\nconst lineAdd = view.addLine({ from: 'a', to: 'b', name: '三回' })","from":"header"}] },
213
+ { name: "getChannelBetween", kind: "method", params: "from / to 两端节点 id", returns: "GisViewChannelBetween | null", doc: "两个节点之间的通道读数(只读):有通道时 hasChannel: true,channelId / channelIndex / groupCount / currentGroupId 与 groups / group / channel(id / name / lineIds / 分组明细)都给全;没有通道时 hasChannel: false、channel: null、groups: [],端点认不出来返回 null", examples: [{"group":"拐点与通道","code":"// getChannelBetween(from, to):只要通道读数 —— 有没有通道 / 几组 / 当前哪组(不做方案判断,端点认不出返回 null)\nconst between = view.getChannelBetween('a', 'b')\nif (between?.hasChannel) {\n console.log(between.channelId, between.groupCount, between.groups.map(group => group.id), between.currentGroupId)\n}\n","from":"header"}] },
214
+ { name: "addChannel", kind: "method", params: "channel: GisViewChannelAddSpec 通道信息 { from, to, name?, id?, channelProps?, path?, routing?, lineData?, group? }(后两个是第二 / 第三段写在对象上的别名);lineData?: GisViewChannelLineData 线路数组(Partial<GisViewLine> / string / number 的一条或一批,按每项 id 建 lineIds);group?: GisViewChannelAddGroup | GisViewChannelAddGroup[] 可选分组信息 { id?, lineIds?, path? };options?: { emit?: boolean; select?: boolean },缺省不选中。只新建通道:两端已有通道时整条失败;旧 circuits / resolution / kind / channelId / lineItems 等入参已移除。path 是中间拐点,通道只写骨架拐点", returns: "GisViewChannelAddResult(channelLineIds 是落地后通道的完整成员表)", doc: "程序化只新建通道:第一段 { from, to, name?, id?, channelProps?, path?, routing?, lineData?, group? },第二段线路数据按每项 id 建 lineIds,第三段可选分组信息({ id?, lineIds?, path? })。两端已有通道时整条失败(reason 指路 addChannelGroupLines / joinChannelGroupLines)。options?: { emit?, select? },缺省不选中新回线。", examples: [{"group":"拐点与通道","code":"// commitAddChannel 只负责把已 ready 的草稿落地;没有草稿的程序化新增用 addChannel / addLine。","from":"header"},{"group":"拐点与通道","code":"// addChannel(spec, lineData?, group?, options?) / addChannels(input, options?)","from":"header"}] },
215
+ { name: "addChannelGroupLines", kind: "method", params: "channelId 目标通道 id;lineData?: GisViewChannelLineData 这次新建的线路数组(按每项 id 建回线);group?: GisViewChannelAddGroup 新分组信息 { id?, path? }(缺省 ${通道 id}::N、不带拐点);options?: { emit?: boolean; select?: boolean }", returns: "GisViewChannelAddResult", doc: "给已有通道新加一组回线:第二段按每项 id 建回线,groupInfo({ id?, groupId?, path?, ... })给新分组的 id 与中间拐点,缺省 ${通道 id}::N、不带拐点;options?: { emit?, select? },缺省不选中", examples: [{"group":"拐点与通道","code":"// addChannelGroupLines(channelId, lineData?, group?, options?) / joinChannelGroupLines(channelId, groupId?, lineData?, options?)","from":"header"}] },
216
+ { name: "addChannelGroups", kind: "method", params: "channelId 目标通道 id;groups: GisViewChannelAddGroupInput | GisViewChannelAddGroupInput[] 每项 { id?, groupId?, path?, lineData? | lines? }(线路数据与 addChannel 第二段同一份写法,lineItems 已移除);options?: { emit?: boolean; label?: string; select?: boolean },emit: false 只落地不通知", returns: "GisViewAddChannelGroupsResult", doc: "给已有通道一次新加多组回线:groups 每项 { id?, groupId?, path?, lineData? | lines?, ... };旧 lineItems 会让整批明确失败且不写数据。逐组落地但只记一步历史、只发一条 addChannelGroups 数据变更;options?: { emit?, label?, select? }", examples: [{"group":"拐点与通道","code":"// addChannelGroups(channelId, groups, options?) / addLine(spec, lineData?, options?):程序化新增","from":"header"}] },
217
+ { name: "joinChannelGroupLines", kind: "method", params: "channelId 目标通道 id;groupId?: string | number 并进哪一组(认分组 id 也认序号,从 0 起;不传挑第一个还有余量的分组);lineData?: GisViewChannelLineData 这次新建的线路数组;options?: { emit?: boolean; select?: boolean }", returns: "GisViewChannelAddResult", doc: "把新回线并进已有通道的某一组:第二段线路数据,groupId 认分组 id 也认序号(从 0 起、按声明顺序),不传就挑第一个还有余量的分组;options?: { emit?, select? }", examples: [{"group":"拐点与通道","code":"// addChannelGroupLines(channelId, lineData?, group?, options?) / joinChannelGroupLines(channelId, groupId?, lineData?, options?)","from":"header"}] },
218
+ { name: "addChannels", kind: "method", params: "input: GisViewChannelAddSpec | GisViewChannelAddSpec[] 一条或一批声明(每条与 addChannel 第一段同一套写法,可在项内写 lineData / group;routing: 'orthogonal' 自动正交折线,path 是中间拐点);options?: { emit?: boolean; select?: boolean },emit: false 只改数据不发 dataChange,缺省不选中", returns: "GisViewAddChannelsResult", doc: "addChannel 的批量版:一次新建多条通道(每项写法同 addChannel),逐条落地但只重排一次场景、只记一步历史、只发一条 addChannels 数据变更;返回 { ok, count, results, lineIds, channelIds, items },失败条目在各自 reason 里说明;options?: { emit?, select? },select 一次选中全部成功的新回线", examples: [{"group":"拐点与通道","code":"// addChannel(spec, lineData?, group?, options?) / addChannels(input, options?)","from":"header"}] },
219
+ { name: "addLine", kind: "method", params: "input: GisViewLineAddSpec 独立线路 { from, to, name?, id?, lineProps?, path?, routing?, lineData? };第二段 GisViewChannelLineData 与 addChannel 的线路数组同一份 —— 每项可以只写一个 id(裸字符串 / 数字按 { id } 收)、写对象给逐条字段,不写就按 from-to-序号 自动编号;options?: { emit?: boolean; select?: boolean },缺省不选中。旧 circuits / kind / lineItems / 通道字段已移除", returns: "GisViewChannelAddResult", doc: "程序化新增独立线路:spec 为 { from, to, name?, id?, lineProps?, path?, routing?, lineData? },第二段每条按自己的 id 建;不碰任何通道。落地走追加式引擎路径,已有通道声明时也能增量加线;options?: { emit?, select? },缺省不选中", examples: [{"group":"拐点与通道","code":"// commitAddChannel 只负责把已 ready 的草稿落地;没有草稿的程序化新增用 addChannel / addLine。","from":"header"},{"group":"拐点与通道","code":"// addChannelGroups(channelId, groups, options?) / addLine(spec, lineData?, options?):程序化新增","from":"header"}] },
218
220
  { name: "removeLine", kind: "method", params: "lineId: string | number 要删的线路 id", returns: "GisViewRemoveLineResult", doc: "移除一条线路(撤销刚落地的新增线路):挂在通道上的回线会从通道声明里一起摘掉,通道空了整条删掉,返回 { ok, line, lineId, index, channelId, removedChannel }", examples: [{"group":"拐点与通道","code":"// removeLine(lineId) / removeLines(lineIds) / removeChannel(channelId):删线路 / 删多条 / 删整条通道\nview.removeLine('l1')\nview.removeLines(['l1', 'l2'])\nview.removeLines('l1') // 单条也认\nview.removeChannel('c1')","from":"header"}] },
219
221
  { name: "removeLines", kind: "method", params: "lineIds: string | number | 数组 一次删多条", returns: "GisViewRemoveLinesResult", doc: "一次移除多条线路(传数组或单条 id):通道声明与分组跟着收拾、删空的通道整条走,返回 { ok, lineIds, indexes, lines, entries, removedChannels }", examples: [{"group":"拐点与通道","code":"// removeLine(lineId) / removeLines(lineIds) / removeChannel(channelId):删线路 / 删多条 / 删整条通道\nview.removeLine('l1')\nview.removeLines(['l1', 'l2'])\nview.removeLines('l1') // 单条也认\nview.removeChannel('c1')","from":"header"}] },
220
222
  { name: "removeChannel", kind: "method", params: "channelId 通道 id", returns: "GisViewRemoveChannelResult", doc: "移除整条通道:它名下的回线一条条摘掉,返回 { ok, channelId, lineIds, lines, removedChannel }", examples: [{"group":"拐点与通道","code":"// removeLine(lineId) / removeLines(lineIds) / removeChannel(channelId):删线路 / 删多条 / 删整条通道\nview.removeLine('l1')\nview.removeLines(['l1', 'l2'])\nview.removeLines('l1') // 单条也认\nview.removeChannel('c1')","from":"header"}] },
@@ -248,9 +250,9 @@ export const API_SECTIONS = [
248
250
  {
249
251
  title: "增删数据",
250
252
  items: [
251
- { name: "addPoint", kind: "method", params: "item: GisViewPoint 新节点(x / y 必填,其余随便带);options?: { emit?: boolean } emit: false 只改数据不发 dataChange", returns: "GisViewAddPointResult", doc: "把现成的数据对象追加到末尾(批量导入、从别处拷过来、按坐标补几条):老数据一条不动,返回 { ok, count, indexes, items }(addPoint 另有单条的 index / item),options.emit: false 时不发 onDataChange;与 addChannel / addLine 的分工是后者走「通道 / 草稿」那一套", examples: [{"group":"增删数据","code":"// addPoint(item, options?) / addPoints(items, options?) / addLines(items, options?)\nview.addPoint({ id: 's-1', x: 116.4, y: 39.9, name: '甲站' })\nconst addedPoints = view.addPoints([{ id: 's-2', x: 116.6, y: 40.1 }, { id: 's-3', x: 116.8, y: 40.2 }])\nview.addLines([{ id: 'x-1', from: 's-1', to: 's-2' }])\nview.addPoints(nextBatch, { emit: false }) // 只落地、不发 dataChange","from":"header"}] },
252
- { name: "addPoints", kind: "method", params: "items: GisViewPoint | GisViewPoint[] 一个或一批新节点;options?: { emit?: boolean } 同上", returns: "GisViewAddItemsResult<GisViewPoint>", doc: "把现成的数据对象追加到末尾(批量导入、从别处拷过来、按坐标补几条):老数据一条不动,返回 { ok, count, indexes, items }(addPoint 另有单条的 index / item),options.emit: false 时不发 onDataChange;与 addChannel / addLine 的分工是后者走「通道 / 草稿」那一套", examples: [{"group":"增删数据","code":"// addPoint(item, options?) / addPoints(items, options?) / addLines(items, options?)\nview.addPoint({ id: 's-1', x: 116.4, y: 39.9, name: '甲站' })\nconst addedPoints = view.addPoints([{ id: 's-2', x: 116.6, y: 40.1 }, { id: 's-3', x: 116.8, y: 40.2 }])\nview.addLines([{ id: 'x-1', from: 's-1', to: 's-2' }])\nview.addPoints(nextBatch, { emit: false }) // 只落地、不发 dataChange","from":"header"}] },
253
- { name: "addLines", kind: "method", params: "items: GisViewLine | GisViewLine[] 一个或一批新线路(通道成员也能这样直接加);options?: { emit?: boolean } 同上", returns: "GisViewAddItemsResult<GisViewLine>", doc: "把现成的数据对象追加到末尾(批量导入、从别处拷过来、按坐标补几条):老数据一条不动,返回 { ok, count, indexes, items }(addPoint 另有单条的 index / item),options.emit: false 时不发 onDataChange;与 addChannel / addLine 的分工是后者走「通道 / 草稿」那一套", examples: [{"group":"增删数据","code":"// addPoint(item, options?) / addPoints(items, options?) / addLines(items, options?)\nview.addPoint({ id: 's-1', x: 116.4, y: 39.9, name: '甲站' })\nconst addedPoints = view.addPoints([{ id: 's-2', x: 116.6, y: 40.1 }, { id: 's-3', x: 116.8, y: 40.2 }])\nview.addLines([{ id: 'x-1', from: 's-1', to: 's-2' }])\nview.addPoints(nextBatch, { emit: false }) // 只落地、不发 dataChange","from":"header"}] },
253
+ { name: "addPoint", kind: "method", params: "item: GisViewPoint 新节点(x / y 必填,其余随便带);options?: { emit?: boolean } emit: false 只改数据不发 dataChange", returns: "GisViewAddPointResult", doc: "把现成的数据对象追加到末尾(批量导入、从别处拷过来、按坐标补几条):老数据一条不动,返回 { ok, count, indexes, items }(addPoint 另有单条的 index / item);options?: { emit?, select? },select: true 选中这次新加的要素,缺省不选中", examples: [{"group":"增删数据","code":"// addPoint(item, options?) / addPoints(items, options?) / addLines(items, options?)\nview.addPoint({ id: 's-1', x: 116.4, y: 39.9, name: '甲站' })\nconst addedPoints = view.addPoints([{ id: 's-2', x: 116.6, y: 40.1 }, { id: 's-3', x: 116.8, y: 40.2 }])\nview.addLines([{ id: 'x-1', from: 's-1', to: 's-2' }])\nview.addPoints(nextBatch, { emit: false }) // 只落地、不发 dataChange","from":"header"}] },
254
+ { name: "addPoints", kind: "method", params: "items: GisViewPoint | GisViewPoint[] 一个或一批新节点;options?: { emit?: boolean } 同上", returns: "GisViewAddItemsResult<GisViewPoint>", doc: "把现成的数据对象追加到末尾(批量导入、从别处拷过来、按坐标补几条):老数据一条不动,返回 { ok, count, indexes, items }(addPoint 另有单条的 index / item);options?: { emit?, select? },select: true 选中这次新加的要素,缺省不选中", examples: [{"group":"增删数据","code":"// addPoint(item, options?) / addPoints(items, options?) / addLines(items, options?)\nview.addPoint({ id: 's-1', x: 116.4, y: 39.9, name: '甲站' })\nconst addedPoints = view.addPoints([{ id: 's-2', x: 116.6, y: 40.1 }, { id: 's-3', x: 116.8, y: 40.2 }])\nview.addLines([{ id: 'x-1', from: 's-1', to: 's-2' }])\nview.addPoints(nextBatch, { emit: false }) // 只落地、不发 dataChange","from":"header"}] },
255
+ { name: "addLines", kind: "method", params: "items: GisViewLine | GisViewLine[] 一个或一批新线路(通道成员也能这样直接加);options?: { emit?: boolean } 同上", returns: "GisViewAddItemsResult<GisViewLine>", doc: "把现成的数据对象追加到末尾(批量导入、从别处拷过来、按坐标补几条):老数据一条不动,返回 { ok, count, indexes, items }(addPoint 另有单条的 index / item);options?: { emit?, select? },select: true 选中这次新加的要素,缺省不选中", examples: [{"group":"增删数据","code":"// addPoint(item, options?) / addPoints(items, options?) / addLines(items, options?)\nview.addPoint({ id: 's-1', x: 116.4, y: 39.9, name: '甲站' })\nconst addedPoints = view.addPoints([{ id: 's-2', x: 116.6, y: 40.1 }, { id: 's-3', x: 116.8, y: 40.2 }])\nview.addLines([{ id: 'x-1', from: 's-1', to: 's-2' }])\nview.addPoints(nextBatch, { emit: false }) // 只落地、不发 dataChange","from":"header"}] },
254
256
  ]
255
257
  },
256
258
  {
@@ -21,7 +21,7 @@ const {
21
21
  <p>Ctrl + 点击线路或通道加点之后,拐点不会分散写进每条回线,而是归到一个新的拐点集合里:一个分组一组(没拆分过就是一条通道一组),组里有 member_line_ids(哪些回线共用)与 group_route(两端 + 拐点),结构对齐 test.json 的 parallel_groups;「导出拐点集合」按钮会把这份集合复制到剪贴板,可以直接回写业务数据。</p>
22
22
  <p>这一轮补上三块:① 通道可以分组了——默认一条通道一组,「拆分分组」按每组几条回线把它拆开(第一组沿用当前拐点、新分组的线路不带拐点),「合并分组」把其余分组并进第一组(被并入的线路跟随第一组的拐点,第一组没有拐点就一条都不带),加点 / 拖点只作用于同一分组,分组声明写回 channels[].groups;② 鹰眼图也支持规则——「规则沿用主图」只搬颜色与层级(主图的像素尺寸不会搬过来),「按电压单独配色」给小图自己配一份(半径缩到 1 ~ 2.6px、高电压用方块、zIndex 沿用电压分档),「分层」控制分层叠画、层级下拉只画某几层,「放大节点 / 放大线路」让节点与线路跟着小图自身的倍数放大(滚轮或 - / + 都算);③ 定向更新——updatePoint / updateLine / updateChannel 改一个节点、线路或通道就重渲染相关的视图,只重解析受影响的那一小片,其余要素与缓存一个字节都不动,鹰眼图与标签覆盖层跟着重画。</p>
23
23
  <p>热力图这一轮把节点那一层补上了:合成数据的节点与线路都按一张「热区场」随机生成 max_loading_rate(在数据范围里撒 4 ~ 6 个热区,节点取自己坐标处的场值、线路取两端中点处的场值 —— 画出来是几团往外化开的色斑,而不是均匀噪点),作用对象因此可以切「节点 + 线路 + 通道」/「只按节点上色」/「线路 + 通道回线」/「只按通道自己的值」四档;面板上的「重新随机一份热力数据」能在任意数据集上现生成一份(换种子、按当前节点范围重建场,只改字段值,坐标与拓扑一动不动),真实数据 new.json 的节点本来没有这一列、线路那一列又恒为 1.5,点一下就能立刻看到热力。作用对象带上节点时图标不再跟着换:图标符号写了 tint(逐实例着色),填充按白色烘焙、由热力色逐实例染上,描边写 currentStrokeColor 不参与染色 —— 形状与描边还是那张图标,只是填充跟着值走;贴图缓存键里因此不再有逐点的热力色,900 节点时绘制调用仍然在 36 上下(以前一个节点一张贴图时会涨到约 1500,10 万级直接把主线程烤住),想看「完全不参与热力图」的效果就把节点从作用对象里去掉 —— 图标本身不会变成内置圆 / 方块。</p>
24
- <p>这一轮补上六件事:① <b>节点复制</b>——按住 Alt(面板上可以改成别的键)点一个节点,复制品立刻压在源节点上跟着鼠标走,松开左键(不再看 Alt 什么时候松)或再点一下落地;也可以点面板上的「先点按钮再选节点」再点一个节点(复制品落在它旁边),或者选中节点按 Ctrl+C、把鼠标移到目标位置按 Ctrl+V。复制全程走 <b>onPointCopy</b>:start / move / commit / end / cancel(复制到剪贴板另发一条 clipboard),落地前的 commit 阶段可以 payload.cancel() 撤掉、payload.update() / setItem() 改数据,id 与整份数据由 copy.createId / copy.createItem 定;落地之后的 end 阶段还能 payload.revert() 整份回滚(面板上「复制后模拟接口失败」那个勾就是它)。② <b>节点合并</b>——把一个节点拖到另一个节点上(碰撞按两个节点画在屏幕上的半径之和 + merge.distance 判,整组拖动不参与),<b>组件不弹任何对话框</b>:pending 停在原地等使用方裁决,面板上的「合并确认」三档分别是「停住(由这个自写弹层来确认)」/ false 直接合并 / 函数返回 Promise(先调接口再决定,resolve(false) 取消);确认会先推进到 confirm 阶段并把合并结果给你(可以改,也可以在这一步因为「接口失败」整份撤掉),确认之后来源节点的数据并进目标(缺省 { ...来源, ...目标 },可用 merge.createItem 整份接管)、<b>尺寸取双方最大、图标优先目标</b>、绑在它上面的线路改绑、合并出来的自环与零长线删掉;两端因此撞成同一条的通道按 merge.channelGroups 处理(各自保留分组 / 合成一个分组,自环的整条删),读数里会带上改端点的通道数与并掉的通道数。取消或接口失败则把拖动的节点与跟随它的线路还原回原位,并补发一条拖拽结束。③ <b>所有快捷键都能改</b>——options.shortcuts 按动作配(框选 / 多选 / 加点 / 复制 / 复制到剪贴板 / 粘贴 / 取消 / 删除 / 适应视图),写法是纯修饰键(ctrl / meta / shift / alt / mod)、组合键(ctrl+c)、单键(escape / 0)或数组,写 false 表示不绑定;面板「视图 · 交互」里那四个下拉就是改框选 / 多选 / 加点 / 复制的修饰键——比如把框选改成 Shift,之后 Shift + 拖动才出选框,按住 Shift 时连光标变体也跟着走。④ <b>选中抬层</b>——selectionOnTop(缺省开)把选中的节点、拖动中的整组、待确认合并的目标节点与选中线路所在的整条通道画在所有要素之上:选中了就一定看得见,与 zIndex 无关;关掉就完全按 zIndex 与数据顺序画。⑤ <b>删除(Delete)</b>——选中拐点 / 线路 / 节点之后按 Delete(或面板上的「删除选中」)先出一份删除载荷:要删什么、连带什么、能按哪些范围删。<b>组件不弹界面</b>:pending 停在原地等 <b>confirm(scope)</b> / cancel(),Esc 也能取消;范围三档是「只删选中的线路」/「删除整个分组」/「删除整条通道」(后两档只在删的是通道回线、且这条通道分过组时才有),选中的拐点与节点一律一起删 —— <b>删节点会连挂在它身上的线路、以及拿它当端点的整条通道一起删</b>,载荷里的 lineCount / channelCount 就是这份连带读数;确认之后整段合成一条历史,撤销一步全回来;开关是 features.remove,快捷键照样走 shortcuts.remove(可以单独配成 shift+delete 之类)。⑥ <b>通道选中模式</b>——channelSelectMode 决定点中一条通道回线时选中什么:line(缺省,只选这一条)/ group(连它所在分组一起)/ channel(整条通道一起);在 group / channel 下 Ctrl + 点击是<b>整批取反</b>,通道以外的线路永远只选它自己。它只影响「下一次点选怎么摊开」,切换走 updateOptions,场景一个字节都不用重排。面板「视图 · 复制合并」里那两项就是它们。</p>
24
+ <p>这一轮补上六件事:① <b>节点复制</b>——按住 Alt(面板上可以改成别的键)点一个节点,复制品立刻压在源节点上跟着鼠标走,松开左键(不再看 Alt 什么时候松)或再点一下落地;也可以点面板上的「先点按钮再选节点」再点一个节点(复制品落在它旁边),或者选中节点按 Ctrl+C、把鼠标移到目标位置按 Ctrl+V。复制全程走 <b>onPointCopy</b>:start / move / commit / end / cancel(复制到剪贴板另发一条 clipboard),落地前的 commit 阶段可以 payload.cancel() 撤掉、payload.update() 改数据,id 与整份数据由 copy.createId / copy.createItem 定;落地之后的 end 阶段还能 payload.revert() 整份回滚(面板上「复制后模拟接口失败」那个勾就是它)。② <b>节点合并</b>——把一个节点拖到另一个节点上(碰撞按两个节点画在屏幕上的半径之和 + merge.distance 判,整组拖动不参与),<b>组件不弹任何对话框</b>:pending 停在原地等使用方裁决,面板上的「合并确认」三档分别是「停住(由这个自写弹层来确认)」/ false 直接合并 / 函数返回 Promise(先调接口再决定,resolve(false) 取消);确认会先推进到 confirm 阶段并把合并结果给你(可以改,也可以在这一步因为「接口失败」整份撤掉),确认之后来源节点的数据并进目标(缺省 { ...来源, ...目标 },可用 merge.createItem 整份接管)、<b>尺寸取双方最大、图标优先目标</b>、绑在它上面的线路改绑、合并出来的自环与零长线删掉;两端因此撞成同一条的通道按 merge.channelGroups 处理(各自保留分组 / 合成一个分组,自环的整条删),读数里会带上改端点的通道数与并掉的通道数。取消或接口失败则把拖动的节点与跟随它的线路还原回原位,并补发一条拖拽结束。③ <b>所有快捷键都能改</b>——options.shortcuts 按动作配(框选 / 多选 / 加点 / 复制 / 复制到剪贴板 / 粘贴 / 取消 / 删除 / 适应视图),写法是纯修饰键(ctrl / meta / shift / alt / mod)、组合键(ctrl+c)、单键(escape / 0)或数组,写 false 表示不绑定;面板「视图 · 交互」里那四个下拉就是改框选 / 多选 / 加点 / 复制的修饰键——比如把框选改成 Shift,之后 Shift + 拖动才出选框,按住 Shift 时连光标变体也跟着走。④ <b>选中抬层</b>——selectionOnTop(缺省开)把选中的节点、拖动中的整组、待确认合并的目标节点与选中线路所在的整条通道画在所有要素之上:选中了就一定看得见,与 zIndex 无关;关掉就完全按 zIndex 与数据顺序画。⑤ <b>删除(Delete)</b>——选中拐点 / 线路 / 节点之后按 Delete(或面板上的「删除选中」)先出一份删除载荷:要删什么、连带什么、能按哪些范围删。<b>组件不弹界面</b>:pending 停在原地等 <b>confirm(scope)</b> / cancel(),Esc 也能取消;范围三档是「只删选中的线路」/「删除整个分组」/「删除整条通道」(后两档只在删的是通道回线、且这条通道分过组时才有),选中的拐点与节点一律一起删 —— <b>删节点会连挂在它身上的线路、以及拿它当端点的整条通道一起删</b>,载荷里的 lineCount / channelCount 就是这份连带读数;确认之后整段合成一条历史,撤销一步全回来;开关是 features.remove,快捷键照样走 shortcuts.remove(可以单独配成 shift+delete 之类)。⑥ <b>通道选中模式</b>——channelSelectMode 决定点中一条通道回线时选中什么:line(缺省,只选这一条)/ group(连它所在分组一起)/ channel(整条通道一起);在 group / channel 下 Ctrl + 点击是<b>整批取反</b>,通道以外的线路永远只选它自己。它只影响「下一次点选怎么摊开」,切换走 updateOptions,场景一个字节都不用重排。面板「视图 · 复制合并」里那两项就是它们。</p>
25
25
  <p>定位口径这一轮统一了:<code>focusOn(target)</code> 缺省只把中心移到目标上,比例一点不动;要按目标范围适配必须显式写 <code>{ zoom: true }</code>,可再配 <code>padding</code> / <code>maxZoom</code>。节点、线路、通道与分组走同一条规则,搜索面板与画布菜单里需要适配的按钮都已经写成显式开启。</p>
26
26
  </div>
27
27
  <span class="demo-badge">WebGL2 · 无内置 GUI</span>
@@ -67,7 +67,7 @@ const {
67
67
  <span>world ({{ canvasMenu.world[0].toFixed(2) }}, {{ canvasMenu.world[1].toFixed(2) }}) · 选中 {{ canvasMenu.selection?.count ?? 0 }} 个</span>
68
68
  <button type="button" @click="closeCanvasMenu">×</button>
69
69
  </div>
70
- <p class="demo-channel-menu-hint">右键载荷(onContextMenu)里就带着 x / y、world、命中的要素与 selection 读数:这份菜单整个写在 demo 里,组件不带任何界面。每一行都是一个公开方法;落在空白处先 preventDefault 掉浏览器自带菜单。空白处多一行「新增场站」:弹一份 demo 自己的表单,收集字段后走 addPoint,落点默认就是这一下右键的 world 坐标。</p>
70
+ <p class="demo-channel-menu-hint">右键载荷(onContextMenu)里带着命中的要素、world 与 originalEvent,选中集读数由 view.getSelectionInfo() / getSelectionData() 取:这份菜单整个写在 demo 里,组件不带任何界面。每一行都是一个公开方法;落在空白处先对 originalEvent 调 preventDefault 收掉浏览器自带菜单。空白处多一行「新增场站」:弹一份 demo 自己的表单,收集字段后走 addPoint,落点默认就是这一下右键的 world 坐标。</p>
71
71
  <div class="demo-channel-menu-actions">
72
72
  <button v-for="row in canvasMenuRows" :key="row.id" type="button" :data-demo="'canvas-menu-' + row.id" :disabled="!row.enabled" :title="row.reason" @click="runCanvasMenuAction(row)">{{ row.label }}</button>
73
73
  </div>