gis-view-webgl2 0.1.4 → 0.1.6

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 (37) hide show
  1. package/README.md +10 -7
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.ts +225 -20
  4. package/dist/index.js +1 -1
  5. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +109 -0
  6. package/docs/GisView.md +53 -53
  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 +91 -0
  8. package/docs/demos/ConnectDemo.vue +114 -3
  9. package/docs/demos/MinimapDemo.vue +216 -17
  10. package/docs/demos/SearchFilterDemo.vue +113 -1
  11. package/docs/demos/api-index.js +28 -20
  12. package/docs/demos/demos.js +1 -1
  13. package/docs/demos/gis-view-demo/GisViewDemoStage.vue +25 -16
  14. package/docs/demos/gis-view-demo/useGisViewDemo.js +27 -16
  15. package/docs/index.html +3 -3
  16. package/docs/internals/13-/351/271/260/347/234/274/345/233/276.md +8 -5
  17. package/docs/internals/16-01-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/212.md +18 -5
  18. package/docs/internals/16-03-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/213.md +11 -1
  19. package/docs/internals/18-/346/200/247/350/203/275/345/256/236/346/265/213/344/270/216WebGPU/350/257/204/344/274/260.md +73 -1
  20. package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +14 -5
  21. package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +2 -2
  22. package/docs/manual/05-/347/224/273/350/264/250/344/270/216/346/211/271/351/207/217/346/233/264/346/226/260.md +1 -1
  23. package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +1 -1
  24. 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 +423 -3
  25. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +99 -30
  26. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +12 -7
  27. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +14 -6
  28. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +137 -6
  29. package/docs/manual/19-/350/257/273/346/225/260/344/270/216/346/213/226/346/213/275/351/242/204/350/247/210.md +4 -0
  30. package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +8 -3
  31. 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 +6 -3
  32. package/docs/steps/README.md +6 -6
  33. package/docs/steps/selftest-demos.html +252 -9
  34. package/docs/steps/selftest-docs.html +3 -3
  35. package/docs/steps/selftest-full.html +357 -13
  36. package/docs/steps/selftest-perf.html +599 -382
  37. package/package.json +1 -1
@@ -142,12 +142,14 @@
142
142
  miniHost.style.cssText = 'position:absolute;left:-9999px;width:180px;height:120px'
143
143
  document.body.append(miniHost)
144
144
  const miniData = {
145
- points: [{ x: 50, y: 50 }],
145
+ points: [{ x: 50, y: 50, size: 10, inheritSize: true }],
146
146
  lines: [
147
147
  { path: [[0, 0], [100, 0]], minX: 0, minY: 0, maxX: 100, maxY: 0 },
148
148
  { path: [[1000, 1000], [1100, 1000]], minX: 1000, minY: 1000, maxX: 1100, maxY: 1000 }
149
149
  ]
150
150
  }
