gis-view-webgl2 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -8
- package/dist/gis-view.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1036 -249
- package/dist/index.js +1 -1
- package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +194 -0
- package/docs/GisView.md +9 -8
- package/docs/GisView/345/256/236/347/216/260/345/216/237/347/220/206.md +2 -2
- package/docs/ai/06-/344/274/232/350/257/235/347/264/242/345/274/225/344/270/216/345/244/215/346/237/245/346/226/271/346/263/225.md +172 -0
- package/docs/api/index.html +545 -59
- package/docs/demos/BackendSyncDemo.vue +10 -8
- package/docs/demos/BasicDemo.vue +101 -101
- package/docs/demos/BatchAddDemo.vue +5 -5
- package/docs/demos/ChannelDemo.vue +617 -10
- package/docs/demos/ConnectDemo.vue +392 -0
- package/docs/demos/CopyMergeDemo.vue +980 -929
- package/docs/demos/DecorationDraftDemo.vue +1300 -0
- package/docs/demos/DemoShell.vue +569 -240
- package/docs/demos/DragDropDemo.vue +11 -11
- package/docs/demos/EffectsDemo.vue +6 -6
- package/docs/demos/EventDemo.vue +327 -327
- package/docs/demos/ExtensionDemo.vue +458 -0
- package/docs/demos/GisViewDemo.vue +61 -48
- package/docs/demos/GraphicDemo.vue +338 -0
- package/docs/demos/LabelDemo.vue +553 -497
- package/docs/demos/MinimapDemo.vue +358 -0
- package/docs/demos/PerfDemo.vue +29 -0
- package/docs/demos/SearchFilterDemo.vue +412 -0
- package/docs/demos/StyleDemo.vue +121 -121
- package/docs/demos/api-index.js +98 -77
- package/docs/demos/demos.js +159 -135
- package/docs/demos/shared/demoData.js +107 -107
- package/docs/demos/shared/demoStyle.css +249 -249
- package/docs/demos/shared/fakeBackend.js +93 -6
- package/docs/format/03-/346/213/220/347/202/271/350/243/205/351/245/260/344/270/216/346/240/267/345/274/217.md +28 -8
- package/docs/format/04-/351/200/232/351/201/223/344/270/216/346/240/207/347/255/276.md +5 -4
- package/docs/format/06-/345/257/274/345/205/245/345/257/274/345/207/272/344/270/216/346/240/207/345/207/206/346/226/207/346/241/243.md +8 -6
- package/docs/format/07-/344/272/213/344/273/266/350/275/275/350/215/267/344/270/216/347/244/272/344/276/213/346/225/260/346/215/256.md +6 -6
- package/docs/guide/sample-document.html +4 -4
- package/docs/index.html +180 -9
- package/docs/internals/11-/350/246/206/347/233/226/345/261/202/346/240/207/347/255/276/344/270/216/346/217/220/347/244/272.md +1 -1
- package/docs/internals/13-/351/271/260/347/234/274/345/233/276.md +3 -2
- package/docs/internals/15-03-/351/200/220/350/275/256/350/256/260/345/275/225-/346/200/247/350/203/275/344/270/216/344/277/256/345/244/215-/344/270/213.md +16 -0
- package/docs/internals/16-03-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/213.md +11 -0
- package/docs/internals/17-/350/260/203/350/257/225/344/270/216/346/211/251/345/261/225/345/273/272/350/256/256.md +1 -1
- package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +18 -4
- package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +110 -25
- package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +13 -8
- package/docs/manual/06-/346/225/260/346/215/256/344/270/216/345/234/272/346/231/257/346/233/264/346/226/260.md +111 -10
- package/docs/manual/07-/346/214/207/351/222/210/344/270/216/350/246/201/347/264/240/347/274/226/350/276/221.md +47 -9
- package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +17 -15
- package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +61 -11
- package/docs/manual/10-/351/200/232/351/201/223/347/274/226/350/276/221/344/270/216/346/226/260/345/242/236.md +74 -7
- package/docs/manual/11-/346/240/267/345/274/217/344/270/216/350/247/204/345/210/231.md +3 -2
- package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +260 -3
- package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +6 -5
- package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +27 -14
- package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +142 -49
- package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +31 -29
- package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +55 -39
- package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +189 -64
- package/docs/manual/19-/350/257/273/346/225/260/344/270/216/346/213/226/346/213/275/351/242/204/350/247/210.md +4 -4
- package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +42 -2
- package/docs/manual/21-/350/257/264/346/230/216/344/270/216/345/270/270/350/247/201/351/227/256/351/242/230.md +22 -6
- package/docs/playground/index.html +5 -5
- package/docs/steps/README.md +14 -10
- package/docs/steps/check-doc-truth.mjs +1 -1
- package/docs/steps/selftest-actions.html +10 -10
- package/docs/steps/selftest-data.html +96 -14
- package/docs/steps/selftest-decoration.html +577 -0
- package/docs/steps/selftest-demo.html +6 -5
- package/docs/steps/selftest-demos.html +2201 -1612
- package/docs/steps/selftest-docs.html +89 -16
- package/docs/steps/selftest-effects.html +5 -5
- package/docs/steps/selftest-features.html +757 -86
- package/docs/steps/selftest-full.html +583 -583
- package/docs/steps/selftest-labels.html +1034 -652
- package/docs/steps/selftest-perf.html +1899 -1844
- package/docs/steps/selftest-scale.html +117 -7
- package/docs/steps/selftest-select.html +24 -17
- package/docs/steps/step-10-full.js +1 -1
- package/docs//346/216/245/345/217/243/345/215/217/350/256/256.md +167 -17
- package/docs//346/225/260/346/215/256/346/240/274/345/274/217.md +1 -1
- package/package.json +43 -43
- package/docs/demos/HistoryDemo.vue +0 -230
- package/docs/demos/InterfaceDemo.vue +0 -309
- package/docs/demos/SampleDemo.vue +0 -283
|
@@ -24,11 +24,23 @@
|
|
|
24
24
|
**拖也拖不动、点了没反应,是不是坏了?**
|
|
25
25
|
不是 —— 功能开关**缺省全关**(见「功能开关」),什么都不写时组件只剩画布与数据。查看类要手动打开:详细配置写 `interactive: true`(平移 / 缩放 / 右键回调)与 `clickable: true`(悬停 / 选中),或一步写 `features: 'view'`;改数据的交互各有自己的开关 —— `boxSelect: true` / `draggable: true` / `waypointEditable: true` / `copy: {…}` / `merge: {…}` / `remove: {…}` / `features: { channelDraft: true, channelMenu: true }`,`features: 'edit'` 一次全开。标签 / 提示 / 鹰眼图不在开关表里:配了 `label` / `lineLabel` / `tooltip` / `minimap` 就等于打开。
|
|
26
26
|
|
|
27
|
+
**怎么一次关闭 / 打开组件的所有功能?**
|
|
28
|
+
`features: false` 或 `features: 'static'` 会把全部 18 个功能开关关掉(`'static'` 连已配置的标签 / 提示 / 鹰眼图也一起关;`false` 与缺省全关同一口径);`features: true` 或 `features: 'edit'` 全部打开。运行时用 `view.setFeatures(...)` 换、`view.getFeatures()` 读 18 项有效值,完整清单见「功能开关」一节的表。
|
|
29
|
+
|
|
30
|
+
**怎么完全关闭拐点的新增、移动和删除?**
|
|
31
|
+
写 `features: { waypoint: false }` 或 `waypointEditable: false`。`waypoint` 就是拐点编辑的唯一总闸:新增(Ctrl + 点击、`addWaypoint` / `addWaypoints` / `addChannelGroupWaypoint`、通道右键框「加拐点」)、移动已有拐点、双击 / Delete 删除会同时停掉,没有只关其中一项的细分开关。已有拐点仍可显示和选中,数据与历史都不动。
|
|
32
|
+
|
|
33
|
+
**怎么在图上加一段与节点无关的线框、图标或 Canvas?**
|
|
34
|
+
用顶层 `graphics` 或 `addGraphic`:`type` 写 `'path'` / `'image'` / `'canvas'`,`x` / `y` 是世界坐标,`width` / `height` 默认是屏幕像素;想让它随相机一起放大缩小就写 `scaleWithView: true`。图形层在画布之上、标签之下,不参与拾取,PNG / SVG 导出都会带上,增删改用 `addGraphic` / `updateGraphic` / `removeGraphic` / `clearGraphics`。
|
|
35
|
+
|
|
36
|
+
**`channel.path` 是必填的吗?**
|
|
37
|
+
不是。它是可选骨架:两端用 `from` / `to` 锚定节点时,省略 `path` 就是两端直连,写 `path` 则只写中间拐点(首尾由节点坐标补);没有锚定时才需要完整折线,整个省略会回落到第一条回线自己的路径。配了 `groups` 时,第一组没写自己的 `path` 也会回落到通道顶层 `path`。只有需要公共中间拐点、或未锚定通道要显式骨架时才写它,见「通道与多回线」。
|
|
38
|
+
|
|
27
39
|
**数据更新之后画面没变?**
|
|
28
|
-
`updateData`
|
|
40
|
+
`updateData`(只换一列时用 `updateData({ points })` / `updateData({ lines })`)会换数据副本;直接改数组里的对象不会触发重画,改单个要素请用 `updatePoint` / `updateLine` / `updateChannel`,或者重新 `updateData` 一次。
|
|
29
41
|
|
|
30
42
|
**接口数据还没回来,先 createGisView 还是等回来再建?**
|
|
31
|
-
先建。空实例照样先铺底图 / 网格,第一次 `updateData` 拿到数据时会按 `initialView` 自动落一次位(容器当时是 0×0 也会记成待办、显示出来补上);相机、筛选、选中、标签与历史都挂在实例上,等接口回来再 `destroy()` 重建会全丢。收 loading 用 `whenDataRendered()
|
|
43
|
+
先建。空实例照样先铺底图 / 网格,第一次 `updateData` 拿到数据时会按 `initialView` 自动落一次位(容器当时是 0×0 也会记成待办、显示出来补上);相机、筛选、选中、标签与历史都挂在实例上,等接口回来再 `destroy()` 重建会全丢。收 loading 用 `whenDataRendered()`,完整写法与几条注意点在「动态换数据与加载完成」的「从接口拿数据」与「接口列名不一样」两段。
|
|
32
44
|
|
|
33
45
|
**节点拖了,外面的数据怎么同步?**
|
|
34
46
|
`onPointDrag` 的载荷里 `points` / `lines` 就是受影响的那一小片,直接回写业务数据;`getData()` 也永远是最新的。
|
|
@@ -62,6 +74,9 @@
|
|
|
62
74
|
|
|
63
75
|
**密度场那一层怎么看都糊成一片 / 看不出色带?** 先看三个数:`threshold`(门槛,抬到 0.15 ~ 0.3 就只留热点,参考图那种「大片浅色 + 白心」就是把门槛抬起来)、`gamma`(对比度,大于 1 把色带一圈圈收在热核外面)、`max`(`'auto'` 是分位膝盖,长尾数据别写死成场的峰值)。再看 `radiusRatio`:太小色斑会碎成一堆点、太大就糊成一整片,缺省 5.5% 是「一眼能看出色斑」的档。数据本身也要有疏密差别 —— 均匀铺满一屏的网格数据,密度场本来就该是平的。
|
|
64
76
|
|
|
77
|
+
**没有通道,能只用点和线吗?**
|
|
78
|
+
可以。`channels` 是可选声明,省略或写 `channels: []` 都能正常运行;`lines` 自己的 `path` / `from` / `to`、`direction`、`lineType` 与新增 / 选中 / 拐点编辑都不依赖通道。交互新增用 `beginAddChannel({ kind: 'line' })`,程序化用 `addLine` / `addLines`,不会往 `channels` 写任何东西。`?demo=connect` 就是从 `channels: []` 开始的纯点 + 线示例。
|
|
79
|
+
|
|
65
80
|
**怎么加一条不挂通道的线路?**
|
|
66
81
|
`view.addLine({ from, to })`(等价于 `addChannel({ from, to, kind: 'line' })`),走交互就是 `beginAddChannel({ kind: 'line' })`。它只往 `lines` 里加一条(或 `circuits` 条)普通线路,`channels` 声明一个字节都不动 —— `getChannels()` 里不会出现它,之后也不用担心它被通道分组、拆分、整条通道删除这些操作牵连。反过来 `beginAddChannel()` / `addChannel()` 是新建通道:新签名只负责新建,已经存在通道时要加一组 / 并进某一组用 `addChannelGroupLines` / `joinChannelGroupLines`(旧写法的 `addChannel` 仍按方案表分流)。
|
|
67
82
|
|
|
@@ -109,7 +124,7 @@
|
|
|
109
124
|
`copy.autoCommit: false` 时两条交互入口(Alt 点击 / 「先点按钮再选节点」)都停在预览态等裁决:调 `payload.commit()`(`onPointCopy` 载荷里给的)或 `commitPointCopy()` 落地,`payload.cancel()` / Esc / 右键取消;停住之后再点一下鼠标也会落地(那是用户主动点的,不受这个开关影响)。不写 `autoCommit` 就是缺省的「**松开左键**就地落地」(不是松开 Alt —— 按住左键期间 Alt 松不松都不影响),见「节点复制」。
|
|
110
125
|
|
|
111
126
|
**把节点拖到另一个节点上没弹确认框?**
|
|
112
|
-
|
|
127
|
+
只有**拖一个节点**(不是整组拖动)才参与合并。先看 `merge.trigger`:缺省 `'collision'` 要等两个节点画在屏幕上的半径相碰,`'center'` 才等拖动节点中心进入目标节点区域;两种都可在缩放很小 / 节点很小时把 `merge.distance` 调大(默认 0)。另外看 `merge.enabled`(**缺省关**:不写 `merge` 就没有合并)与目标节点的 `mergeable`;把确认方式换成 `merge.confirm: false` 就不再弹框、松手即合并,见「节点合并」。
|
|
113
128
|
|
|
114
129
|
**怎么把框选从 Shift 改成 Ctrl?**
|
|
115
130
|
`shortcuts: { boxSelect: 'ctrl' }`(默认只有框选是 Shift;多选 / 加点默认就是 Ctrl,想一起改再写 `multiSelect` / `addWaypoint`);改完 `ctrlCursor` 与 `cursorStyle.ctrl` 那套替代光标也跟着新的修饰键走,见「键盘交互」。
|
|
@@ -160,17 +175,18 @@
|
|
|
160
175
|
| 热力图 | `GisViewHeatmap`、`GisViewHeatmapGlow`、`GisViewHeatmapDensity`、`GisViewHeatmapDensityCore`、`GisViewHeatmapTarget`、`GisViewHeatmapMissing`、`GisViewHeatmapOverride` |
|
|
161
176
|
| 覆盖层 | `GisViewLabelOptions`、`GisViewLineLabelOptions`、`GisViewItemLabel`、`GisViewLabelState`、`GisViewLabelPatch`、`GisViewSetLabelContentOptions`、`GisViewLabelListItem`、`GisViewItemLabelsInfo`、`GisViewRemoveLabelResult`、`GisViewSetLabelStatesResult`、`GisViewLabelChangePayload`、`GisViewLabelDragPayload`、`GisViewLabelClickPayload`、`GisViewLabelPointerPayload`、`GisViewLabelRef`、`GisViewLabelTarget`、`GisViewLabelBatchEntry`、`GisViewLabelRemoveEntry`、`GisViewLabelBatchSkip`、`GisViewAddLabelsResult`、`GisViewUpdateLabelsResult`、`GisViewRemoveLabelsResult`、`GisViewLineDecoration`、`GisViewTooltipOptions`、`GisViewLabelPayload`、`GisViewLineLabelPayload` |
|
|
162
177
|
| 筛选 | `GisViewFilter`、`GisViewFilterResult`、`GisViewFilterCascade`、`GisViewFilterWorkerOptions` |
|
|
163
|
-
|
|
|
178
|
+
| 大数据展示模式 | `GisViewDenseMode`、`GisViewDenseModeOptions`、`GisViewDenseModeInput`、`GisViewDenseState` |
|
|
179
|
+
| 通道 | `GisViewChannelInput`、`GisViewChannelBuildOptions`、`GisViewChannelBuild`、`GisViewChannelInfo`、`GisViewChannelGroupInfo`、`GisViewChannelGroupWaypoints`、`GisViewChannelGroupLineWaypoints`、`GisViewChannelMember`、`GisViewChannelContext`、`GisViewChannelContextPayload`、`GisViewChannelAddPlan`、`GisViewChannelBetween`、`GisViewChannelAddOption`、`GisViewChannelAddInput`、`GisViewChannelAddResult`、`GisViewChannelDraftOptions`、`GisViewChannelDraftPayload`、`GisViewChannelLineMoveTarget`、`GisViewChannelLineMoveResult`、`GisViewRemoveLineResult`、`GisViewRemoveChannelResult`、`GisViewRemoveLinesResult`、`GisViewChannelInView`、`GisViewChannelAction`、`GisViewChannelActionId`、`GisViewChannelDraftStyle`、`GisViewChannelWaypointDragMode`、`createChannels`、`CHANNEL_DEFAULT_COLORS` |
|
|
164
180
|
| 事件载荷 | `GisViewPickResult`、`GisViewPickAtResult`、`GisViewRectQueryResult`、`GisViewSelectionInfo`、`GisViewSelectedItems`、`GisViewSelectPayload`、`GisViewClickPayload`、`GisViewPointerPayload`、`GisViewPointerAction`、`GisViewPointerActionType`、`GisViewPointerTarget`、`GisViewPointerModifiers`、`GisViewWaypointActionData`、`GisViewLabelActionData`、`GisViewHoverPayload`、`GisViewCursorPayload`、`GisViewPointDragPayload`、`GisViewDragPointChange`、`GisViewDragLineChange`、`GisViewPointCopyPayload`、`GisViewPointCopyState`、`GisViewPointMergePayload`、`GisViewMergeImpact`、`GisViewMergeResult`、`GisViewMergeFailure`、`GisViewWaypointPayload`、`GisViewWaypointInfo`、`GisViewWaypointKey`、`GisViewParallelGroup`、`GisViewReadyPayload`、`GisViewEngineStats`、`GisViewEngineHeatDensity`、`GisViewDataChangeType`、`GisViewDataChangePayload`、`GisViewSelectChangePayload`、`GisViewShortcutPayload`、`GisViewRenderPayload`、`GisViewAddItemsResult`、`GisViewAddPointResult` |
|
|
165
181
|
| 快捷键与复制 / 合并 / 删除 | `GisViewShortcuts`、`GisViewShortcut`、`GisViewShortcutAction`、`GisViewCopyOptions`、`GisViewCopyContext`、`GisViewCopyStartOptions`、`GisViewPasteOptions`、`GisViewCopyItem`、`GisViewClipboardItem`、`GisViewMergeOptions`、`GisViewMergeContext`、`GisViewPointStyleSnapshot`、`GisViewPointRef`、`GisViewMergeResult`、`GisViewMergeFailure`、`GisViewMergeOutcome`、`GisViewRemovePointsOutcome`、`GisViewRemoveSelectedOutcome`、`GisViewRemoveOptions`、`GisViewRemoveScope`、`GisViewRemovePayload`、`GisViewRemoveResult`、`GisViewRemoveOutcome`、`GisViewRemoveFailure`、`GisViewRemoveAction`、`GisViewRemovePoint`、`GisViewRemoveLine`、`GisViewRemoveWaypoint`、`GisViewRemoveGroup`、`GisViewRemoveChannel`、`GisViewRemoveCounts`、`GisViewChannelSelectMode`、`GisViewUpdateItemsOutcome`、`GisViewUpdateItemEntry` |
|
|
166
182
|
| 相机与视图 | `GisViewCamera`、`GisViewViewport`、`GisViewFocusOptions`、`GisViewFocusTarget`、`GisViewExportImageOptions`、`GisViewExportSelectionOptions`、`GisViewExportSelectionResult` |
|
|
167
|
-
| 导出数据 | `GisViewExportDataOptions`、`GisViewExportDataResult`、`GisViewFeatureRef`、`GisViewExportFilterOptions`、`GisViewExportMeta`、`GisViewExportedLabel`、`GisViewStationRecord`、`GisViewPointRecord`、`GisViewLineRecord`、`GisViewChannelRecord`、`
|
|
183
|
+
| 导出数据 | `GisViewExportScope`、`GisViewExportPart`、`GisViewExportDataOptions`、`GisViewExportDataResult`、`GisViewFeatureRef`、`GisViewExportFilterOptions`、`GisViewExportMeta`、`GisViewExportedLabel`、`GisViewStationRecord`、`GisViewPointRecord`、`GisViewLineRecord`、`GisViewChannelRecord`、`GisViewExportFormat`、`GisViewBuiltinExportFormat`、`GisViewExportFormatContext`、`GisViewExportFormatAdapter` |
|
|
168
184
|
| 开关 / 历史 / 事件 | `GisViewFeatureName`、`GisViewFeatures`、`GisViewFeatureState`、`GisViewFeaturePreset`、`GisViewFeaturesInput`、`FEATURE_NAMES`、`FEATURE_PRESETS`、`FEATURE_PRESET_NAMES`、`FEATURE_EDIT_NAMES`、`featureDefault`、`normalizeFeatures`、`featureOn`、`hasForcedFeatures`、`GisViewHistoryOptions`、`GisViewHistoryState`、`GisViewHistoryEntry`、`GisViewHistoryRecord`、`GisViewHistoryOp`、`GisViewHistoryOpEntry`、`GisViewHistoryRecorder`、`GisViewHistoryHost`、`GisViewHistoryPayload`、`GisViewHistoryStepResult`、`GisViewHistoryAction`、`GisViewHistoryCollection`、`createGisHistory`、`normalizeHistoryOptions`、`applyHistoryOps`、`DEFAULT_HISTORY_LIMIT`、`HISTORY_COLLECTIONS`、`GisViewEventMap`、`GisViewEventName`、`GisViewEventNameInput`、`GisViewEventWildcard`、`GisViewEventListener`、`GisViewAnyEventListener`、`GisViewEventBus`、`GisViewEventBusHost`、`createGisEventBus`、`EVENT_NAMES` |
|
|
169
185
|
| 动画效果与预热 | `GisViewLineEffect`、`GisViewLineEffectInput`、`GisViewPointEffect`、`GisViewPointEffectInput`、`GisViewEffectsOptions`、`GisViewEffectsInput`、`GisViewEffectsReadout`、`GisViewLineEffectReadout`、`GisViewPointEffectReadout`、`GisViewPrebakeOptions`、`GisViewPrebakeCombo`、`GisViewPrebakeReadout`、`GisViewSymbolCombos`、`PREBAKE_COMBO_KEYS`、`LINE_EFFECT_DEFAULTS`、`LINE_EFFECT_KEYS`、`POINT_EFFECT_DEFAULTS`、`POINT_EFFECT_KEYS`、`EFFECT_OPTION_KEYS`、`PREBAKE_OPTION_KEYS`、`normalizeLineEffect`、`normalizePointEffect`、`normalizeEffectsOptions`、`normalizePrebakeCombo`、`normalizePrebakeOptions`、`isEffectConfig`、`isEffectsExplicitlyOff`、`wrapPhase`、`phaseSeed`、`polylineLength`、`samplePolyline`、`lineEffectDistance`、`pointEffectPhase`、`sameLineEffect`、`samePointEffect` |
|
|
170
186
|
| 坐标与映射 | `GisViewGeoProjection`、`GisViewGeoMapper`、`GisViewGeoMapperOptions`、`GisViewPointerState`、`GisViewResolvedGeoJson`、`createGeoProjection`、`createGeoMapper` |
|
|
171
187
|
| 搜索 | `GisViewSearchOptions`、`GisViewSearchScope`、`GisViewSearchHit`、`GisViewSearchResult`、`GisViewVisiblePoint` |
|
|
172
188
|
| 背景 | `GisViewBackground`、`GisViewBackgroundImage`、`GisViewGeoJsonStyle`、`GeoJsonInput`、`GeoJsonFeatureCollection`、`GeoJsonFeature`、`GeoJsonGeometry`、`GeoJsonPosition` |
|
|
173
|
-
| 鹰眼图 | `GisViewMinimapOptions`、`GisViewMinimapExtent`、`GisViewMinimapPointStyle`、`GisViewMinimapLineStyle`、`GisViewMinimapBoxStyle`、`GisViewMinimapPointRule`、`GisViewMinimapLineRule`、`GisViewMinimapDrawData`、`GisViewMinimapDrawInfo` |
|
|
189
|
+
| 鹰眼图 | `GisViewMinimapOptions`、`GisViewMinimapExtent`、`GisViewMinimapPointStyle`、`GisViewMinimapLineStyle`、`GisViewMinimapBoxStyle`、`GisViewMinimapPointRule`、`GisViewMinimapLineRule`、`GisViewMinimapDrawData`、`GisViewMinimapDrawInfo`、`GisViewMinimapLayer`、`GisViewMinimapLayerPlacement`、`GisViewMinimapLayerStage`、`GisViewMinimapInteractionType`、`GisViewMinimapInteractionInfo` |
|
|
174
190
|
|
|
175
191
|
---
|
|
176
192
|
|
|
@@ -194,8 +194,8 @@
|
|
|
194
194
|
{
|
|
195
195
|
name: '给节点换色 / 换大小',
|
|
196
196
|
code: [
|
|
197
|
-
'view.
|
|
198
|
-
'view.
|
|
197
|
+
'view.updatePoint(\'a\', { color: \'#e0592e\' })',
|
|
198
|
+
'view.updatePoint(\'a\', { size: 22 })',
|
|
199
199
|
'view.getPointStyle(\'a\')'
|
|
200
200
|
].join('\n')
|
|
201
201
|
},
|
|
@@ -248,7 +248,7 @@
|
|
|
248
248
|
code: [
|
|
249
249
|
'const points = view.getData().points',
|
|
250
250
|
'view.updatePoint(\'c\', { name: \'丙站(改)\' })',
|
|
251
|
-
'view.
|
|
251
|
+
'view.updatePoint(\'c\', { color: \'#2ea36b\' })',
|
|
252
252
|
'view.focusOn(\'c\', { padding: 80 });',
|
|
253
253
|
'({ points: points.length, camera: view.getCamera() })'
|
|
254
254
|
].join('\n')
|
|
@@ -267,8 +267,8 @@
|
|
|
267
267
|
'// 右边已经建好一份示例场景(节点 a / b / c,线路 l1 ~ l3,通道 c1,外加一份 GeoJSON 底图)',
|
|
268
268
|
'// 上面「示例」下拉里有内置示例,也有「这一份文档」抽出来的示例(从文档中心点进来时)',
|
|
269
269
|
'',
|
|
270
|
-
'view.
|
|
271
|
-
'view.
|
|
270
|
+
'view.updatePoint(\'a\', { color: \'#e0592e\' })',
|
|
271
|
+
'view.updatePoint(\'a\', { size: 20 })',
|
|
272
272
|
'view.getData().points.length // 最后一行是返回值,会打印在下面'
|
|
273
273
|
].join('\n')
|
|
274
274
|
|
package/docs/steps/README.md
CHANGED
|
@@ -16,14 +16,14 @@ npm run dev # 或 npx vite
|
|
|
16
16
|
控制台里 `__gisSteps.view` 就是当前这一步的视图,`__gisSteps.DATA` 是那份园区数据,可以随手调 API。
|
|
17
17
|
地址栏里带步骤号(`index.html#7`)可以直接打开某一步,也能收藏 / 分享。
|
|
18
18
|
|
|
19
|
-
自测页就在同一个目录下(`selftest.html` / `selftest-demo.html` / `selftest-demos.html` / `selftest-labels.html` / `selftest-select.html` / `selftest-actions.html` / `selftest-scale.html` / `selftest-perf.html` / `selftest-features.html` / `selftest-full.html` / `selftest-effects.html` / `selftest-data.html` / `selftest-docs.html`):打开就跑,标题报 `PASS` / `FAIL`,页面上的清单是逐条断言。**无头环境下 `requestAnimationFrame` 基本不推进**,动画类断言要用真时间(CDP 或直接开浏览器)跑 —— `selftest-effects.html` 因此自己装了一个「帧泵」(把 `requestAnimationFrame` 收进 Map、手动按帧推进),涟漪与动点的断言才测得动。另有 docs/index.html(文档中心:把仓库里的 markdown 读成 HTML)、docs/api/index.html(API 速查:
|
|
19
|
+
自测页就在同一个目录下(`selftest.html` / `selftest-demo.html` / `selftest-demos.html` / `selftest-labels.html` / `selftest-select.html` / `selftest-actions.html` / `selftest-scale.html` / `selftest-perf.html` / `selftest-features.html` / `selftest-full.html` / `selftest-effects.html` / `selftest-data.html` / `selftest-decoration.html` / `selftest-docs.html`):打开就跑,标题报 `PASS` / `FAIL`,页面上的清单是逐条断言。**无头环境下 `requestAnimationFrame` 基本不推进**,动画类断言要用真时间(CDP 或直接开浏览器)跑 —— `selftest-effects.html` 因此自己装了一个「帧泵」(把 `requestAnimationFrame` 收进 Map、手动按帧推进),涟漪与动点的断言才测得动。另有 docs/index.html(文档中心:把仓库里的 markdown 读成 HTML)、docs/api/index.html(API 速查:249 个实例成员逐个一张卡,参数 / 返回 / 说明 / 示例与复制,搜索 + `#成员名` 深链、左侧分组跟随与深浅主题)、docs/guide/sample-document.html(sample-document.json 的交互图解)与 docs/steps/selftest-docs.html(上面这三个页面的自测:清单 / 渲染 / 点选标注一页跑完)。
|
|
20
20
|
|
|
21
21
|
## 怎么查全部属性与方法
|
|
22
22
|
|
|
23
|
-
组件的完整 API 是同一份清单的五个去处(当前:实例成员 **
|
|
23
|
+
组件的完整 API 是同一份清单的五个去处(当前:实例成员 **249** 个 = 246 个方法 + `el` / `canvas` / `gl` 三个属性,`options` **92** 个键,数据字段 **245** 个):
|
|
24
24
|
|
|
25
25
|
- **手册里查**:`docs/manual/` 把整本手册拆成 21 章(索引页 `docs/GisView.md` 有目录与按任务的快速跳转)—— 找方法先看 [`16 实例方法总表`](../manual/16-实例方法总表.md)、要把每个成员逐个列全(参数写「名字: 类型 = 默认值」、返回值只写类型)看 [`17 参数速查`](../manual/17-参数速查.md)、要能直接复制的调用看 [`18 逐方法示例`](../manual/18-逐方法示例.md);`options` 表与数据字段表分别在 [`03 配置项(options)`](../manual/03-配置项.md) 与 [`02 坐标与数据`](../manual/02-坐标与数据.md)。同名的接口在 `src/gis-view/types.d.ts`。
|
|
26
|
-
- **页面上查**:`docs/api/index.html`(API 速查)—— 左栏按
|
|
26
|
+
- **页面上查**:`docs/api/index.html`(API 速查)—— 左栏按 16 个分组列出全部成员,带搜索(Ctrl+K)与方法 / 属性 / options 三档开关,当前读到的分组会跟着高亮;右栏每个成员一张卡,参数 / 返回 / 说明之外,示例块直接来自 [`18 逐方法示例`](../manual/18-逐方法示例.md),带高亮与「复制名字 / 复制代码」,旁边还有「在调试页跑 →」;地址栏带 `#updateLine` 这样的 hash 直接跳到那一条。右上角主题按钮与文档中心共用 `gisDocs.reading`,跟随系统 / 浅色 / 深色三档换页不会各记一套;深链落点还留了 `scroll-margin-top`,不会被吸顶栏盖住。页面数据来自 `docs/demos/api-index.js`(生成物,禁止手改),自测页 `docs/steps/selftest-docs.html` 盯着它别掉队。
|
|
27
27
|
- **边改边跑**:`docs/playground/index.html`(调试演示页)—— 左边编辑框、右边一个真实实例(3 点 / 3 线 / 一条两组通道 + GeoJSON 底图),`Ctrl+Enter` / `Ctrl+S` 或「运行」就把代码跑在那个实例上(最后一行是返回值就打印、顶层 await 也能跑、抛错红字打在输出区并给中文提示、`createGisView('#map', …)` 沿用右边这个实例);作用域里备好 `view` / `createGisView` / `createChannels` / `log` / `sleep` / `container`(`el` / `canvas` 同一个)与输入口径快照 `points` / `lines` / `channels` / `data`;「示例」一个下拉里既有内置示例、也有「这一份文档」抽出来的示例(片段会标「(片段)」,来源文档与段数挂在子栏上),`reset` 重置场景、`copy` 复制代码、地址栏 `#code=` 直接带代码进来;从文档中心 / API 速查页的「在调试页跑 →」进来时,代码已经填好,改两笔就能看效果。
|
|
28
28
|
- **demo 里查**:`npm run dev` 之后打开 `http://localhost:5173/?demo=full`,右侧面板切到「视图 · 交互」页签,「实例方法速查」下面是整份清单 —— 搜索框、分组下拉、计数,点一条就把 `view.方法(` 填进下面的表达式框,回车当场对当前视图求值(返回值与报错都打在框下);再下面是 `options` 的折叠清单(同样可搜索)。
|
|
29
29
|
- **自己验**:`node docs/steps/check-doc-coverage.mjs` 做 10 项双向对账(实例成员 × 文档 / × 参数速查 / × 类型、参数速查与实例方法主表各自反查过期条目、类型反查过期声明、数据字段 × 文档、options 键 × 文档 / options 表反查过期字段、demo 清单是否最新),另有 `node docs/steps/check-doc-truth.mjs` 做 6 项「值」对账(options 字面量默认值 × 源码默认、事件名 × `EVENT_NAMES`、开关 × `FEATURE_NAMES`、总表 × 详解小节、白名单键的落点)。改了组件源码或文档之后先 `node docs/steps/gen-api-index.mjs` 重新生成 `docs/demos/api-index.js`(demo 面板读的就是这一份,**禁止手改**;带 `--check` 只校验不写文件)。给组件加一个新方法的五步顺序见《实现原理》15.53。手册拆章之后,`docs/steps/doc-sources.mjs` 是唯一一份「手册有哪几份、按什么顺序拼」的清单 —— `api-source.mjs` 与三个对账脚本都从它读,加一章只改它。
|
|
@@ -106,19 +106,23 @@ API 名字与前面几步是一一对应的;第 11 步再补两件「数据之
|
|
|
106
106
|
- `selftest.html`:手写版第 1 ~ 9 步的断言(拾取、草稿的 step 1 / 2、落地、移除、连续新增、平移……);
|
|
107
107
|
- `selftest-full.html`:真组件上的同一套断言,外加把 `index.html` 的十三个步骤逐个挂载一遍(第 13 步再点一遍画质 / 批量 / 撤销 / 重做 / 功能预设五个按钮);
|
|
108
108
|
这一轮又补了四条:小图上的 GeoJSON 底图在主视图放大 3 倍前后红色描边像素数不变(容差 20%)、小图放大 6 倍时 `pointZoomScale` 指数 0 / 1 的像素对比(指数 1 至少 4 倍面积、指数 0 不长大,且倍数回到 1 时指数 1 也不再放大)、以及 stub host 上「拖节点没有结束事件、空闲计时器也会把 `hideOnInteract` 收起的标签层放出来」。
|
|
109
|
-
- `selftest-features.html
|
|
109
|
+
- `selftest-features.html`(611 条):真组件上的线型 / 线路装饰 / 光标 / 表现型配置断言 —— 把画面用 `exportImage` 读回 2D 画布数像素(实线 > 虚线 > 点线的墨量、`dc_line` 中点装饰的像素差、`showGrid` 只重画一帧),光标则用合成的指针事件与 Ctrl 键读出 `canvas.style.cursor`;装饰那几条还会量「几何变了装饰跟不跟着走」—— 写回新路径、拖绑定的节点、加 / 删拐点之后,装饰的紫色像素质心都要落在新中点上(容差 3px);另外两条量的是**固定档**:`anchor: 'fixed'` + `at` 先把装饰钉在写死的世界坐标上,再把线从水平拖成斜的,装饰必须留在原坐标、不能跑到新中点。线路标签在单独一张画布上验:默认挂在中点、内容取 `name`,`position: 0` 落到起点,`hideOnInteract` 在缩放期间给整层加 `--hidden`、停手恢复,关掉 `lineLabel` 之后标签消失。装饰缺省 `auto` 的那一串也在这里量:没有拐点时拖端点装饰保持同一个比例(一直挂在中点)、加上拐点之后钉在那一刻的坐标上(改折线也不动)、把拐点删回直线又回到比例跟随、`anchor: 'follow'` 则一直跟到折线的新中点;`parallel` 用一条横条符号验(默认包围盒 26×6,平行之后转成 4×26),方形符号走的是 gl.POINTS 那条路(旋转后外接盒从 24×24 撑成 32×32)。通道里「每条回线各显示自己的标签」也在这里验:没有逐条配置时会被 32px 屏幕小格去重(只显示 1 ~ 2 条),给每条回线写上自己的 `lineLabelPosition` 之后 8 条全都在、位置各不相同,斜线上的标签按线路方向转、朝左的自动翻半圈(rotate = 0)。线路的层序(`zIndex`)也在这里验:画布正中一横一竖两条粗线交叉,默认后写的竖线在上(中心像素蓝),规则里给横线写 `zIndex: 5` 就换成红,`updateLine` 给竖线写 `zIndex: 9` 又换回蓝,写回 0 再回到红。这一轮另外验了插件上下文:`options.plugins` 与 `view.use()` 拿到的第二个参数是同一个冻结对象(`apiVersion === 1`、`view` 指回实例),旧的一参插件写法照常可用。这一轮另补 3 条合并触发时机断言(center 差 10px 不触发、继续拖入目标区域触发并带 trigger、collision 同距离照旧触发)。这一轮再补 1 条:13 个旧快捷方法已从实例删除,`updatePoint` / `updateLine` / `updateChannel` / `updateOptions` / `setFilter` / `setDropPreview` 仍在。对账组另补 8 条:`getChangedChannels({ ignoreGroups })` 忽略分组增删、已有分组拐点仍报、`reasons` 收窄与线路字段 / 路径 / 显隐变化,以及删除通道 / 删除线路 / 无通道场景同步清账。
|
|
110
|
+
- `selftest-decoration.html`(75 条):动态装饰器草稿专项自测 —— 开启后的 `start` 载荷、默认 14px 触发半径与每条线路不限量、指针在线路上 `hover`(命中线 / 自身 `decoration` / 平行角)、同一位置重复移动不重复发事件、移出目标 `leave`、在线路与空白处 `pointerup` 的 `ready` 差异、按下 / 抬起 `action.type` 都是 `decorationDraft`、单线 / 分组 / 整条通道三档作用域与非通道回退、`maxPerLine` 满额时悬浮与松手都不触发且提交前复核不写数据、`allowedLines` / `allowedChannels` / `allowedGroups` 三种白名单在悬浮与提交两处都拦截、`update` 改图标 / 尺寸 / 方向、`commit` 一次 `batch` 写四条且只发一条 `dataChange`、`cancel` 不改数据、重复 `begin` 替换旧草稿、整份换数据后过期草稿静默清掉。节点侧另有 32 条:`target: 'point'` 命中与只展开一个节点目标、方向 / 距离换算成屏幕偏移、相对偏移 / 固定世界坐标 / 鼠标位置三种位置来源、连接线样式随提交保存、`allowedPoints` 与 `maxPerPoint` 在悬浮 / 提交两处拦截、`both` 等距时节点优先且最近节点被白名单拦住时回退线路、节点与线路的重复 `reject` / `offset` 两种策略、节点装饰 get / update / remove 与整批提交、度转角与连接线进入矢量快照。这一轮另补 8 条节点断言:既有的 2 条「装饰随移动」仍保留;新增 6 条覆盖 `pointer + follow`、`fixed + follow`、`offset + fixed` 三种跨组合的预览与提交结果,确认位置来源和落地关系解耦后分别写成 `offset` 或 `at`。
|
|
110
111
|
- `selftest-demo.html`:demo 级自测 —— 面板上的「定位直流线路」按当前数据集的 `dc_line` 条数走(默认数据集 `new.json` 里现在自带 5 条,点一下直接选中并定位到第一条;一条都没有的数据集才切到样例数据、定位到「直流送电」,中点挂着 `PATH_DC_LINE` 装饰),断言按数据算出来的条数写、不写死数字,读数与选中面板都跟着更新;打开时说明段落默认收起、面板按组折叠,节点标签只给最高电压那一档、线路标签默认整个不显示(面板上勾「显示」再按「显示范围」展开),面板上有 PATH_DC_LINE 的「标记定位」下拉(自动 / 一直跟随 / 写死坐标三档)。「通道回线各自显示」这一档从数据层核对(勾上之后 3669 条回线都带上了自己的 `lineLabelPosition`),再放大三步后勾「与线路平行」,画出来的标签 transform 里要出现 `rotate(...)`。这一轮又补了「数据映射」下拉(raw / lngLat / fit / scale)与「节点 / geojson 比例」滑块:切到 fit 后 320 个站点都要落进 GeoJSON 画出来的范围、读数里给出换算比例;「经纬度 → 屏幕」「屏幕 → 经纬度」两个按钮来回换算一致(屏幕读数保留 1 位小数,允许 0.01° 的回差);在画布上挪一下鼠标,面板上就要出现屏幕像素与经纬度两行读数。
|
|
111
|
-
- `selftest-perf.html
|
|
112
|
-
- `selftest-scale.html
|
|
112
|
+
- `selftest-perf.html`(136 条):大数据量与画质档位的性能自测 —— 20000 点 / 8000 线的建场景与整份重排耗时、500 次拾取、逐个改 500 个节点对 `view.batch()`、四档画质与 `'auto'` 的自动升降;前面几轮补过 8 条交互期细线的断言(`getStats()` 给出 `antialias` / `software` / `thinLines` 三个读数;交互期 1px 实线整批走 `gl.LINES` 快路径、停手回到四边形展开;`interactionThinLines: false`、线宽 8px、整批带虚线这三种情况下都不走快路径,改回细实线又走);上一轮补过 7 条线路标签候选复用的断言(复用态与整层重排逐字一致,改线路内容 / 挪节点 / 相机变化之后缓存都作废)与 1 条连续更新的耗时 DIAG(2 万点 / 1 万线,中位 0.30 ms / 次);**这一轮再补 3 条标签避让盒的断言**(4 万点 + 4 万条长线放大后连续整层重排:第 2 轮起一次布局都不读、同一轮里没有哪条标签被量第二次)与 1 条量测读数 DIAG(逐轮 1427 / 0 / 0 / 0 / 0 次,旧写法 2662 / 0 … 次、第 1 轮重复量 1235 条;**上一轮把「没有哪条标签被量第二次」从按 DOM 节点去重改成按标签身份(`labelKind` # `labelIndex` # `labelLabel`)去重** —— 标签节点是池化复用的,按节点去重会把「同一格换了条标签」误报成重复量,旧代码实测第 1 轮 1235 条、新代码 0 条;**这一轮再补 3 条线路批次视口裁剪的断言**(20000 点 / 8000 线放大到 `scale: 10`:`lineInstances` 8000 → 27、相机挪回来回到 8000、来回一趟整幅像素差 0 / 241164)与 1 条读数 DIAG。这一轮再补 2 条点视口网格的断言(网格可用时枚举顺序与整份筛出来的下标序列一致、回调返回 `false` 最多只访问一个);页面每段之间让出一帧,边跑边出结果。
|
|
113
|
+
- `selftest-scale.html`(107 条):大数据交互这一轮的自测 —— 2000 点 / 2000 线、筛选开着:`batch()` 里改 200 个节点只发 1 条 `onFilter` 与 1 条 `onDataChange`(对照组逐个调用是 200 条);改字段 / 改坐标 / 改线路之后「增量补写的隐藏集」与「从零建一个同样条件的视图」逐位一致;点挪到被筛掉的坐标上,按 `coordinate` 联动的线路当场隐藏、挪回去又出来;线段网格的步进登记与多层粗格(沿线查得到、线不经过的角落不进候选、跨层只返回一次、2 万条长线密集时小范围查询的候选远少于总条数、整图估计值与总条数同量级);点 / 线 / 空白的拾取;搜索文本缓存的命中与「改过字段之后按新值搜得到、旧值搜不到」。最底下几条 DIAG 是耗时读数(`Date.now()`),无头浏览器里虚拟时钟不往前走、显示 0.0 ms,带界面的浏览器里才是真实数字。;这一轮又补了 8 条:三万点的发散渲染(超过 2 万点走分批铺开)铺完之后装饰 400 个一条不少、挪一条带装饰的线的端点只动那一条线的装饰且落在新路径中点上、贴图符号进的是贴图批次、选中的节点抬到最上层、整份重排之后装饰与动点不翻倍,以及「节点自己配的涟漪数得清」(这一条顺带抓出了发散渲染期间涟漪补不齐的 bug)。这一轮再补一条 `createPointGrid.estimate` 与 `query` 同口径、带 `stopAfter` 提前封顶的断言(不建候选数组也能判断值不值得走网格)。
|
|
113
114
|
- `bench-filter.html`:基准页(不是自测,只会打印读数)—— 20000 点 / 20000 线、`cascade: 'binding'` 的筛选开着(`filterWorker: false`),量「连换 10 次筛选条件」「`batch()` 里改 500 个节点(筛选开着 / 关掉)」「单个 `updatePoint`」「默认全字段 `search` 跑两次」这几档,每档先热身一遍再计时(`Date.now()`)。标题从 `bench running` 变成 `bench done` 表示跑完;别用 `--virtual-time-budget` 打开它 —— 虚拟时钟下同步脚本里的 `Date.now()` 不往前走,量出来全是 0。
|
|
114
115
|
- `bench-scale.html`:百万级基准页(同样只打印读数)—— 点 / 线规模与视口走 URL 参数,`window.__bench.run(options)` 量生成数据、建场景与发散渲染、平移 / 缩放 / 放大 / 拾取、`refresh` / `updatePoint` / `updateOptions`、内存账与销毁回收;另有 `prepare`(只建场景)、`__bench.op(name)`(单步)与 `__bench.micro(count)`(原语微基准)。配 `scripts/bench-scale.mjs` 用(CDP 驱动,`--points=1000000 --gpu --port=93xx`,另有 `--profile` 抓 CPU profile、`--heap` 抓堆快照);跑全部自测页用 `scripts/run-selftests.mjs`(`node scripts/run-selftests.mjs [页面名…]`,无头 Chrome + 真时间),两个脚本都要先起 dev server。
|
|
115
|
-
- `selftest-labels.html
|
|
116
|
+
- `selftest-labels.html`(161 条):标签拖动与编辑、HTML 语义、高密度画布、导出分项多选的专项自测 —— 按住标签拖 90 px 之后标签真的挪了那么多、同时画布不平移,`labelDrag` 的 `start` / `move` / `end` 三态与 `offset` / `delta` 对得上,偏移进得了 `getLabelStates()`、再拖动节点时偏移照旧叠在节点位移上,`resetLabel` 之后回到默认锚点;`updateLabel` 改内容 / 字号 / 颜色(`style` 是增量合并,只改字号不动颜色)、`resetLabels` 一次清干净;点一下标签发 `labelClick` 并把这一条交给面板;线路标签的拖动与恢复默认同一条路;导出侧验「选什么导什么」(选场站只出场站、选通道自动带上拐点、不认识的分项记一条提示),`exportData({ scope: 'selection' })` 验场站 + 通道 / 只出线路 / 没选中时的 `ok: false` 三个分支。HTML 专项验全局 / 逐条 `true` / `false`、模板与 `field` / 默认名称转义、`updateLabel` 双向切换、线路标签富文本、点击载荷保留 `html` 与原标记、`getLabels()` 报运行时偏移、只改样式不替换 HTML 子节点、导出 → 回灌保持逐条语义,以及 120 条同内容标签首轮布局读取 ≤ 4 次的缓存断言。高密度专项用 1100 条纯文本验缺省 `auto` 只留一张画布、混入一条 `html: true` 仍保留那一条 DOM、`labelRender: 'dom'` 回退逐条 DOM 并清空画布、切回 `auto` 回收上一轮 div、SVG 导出包含画布标签与缩放只推一次。另验 GeoJSON 区域文字同一相机重复刷新 0 次 `style` 写入、只改字号复用同一 DOM 节点、离屏时改样式再回到视野仍能恢复显示、只改锚点位置也会重新投影,以及同一帧 12 次相机变化只做 1 次重投影。设备像素专项把 `devicePixelRatio` 临时设为 1.25,验标签落点与拖动期整层位移都落在物理像素网格上,并确认逐条标签不再各自声明 `will-change`。
|
|
116
117
|
- `selftest-actions.html`:事件判读与只读接口的专项自测 —— 左右键、拖动、框选、拐点与标签的 `action` 三件套(type / target / data)、`button` / `modifiers` / `moved` / `double`、Shift 框选(含没框到东西的空框)与 Ctrl 取反在 `selectChange.action.type` 上的分辨、`pickAt` 的客户端坐标与只读性、`getFeaturesInRect`(含宽高为负的矩形)与 `getChannelsInView`、`addPoints` / `addPoint` / `addLines` / `removeLines` 的返回与 `dataChange` 类型;最后一段把第 12 步 `step-12-events.js` 挂起来跑一遍(判读读数、`probeAt` / `probeRect`、两条走廊)。页面底部流式打印 PASS / FAIL。
|
|
117
|
-
- `selftest-
|
|
118
|
+
- `selftest-select.html`:选择与框选的专项自测(59 条)—— 点选 / 悬停 / Ctrl 取反 / 多选开关的像素级状态样式与恢复,`select` / `deselect` / 通道写法 / 变更来源的接口断言,以及 SVG 状态贴图的预热与不同尺寸配方;这一轮补了 `selectionKinds: 'all'` 下 `selectInBox` 一次收下节点、独立线路与按 `channelSelectMode` 摊开的通道回线。
|
|
119
|
+
- `selftest-demos.html`:演示外壳与 20 个页签(19 个功能 + 综合,按基础 / 交互 / 样式 / 数据 / 性能 / 综合六组排列)的自测(297 条)—— 逐个用 `?demo=<id>` 打开基础视图、标签交互、标签显示、鹰眼图配置、样式与规则、悬停与提示、通道与数据、编辑与保存、事件与方法、批量复制与合并、拖拽落点、可视化连线、动态装饰器、批量新增、搜索与筛选、扩展能力、性能与质量、动画效果、扩展图形与综合示例,画布与右栏读数都在、切页签会把 `?demo=` 写回地址栏、切走之后上一个实例被销毁(不留画布)、缺省页签是综合示例、页签总数与注册表一致、顶部六个分组按钮一次只展开一组且组名可见;鹰眼图配置页另验 `layers` 的自定义 `draw` 真被调用、`placement` 切到数据前 / 后时 `drawInfo.stage` 跟着变、框拖动触发 `onInteraction` 并带相机、尺寸切换走 `updateOptions` 重铺画布、DPR 2 下 `maxPixelRatio` 的 1× / 2× 备份尺寸、关掉扩展层与 `zoomMinimapBy` / `setMinimapZoom`;批量复制 / 合并、通道拐点 / 分组两页另有假接口断言(Shift 框选、整份请求体、标准响应包与资源级结果、版本号 / opId、200 / 网络失败 / 500 / 409、原 opId 重试与幂等重放不推进版本、409 rebase、本地回滚);假后端严格契约另有一组直接断言(请求头与 body 不一致返回 400、baseVersion 非有限数字返回 400、同 opId 换 body 返回 409 `IDEMPOTENCY_CONFLICT` 且版本不前进),通道页还验了自定义格式按钮注册 → 导出 → 注销;扩展能力页另验自定义运算符 / 图元 / `view.use` 插件 / `defineExportFormat` 的注册、切换、卸载与业务 DTO 结构,并验同名重新注册图元当场换画笔;可视化连线页另验初始 `channels` 为空、正交折线拐点、预览箭头、取消不写数据,以及默认提交 / `addLines` / `addChannels` 三种落地;动态装饰器页另验吸附方式只有「跟随 / 固定」、两档下位置都有「相对节点 / 固定位置 / 鼠标位置」三档,固定 + 相对节点只改 `anchor`,固定 / 跟随 + 鼠标位置分别映射为 `placement: pointer` + 对应 `anchor` 且不带 `at`,跟随 + 固定位置仍传 `at`;通道与数据页另验「线路分组」弹框:分组卡片与「场站对标标识」都在,行内「确认移入」只改本地草稿、组件数据不动,取消 / Esc / 遮罩关闭不写数据,「确定分组」只调用一次 `splitChannelGroups` 把整份 groups 一次落地。页面底部流式打印 PASS / FAIL。
|
|
118
120
|
- `selftest-effects.html`:线路 / 节点动画与贴图预热的专项自测(83 条,页面自带「帧泵」—— 无头环境里 `requestAnimationFrame` 几乎不推进,它把 rAF 收进 Map、手动推帧,动画类断言才测得动):纯函数组(归一化、三层「关」的语义、按弧长采样、拐点连续、相位打散)、引擎组(铺批次 / 关掉一层 / 恢复 / 逐要素覆盖 / `getLineEffects` 全程贴线)、像素组(画布上真的有一个红点在两帧之间挪了位置)、**涟漪跟手组**(开着动画拖节点:拖动前圆心在节点坐标上、拖动中跟着走且是就地改实例缓冲、松手后整份重建也对齐、再拖回原位跟着回来,另有一条像素级断言量绿圈质心落在新坐标上)、**预热组**(全排列条数、第二次命中缓存、`getSymbolCombos()` 的去重与线路侧那一条、照着它配的一份**零张新画布**、反向一条「没画过的组合会当场烤、画布 +1」)、提醒组(`effects.line` 里写 `show` 被点名)。
|
|
119
|
-
- `selftest-data.html`:标准数据文档与渲染后端开关的自测(
|
|
121
|
+
- `selftest-data.html`:标准数据文档与渲染后端开关的自测(103 条)—— `normalizeDocument` 的标准写法与 JSON 安全(无效日期写 `null` 并告警、类实例不静默变成空对象)、整份文档直接当 `createGisView` 入参、`exportData({ format: 'full' })` 导出 → 重建 → 再导出逐字节相同、`importData` 的整份与分列、`renderer` 三档与能力探测、网格顶点缓存窗口,以及自定义导出格式的注册 / 上下文 DTO 映射 / 深拷贝 / 视口导出 / 注销后失效;断言全在接口层,不读像素。
|
|
120
122
|
- `check-sample-fields.mjs`:不是页面、是对账脚本(`node docs/steps/check-sample-fields.mjs`)—— 核对 `sample-document.json` 自己就是标准写法(`normalizeDocument` 后逐字节相同、0 条提醒)、`docs/sample-document字段详解.md` 里的读数与 `documentCounts()` 一致、以及它的「字段索引」与从文件里摘出来的字段路径集合一个不多一个不少(当前 154 个);改完样本数据跑 `--write` 重写那两段机器块(幂等)。
|
|
121
|
-
- `selftest-docs.html`:三个文档页 + 调试页的自测(
|
|
123
|
+
- `selftest-docs.html`:三个文档页 + 调试页的自测(130 条)—— 文档中心(`docs/index.html`)的清单 92 份 md(10 个根 + 82 份子文档,含新的 `接口协议.md`;树形侧栏按 `parent` / `groupLabel` 分成 6 组 `details`)、首屏 README 渲染、markdown 渲染器(表格 / 带语言代码块 / 两级列表 / 引用 / 行内代码 / md 链接 / 目录 / 首条摘要卡片 / 首段导语)、元信息里的阅读时长、索引页的子文档链接、换一份子文档、代码块复制按钮、跨文档搜索与深链、跨文档锚点链接(`文件.md#小节` 原地切文档并闪一下)、能直接跑的围栏才有的「在调试页跑」与元信息行的「这一份的调试页(N 段示例)」、390px 窄屏首屏 / 深链 / 横向溢出回归、阅读百分比与「回到顶部」的联动;调试演示页(`docs/playground/index.html`)的一个示例下拉(内置示例 + 「这一份文档」两组 optgroup)、`?doc=` 读文档抽示例、片段标注与来源子栏、顶层 await 运行、`channels` 快照、光标行列、草稿自动保存与恢复、`Ctrl+S` 运行、复制 / 清空输出、状态胶囊;API 速查页(`docs/api/index.html`)的 `__gisApi` 钩子、六张统计卡、249 张成员卡都带签名网格与示例、92 个 options 四列网格、搜索过滤与命中读数、`select(名字)` 高亮、类型开关能单独收起属性、左侧当前分组高亮、深浅主题切换与阅读设置共用;图解页(`docs/guide/sample-document.html`)的 47 条数据列表、点线路 / 分组 / 标签状态的图上标注、`点击时居中` 与 `显示编号` 开关、清空标注;三个页面都跑在 iframe 里,要用 dev server 打开。
|
|
124
|
+
|
|
125
|
+
本轮补上的两组断言:`selftest-features.html` 新增 8 条 —— 3 条验 `channelSelectMode` 在 Ctrl + 点击加点后的高亮范围(`line` / `group` / `channel`),5 条验分组 `channelWaypointDragMode: 'vertex'`(`{ x, y }` 偏移归一化并参与实际路由、同组同序号拐点按世界增量平移、相邻拐点与其它分组不动、只写 `waypointOffsets` 不改公共 `path`、`parallel_groups.group_route` 带刚性偏移)。`selftest-demos.html` 新增 1 条,验通道演示页可切到 `vertex`,且「准备两分组拐点」会拆成两组、每组两个拐点。
|
|
122
126
|
|
|
123
127
|
这些页面都要用 dev server 打开(第 10 ~ 13 步与 demo 自测要加载 `src/gis-view` 的 CSS;`selftest-demo.html` 会把 7 MB 的 `new.json` 与样例数据都加载一遍),页面底部会打印 PASS / FAIL。
|
|
124
128
|
|
|
@@ -121,4 +121,4 @@ const dropped = known.filter(name => !tableKeys.includes(name) && !intro.include
|
|
|
121
121
|
check(dropped.length === 0, `白名单键 × options 一章(${known.length} 个)`, dropped.join(' '))
|
|
122
122
|
|
|
123
123
|
console.log(fail.length ? `\n文档值对账:有 ${fail.length} 处不一致` : '\n文档值对账:全部一致')
|
|
124
|
-
process.exit(fail.length ? 1 : 0)
|
|
124
|
+
process.exit(fail.length ? 1 : 0)
|
|
@@ -168,7 +168,7 @@
|
|
|
168
168
|
|
|
169
169
|
/* ---------- 程序化选中与点空白 ---------- */
|
|
170
170
|
const apiBefore = countOf('selectChange')
|
|
171
|
-
view.
|
|
171
|
+
view.selectFeatures('p2')
|
|
172
172
|
await settle(40)
|
|
173
173
|
check('接口选中走 selectChange:action.type 是 select、cause 是 api', countOf('selectChange') === apiBefore + 1 && last('selectChange')?.action?.type === 'select' && last('selectChange')?.cause === 'api', JSON.stringify({ action: last('selectChange')?.action?.type, cause: last('selectChange')?.cause }))
|
|
174
174
|
const clearBefore = countOf('selectChange')
|
|
@@ -429,19 +429,19 @@
|
|
|
429
429
|
check('clearHover:清掉程序化悬停并补发一条 onHover(null)', view2.clearHover() === null && view2.getHover() === null && last2('hover') === null, JSON.stringify(last2('hover')))
|
|
430
430
|
|
|
431
431
|
/* 选中读数缓存 / 包围盒 / 批量更新 / 删除选中 */
|
|
432
|
-
view2.
|
|
432
|
+
view2.selectFeatures('a')
|
|
433
433
|
const infoA2 = view2.getSelectionData()
|
|
434
434
|
check('getSelectionData:接口选中之后读数当场更新(缓存不会给上一份)', infoA2.count === 1 && infoA2.ids[0] === 'a' && infoA2.pointCount === 1, JSON.stringify(infoA2))
|
|
435
435
|
const changesBeforeRepeat = count2('selectChange')
|
|
436
|
-
view2.
|
|
436
|
+
view2.selectFeatures('a')
|
|
437
437
|
check('选中集没变时不重复发 selectChange(同一条选中只发一条)', count2('selectChange') === changesBeforeRepeat, `${changesBeforeRepeat} -> ${count2('selectChange')}`)
|
|
438
|
-
view2.
|
|
438
|
+
view2.selectFeatures(['a', 'b'])
|
|
439
439
|
const bounds2 = view2.getSelectionData().bounds
|
|
440
440
|
check('getSelectionData().bounds:给出选中集的世界包围盒(两个节点时是这一段)', bounds2?.minX === 0 && bounds2?.maxX === 260 && bounds2?.minY === 0 && bounds2?.maxY === 0, JSON.stringify(bounds2))
|
|
441
|
-
view2.
|
|
441
|
+
view2.selectFeatures(null)
|
|
442
442
|
check('getSelectionData:没有选中时 bounds 是 null,读数也跟着回到 0', view2.getSelectionData().bounds === null && view2.getSelectionData().count === 0, '')
|
|
443
443
|
const dataChangesBefore2 = count2('dataChange')
|
|
444
|
-
view2.
|
|
444
|
+
view2.selectFeatures('a')
|
|
445
445
|
const updateOne2 = view2.patchPoints([{ id: 'a', name: '甲改' }, { index: 2, name: '丙改' }])
|
|
446
446
|
check('patchPoints:一次改几条(id / index 之外的字段就是补丁),返回 indexes / items / count', updateOne2?.ok === true && updateOne2.count === 2 && updateOne2.indexes[0] === 0 && updateOne2.items[0].name === '甲改' && updateOne2.items[1].name === '丙改', JSON.stringify(updateOne2 && { indexes: updateOne2.indexes, count: updateOne2.count }))
|
|
447
447
|
check('patchPoints:只发一条 updatePoints 数据变更(批量改不必逐条付事件代价)', count2('dataChange') === dataChangesBefore2 + 1 && last2('dataChange')?.type === 'updatePoints' && last2('dataChange')?.count === 2, JSON.stringify({ type: last2('dataChange')?.type }))
|
|
@@ -450,17 +450,17 @@
|
|
|
450
450
|
check('patchPoints:一份公共补丁盖到一批定位上,认不出来的进 skipped', updateMany2?.ok === true && updateMany2.count === 2 && updateMany2.skipped.length === 1 && view2.getData().points[0].status === 1 && view2.getData().points[1].status === 1, JSON.stringify({ count: updateMany2?.count, skipped: updateMany2?.skipped }))
|
|
451
451
|
const updateLines2 = view2.patchLines([{ id: 'lab', width: 7 }])
|
|
452
452
|
check('patchLines:与 patchPoints 同一套写法,dataChange 的 type 是 updateLines', updateLines2?.ok === true && updateLines2.items[0].width === 7 && last2('dataChange')?.type === 'updateLines' && view2.getData().lines[0].width === 7, JSON.stringify({ width: view2.getData().lines[0].width }))
|
|
453
|
-
view2.
|
|
453
|
+
view2.selectFeatures(['a', 'b'])
|
|
454
454
|
const removeOutcome2 = view2.removeSelected()
|
|
455
455
|
check('removeSelected:删掉选中的节点与连带的线路,返回 indexes / ids / items', removeOutcome2?.ok === true && removeOutcome2.pointIndexes.length === 2 && removeOutcome2.pointIds.join() === 'a,b' && removeOutcome2.lineIds.join() === 'lab' && removeOutcome2.lines.length === 1, JSON.stringify({ points: removeOutcome2?.pointIds, lines: removeOutcome2?.lineIds }))
|
|
456
456
|
check('removeSelected:一空掉的通道整条跟着走,删完清空选中', removeOutcome2.removedChannels === 1 && view2.getChannels().length === 0 && view2.getSelections().length === 0 && view2.getData().points.length === 1, JSON.stringify({ channels: removeOutcome2?.removedChannels, left: view2.getData().points.length }))
|
|
457
457
|
check('removeSelected:发一条 removeSelected 数据变更(不是删点 + 删线两条)', last2('dataChange')?.type === 'removeSelected' && last2('dataChange')?.ok === true, JSON.stringify({ type: last2('dataChange')?.type }))
|
|
458
458
|
const emptyRemove2 = view2.removeSelected()
|
|
459
459
|
check('removeSelected:没有选中东西时 ok 为 false 并说明原因', emptyRemove2?.ok === false && typeof emptyRemove2.reason === 'string' && emptyRemove2.pointIndexes.length === 0, JSON.stringify(emptyRemove2))
|
|
460
|
-
//
|
|
460
|
+
// updateData({ points }) 的整份替换语义没被批量补丁顶掉:给空数组就是清空
|
|
461
461
|
const replaceBefore2 = count2('dataChange')
|
|
462
|
-
view2.
|
|
463
|
-
check('
|
|
462
|
+
view2.updateData({ points: [] })
|
|
463
|
+
check('updateData({ points }):整份替换语义还在(传空数组就是清空,不走批量补丁;事件类型是 updateData)', view2.getData().points.length === 0 && count2('dataChange') === replaceBefore2 + 1 && last2('dataChange')?.type === 'updateData', JSON.stringify({ left: view2.getData().points.length, type: last2('dataChange')?.type }))
|
|
464
464
|
view2.destroy()
|
|
465
465
|
holder2.remove()
|
|
466
466
|
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
try {
|
|
23
23
|
const {
|
|
24
24
|
createGisView, normalizeDocument, isGisViewDocument, documentCounts,
|
|
25
|
-
GIS_VIEW_DOCUMENT_FORMAT, GIS_VIEW_DOCUMENT_VERSION, RENDERER_MODES, normalizeRendererOption
|
|
25
|
+
GIS_VIEW_DOCUMENT_FORMAT, GIS_VIEW_DOCUMENT_VERSION, RENDERER_MODES, normalizeRendererOption,
|
|
26
|
+
registerExportFormat, defineExportFormat, unregisterExportFormat, hasExportFormat, listExportFormats
|
|
26
27
|
} = await import('../../src/gis-view/index.js')
|
|
27
28
|
|
|
28
29
|
const box = () => {
|
|
@@ -82,8 +83,28 @@
|
|
|
82
83
|
circular.self = circular
|
|
83
84
|
const cycleDoc = normalizeDocument({ points: [circular] })
|
|
84
85
|
check('JSON 安全:循环引用不炸、记一条提醒', cycleDoc.document.points[0].self === undefined && cycleDoc.warnings.some(message => message.includes('循环引用')), cycleDoc.warnings.join(' / ').slice(0, 80))
|
|
86
|
+
const boxed = new (class Box { constructor() { this.ratio = 0.5 } })()
|
|
87
|
+
const oddValues = normalizeDocument({ points: [{ id: 'odd', x: 0, y: 0, updatedAt: new Date(Number.NaN), boxed }] })
|
|
88
|
+
check('JSON 安全:无效日期写 null、类实例跳过,都不再抛异常或悄悄写成空对象',
|
|
89
|
+
oddValues.document.points[0].updatedAt === null && oddValues.document.points[0].boxed === undefined &&
|
|
90
|
+
oddValues.warnings.some(message => message.includes('无效日期')) && oddValues.warnings.some(message => message.includes('Box')),
|
|
91
|
+
oddValues.warnings.join(' / ').slice(0, 140))
|
|
92
|
+
const typedArrays = normalizeDocument({
|
|
93
|
+
points: [{
|
|
94
|
+
id: 'typed', x: 0, y: 0,
|
|
95
|
+
big: new BigInt64Array([1n, 2n]),
|
|
96
|
+
floats: new Float32Array([Number.NaN, Infinity, 1.5]),
|
|
97
|
+
bytes: new DataView(new ArrayBuffer(4))
|
|
98
|
+
}]
|
|
99
|
+
})
|
|
100
|
+
const typedPoint = typedArrays.document.points[0]
|
|
101
|
+
check('JSON 安全:类型化数组逐项转普通数组(BigInt → 字符串、NaN / Infinity → null),DataView 跳过并提醒',
|
|
102
|
+
Array.isArray(typedPoint.big) && typedPoint.big.join(',') === '1,2' && typedPoint.big.every(value => typeof value === 'string') && JSON.stringify(typedPoint.floats) === '[null,null,1.5]' && typedPoint.bytes === undefined &&
|
|
103
|
+
typedArrays.warnings.some(message => message.includes('BigInt')) && typedArrays.warnings.some(message => message.includes('DataView')),
|
|
104
|
+
typedArrays.warnings.join(' / ').slice(0, 180))
|
|
85
105
|
check('顶层不认识的键点一次名,其余照常读', normalizeDocument({ points: [], 写错的列: 1 }).warnings.some(message => message.includes('写错的列')))
|
|
86
106
|
check('documentCounts 把拐点 / 标记 / 标签 / 分组都算清', JSON.stringify(documentCounts(doc)) === JSON.stringify({ points: 2, lines: 1, channels: 1, groups: 1, waypoints: 2, decorations: 1, labels: 2, labelStates: 1 }), JSON.stringify(documentCounts(doc)))
|
|
107
|
+
check('documentCounts 遇到非数组列按 0 处理,不抛异常', JSON.stringify(documentCounts({ points: 1, lines: { at: 0 }, channels: 'x', labelStates: false })) === JSON.stringify({ points: 0, lines: 0, channels: 0, groups: 0, waypoints: 0, decorations: 0, labels: 0, labelStates: 0 }))
|
|
87
108
|
|
|
88
109
|
/* ---------------- 文档直接当 createGisView 入参 ---------------- */
|
|
89
110
|
const warnings = []
|
|
@@ -127,6 +148,67 @@
|
|
|
127
148
|
threw = String(error.message).includes('full')
|
|
128
149
|
}
|
|
129
150
|
check('exportData 认 full,写错格式的报错里也点名了它', threw)
|
|
151
|
+
/* ---------------- 自定义导出格式:业务 DTO 不用碰组件内部数据 ---------------- */
|
|
152
|
+
const customLog = []
|
|
153
|
+
const registered = registerExportFormat('backend-dto', ({ document: source, counts, parts, warnings: notes }) => {
|
|
154
|
+
source.points[0].name = 'adapter 改的是拷贝'
|
|
155
|
+
customLog.push(parts.join(','), notes.length)
|
|
156
|
+
return {
|
|
157
|
+
station_data: source.points.map(point => ({ point_id: point.id, point_name: point.name })),
|
|
158
|
+
line_data: source.lines.map(line => ({ line_id: line.id, from: line.from, to: line.to })),
|
|
159
|
+
counts,
|
|
160
|
+
parts
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
check('自定义导出格式能注册:内置名保留、列表按「内置 + 自定义」给出',
|
|
164
|
+
registered && registerExportFormat('FULL', () => {}) === false && hasExportFormat('full') && hasExportFormat('backend-dto') &&
|
|
165
|
+
listExportFormats().join(',') === 'view,new,test,input,full,backend-dto',
|
|
166
|
+
listExportFormats().join(','))
|
|
167
|
+
const custom = view.exportData({ format: 'backend-dto', parts: ['station', 'line'], labels: false })
|
|
168
|
+
check('自定义导出格式拿到筛选后的标准文档、计数与 parts,返回业务 DTO', custom.station_data.length === 2 && custom.line_data.length === 1 && custom.counts.points === 2 && custom.parts.join(',') === 'points,lines,parallelGroups' && customLog.join('|') === 'points,lines,parallelGroups|0', JSON.stringify({ counts: custom.counts, parts: custom.parts, log: customLog }))
|
|
169
|
+
check('自定义导出格式拿到的是深拷贝:改它不动组件内部数据', view.getData().points[0].name === '甲站' && custom.station_data[0].point_name === 'adapter 改的是拷贝')
|
|
170
|
+
const customViewport = view.exportData({ scope: 'viewport', format: 'backend-dto', margin: 12 })
|
|
171
|
+
check('exportData(scope: viewport) 也认自定义格式,返回适配器结果本身(业务范围读数不额外注入)', customViewport && customViewport.station_data.length === 2 && !('viewRect' in customViewport) && !('scope' in customViewport) && !('margin' in customViewport), JSON.stringify({ keys: Object.keys(customViewport ?? {}), counts: customViewport?.counts }))
|
|
172
|
+
check('注销自定义格式之后立刻失效(不再偷偷回落到内置 view)', unregisterExportFormat('backend-dto') && !hasExportFormat('backend-dto') && (() => {
|
|
173
|
+
try {
|
|
174
|
+
view.exportData({ format: 'backend-dto' })
|
|
175
|
+
return false
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
return String(error.message).includes('registerExportFormat')
|
|
179
|
+
}
|
|
180
|
+
})())
|
|
181
|
+
const pluginLifecycle = (() => {
|
|
182
|
+
const dispose = defineExportFormat('plugin-dto', () => ({ source: 'plugin' }))
|
|
183
|
+
const first = view.exportData({ format: 'plugin-dto' }).source === 'plugin'
|
|
184
|
+
registerExportFormat('plugin-dto', () => ({ source: 'replacement' }))
|
|
185
|
+
const staleRemove = dispose()
|
|
186
|
+
const after = view.exportData({ format: 'plugin-dto' }).source === 'replacement'
|
|
187
|
+
const removed = unregisterExportFormat('plugin-dto')
|
|
188
|
+
const second = dispose()
|
|
189
|
+
return { first, staleRemove, after, removed, second }
|
|
190
|
+
})()
|
|
191
|
+
check('defineExportFormat 返回作用域清理函数:后来者覆盖注册后,旧插件卸载不误删新适配器',
|
|
192
|
+
pluginLifecycle.first && pluginLifecycle.staleRemove === false && pluginLifecycle.after && pluginLifecycle.removed && pluginLifecycle.second === false,
|
|
193
|
+
JSON.stringify(pluginLifecycle))
|
|
194
|
+
const sharedAdapter = () => ({ source: 'shared' })
|
|
195
|
+
const sharedOffA = defineExportFormat('shared-dto', sharedAdapter)
|
|
196
|
+
const sharedOffB = defineExportFormat('shared-dto', sharedAdapter)
|
|
197
|
+
const sharedStale = sharedOffA()
|
|
198
|
+
const sharedSurvived = view.exportData({ format: 'shared-dto' }).source === 'shared'
|
|
199
|
+
const sharedActive = sharedOffB()
|
|
200
|
+
const sharedRepeated = sharedOffB()
|
|
201
|
+
check('defineExportFormat:同名重复注册即使 adapter 相同也算新注册,旧清理函数不误删、新清理函数幂等',
|
|
202
|
+
sharedStale === false && sharedSurvived && sharedActive === true && sharedRepeated === false && !hasExportFormat('shared-dto'),
|
|
203
|
+
JSON.stringify({ stale: sharedStale, survived: sharedSurvived, active: sharedActive, repeated: sharedRepeated }))
|
|
204
|
+
let defineReserved = false
|
|
205
|
+
try {
|
|
206
|
+
defineExportFormat('FULL', () => {})
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
defineReserved = error instanceof TypeError && String(error.message).includes('保留')
|
|
210
|
+
}
|
|
211
|
+
check('defineExportFormat 撞内置保留名(含大小写变体)直接抛 TypeError,不静默降级', defineReserved)
|
|
130
212
|
const filters = createGisView(box(), { ...doc, filter: { points: { vn_kv: { gte: 600 } } } })
|
|
131
213
|
const filterText = JSON.stringify(filters.exportData({ format: 'full' }))
|
|
132
214
|
const filterBack = createGisView(box(), JSON.parse(filterText))
|
|
@@ -184,7 +266,7 @@
|
|
|
184
266
|
|
|
185
267
|
/* ---------------- 动态换数据 / 加载完成(切场景 / 增量追加) ---------------- */
|
|
186
268
|
// 接口的列名不是 points / lines / channels 时,先在调用方映射成这三列再喂进来
|
|
187
|
-
//
|
|
269
|
+
// (映射写在业务侧,见 docs/manual/06-数据与场景更新.md 的「接口列名不一样」);组件本身只认这三列。
|
|
188
270
|
const sceneA = {
|
|
189
271
|
points: [
|
|
190
272
|
{ id: 'a', x: 0, y: 0, name: '甲站', vn_kv: 1050 },
|
|
@@ -230,8 +312,8 @@
|
|
|
230
312
|
check('addPoints 追加场站:老数据不动,新站排在末尾', addedPoints.ok && addedPoints.count === 2 && addedPoints.indexes.join(',') === '2,3' && sceneView.getData().points.length === 4 && sceneView.getData().points[2].name === '丙站')
|
|
231
313
|
check('addChannels 批量新增:逐条落地、坏的条目不影响其余(results 与顺序一一对应)', added.ok && added.count === 2 && added.results.length === 3 && added.results[0].ok && added.results[2].ok === false && added.lineIds.length === 3 && added.channelIds.length === 2, JSON.stringify({ count: added.count, lineIds: added.lineIds, reason: added.results[2].reason }))
|
|
232
314
|
check('addChannels 落地成数据:新回线挂在通道声明上,一次数据变更(type: addChannels)', sceneView.getData().lines.length === 5 && sceneView.getOptions().channels.length === 3 && sceneChanges.filter(item => item.type === 'addChannels').length === 1)
|
|
233
|
-
sceneView.
|
|
234
|
-
check('
|
|
315
|
+
sceneView.updateData({ channels: sceneView.getOptions().channels.slice(0, 2) })
|
|
316
|
+
check('updateData({ channels }) 只换通道声明(回线与场站不动)', sceneView.getOptions().channels.length === 2 && sceneView.getData().points.length === 4 && sceneView.getData().lines.length === 5)
|
|
235
317
|
const sceneImport = sceneView.importData(sceneA)
|
|
236
318
|
check('importData 换回整份场景:三列整份应用(applied 都报 true)', sceneImport.ok && sceneImport.applied.points && sceneImport.applied.lines && sceneImport.applied.channels && sceneView.getData().points.length === 2 && sceneView.getData().lines.length === 2 && sceneView.getOptions().channels.length === 1)
|
|
237
319
|
await sceneView.whenDataRendered()
|
|
@@ -293,7 +375,7 @@
|
|
|
293
375
|
`live ${JSON.stringify(holeLive)} / declared ${JSON.stringify(holeDeclared)}`)
|
|
294
376
|
holeView.destroy()
|
|
295
377
|
|
|
296
|
-
/* ---------------- 视口内导出(
|
|
378
|
+
/* ---------------- 视口内导出(exportData({ scope: 'viewport' })) ---------------- */
|
|
297
379
|
const viewportScene = {
|
|
298
380
|
points: [{ id: 'n1', x: 0, y: 0 }, { id: 'n2', x: 40, y: 0 }, { id: 'f1', x: 4000, y: 0 }, { id: 'f2', x: 4040, y: 0 }],
|
|
299
381
|
lines: [{ id: 'nl', from: 'n1', to: 'n2' }, { id: 'cl1', from: 'f1', to: 'f2' }, { id: 'cl2', from: 'f1', to: 'f2' }],
|
|
@@ -303,19 +385,19 @@
|
|
|
303
385
|
viewportView.addChannelGroupWaypoint('vch')
|
|
304
386
|
viewportView.setCamera({ center: [20, 0], scale: 1 })
|
|
305
387
|
const viewRect = viewportView.getViewRect()
|
|
306
|
-
const inView = viewportView.
|
|
388
|
+
const inView = viewportView.exportData({ scope: 'viewport' })
|
|
307
389
|
check('视口导出:节点按视口筛(近的两站出、远的两站与远端的通道都不出)', inView.counts.points === 2 && inView.counts.lines === 1 && inView.counts.channels === 0 && inView.counts.parallelGroups === 0, JSON.stringify(inView.counts))
|
|
308
390
|
check('视口导出:带 viewRect / margin 两条读数(缺省 margin 0、viewRect 与 getViewRect() 同一份)', inView.margin === 0 && inView.viewRect.minX === viewRect.minX && inView.viewRect.maxX === viewRect.maxX && inView.viewRect.maxY === viewRect.maxY, JSON.stringify(inView.viewRect))
|
|
309
|
-
const wide = viewportView.
|
|
391
|
+
const wide = viewportView.exportData({ scope: 'viewport', margin: 5000 })
|
|
310
392
|
check('margin 是屏幕像素外扩:给足够大的值远端全进来(通道与它的两回线、拐点分组一起)', wide.counts.points === 4 && wide.counts.lines === 3 && wide.counts.channels === 1 && wide.counts.parallelGroups === 1, JSON.stringify(wide.counts))
|
|
311
|
-
const inner = viewportView.
|
|
393
|
+
const inner = viewportView.exportData({ scope: 'viewport', format: 'input' })
|
|
312
394
|
check('视口导出也认 format: input(出与创建入参同构的三列,只有视口里那一条线路)', inner.points.map(point => point.id).join(',') === 'n1,n2' && inner.lines.map(line => line.id).join(',') === 'nl' && inner.channels.length === 0, JSON.stringify(inner.points.map(point => point.id)))
|
|
313
|
-
const stationOnly = viewportView.
|
|
395
|
+
const stationOnly = viewportView.exportData({ scope: 'viewport', parts: ['station'] })
|
|
314
396
|
check('parts 与 exportData 同写:只要 station 就只出节点那一份', stationOnly.counts.points === 2 && stationOnly.counts.lines === 0 && stationOnly.counts.channels === 0, JSON.stringify(stationOnly.counts))
|
|
315
|
-
const rectOnly = viewportView.
|
|
397
|
+
const rectOnly = viewportView.exportData({ scope: 'viewport', rect: { minX: 3980, minY: -50, maxX: 4100, maxY: 50 } })
|
|
316
398
|
check('rect 直接给世界矩形:只出远端那一撮(通道与两回线都在,viewRect 就是给的那份)', rectOnly.counts.points === 2 && rectOnly.counts.lines === 2 && rectOnly.counts.channels === 1 && rectOnly.viewRect.minX === 3980 && rectOnly.viewRect.maxX === 4100, JSON.stringify(rectOnly.counts))
|
|
317
|
-
const viewportDoc = viewportView.
|
|
318
|
-
check('format: full
|
|
399
|
+
const viewportDoc = viewportView.exportData({ scope: 'viewport', format: 'full' })
|
|
400
|
+
check('format: full 给标准文档本身(视口内两份节点、通道一份不落,不夹带范围读数)', viewportDoc.format === GIS_VIEW_DOCUMENT_FORMAT && viewportDoc.points.length === 2 && viewportDoc.channels.length === 0 && !('viewRect' in viewportDoc) && !('scope' in viewportDoc), JSON.stringify(viewportDoc.points.map(point => point.id)))
|
|
319
401
|
viewportView.destroy()
|
|
320
402
|
|
|
321
403
|
/* ---------------- 第94轮:批量复制 / 合并改写 / 批量新增 ---------------- */
|
|
@@ -341,7 +423,7 @@
|
|
|
341
423
|
})
|
|
342
424
|
batchView.on('pointCopy', payload => batchEvents.push(payload))
|
|
343
425
|
batchView.updateOptions({ onDataChange: payload => batchChanges.push(payload) })
|
|
344
|
-
batchView.
|
|
426
|
+
batchView.selectFeatures(['ba', 'bb'])
|
|
345
427
|
const batchClipboard = batchView.copyPointsToClipboard()
|
|
346
428
|
check('批量 Ctrl+C:只选节点时剪贴板只收两个节点,不自动带线路 / 通道',
|
|
347
429
|
batchClipboard?.count === 2 && batchClipboard.sources.map(source => source.item.id).join(',') === 'ba,bb' && batchClipboard.lines.length === 0 && batchClipboard.channels.length === 0,
|
|
@@ -366,7 +448,7 @@
|
|
|
366
448
|
check('批量落地发一条 copyPoints 数据变更与 phase: batch 事件', batchChanges.some(payload => payload.type === 'copyPoints') && batchEvents.some(payload => payload.phase === 'batch' && payload.count === 2))
|
|
367
449
|
pastedBatch?.revert()
|
|
368
450
|
check('批量载荷 revert() 一次撤掉整组节点、线路与通道', batchView.getData().points.length === 3 && batchView.getData().lines.length === 2 && batchView.getOptions().channels.length === 1, JSON.stringify({ points: batchView.getData().points.length, lines: batchView.getData().lines.length, channels: batchView.getOptions().channels.length }))
|
|
369
|
-
batchView.
|
|
451
|
+
batchView.selectFeatures(['ba', 'bb'])
|
|
370
452
|
const selectedCopy = batchView.copySelectedPoints({ coordinate: [0, 40], select: true })
|
|
371
453
|
check('copySelectedPoints:只选节点时整组复制不自动带线路 / 通道', selectedCopy.ok && selectedCopy.count === 2 && selectedCopy.lines.length === 0 && selectedCopy.channels.length === 0 && batchView.getSelectionData().ids.length === 2 && selectedCopy.items.every(item => String(item.id).startsWith('copy-')), JSON.stringify({ ids: selectedCopy.items.map(item => item.id), lines: selectedCopy.lines.length, channels: selectedCopy.channels.length }))
|
|
372
454
|
selectedCopy.revert()
|