151
+ const miniViewport = { cssWidth: 450, cssHeight: 600, pixelRatio: 1, displayScale: 1 }
152
+ let miniCameraScale = 1
151
153
  const mini = createMinimap(miniHost, {
152
154
  enabled: true,
153
155
  width: 180,
@@ -157,8 +159,8 @@
157
159
  maxPixelRatio: 1
158
160
  }, {
159
161
  getData: () => miniData,
160
- getCamera: () => ({ center: [50, 50], scale: 1 }),
161
- getViewport: () => ({ width: 180, height: 120, pixelRatio: 1, displayScale: 1 }),
162
+ getCamera: () => ({ center: [50, 50], scale: miniCameraScale }),
163
+ getViewport: () => miniViewport,
162
164
  getFallbackBounds: () => ({ minX: 0, minY: 0, maxX: 100, maxY: 100 }),
163
165
  getZoomBaseScale: () => 0,
164
166
  getBackground: () => null
@@ -168,6 +170,21 @@
168
170
  check('鹰眼图线路层:首帧只铺固定范围里的线路(范围外那条被跳过)',
169
171
  miniFirst.lineRasterDraws > 0 && miniFirst.linePathsDrawn === miniFirst.lineRasterDraws,
170
172
  JSON.stringify(miniFirst))
173
+ const inheritedAtBase = miniFirst.paintedInheritedSizeFactor
174
+ miniCameraScale = 2
175
+ mini.refresh()
176
+ await wait(100)
177
+ const inheritedAfterMainZoom = mini.getPaintStats().paintedInheritedSizeFactor
178
+ check('鹰眼图:沿用主图尺寸按 CSS 视口比例缩放,主图放大不改变继承节点大小',
179
+ Math.abs(inheritedAtBase - 0.2) < 1e-6 && Math.abs(inheritedAfterMainZoom - inheritedAtBase) < 1e-6,
180
+ `${inheritedAtBase} -> ${inheritedAfterMainZoom}`)
181
+ miniViewport.cssHeight = 300
182
+ mini.refresh()
183
+ await wait(100)
184
+ const inheritedAfterResize = mini.getPaintStats().paintedInheritedSizeFactor
185
+ check('鹰眼图:主图视口尺寸变化后,沿用主图尺寸按新的视口比例重铺',
186
+ Math.abs(inheritedAfterResize - 0.4) < 1e-6,
187
+ String(inheritedAfterResize))
171
188
  mini.refresh()
172
189
  await wait(100)
173
190
  const miniRepeat = mini.getPaintStats()
@@ -422,6 +439,25 @@
422
439
  const draftArrow = channelDraftArrowPlacement([[0, 0], [60, 0], [60, 80]])
423
440
  check('可视化连线:预览箭头中心按整条折线弧长取中点(不是贴在最后一段末端),与落地线路的单箭头同口径', Boolean(draftArrow) && Math.abs(draftArrow.center[0] - 60) < 1e-9 && Math.abs(draftArrow.center[1] - 10) < 1e-9 && draftArrow.unitX === 0 && draftArrow.unitY === 1, JSON.stringify(draftArrow))
424
441
 
442
+ const startPointSelect = document.querySelector('[data-demo=connect-start-point]')
443
+ startPointSelect.value = 'node-0'
444
+ startPointSelect.dispatchEvent(new Event('change', { bubbles: true }))
445
+ await wait(60)
446
+ document.querySelector('[data-demo=connect-start]').click()
447
+ await wait(80)
448
+ const presetStartDraft = view.getAddChannelDraft()
449
+ check('可视化连线:下拉指定起点后,beginAddChannel 直接进入 from 草稿且下一次点击只选终点', presetStartDraft?.phase === 'from' && String(presetStartDraft?.from?.id) === 'node-0', presetStartDraft ? `${presetStartDraft.phase} / from ${presetStartDraft.from?.id}` : '没有草稿')
450
+ await clickNode('node-3')
451
+ const presetReadyDraft = await until(() => {
452
+ const current = view.getAddChannelDraft()
453
+ return current?.phase === 'ready' ? current : null
454
+ }, 2500)
455
+ check('可视化连线:指定起点 + 一次节点点击即可形成 ready 草稿', presetReadyDraft?.from?.id === 'node-0' && presetReadyDraft?.to?.id === 'node-3', presetReadyDraft ? `${presetReadyDraft.from?.id} → ${presetReadyDraft.to?.id}` : '没有 ready 草稿')
456
+ view.cancelAddChannel()
457
+ startPointSelect.value = ''
458
+ startPointSelect.dispatchEvent(new Event('change', { bubbles: true }))
459
+ await wait(80)
460
+
425
461
  const cancelBefore = view.getData()
426
462
  const cancelDraft = await selectPair('node-0', 'node-3')
427
463
  const style = cancelDraft?.style ?? view.getOptions().channelDraftStyle
@@ -457,6 +493,21 @@
457
493
  const spacingInput = document.querySelector('[data-demo=connect-spacing]')
458
494
  spacingInput.value = '26'
459
495
  spacingInput.dispatchEvent(new Event('input', { bubbles: true }))
496
+ const dotSizeInput = document.querySelector('[data-demo=connect-dot-size]')
497
+ dotSizeInput.value = '9'
498
+ dotSizeInput.dispatchEvent(new Event('input', { bubbles: true }))
499
+ const dotShapeSelect = document.querySelector('[data-demo=connect-dot-shape]')
500
+ dotShapeSelect.value = 'square'
501
+ dotShapeSelect.dispatchEvent(new Event('change', { bubbles: true }))
502
+ const startDotColorInput = document.querySelector('[data-demo=connect-dot-from-color]')
503
+ startDotColorInput.value = '#00aa88'
504
+ startDotColorInput.dispatchEvent(new Event('input', { bubbles: true }))
505
+ const endDotColorInput = document.querySelector('[data-demo=connect-dot-to-color]')
506
+ endDotColorInput.value = '#aa2200'
507
+ endDotColorInput.dispatchEvent(new Event('input', { bubbles: true }))
508
+ const draftLayerSelect = document.querySelector('[data-demo=connect-draft-layer]')
509
+ draftLayerSelect.value = 'below-points'
510
+ draftLayerSelect.dispatchEvent(new Event('change', { bubbles: true }))
460
511
  await wait(100)
461
512
  const beforeBlankCancel = view.getData()
462
513
  document.querySelector('[data-demo=connect-start]').click()
@@ -467,10 +518,14 @@
467
518
  return current?.phase === 'from' ? current : null
468
519
  }, 2500)
469
520
  check('可视化连线:3 回通道草稿把箭头位置与回线间距直接带进本次样式', stagedDraft?.kind === 'channel' && stagedDraft?.circuits === 3 && stagedDraft?.style?.arrowPositions?.join(',') === '0.25,0.75' && stagedDraft?.style?.circuitSpacing === 26, stagedDraft ? JSON.stringify({ kind: stagedDraft.kind, circuits: stagedDraft.circuits, arrows: stagedDraft.style?.arrowPositions, spacing: stagedDraft.style?.circuitSpacing }) : '没有 from 草稿')
521
+ check('可视化连线:两端示例点的尺寸 / 形状 / 分端颜色与 below-points 层级都进入草稿样式', stagedDraft?.style?.dots?.size === 9 && stagedDraft?.style?.dots?.shape === 'square' && stagedDraft?.style?.dots?.from?.color === '#00aa88' && stagedDraft?.style?.dots?.to?.color === '#aa2200' && stagedDraft?.style?.layer === 'below-points', JSON.stringify(stagedDraft?.style ? { dots: stagedDraft.style.dots, layer: stagedDraft.style.layer } : null))
470
522
  await clickCanvasAt(box.left + 18, box.top + box.height - 18)
471
523
  const blankCancelled = await until(() => view.getAddChannelDraft() === null, 2500)
472
524
  const afterBlankCancel = view.getData()
473
525
  check('可视化连线:选好起点后点空白立即取消,readout 报 click-outside-node 且数据不增加', Boolean(blankCancelled) && text('connect-cancel-readout').includes('click-outside-node') && afterBlankCancel.lines.length === beforeBlankCancel.lines.length && afterBlankCancel.points.length === beforeBlankCancel.points.length && channelList().length === 0, `${text('connect-cancel-readout')} / ${beforeBlankCancel.lines.length} -> ${afterBlankCancel.lines.length} 线`)
526
+ draftLayerSelect.value = 'above'
527
+ draftLayerSelect.dispatchEvent(new Event('change', { bubbles: true }))
528
+ await wait(60)
474
529
 
475
530
  // 恢复独立线路配置,继续验证后面的真实落地入口
476
531
  kindSelect.value = 'line'
@@ -480,6 +535,13 @@
480
535
  spacingInput.value = '12'
481
536
  spacingInput.dispatchEvent(new Event('input', { bubbles: true }))
482
537
  await wait(100)
538
+ const beforeObjectLine = view.getData()
539
+ const objectLineDraft = await selectPair('node-3', 'node-6')
540
+ document.querySelector('[data-demo=connect-commit-object]').click()
541
+ await wait(160)
542
+ const afterObjectLine = view.getData()
543
+ const objectLine = afterObjectLine.lines.at(-1)
544
+ check('可视化连线:commitAddChannel 直接传线路对象即可落地,对象里的 path / 颜色 / 线宽优先于草稿', Boolean(objectLineDraft) && afterObjectLine.lines.length === beforeObjectLine.lines.length + 1 && channelList().length === 0 && String(objectLine?.id).startsWith('connect-object-line-') && (objectLine?.path?.length ?? 0) > 2 && objectLine?.color === '#2f6fd0' && objectLine?.width === 2.4, `${beforeObjectLine.lines.length} -> ${afterObjectLine.lines.length} 线 / ${objectLine?.id ?? '无'} / path ${objectLine?.path?.length ?? 0} / ${objectLine?.color} ${objectLine?.width}`)
483
545
  kindSelect.value = 'channel-2'
484
546
  kindSelect.dispatchEvent(new Event('change', { bubbles: true }))
485
547
  await wait(80)
@@ -490,6 +552,13 @@
490
552
  const afterChannels = view.getData()
491
553
  const addedChannel = channelList().at(-1)
492
554
  check('可视化连线:addChannels 入口真实新建通道并按 circuits 加回线', Boolean(channelDraft) && channelList().length === 1 && afterChannels.lines.length === beforeChannels.lines.length + 2 && addedChannel?.path?.length === 1, `${channelList().length} 通道 / ${beforeChannels.lines.length} -> ${afterChannels.lines.length} 线 / path ${addedChannel?.path?.length ?? 0} / ${text('connect-action-readout')}`)
555
+ const beforeObjectChannel = view.getData()
556
+ const objectChannelDraft = await selectPair('node-2', 'node-10')
557
+ document.querySelector('[data-demo=connect-commit-object]').click()
558
+ await wait(180)
559
+ const afterObjectChannel = view.getData()
560
+ const objectChannel = channelList().at(-1)
561
+ check('可视化连线:commitAddChannel 直接传通道对象即可按 lineIds / groups 落地', Boolean(objectChannelDraft) && channelList().length === 2 && afterObjectChannel.lines.length === beforeObjectChannel.lines.length + 2 && String(objectChannel?.id).startsWith('connect-object-channel-') && objectChannel?.lineIds?.length === 2 && objectChannel.lineIds.every(id => String(id).startsWith('connect-object-line-')), `${channelList().length} 通道 / ${beforeObjectChannel.lines.length} -> ${afterObjectChannel.lines.length} 线 / ${objectChannel?.id ?? '无'} / ${JSON.stringify(objectChannel?.lineIds ?? [])}`)
493
562
  check('可视化连线:读数面板同步报出纯点 + 线 / 通道数量', text('connect-data-readout').includes('节点') && text('connect-data-readout').includes('通道'), text('connect-data-readout'))
494
563
  }
495
564
  else {
@@ -573,6 +642,111 @@
573
642
  document.querySelector('[data-demo=minimap-zoom-reset]').click()
574
643
  await wait(120)
575
644
  check('鹰眼图配置:1× 按钮把倍数复位成 1', Math.abs(view.getMinimapZoom() - 1) < 1e-9, text('minimap-zoom-readout'))
645
+ const extentFollowInput = document.querySelector('[data-demo=minimap-extent-follow]')
646
+ check('鹰眼图配置:extentFollowCamera 默认关闭,自动范围仍按数据 ∪ 底图', Boolean(extentFollowInput) && !extentFollowInput.checked && setup.minimapOptions().extentFollowCamera === false && view.getOptions().minimap.extentFollowCamera === false, extentFollowInput ? `checked ${extentFollowInput.checked} / option ${setup.minimapOptions().extentFollowCamera}` : '没有范围开关')
647
+ extentFollowInput.click()
648
+ await wait(160)
649
+ check('鹰眼图配置:范围跟相机开关打开后下发 extentFollowCamera=true', setup.minimapOptions().extentFollowCamera === true && view.getOptions().minimap.extentFollowCamera === true, String(setup.minimapOptions().extentFollowCamera))
650
+ extentFollowInput.click()
651
+ await wait(120)
652
+ const extentContentInput = document.querySelector('[data-demo=minimap-extent-content]')
653
+ check('鹰眼图配置:extentFollowContent 默认关闭,内容变化不会重算小图范围', Boolean(extentContentInput) && !extentContentInput.checked && setup.minimapOptions().extentFollowContent === false && view.getOptions().minimap.extentFollowContent === false, extentContentInput ? `checked ${extentContentInput.checked} / option ${setup.minimapOptions().extentFollowContent}` : '没有内容范围开关')
654
+ extentContentInput.click()
655
+ await wait(160)
656
+ check('鹰眼图配置:内容范围开关打开后下发 extentFollowContent=true', setup.minimapOptions().extentFollowContent === true && view.getOptions().minimap.extentFollowContent === true, String(setup.minimapOptions().extentFollowContent))
657
+ extentContentInput.click()
658
+ await wait(120)
659
+ const dragHandle = document.querySelector('[data-demo=minimap-drag-handle]')
660
+ const dragRoot = document.querySelector('.gis-view__minimap')
661
+ const dragRectBefore = dragRoot?.getBoundingClientRect()
662
+ if (dragHandle && dragRoot && dragRectBefore) {
663
+ const pointer = { bubbles: true, cancelable: true, composed: true, pointerId: 23, pointerType: 'mouse', isPrimary: true, button: 0 }
664
+ const startX = dragRectBefore.left + dragRectBefore.width / 2
665
+ const startY = dragRectBefore.top + 8
666
+ dragHandle.dispatchEvent(new PointerEvent('pointerdown', { ...pointer, clientX: startX, clientY: startY }))
667
+ dragHandle.dispatchEvent(new PointerEvent('pointermove', { ...pointer, clientX: startX + 18, clientY: startY + 12 }))
668
+ dragHandle.dispatchEvent(new PointerEvent('pointerup', { ...pointer, clientX: startX + 18, clientY: startY + 12 }))
669
+ await wait(160)
670
+ const dragOffset = view.getOptions().minimap.offset
671
+ const dragRectAfter = dragRoot.getBoundingClientRect()
672
+ const moved = Math.abs((dragRectAfter.left - dragRectBefore.left) - 18) < 1 && Math.abs((dragRectAfter.top - dragRectBefore.top) - 12) < 1
673
+ check('鹰眼图配置:顶部手柄能整体拖动鹰眼图,offset 与容器位置同步', dragOffset?.[0] === 18 && dragOffset?.[1] === 12 && moved && text('minimap-offset-readout').includes('18, 12'), `offset=${JSON.stringify(dragOffset)} / ${dragRectBefore.left},${dragRectBefore.top} -> ${dragRectAfter.left},${dragRectAfter.top}`)
674
+ document.querySelector('[data-demo=minimap-offset-reset]').click()
675
+ await wait(120)
676
+ const resetOffset = view.getOptions().minimap.offset
677
+ check('鹰眼图配置:整图位置复位回到 offset [0, 0]', resetOffset?.[0] === 0 && resetOffset?.[1] === 0 && text('minimap-offset-readout').includes('0, 0'), `${JSON.stringify(resetOffset)} / ${text('minimap-offset-readout')}`)
678
+ }
679
+ else {
680
+ check('鹰眼图配置:能找到整图拖动手柄', false, dragHandle ? '小图根节点尺寸为 0' : '没有整图拖动手柄')
681
+ }
682
+ const pointSizeScaleInput = document.querySelector('[data-demo=minimap-point-size-scale]')
683
+ const lineWidthScaleInput = document.querySelector('[data-demo=minimap-line-width-scale]')
684
+ check(
685
+ '鹰眼图配置:节点 / 线路独立整体倍数控件默认都是 1',
686
+ Boolean(pointSizeScaleInput) && Boolean(lineWidthScaleInput) &&
687
+ setup.minimapOptions().pointSizeScale === 1 && setup.minimapOptions().lineWidthScale === 1,
688
+ `${setup.minimapOptions().pointSizeScale} / ${setup.minimapOptions().lineWidthScale}`
689
+ )
690
+ pointSizeScaleInput.value = '2'
691
+ pointSizeScaleInput.dispatchEvent(new Event('input', { bubbles: true }))
692
+ lineWidthScaleInput.value = '1.6'
693
+ lineWidthScaleInput.dispatchEvent(new Event('input', { bubbles: true }))
694
+ pointSizeScaleInput.dispatchEvent(new Event('change', { bubbles: true }))
695
+ await wait(160)
696
+ check(
697
+ '鹰眼图配置:独立整体倍数会随控件下发到 pointSizeScale / lineWidthScale',
698
+ setup.minimapOptions().pointSizeScale === 2 && setup.minimapOptions().lineWidthScale === 1.6 &&
699
+ view.getOptions().minimap.pointSizeScale === 2 && view.getOptions().minimap.lineWidthScale === 1.6,
700
+ `${setup.minimapOptions().pointSizeScale} / ${setup.minimapOptions().lineWidthScale}`
701
+ )
702
+ pointSizeScaleInput.value = '1'
703
+ lineWidthScaleInput.value = '1'
704
+ pointSizeScaleInput.dispatchEvent(new Event('input', { bubbles: true }))
705
+ lineWidthScaleInput.dispatchEvent(new Event('input', { bubbles: true }))
706
+ pointSizeScaleInput.dispatchEvent(new Event('change', { bubbles: true }))
707
+ await wait(120)
708
+
709
+ setup.zoomMode = 'off'
710
+ setup.dragAction = 'box'
711
+ setup.applyMinimap()
712
+ view.zoomBy(2)
713
+ await wait(260)
714
+ const boxRoot = document.querySelector('.gis-view__minimap')
715
+ const boxElement = boxRoot?.querySelector('.gis-view__minimap-box')
716
+ const rootRect = boxRoot?.getBoundingClientRect()
717
+ const viewBoxRect = boxElement?.getBoundingClientRect()
718
+ let boxSelectHit = false
719
+ if (boxRoot && rootRect && viewBoxRect) {
720
+ const startX = rootRect.left + 6
721
+ const startY = rootRect.top + 6
722
+ const outside = startX < viewBoxRect.left || startX > viewBoxRect.right || startY < viewBoxRect.top || startY > viewBoxRect.bottom
723
+ if (outside) {
724
+ const pointer = { bubbles: true, cancelable: true, composed: true, pointerId: 19, pointerType: 'mouse', isPrimary: true, button: 0 }
725
+ boxRoot.dispatchEvent(new PointerEvent('pointerdown', { ...pointer, clientX: startX, clientY: startY }))
726
+ boxRoot.dispatchEvent(new PointerEvent('pointermove', { ...pointer, clientX: startX + 36, clientY: startY + 24 }))
727
+ boxRoot.dispatchEvent(new PointerEvent('pointerup', { ...pointer, clientX: startX + 36, clientY: startY + 24 }))
728
+ boxSelectHit = Boolean(await until(() => text('minimap-interaction').includes('select'), 1800))
729
+ }
730
+ }
731
+ check('鹰眼图配置:缩放关闭时 dragAction=box 仍能在空白处直接框选主图范围', boxSelectHit && setup.minimapOptions().zoomable === false && setup.dragAction === 'box', `${text('minimap-interaction')} / zoomable ${setup.minimapOptions().zoomable} / dragAction ${setup.dragAction}`)
732
+
733
+ setup.zoomMode = 'manual'
734
+ setup.dragAction = 'pan'
735
+ setup.applyMinimap()
736
+ view.fitView(44)
737
+ await wait(260)
738
+ setup.zoomMode = 'follow'
739
+ setup.followZoomScale = 1
740
+ setup.applyMinimap()
741
+ await wait(220)
742
+ const followBase = view.getMinimapZoom()
743
+ view.zoomBy(1.5)
744
+ const followed = await until(() => view.getMinimapZoom() > followBase + 0.05, 1800)
745
+ const followZoom = view.getMinimapZoom()
746
+ check('鹰眼图配置:zoomable=follow 时主图放大,小图整体倍数按 followZoomScale 跟着变化', Boolean(followed) && setup.minimapOptions().zoomable === 'follow' && followZoom > followBase, `${followBase.toFixed(3)} -> ${followZoom.toFixed(3)} / ${setup.minimapOptions().zoomable}`)
747
+ view.setMinimapZoom(1)
748
+ await wait(100)
749
+ check('鹰眼图配置:跟随主图时 setMinimapZoom 不抢控制权,倍数仍由主图决定', Math.abs(view.getMinimapZoom() - followZoom) < 1e-9, `${followZoom} -> ${view.getMinimapZoom()}`)
576
750
  }
577
751
  else {
578
752
  check('鹰眼图配置:画布与视图都起来了', false, `${ready ? 'canvas 就绪' : '没有 canvas'} / ${view ? 'view 就绪' : '没有 view'}`)
@@ -660,6 +834,71 @@
660
834
  view.focusOn(groupHit)
661
835
  view.selectFeatures(groupHit)
662
836
  check('搜索与筛选:scope=groups 返回通道分组,并可直接聚焦 / 选中分组命中', Boolean(groupReady) && readout('search-filter-summary').includes('节点 0') && readout('search-filter-summary').includes('通道 0') && groupHit?.kind === 'group' && view.getSelections().length > 0, `${readout('search-filter-summary')} / ${JSON.stringify(groupHit)}`)
837
+ // 相机飞行:duration 0 立即落位;普通时长能读进度并随时停;程序化改相机会自动让路
838
+ view.flyTo({ minX: 0, minY: 0, maxX: 10, maxY: 10 }, { duration: 0 })
839
+ const landed = view.getCamera()
840
+ check('相机飞行:duration=0 立即落位,中心与目标一致且没有残留动画',
841
+ Math.abs(landed.center[0] - 5) < 1e-6 && Math.abs(landed.center[1] - 5) < 1e-6 && view.cameraAnimating === false && view.getCameraAnimation() === null,
842
+ JSON.stringify(landed))
843
+ view.flyTo({ minX: 1000, minY: 1000, maxX: 1010, maxY: 1010 }, { duration: 600 })
844
+ const flight = view.getCameraAnimation()
845
+ check('相机飞行:进行中 cameraAnimating / getCameraAnimation / 终点读数都对得上',
846
+ view.cameraAnimating === true && flight?.active === true && flight.duration === 600 && Math.abs(flight.to.center[0] - 1005) < 1e-6 && Math.abs(flight.to.center[1] - 1005) < 1e-6,
847
+ JSON.stringify(flight))
848
+ const stopReturned = view.stopCameraAnimation()
849
+ check('相机飞行:stopCameraAnimation 链式返回实例、立即结束动画',
850
+ stopReturned === view && view.cameraAnimating === false && view.getCameraAnimation() === null)
851
+ view.flyTo({ minX: 1000, minY: 1000, maxX: 1010, maxY: 1010 }, { duration: 600 })
852
+ view.setCamera({ center: [12, 34] })
853
+ const overridden = view.getCamera()
854
+ check('相机飞行:程序化 setCamera 会取消飞行,最终相机以调用方为准',
855
+ view.cameraAnimating === false && overridden.center[0] === 12 && overridden.center[1] === 34,
856
+ JSON.stringify(overridden))
857
+ click('search-filter-fly-all')
858
+ await wait(60)
859
+ const demoFlying = readout('search-filter-camera').includes('true') && view.cameraAnimating && readout('search-filter-action').includes('flyTo')
860
+ click('search-filter-stop-flight')
861
+ await wait(60)
862
+ const demoStopped = readout('search-filter-camera').includes('false') && !view.cameraAnimating && readout('search-filter-action').includes('stopCameraAnimation')
863
+ check('相机飞行 demo:按钮能启动并停止飞行,读数同步 cameraAnimating', demoFlying && demoStopped, `${readout('search-filter-camera')} / ${readout('search-filter-action')}`)
864
+ // flyToCamera:只给 center 时比例沿用当前相机;只给 scale 时中心沿用当前相机
865
+ const cameraApiScale = view.getCamera().scale
866
+ const flyCameraReturned = view.flyToCamera({ center: [20, 30] }, { duration: 0 })
867
+ const centerOnly = view.getCamera()
868
+ view.flyToCamera({ scale: 5 }, { duration: 0 })
869
+ const scaleOnly = view.getCamera()
870
+ check('flyToCamera:未写的相机字段沿用当前值,duration=0 立即落位并链式返回实例',
871
+ flyCameraReturned === view && Math.abs(centerOnly.center[0] - 20) < 1e-6 && Math.abs(centerOnly.center[1] - 30) < 1e-6 && Math.abs(centerOnly.scale - cameraApiScale) < 1e-6 &&
872
+ Math.abs(scaleOnly.center[0] - 20) < 1e-6 && Math.abs(scaleOnly.center[1] - 30) < 1e-6 && Math.abs(scaleOnly.scale - 5) < 1e-6,
873
+ `center-only ${JSON.stringify(centerOnly)} / scale-only ${JSON.stringify(scaleOnly)}`)
874
+ click('search-filter-fly-camera')
875
+ await wait(80)
876
+ const cameraPresetFlying = view.cameraAnimating && readout('search-filter-action').includes('flyToCamera')
877
+ view.stopCameraAnimation()
878
+ check('flyToCamera demo:按钮直接飞相机目标,不经过要素解析', cameraPresetFlying, `${readout('search-filter-camera')} / ${readout('search-filter-action')}`)
879
+ // 线路窄样式:换色 / 加宽后 getLineStyle 立刻是新值,dataVersion 不动
880
+ const styleVersionBefore = view.dataVersion
881
+ const styleBefore = view.getLineStyle(0)
882
+ click('search-filter-line-recolor')
883
+ await wait(100)
884
+ const styleAfter = view.getLineStyle(0)
885
+ check('getLineStyle:线路换色 / 加宽后读到最新解析值,dataVersion 不变',
886
+ styleBefore !== null && styleAfter !== null && Math.abs(styleAfter.width - 3.2) < 1e-6 && Math.abs(styleAfter.opacity - 1) < 1e-6 &&
887
+ styleAfter.color.some((value, index) => Math.abs(value - styleBefore.color[index]) > 1e-3) &&
888
+ styleVersionBefore === view.dataVersion && readout('search-filter-line-style').includes('width=3.20'),
889
+ `${JSON.stringify(styleBefore)} -> ${JSON.stringify(styleAfter)} · dataVersion ${styleVersionBefore} -> ${view.dataVersion}`)
890
+ click('search-filter-line-recolor')
891
+ await wait(80)
892
+ // 数据修订号:改点后递增,onDataChange 载荷与只读属性是同一个数
893
+ const dataVersionBefore = view.dataVersion
894
+ click('search-filter-data-bump')
895
+ const dataVersionText = await until(() => {
896
+ const text = readout('search-filter-data-version')
897
+ return view.dataVersion > dataVersionBefore && text.includes(`dataVersion=${view.dataVersion}`) && text.includes(`onDataChange=${view.dataVersion}`) ? text : ''
898
+ }, 3000) ?? ''
899
+ check('dataVersion:改一个点后递增,onDataChange 载荷与只读属性同步',
900
+ Boolean(dataVersionText) && view.dataVersion > dataVersionBefore,
901
+ `${dataVersionBefore} -> ${view.dataVersion} / ${dataVersionText}`)
663
902
  click('search-filter-preset-symbol')
664
903
  const symbolReady = await until(() => readout('search-filter-summary').includes('节点 8'), 4000)
665
904
  check('搜索与筛选:显式 fields=[symbol] 能搜默认跳过的符号字段', Boolean(symbolReady) && readout('search-filter-hit-0').includes('symbol = square'), `${readout('search-filter-summary')} / ${readout('search-filter-hit-0')}`)
@@ -2254,18 +2493,22 @@
2254
2493
  await wait(150)
2255
2494
  const apiItems = () => [...document.querySelectorAll('.demo-api-list:not(.demo-api-list--options) .demo-api-item')]
2256
2495
  const optionItems = () => [...document.querySelectorAll('.demo-api-list--options .demo-api-item')]
2257
- check('实例方法速查:整份成员清单按分组列全(16 组 / 256 个成员)',
2258
- Boolean(ready) && document.querySelectorAll('.demo-api-group').length === 16 && apiItems().length === 256,
2496
+ check('实例方法速查:整份成员清单按分组列全(16 组 / 264 个成员)',
2497
+ Boolean(ready) && document.querySelectorAll('.demo-api-group').length === 16 && apiItems().length === 264,
2259
2498
  `${document.querySelectorAll('.demo-api-group').length} 组 / ${apiItems().length} 个成员`)
2260
2499
  const apiNames = apiItems().map(node => node.querySelector('b')?.textContent)
2261
2500
  const roundNames = ['registerSymbol', 'unregisterSymbol', 'listSymbols']
2262
2501
  check('实例方法速查:本轮新增的三个方法都在清单里(registerSymbol / unregisterSymbol / listSymbols)',
2263
2502
  roundNames.every(name => apiNames.includes(name)),
2264
2503
  roundNames.filter(name => !apiNames.includes(name)).join(' / ') || '三个都在')
2265
- const roundNamesAuto = ['getZoomBaseScale', 'setZoomBaseScale']
2266
- check('实例方法速查:自适应比例的读 / 写两个方法在清单里(getZoomBaseScale / setZoomBaseScale)',
2267
- roundNamesAuto.every(name => apiNames.includes(name)),
2268
- roundNamesAuto.filter(name => !apiNames.includes(name)).join(' / ') || '两个都在')
2504
+ const roundNamesAuto = ['getZoomBaseScale', 'setZoomBaseScale']
2505
+ check('实例方法速查:自适应比例的读 / 写两个方法在清单里(getZoomBaseScale / setZoomBaseScale)',
2506
+ roundNamesAuto.every(name => apiNames.includes(name)),
2507
+ roundNamesAuto.filter(name => !apiNames.includes(name)).join(' / ') || '两个都在')
2508
+ const roundNamesCameraApi = ['flyToCamera', 'getLineStyle', 'dataVersion']
2509
+ check('实例方法速查:相机目标、线路样式快照与数据版本三个成员在清单里',
2510
+ roundNamesCameraApi.every(name => apiNames.includes(name)),
2511
+ roundNamesCameraApi.filter(name => !apiNames.includes(name)).join(' / ') || '三个都在')
2269
2512
  const roundNames1597 = ['addChannelGroupLines', 'joinChannelGroupLines', 'endAddChannel']
2270
2513
  check('实例方法速查:这一轮拆出来的方法也在清单里(addChannelGroupLines / joinChannelGroupLines / endAddChannel)',
2271
2514
  roundNames1597.every(name => apiNames.includes(name)),
@@ -520,8 +520,8 @@
520
520
  if (check('API 速查:挂上了 __gisApi 钩子(数据来自 api-index.js,没钩子多半是 file:// 打开的)', Boolean(apiHook))) {
521
521
  const apiDoc = apiFrame.contentDocument
522
522
  const summary = apiHook.summary
523
- check('API 速查:清单来自 api-index(256 个成员 / 253 个方法 / 96 个 options / 16 个分组)',
524
- summary.members === 256 && summary.methods === 253 && summary.options === 96 && summary.sections === 16,
523
+ check('API 速查:清单来自 api-index(264 个成员 / 259 个方法 / 5 个属性 / 96 个 options / 16 个分组)',
524
+ summary.members === 264 && summary.methods === 259 && summary.properties === 5 && summary.options === 96 && summary.sections === 16,
525
525
  JSON.stringify(summary))
526
526
  const topJumps = [...apiDoc.querySelectorAll('.doc-jump')]
527
527
  const jumpTargets = topJumps.map(node => `${node.dataset.docJump}:${node.getAttribute('href')}`)
@@ -559,7 +559,7 @@
559
559
  `${apiDoc.querySelectorAll('.api-member').length} 张卡片 / 属性 ${apiDoc.querySelectorAll('.api-member[data-kind="property"]').length} 张`)
560
560
  const noExample = [...apiDoc.querySelectorAll('.api-member')].filter(node => Number(node.dataset.examples) < 1)
561
561
  check('API 速查:每个成员都带至少一段示例(不出现空卡片)', noExample.length === 0,
562
- noExample.length ? noExample.map(node => node.dataset.name).join(' / ') : '256 张卡片都有示例')
562
+ noExample.length ? noExample.map(node => node.dataset.name).join(' / ') : '264 张卡片都有示例')
563
563
  check('API 速查:96 个 options 都列全(名字 / 类型 / 默认值 / 说明四件套)',
564
564
  apiDoc.querySelectorAll('.api-option').length === summary.options && [...apiDoc.querySelectorAll('.api-option')].every(node => node.querySelector('.name code') && node.querySelector('.type') && node.querySelector('.def') && node.querySelector('.doc')),
565
565
  `${apiDoc.querySelectorAll('.api-option').length} 个`)