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
|
@@ -33,7 +33,7 @@ view.getFilter() // 当前条件(副本)
|
|
|
33
33
|
view.getFilterResult() // { hiddenPoints, hiddenLines, visiblePoints, visibleLines, worker, durationMs }
|
|
34
34
|
view.getVisibleData() // 过滤之后的数据副本
|
|
35
35
|
view.listPoints() // 可见的点:{ index, item, x, y, radius, … },自建标签 / 读数直接用
|
|
36
|
-
view.
|
|
36
|
+
view.setFilter(null) // 清空条件
|
|
37
37
|
|
|
38
38
|
// 想在交给 setFilter 之前先看这份条件会被怎么解释:normalizeFilter 与组件内部同一套口径
|
|
39
39
|
import { normalizeFilter } from 'gis-view'
|
|
@@ -50,7 +50,7 @@ normalizeFilter({ points: {}, cascade: 'both', nope: 1 }) // unknownKeys: ['nop
|
|
|
50
50
|
| `cascade` | `false \| 'binding' \| 'coordinate' \| 'both'` | `'binding'` | 点被过滤后哪些线路一起消失:按 `from` / `to` 绑定、按端点坐标(端点正好落在被过滤的节点上)、两者都算、或完全不联动 |
|
|
51
51
|
| `worker` | `boolean` | 跟随 `filterWorker` | 这一次筛选是否允许放进 Worker |
|
|
52
52
|
|
|
53
|
-
联动只影响表现,数据本身不动:`updateData` / `
|
|
53
|
+
联动只影响表现,数据本身不动:`updateData` / `updateOptions` 换了数据之后按同一个条件重算。通道回线也是普通线路,跟着同一个 `cascade` 走:`binding` / `coordinate` / `both` 时绑着被过滤节点的通道回线一起收起来,`false` 时照旧画 —— 被过滤的节点仍然当锚点用(几何不跟着筛选断,回线端点还钉在它原来的坐标上),只是节点自己不显示。只有节点真的不在数据里(删掉、或者写了 `visible: false`)时,两端锚不住的通道与它的回线才整条不显示(和「两端没有节点就不显示」是同一条规则)。拖拽节点松手后,按端点坐标联动的筛选会按新坐标重算一次。需要「把某个要素找出来并居中」而不是隐藏其它要素时用 `search`,见「搜索与定位」。
|
|
54
54
|
|
|
55
55
|
### 大数据下的 Worker 筛选
|
|
56
56
|
|
|
@@ -124,6 +124,13 @@ view.focusOn({ minX: 116, minY: 39, maxX: 117, maxY: 40 }, { center: true }) //
|
|
|
124
124
|
| `fields` | `string[]` | 要素的全部字段 | 限定参与匹配的字段路径,如 `['name', 'props.vn_kv']` |
|
|
125
125
|
| `limit` | `number` | `200` | 每类的命中上限,`0` 表示不限 |
|
|
126
126
|
|
|
127
|
+
补充口径:
|
|
128
|
+
|
|
129
|
+
- `limit` 按「节点 / 线路 / 通道」三类分别计数;省略或写成非数字 / `NaN` 时回落到默认 `200`,不会退化成不限。`0` 与负数都表示不限。
|
|
130
|
+
- `fields` 会忽略空项并去重;数组为空或没有有效字段路径时,与不写 `fields` 一样按全部字段处理。
|
|
131
|
+
- 默认全字段模式会跳过 `symbol` / `style` / `tooltip` 这类外观结构;但 `fields` 显式点名时按路径直接读取,不受这条跳过规则限制。例如 `fields: ['symbol']`、`fields: ['style.color']` 能命中对应值。
|
|
132
|
+
- 搜索始终基于全量数据,不受当前 `setFilter` 影响:被筛选隐藏的要素仍然可以被搜到。`scope` 写错或传空数组时会回落到三类全搜;重复线路 id 按数据里的第一条解析(见 [ID 解析口径](02-坐标与数据.md#id-解析口径))。
|
|
133
|
+
|
|
127
134
|
搜索**不动视角**(`focus` / `scale` / `padding` 三个选项随之取消):命中里带着 `bounds`,要定位就自己调 `focusOn(result.hits, …)` —— 一批命中的整体中心就是屏幕中心,`{ select: true }` 还能顺带把它们选上。
|
|
128
135
|
|
|
129
136
|
匹配的是要素自己的字段:`id`、`name`、`from` / `to`,以及任意业务字段——`props` 这类普通对象会再往里摊一层,所以 `props.vn_kv` 里的值也能搜到;样式与符号字段不参与匹配。**通道连成员回线一起搜**:命中的是通道里的某条回线时,命中里会带上 `channelIndex`、`memberIndex`(第几回,0 开始)与 `memberLineIndex` / `memberLineId`,`bounds` 也换成那条回线的范围,`focusOn(hit)` 因此会直接定位到通道里的这条线路;反过来搜线路时,属于通道的线路也会带上 `channelIndex` / `memberIndex`。
|
|
@@ -301,8 +308,6 @@ view.updateBackground({
|
|
|
301
308
|
|
|
302
309
|
背景层与数据同坐标系:数据在哪,底图就画在哪,缩放 / 平移跟着一起走,永远画在数据下面。没有现成 GeoJSON 时可以在业务侧按数据范围现生成一份(街区、经纬网、河流都可以拼成 `FeatureCollection`),`docs/demos/demoBackground.js` 的 `buildDemoBasemap(bounds, { districts, grid, padding })` 就是一个纯 JS 示例(默认街区与经纬网都开),demo 与 `docs/steps/step-11-background.js` 共用它。
|
|
303
310
|
|
|
304
|
-
背景层与数据同坐标系:数据在哪,底图就画在哪,缩放 / 平移跟着一起走,永远画在数据下面。没有现成 GeoJSON 时可以在业务侧按数据范围现生成一份(街区、经纬网、河流都可以拼成 `FeatureCollection`),`docs/demos/demoBackground.js` 的 `buildDemoBasemap(bounds, { districts, grid, padding })` 就是一个纯 JS 示例(默认街区与经纬网都开),demo 与 `docs/steps/step-11-background.js` 共用它。
|
|
305
|
-
|
|
306
311
|
## 鹰眼图
|
|
307
312
|
|
|
308
313
|
配了 `minimap` 就有一套内置的小地图:组件自己在画布上挂一层缩略图,画的是**筛选之后仍然可见**的节点与线路(通道的骨架与回线也在内),视口框跟着相机走。不配就不产生任何 DOM:容器按需挂载,写 `minimap: null`(或 `enabled: false`、用 `features` / `setFeatures` 把 `minimap` 关掉)时那一层会从宿主里摘掉,运行时再配上(`updateOptions({ minimap })`)会当场补挂上,`destroy()` 之后宿主里不留东西。
|
|
@@ -348,6 +353,8 @@ createGisView(container, {
|
|
|
348
353
|
// 拖动时拉出的框选矩形:只写想单独覆盖的字段,其余沿用 boxStyle
|
|
349
354
|
selectStyle: { fill: '#ffb020', stroke: '#ffb020' },
|
|
350
355
|
interactive: true,
|
|
356
|
+
// 一次手势完成后的回调(无位移的点击不发):载荷带 type / local / world / zoom / extent / camera / viewport
|
|
357
|
+
onInteraction: info => console.log(info.type, info.world),
|
|
351
358
|
longPressDelay: 180,
|
|
352
359
|
zoomable: false, // 打开后小图自身也能整体放大缩小,滚轮与下面这组倍数都归它
|
|
353
360
|
zoom: 1, // 初始倍数,1 表示「刚好铺满小图」
|
|
@@ -359,7 +366,14 @@ createGisView(container, {
|
|
|
359
366
|
// 容器外观:附加类名 + 内联样式(中划线 / CSS 变量与驼峰键都认,换配置时旧键会清掉)
|
|
360
367
|
className: 'my-minimap',
|
|
361
368
|
style: { boxShadow: '0 2px 10px rgba(0, 0, 0, 0.18)' },
|
|
362
|
-
//
|
|
369
|
+
// 备份像素比上限:高分屏 / 宿主放大时封顶,1 = 按 CSS 像素铺(面积越小越省填充)
|
|
370
|
+
maxPixelRatio: 2,
|
|
371
|
+
// 可拆分的自定义层:placement 选择画在数据前 / 数据后,同段内再按 order 从低到高
|
|
372
|
+
layers: [
|
|
373
|
+
{ id: 'grid', order: 10, placement: 'below-data', draw: ({ context, width, height }) => { /* 画网格 */ } },
|
|
374
|
+
{ id: 'compass', order: 20, placement: 'above-data', draw: ({ context, width }) => { /* 画指北针 */ } }
|
|
375
|
+
],
|
|
376
|
+
// 兼容旧写法的最终一层:所有 layers 之后、视口框之前调用(drawInfo.stage === 'overlay')
|
|
363
377
|
draw: ({ context, width }) => {
|
|
364
378
|
// 例:在右上角画一枚指北针(按 CSS 像素画,ratio 是画布备份像素比)
|
|
365
379
|
context.fillStyle = '#3678bf'
|
|
@@ -384,7 +398,9 @@ createGisView(container, {
|
|
|
384
398
|
| `padding` | `number` | `0.04` | 数据四周留白,取短边比例 |
|
|
385
399
|
| `extent` | `'auto' \| 'data' \| 'background' \| GisViewBounds \| [[number, number], [number, number]]` | `'auto'` | 小图按哪段世界范围铺:`auto` 取「数据范围 ∪ 底图范围」,`data` 只看数据(旧行为),`background` 只看底图;数据与 GeoJSON 底图不在一个坐标系时,`auto` 能把两者一起框进小图,底图不会缩成一个点/糊成一片。例外:相机正落在其中一份范围里、而那一份合并后连 6 个像素都占不到时,`auto` 只画正在看的那一份(数据还没映射到底图、两者离得很远就是这样),相机挪进 / 挪出会各重画一次。也可以直接写一份世界范围(`{ minX, minY, maxX, maxY }`,也认 `[[minX, minY], [maxX, maxY]]`):固定按它铺,不跟数据与底图走 |
|
|
386
400
|
| `className` / `style` | `string` / `Record<string, string \| number>` | 无 | 小图容器自己的类名与内联样式(中划线 / CSS 变量键与驼峰键都认):想按自己的设计系统改外观时用;换配置时上一轮写过、这一轮没写的键会被清掉 |
|
|
387
|
-
| `draw` | `(info: GisViewMinimapDrawInfo) => void` | 无 |
|
|
401
|
+
| `draw` | `(info: GisViewMinimapDrawInfo) => void` | 无 | 兼容旧写法的最终一层:在所有 `layers` 之后、视口框之前调用(`info.stage === 'overlay'`;视口框由 DOM 画在它上面)。`info` 里是 `context`(已按备份像素比缩放好,直接按 CSS 像素画)、`canvas`、`width` / `height` / `ratio`、`extent` / `bounds` / `scale` / `zoom`、`project` / `unproject`(与内置图层同一套投影)与这一帧的 `data` / `background`;钩子里改过的上下文状态由组件 save / restore 收好,不会漏进下一帧 |
|
|
402
|
+
| `layers` | `GisViewMinimapLayer[]` | 无 | 可拆分的自定义绘制层:每项写 `{ id?, order?, placement?, enabled?, draw }`。`placement: 'below-data'` 画在内置线路 / 节点之前,`'above-data'`(默认)画在之后;同一段内按 `order` 从小到大,相同或未写时按数组顺序。每层独立 `save` / `restore`,一层改过的画布状态不会串到下一层;`enabled: false` 可临时关掉一层。回调里的 `stage` 就是当前段,旧的单层 `draw` 固定最后执行。排序在 `updateOptions` 时缓存,绘制帧不再逐层排序 |
|
|
403
|
+
| `maxPixelRatio` | `number` | `4` | 小图画布备份像素比的上限(实际倍率 = `min(devicePixelRatio × 宿主显示比例, maxPixelRatio)`,夹在 1 ~ 4)。默认 4 保留高分屏清晰度;小图尺寸大或高分屏填充压力高时降到 1 ~ 2 可明显减少像素填充,代价是缩略图边缘变软 |
|
|
388
404
|
| `background` | `'inherit' \| false \| 'none' \| GisViewBackground \| string` | `'inherit'` | 小图画什么底图:`inherit` 复用主视图解析好的背景(底色 / 图片 / GeoJSON 的逐区域配色与区域文字都跟着),传对象就是小图自己的一份(结构与主视图的 `background` 完全一致,用 Canvas2D 画),`false` / `'none'` 关掉;传字符串按旧行为当容器底色 |
|
|
389
405
|
| `backgroundStyle` | `string` | `'rgba(255, 255, 255, 0.92)'` | 小图容器自己的 CSS 底色,垫在 `background` 下面(想让小图透出主视图底色就写 `transparent`) |
|
|
390
406
|
| `borderColor` / `borderWidth` / `radius` | `string` / `number` | `#d7e2ef`、1px、6px | 小图自身的外观 |
|
|
@@ -404,6 +420,7 @@ createGisView(container, {
|
|
|
404
420
|
| `boxStyle` | `{ fill?, fillOpacity?, stroke?, strokeWidth?, strokeStyle?, radius?, shadow? }` | 蓝底 0.14 + 1px 蓝框 | 视口框(代表当前视口)的样式:填充色与不透明度、边框颜色 / 宽度 / 线型(`solid` / `dashed` / `dotted`,`strokeWidth: 0` 不画边框)、圆角与 CSS `box-shadow` |
|
|
405
421
|
| `selectStyle` | `GisViewMinimapBoxStyle` | 沿用 `boxStyle`(虚线框、填充 0.12) | 拖动时拉出的框选矩形单独覆盖的样式字段,没写的字段继续沿用 `boxStyle` —— 只换底色 / 边框色就能和视口框区分开 |
|
|
406
422
|
| `interactive` | `boolean` | `true` | 是否允许在小图上拖动 |
|
|
423
|
+
| `onInteraction` | `(info: GisViewMinimapInteractionInfo) => void` | 无 | 一次手势真正完成时通知一条:`type` 为 `box-drag` / `select` / `pan` / `zoom` / `wheel-main` / `long-press`;载荷带 `local` / `world`、小图倍数 `zoom`、当前范围 `extent`、相机 `camera` 与视口 `viewport`。没有位移的点击不触发,`pointermove` 期间也不逐帧回调;长按在触发跟随的那一刻回调。适合同步业务状态、埋点或驱动外部图例 |
|
|
407
424
|
| `longPressDelay` | `number` | `180` | 空白处按住多久之后开始跟着拖(毫秒) |
|
|
408
425
|
| `wheelZoom` | `boolean` | `true` | 是否允许在小图上用滚轮缩放**主图**(锚点落在光标处);`zoomable` 打开时主图改成 Ctrl / ⌘ + 滚轮 |
|
|
409
426
|
| `zoomable` | `boolean` | `false` | 鹰眼图自身是否可以整体放大缩小:打开后滚轮直接缩放小图(锚点落在光标处),Ctrl / ⌘ + 滚轮仍然缩放主图 |
|
|
@@ -420,7 +437,7 @@ createGisView(container, {
|
|
|
420
437
|
|
|
421
438
|
小图的背景默认跟主视图一致(`background: 'inherit'`):底色、图片、GeoJSON 的填充 / 描边 / 逐区域配色与区域文字都换到小图自己的尺子上重画 —— GeoJSON 的线宽(最低 0.4px 保可见)、点要素半径(固定 1.2px)与区域文字字号(固定档,夹在 5 ~ 12px)都是小图上的固定像素,不再乘「小图比例 ÷ 主图比例」:缩放主视图时底图不会单独跟着放大 / 缩小,底图也不跟 `pointMapZoomScale` / `lineMapZoomScale` 走(那两个指数只管节点与线路),区域装不下这行字(屏幕矩形比文字小)就整条跳过;排布时按区域从大到小放,和已经放下的标签重叠的也跳过 —— 小图上几十个区域各画一行字只会叠成一团,最后一条都读不出来;图片按四角或 `fit` 算出的四边形做仿射 + 裁剪贴图(任意四边形都不会画歪),GeoJSON 面用奇偶规则填充(洞自动镂空)、纯线几何的区域再描一遍。也可以给 `minimap.background` 单独配一份(结构与主视图的 `background` 一样:`color` / `image` / `geoJson` / `geoJsonStyle` / `opacity`),「主图画数据、小图画行政边界」这类分工就很直接;`backgroundStyle` 是小图容器自己的 CSS 底色,垫在背景下面。主视图换了背景(`updateBackground` / `updateOptions({ background })`)小图会自动跟着重画。
|
|
422
439
|
|
|
423
|
-
交互与开销:**在框上按下直接拖框**(世界位移 = 屏幕位移 ÷ 小图比例),**空白处单击不改变视角**,按住超过 `longPressDelay` 才进入「跟着拖」,拖动过程中始终跟着光标;光标停在小图上滚动滚轮按光标位置缩放主图(`wheelZoom: false`
|
|
440
|
+
交互与开销:**在框上按下直接拖框**(世界位移 = 屏幕位移 ÷ 小图比例),**空白处单击不改变视角**,按住超过 `longPressDelay` 才进入「跟着拖」,拖动过程中始终跟着光标;光标停在小图上滚动滚轮按光标位置缩放主图(`wheelZoom: false` 关掉)。每个有位移的手势结束时可通过 `onInteraction` 收到一条通知,不必自己在小图 DOM 上再挂一套指针监听。底图画的是筛选之后真正画出来的线,通道骨架与回线都在内(直接问引擎要几何,不用自己再推一遍通道偏移),画法是先铺线、再把节点整层盖上去 —— 节点层级高于线路,密集底图上的节点不会被线咬掉。底图只在数据或筛选结果变化时重画(节点直接写进像素缓冲,十万级也只在重建时花一次时间),相机变化只改视口框的 `transform`,所以拖动小图不掉帧。小图还会缓存**上一次的节点像素结果**与**数据范围**:一次改动排在前后两帧各重画一次(引擎重排之后要再取一次几何),第二次与第一次的数据、范围、尺寸完全一样时直接复用那份像素,十万点下第二次重画从 ~20ms 降到 0.3ms(配了 `pointMapZoomScale` / `lineMapZoomScale` 时把「主视图倍数」也并进缓存的键,倍数变了才重铺);`extent: auto` 的取舍跟相机有关,相机挪进 / 挪出那份小范围时才补一次重画(每帧只做几次比较,判断用的范围是缓存的)。小图拖框 / 小图滚轮带来的相机变化不算主画布上的交互:配了 `hideOnInteract` 的图层不会在这时被收起来,拖动结束还会补一次真实排版 —— 不会出现「动一下鹰眼图,主视图标签全没了」。真在主画布上拖动过之后、两层标签同时收着时,收尾计时器也照样挂上 —— 覆盖层停手的收尾是无条件的,不会因为「两层都在收着、当下没东西可排」把恢复一起跳过。
|
|
424
441
|
|
|
425
442
|
`zoomable: true` 之后小图自身也能整体放大缩小,空白处的拖动换成下面两种动作(单击不再需要长按,空白处不再走 `longPressDelay` 那条路):
|
|
426
443
|
|
|
@@ -431,7 +448,7 @@ createGisView(container, {
|
|
|
431
448
|
|
|
432
449
|
小图的范围(`extent`)默认是 `auto`:取「筛选后可见数据的范围」与「底图范围」的并集(GeoJSON 底图用画出来的 `bounds`,图片底图用四角算出的范围),所以数据与 GeoJSON 不在一个坐标系时两者会一起进小图 —— 底图铺满小图、数据落在自己该在的位置,不会出现「按数据范围缩放、底图缩成一个点或糊成一片」的旧问题。`extent: 'data'` 退回旧行为(只看数据),`extent: 'background'` 只看底图范围;数据为空时 `auto` 直接取底图范围。数据用 `createGeoMapper` 映射过之后两者本来就在同一个坐标系里,`auto` 下小图与主图的视野完全一致。
|
|
433
450
|
|
|
434
|
-
|
|
451
|
+
**配置、扩展与开销**:`extent` 除了三档预设,还能直接写一份世界范围(`{ minX, minY, maxX, maxY }` 或 `[[minX, minY], [maxX, maxY]]`)—— 固定按它铺、不跟数据与底图走,「只看某个片区」或者让两张小图的视野对齐(前后对比、打印出图)时用;容器还能加 `className` 与 `style`(中划线 / CSS 变量键与驼峰键都认,换配置时上一轮写过、这一轮没写的键会清掉),按自己的设计系统做阴影、字号、层级这类外观不必再绕 `backgroundStyle`。**可扩展**:`layers` 把每帧的自定义内容拆成一组互不干扰的绘制层 —— `placement` 决定画在数据前还是数据后,同段内按 `order` 从小到大,独立 `save` / `restore`,适合把网格、分区底色、指北针、业务标记分开维护;排序与分段在 `updateOptions` 时缓存,绘制帧只遍历当前段,层数多也不会每帧重复排序;旧的单层 `draw` 继续兼容,`stage` 固定为 `overlay` 并排在最后。回调拿到 `context`(已经按备份像素比缩放好,直接按 CSS 像素画)、`width` / `height` / `ratio`、当前 `extent` / `bounds` / `scale` / `zoom` 与 `project` / `unproject`(与内置图层同一套投影),以及这一帧的 `data` 与 `background`。`maxPixelRatio` 是性能旋钮:默认 4,实际倍率取 `devicePixelRatio × 宿主显示比例` 与该上限的较小值;小图在高分屏 / 宿主放大时不必无限加像素,调到 1 ~ 2 能省填充率。
|
|
435
452
|
|
|
436
453
|
视口框的拖角编辑(曾经的 `boxEditable` / `boxHandleSize` 与四角把手)已按需求整块去掉:框上不再有把手,视口框只能整体拖动;改主图视野用滚轮 / 框选(`zoomable`)或 `fitBounds` 这类实例方法。
|
|
437
454
|
|
|
@@ -441,10 +458,8 @@ createGisView(container, {
|
|
|
441
458
|
|
|
442
459
|
| 近义 / 别名 | 关系 |
|
|
443
460
|
| --- | --- |
|
|
444
|
-
| `
|
|
445
|
-
| `deselect(refs?)` ↔ `selectFeatures(refs, { mode: 'remove' })` | 取消选中的两个入口:`deselect` 是「去掉这几条」的短写法(写法解析完全一样),不给目标就是取消全部(`select(null)` / `select([])` 同义) |
|
|
461
|
+
| `deselect(refs?)` ↔ `selectFeatures(refs, { mode: 'remove' })` | 取消选中的两个入口:`deselect` 是「去掉这几条」的短写法(写法解析完全一样),不给目标就是取消全部(`selectFeatures(null)` / `selectFeatures([])` 同义) |
|
|
446
462
|
| `zoomIn(step?)` / `zoomOut(step?)` ↔ `zoomBy(factor, options?)` | 前者按 `zoomStep`(缺省 1.4)乘 / 除一档(`zoomOut(step)` 就是 `zoomBy(1 / step)`);要给具体倍数、要指定锚点用 `zoomBy` |
|
|
447
|
-
| `setEffects(effects)` ↔ `updateOptions({ effects })` | 同一条路:`setEffects(false)` / `getEffects()` 是这一对配置的读写快捷写法 |
|
|
448
463
|
| `addChannels(items, options?)` ↔ `addChannel(spec, lineData?, group?)` / `addChannel(input, lineItems?)` | 前者是批量版(一次多条通道 / 独立线路,逐条落地但只重排一次、只记一步历史、只发一条事件,每项里的 `lineItems` 照认),后者一次一条 |
|
|
449
464
|
| `addChannel(spec, lineData?, group?)`(新签名,只新建)↔ `addChannelGroupLines(channelId, lineData, groupInfo?)` / `joinChannelGroupLines(channelId, groupId, lineData)` | 新签名只负责「两个节点之间还没有通道」的新建(第二段 / 第三段显式给了才算);已经有通道时要新加一组 / 并进某一组就走拆出来的这两个方法(语义直说,不必自己拼 `resolution`);只写 `{ from, to }` 的老调用照旧按方案表分流 |
|
|
450
465
|
| `updateChannel(id, { group })`(单数:改一组的信息)↔ `updateChannel(id, { groups })`(复数:整份换分组声明) | 同一份分组数据的两档写法:只改某一组的字段(id 之外的字段 / 骨架 / 成员)用 `group`,整份重拆用 `groups`;两个一起给时以 `groups` 为准(`group` 丢掉并给一条 `warnOnce`) |
|
|
@@ -452,7 +467,6 @@ createGisView(container, {
|
|
|
452
467
|
| `getChannelAddOptions(from, to, count?, kind?)` ↔ `getChannelBetween(from, to)` | 同一份通道读数的一宽一窄:方案表要 `count` / `kind` 才能算余量与可选方案,通道读数只回答「有没有通道 / 有几组 / 当前哪组」;界面做「这两个点之间已经有通道」这类判断用窄的那条,不必凑参数 |
|
|
453
468
|
| `addChannel` 顶层 `channelId` / `groupId` / `groupIndex` ↔ `resolution: { type: 'joinGroup', … }` | 同一件事的两种写法(与 `updateLine` 的分组指令同款):并进已有通道时指定「哪条通道、哪一组」;只给 `channelId` 按这条通道自己的推荐顺序挑(新分组 → 第一个能并的分组)。结果里的 `channelLineIds` 是更新后的完整成员表,见「新增通道」 |
|
|
454
469
|
| `project(x, y)` / `unproject(x, y)` ↔ `clientToScreen(clientX, clientY)` / `screenToClient(x, y)` ↔ `clientToWorld(clientX, clientY)` / `worldToClient(x, y)` | 同一套换算链的三档入口:`project` / `unproject` 收**画布 CSS 像素**,`clientTo*` 这一对吃鼠标事件里的 `clientX` / `clientY`(自动减 canvas rect、除宿主缩放)只换到画布像素,再接一层就是世界坐标(`clientToWorld` / `worldToClient`);经纬度那一对是 `lngLatToScreen` / `screenToLngLat` |
|
|
455
|
-
| `clearDropPreview()` ↔ `setDropPreview(null)` | 同一条路(拖拽离开 / 取消时用);`getDropPreview()` 读当前那一份预览 |
|
|
456
470
|
| `fitView(padding?)` / `applyInitialView()` / `resetView()` / `zoomToBounds(bounds, padding?)` | 视角的四个语义:适应**数据范围**(并记下首次落位)/ 按 `options.initialView` 落位(可反复调用)/ 回到**首次落位那一刻**的相机 / 按给定的世界坐标范围适应 |
|
|
457
471
|
| `focusOn(ref, options?)` ↔ `zoomToBounds(bounds, padding?)` | `focusOn` 认要素(id / 下标 / 搜索命中 / 坐标 / 矩形 / 一批),内部算出范围再走适应那一条,另带 `center` / `scale` / `maxZoom` / `select` 这些选项 |
|
|
458
472
|
| `pick(x, y)` ↔ `pickAt(clientX, clientY)` | 坐标口径不同:`pick` 收画布 CSS 像素,`pickAt` 直接吃鼠标事件里的客户端坐标(结果里另加 `target` 与 `lngLat`) |
|
|
@@ -462,9 +476,8 @@ createGisView(container, {
|
|
|
462
476
|
| `listPoints(options?)` / `listLines(options?)` ↔ `getFeaturesInRect(rect)` | 前者按条件列快照(`inView: true` 就是「视口里的」),后者按像素矩形问「框内有什么」(与框选同一套坐标与判定) |
|
|
463
477
|
| `copyPoint(...)` ↔ `copyPoints(...)` / `copySelectedPoints(...)` / `copyPointToClipboard()` / `copyPointsToClipboard()` / `pastePoint(...)` / `pastePoints(...)` / `getPointClipboard()` | 复制分三档:`copyPoint` 是单节点「就地复制到旁边」一步到位,`copyPoints` / `copySelectedPoints` 是框选 / 多选后的整组复制(只带选中节点与选中线路 / 通道,自动补端点、一次历史、可 `revert()`;`withLines` / `withChannels: true` 恢复来源内部全带的旧口径),剪贴板这一组走 Ctrl+C / Ctrl+V(`copyPointToClipboard` 默认收整个选中集,`copyPointsToClipboard` 只收给定的一批,`pastePoints` 强制整组落地;`commitPointCopy` / `cancelPointCopy` / `updateCopyPoint` 是拖动式复制预览的三个动作) |
|
|
464
478
|
| `removePoints(...)` / `removeLines(...)` / `removeSelected()` ↔ `requestRemove()` / `resolveRemove(...)` | 直接删(接口入口,不受 `features.remove` 限制)/ 先出一份删除载荷交给 `onRemove` 裁决(Delete 键与 `remove` 开关走的是这条) |
|
|
465
|
-
| `setChannelName(id, name)` ↔ `updateChannel(id, { name })` | 同一条路:前者只改名称一项,后者是整份补丁写法(写 `groups` / `lineIds` 还能顺带改分组与成员) |
|
|
466
479
|
| `updateChannel(id, patch, linePatches?, lineIds?)` ↔ `updateChannel(id, patch)` / `updateLine(id, patch)` | 一次把「通道声明 + 成员回线各自的字段 / 分组」下发(内部就是声明 + 逐条 `updateLine`,外面包一层 `batch`):只改声明给两段,只改一条回线用 `updateLine`,整条通道一起改(回线线宽 / 逐回颜色)已有 `setChannelLineWidth` / `setChannelColors` |
|
|
467
|
-
| `updateChannel(id, patch, linePatches?, lineIds?)` ↔ `
|
|
480
|
+
| `updateChannel(id, patch, linePatches?, lineIds?)` ↔ `updateData({ channels })` | 补丁 vs 整份:前者按 id 只改一条通道的声明(补丁里的 `lineIds` 是这条通道成员的**整表**,增删线路就是改它;与 `groups` 调和后落地;第四段单独传的 `lineIds` 是整表对齐 —— 多了建、少了从数据里删),后者把 `channels` 数组整份换掉、点不出某一条(声明里的 `lineIds` 同样是成员整表:与 `groups` 冲突时按它收窄 —— 与补丁路径同一份口径)。两个入口都会把引擎算出的最终分组(id / 拐点路径)写回数据副本、成员表对齐到各组,声明里写错的成员 id 各报一句 `onWarning` |
|
|
468
481
|
| `addChannelLines(channelId, lineIds, options?)` / `removeChannelLines(channelId, lineIds)` ↔ `removeChannel(channelId)` / `removeLines(lineIds)` | 粒度的三档:把回线**挂进 / 摘出通道**(回线本身留着) / **删整条通道**(成员回线一起走) / **删线路**(在哪都删掉);`requestRemove()` 的确认框里「只删回线 / 连分组 / 连整条通道」三档走的是删除确认那一套 |
|
|
469
482
|
| `removeChannelGroup(channelId, groupId, options?)` ↔ `mergeChannelGroups(channelId, groupIds, targetGroupId?)` / `moveChannelLine(...)` | 都是「整组粒度的重排」:`removeChannelGroup` 是「把这一组并进相邻组」的一步写法(单组通道拒绝),`mergeChannelGroups` 一次可以并好几组、`moveChannelLine` 是一条回线的粒度 |
|
|
470
483
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 导出图片
|
|
6
6
|
|
|
7
|
-
`exportImage` 把当前画面(画布内容 + DOM 标签,节点标签与线路标签都在内)导出成 data URL,**图片类型与背景都能选**:
|
|
7
|
+
`exportImage` 把当前画面(画布内容 + 扩展图形层 + DOM 标签,节点标签与线路标签都在内)导出成 data URL,**图片类型与背景都能选**:
|
|
8
8
|
|
|
9
9
|
```js
|
|
10
10
|
const url = await view.exportImage({ scale: 2 }) // 位图:带背景的 png
|
|
@@ -35,7 +35,7 @@ await view.exportImage({ type: 'svg', background: 'none', download: '线路图.s
|
|
|
35
35
|
|
|
36
36
|
导出用的是当前这一帧的实际画面:位图内部先同步重画一帧再取像素 —— 透明底与换底色这两种写法出的都是「只给导出用」的那一帧,读完像素立刻补画一帧正常画面的把画布还原,屏幕上不会停在导出那一版。整个过程不需要开 `preserveDrawingBuffer`,所以也没有常驻的额外显存开销。
|
|
37
37
|
|
|
38
|
-
`type: 'svg'` 走的是矢量:线(含虚线图案与相位)、点(内置圆 / 方与 SVG 符号)、线路装饰、拐点、GeoJSON
|
|
38
|
+
`type: 'svg'` 走的是矢量:线(含虚线图案与相位)、点(内置圆 / 方与 SVG 符号)、线路装饰、拐点、GeoJSON 区域、扩展图形与标签都是真正的 SVG 元素,颜色 / 线宽 / 线型取的就是当前那一帧解析出来的值(悬停、选中样式同样在内),放大到任意倍都不糊。PNG / JPEG / WebP 这一支先画引擎画布,再按「扩展图形 → 标签」叠上去,所以 `graphics` 里的 SVG 路径、图片与 Canvas 都在合成结果里。两点差异:热力密度场是烘焙出来的贴图、不进 SVG;位图符号(`symbol.type: 'image'`)、自定义图元(`registerSymbol` 的画笔)与图片 / Canvas 图形都以 `<image href>` 出现(前者引用原地址,后两者是原图或 data URL),把导出的 svg 当图片内嵌到别的页面时,跨域图片与外部字体会被浏览器挡掉(直接打开 svg 文件没问题)。需要那一份矢量数据自己拼别的格式时,用 `view.snapshotScene({ background, labels })`(它已经带上 `graphics`),或用导出的 `buildSceneSvg(scene, { scale })`;`svgDataUrl(markup)` 是把一段 SVG 标记转成 `data:` URL 的小工具(组件内部导 svg 图片时走它,自己拿到标记要拼进 `<img src>` 或 CSS 时也可以直接用)。
|
|
39
39
|
|
|
40
40
|
## 导出选中的节点
|
|
41
41
|
|
|
@@ -62,7 +62,7 @@ await view.exportSelection({ points: ['node-1', 2], format: 'data', withLines: f
|
|
|
62
62
|
|
|
63
63
|
返回 `{ ok, reason, points, lines, counts, box, pointIndexes, lineIndexes, image }`:`points` / `lines` 是**拷贝**(改它不影响组件内部那一份,`path` / `start` / `end` 这些坐标数组也复制过),`box` 是这批要素的世界坐标盒(节点坐标 ∪ 带上线路的全部顶点 —— 拐点与通道偏移可能伸到节点之外),`pointIndexes` / `lineIndexes` 是它们在组件数据里的下标。没选中又没给 `points`(`没有要导出的节点`)、或者给的引用一个都解析不出来(`没找到要导出的节点`)时 `ok: false` 并把其余字段回空,不抛异常。出图的临时状态在 `finally` 里还原:筛选换回使用方那一份、相机回原位、标签层与鹰眼图各刷一次。
|
|
64
64
|
|
|
65
|
-
这一份出的是「声明数据」(组件内部的 `points` / `lines`)。要按**业务表口径**分项导出同一批选中(`station_data` / `channel_data` / `line_data` / `parallel_groups`,还能多选),用 `
|
|
65
|
+
这一份出的是「声明数据」(组件内部的 `points` / `lines`)。要按**业务表口径**分项导出同一批选中(`station_data` / `channel_data` / `line_data` / `parallel_groups`,还能多选),用 `exportData({ scope: 'selection', ... })`,见「导出选中的数据(分项)」。
|
|
66
66
|
|
|
67
67
|
## 导出数据
|
|
68
68
|
|
|
@@ -71,17 +71,15 @@ await view.exportSelection({ points: ['node-1', 2], format: 'data', withLines: f
|
|
|
71
71
|
第 5 个口径 `format: 'full'` 给的是**标准数据文档**:组件接收的格式与导出的格式是**同一份写法**(`format` / `version` / `points` / `lines` / `channels` / `labelStates` / `view` / `filter` 八列),整份喂回 `createGisView` / `importData` 就回到同一屏,见「标准数据文档(full):接收 == 导出」。
|
|
72
72
|
|
|
73
73
|
```js
|
|
74
|
-
//
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
const newJson = view.exportData({ format: 'new' }) // { max_lat, min_lat, max_lon, min_lon, show_station_list, show_station_data, station_data, channel_data, tc_file_path, warning_msg, … }
|
|
84
|
-
const testJson = view.exportData({ format: 'test' })// { point_data, line_data, switch_data, parallel_groups, … }
|
|
74
|
+
// 统一入口:scope 选范围,parts 选分项,format 选口径
|
|
75
|
+
const all = view.exportData({ scope: 'all', format: 'view' }) // 全量(scope 缺省就是 all)
|
|
76
|
+
const inViewport = view.exportData({ scope: 'viewport', margin: 24 }) // 当前视口,可加屏幕像素外扩
|
|
77
|
+
const selected = view.exportData({ scope: 'selection', parts: ['station', 'channel'], withLines: true })
|
|
78
|
+
// scope 也认 visible / view / selected 这几个同义词;写错会抛异常(不静默按全量导)
|
|
79
|
+
|
|
80
|
+
const four = view.exportData({ format: 'view' }) // { points, lines, channels, parallelGroups, counts, warnings }
|
|
81
|
+
const newJson = view.exportData({ format: 'new' }) // { max_lat, min_lat, max_lon, min_lon, show_station_list, show_station_data, station_data, channel_data, tc_file_path, warning_msg, … }
|
|
82
|
+
const testJson = view.exportData({ format: 'test' }) // { point_data, line_data, switch_data, parallel_groups, … }
|
|
85
83
|
// 回灌口径:与 createGisView 入参同构(含拖过的点、改过的拐点、拆分过的分组)
|
|
86
84
|
const input = view.exportData({ format: 'input' }) // { points, lines, channels, labelStates, counts, warnings }
|
|
87
85
|
view.updateData(input.points, input.lines); view.updateOptions({ channels: input.channels })
|
|
@@ -98,25 +96,41 @@ rebuilt.importData(doc) // 也可以对已有实例整份灌回
|
|
|
98
96
|
|
|
99
97
|
// 多选分项:选什么就导什么,选了通道 / 线路会自动带上拐点
|
|
100
98
|
view.exportData({ format: 'view', parts: ['station', 'channel'] }) // { points, channels, parallelGroups, … }
|
|
101
|
-
view.
|
|
99
|
+
view.exportData({ scope: 'selection', parts: ['station', 'channel'] }) // 同一套分项,换成「选中的这一撮」
|
|
102
100
|
|
|
103
101
|
// 只要一部分:id / 下标 / { id } / { index } 都收(单个或数组)
|
|
104
|
-
view.
|
|
105
|
-
view.
|
|
106
|
-
view.
|
|
107
|
-
|
|
108
|
-
//
|
|
109
|
-
view.
|
|
110
|
-
view.
|
|
111
|
-
view.
|
|
102
|
+
view.exportData({ format: 'test', parts: ['line'], lines: ['line-1', 2] })
|
|
103
|
+
view.exportData({ format: 'new', parts: ['channel'], channels: [0], lines: [3, 4] }) // 通道里只留这几条回线
|
|
104
|
+
view.exportData({ format: 'test', parts: ['waypoint'], channels: ['ch-1'] }) // 只出这条通道的拐点分组
|
|
105
|
+
|
|
106
|
+
// 分项导出只保留统一入口:范围、分项与口径各写一个字段
|
|
107
|
+
view.exportData({ scope: 'viewport', margin: 24 })
|
|
108
|
+
view.exportData({ scope: 'selection', parts: ['station'] })
|
|
109
|
+
view.exportData({ format: 'new', parts: ['station'] })
|
|
110
|
+
view.exportData({ format: 'test', parts: ['station'] })
|
|
111
|
+
view.exportData({ format: 'test', parts: ['line'] })
|
|
112
|
+
view.exportData({ format: 'new', parts: ['channel'] })
|
|
113
|
+
view.exportData({ format: 'test', parts: ['waypoint'] })
|
|
112
114
|
```
|
|
113
115
|
|
|
116
|
+
### 统一入口(scope + parts)
|
|
117
|
+
|
|
118
|
+
先选**范围**(`scope`),再选**分项**(`parts`),最后按 `format` 落成业务表或标准文档:
|
|
119
|
+
|
|
120
|
+
| `scope` | 导哪一块 | 范围相关选项 |
|
|
121
|
+
| --- | --- | --- |
|
|
122
|
+
| `'all'`(缺省) | 全部数据,再按 `points` / `lines` / `channels` 收窄 | —— |
|
|
123
|
+
| `'viewport'` | 当前视口内的节点 / 线路 / 通道(通道按成员回线是否可见整条带上) | `margin`(屏幕 CSS 像素外扩)、`rect`(直接给世界矩形);这一档不认 `points` / `lines` / `channels` 三个筛选 |
|
|
124
|
+
| `'selection'` | 当前选中集;`points` 可显式点名,线路取两端都在集合里的那些,通道跟成员线路 | `points` / `lines` / `channels`、`withLines: false` 可不要线路;没有节点时返回 `ok: false` + `reason`,不抛异常 |
|
|
125
|
+
|
|
126
|
+
`exportData` 的 `format: 'full'` 始终返回标准文档本身,不夹带 `scope` / `viewRect` / `counts` 这些读数;`input` 与自定义格式同样保持导出器的原始结果。所有导出都只走这一个入口:范围写 `scope`、分项写 `parts`、口径写 `format`,没有再按业务表拆出第二套实例方法。
|
|
127
|
+
|
|
114
128
|
### 五套口径
|
|
115
129
|
|
|
116
130
|
| 口径 | 节点 | 线路 | 通道 | 拐点 |
|
|
117
131
|
| --- | --- | --- | --- | --- |
|
|
118
|
-
| `new
|
|
119
|
-
| `test
|
|
132
|
+
| `new` | `station_data`(外加 `show_station_list` / `show_station_data` 与四至范围) | 通道里的回线折在 `channel_data[].line_data` | `channel_data` | 跟着通道走:在 `coords`(拆过分组时另有一份 `groups`) |
|
|
133
|
+
| `test` | `point_data` | `line_data` | 回线上的 `channel_id` / `channel_name` / `parallel_group_id` | `parallel_groups` |
|
|
120
134
|
| `view`(`exportData` 缺省) | `points`(= `station_data` 那一套) | `lines`(= `line_data` 那一套,通道回线也在内) | `channels` | `parallelGroups` |
|
|
121
135
|
| `input`(`exportData({ format: 'input' })`) | `points`(与 `createGisView` 的入参同构:数据对象本身) | `lines`(`path` 拐点在要素里) | `channels`(`groups` 分组声明与拐点都在里面) | 折在 `lines[].path` 与 `channels[].groups[].path` 里,`counts.parallelGroups` 报的是通道分组总数 |
|
|
122
136
|
| `full`(`exportData({ format: 'full' })`) | `points`(标准写法:坐标一律 `[x, y]`,`labels` 一律数组) | `lines`(折线 / `decorations` 标记 / `labels` 标签都在要素里) | `channels`(`groups` 一律展开,单组也写全) | 折在 `lines[].path` 与 `channels[].groups[].path` 里,`documentCounts()` 里是 `waypoints` |
|
|
@@ -127,18 +141,18 @@ view.exportViewportData({ format: 'input' }) // 换成与创建入参同构的
|
|
|
127
141
|
|
|
128
142
|
`full` 是**文档口径**:它不是业务表,而是组件自己的整份数据(接收与导出同一份写法),下一节单独说。
|
|
129
143
|
|
|
130
|
-
### 视口内导出(`
|
|
144
|
+
### 视口内导出(`scope: 'viewport'`)
|
|
131
145
|
|
|
132
|
-
`
|
|
146
|
+
`exportData({ scope: 'viewport', ... })` 把**范围换成当前视口**:节点取中心落在可视范围里的,线路取包围盒沾到可视范围的(与 `listLines({ inView: true })`、与图上画出来的那一份可见判定一致 —— 被筛选 / 隐藏掉的不算,发散渲染还没铺到的也不算),通道取「成员回线里有一条在视口里」的那些,**分组一起跟着走**,不会只出半条通道。
|
|
133
147
|
|
|
134
|
-
`margin` 是**屏幕 CSS 像素**的外扩:把贴着视口边的一圈也带上(按当前 scale 换算成世界单位),`rect` 可以直接给一份世界矩形替掉视口(写法同 `getViewRect()`)。`format` / `parts` / `labels` / `box` 都照 `exportData` 写;`points` / `lines` / `channels`
|
|
148
|
+
`margin` 是**屏幕 CSS 像素**的外扩:把贴着视口边的一圈也带上(按当前 scale 换算成世界单位),`rect` 可以直接给一份世界矩形替掉视口(写法同 `getViewRect()`)。`format` / `parts` / `labels` / `box` 都照 `exportData` 写;`points` / `lines` / `channels` 这三个筛选这一档不认(范围由视口定)。`view` / `new` / `test` 业务口径会返回 `scope: 'viewport'` 与 `viewRect`(这次筛用的世界矩形)/ `margin` 三条读数;`format: 'full' | 'input' | 自定义格式` 保持导出器原始结果,不附这些读数。
|
|
135
149
|
|
|
136
150
|
演示页「编辑与保存」(`?demo=backend`)把这一步和分组 / 拐点 / 场站改动的接口请求体摆在一起看,见 `docs/demos/BackendSyncDemo.vue`;两种推送策略(点保存一起传 / 改完就传的合并 + 延迟)也在这一页。
|
|
137
151
|
|
|
138
152
|
```js
|
|
139
|
-
const visible = view.
|
|
140
|
-
// { points, lines, channels, parallelGroups, counts, warnings, viewRect, margin }
|
|
141
|
-
const inner = view.
|
|
153
|
+
const visible = view.exportData({ scope: 'viewport', margin: 24 })
|
|
154
|
+
// 业务口径:{ points, lines, channels, parallelGroups, counts, warnings, scope, viewRect, margin }
|
|
155
|
+
const inner = view.exportData({ scope: 'viewport', format: 'input' }) // 与创建入参同构,保持原始结果形状
|
|
142
156
|
```
|
|
143
157
|
|
|
144
158
|
### 标准数据文档(`full`):接收 == 导出
|
|
@@ -168,8 +182,8 @@ next.importData(saved) // 已有实例上整份灌回来(顶
|
|
|
168
182
|
三条保证:
|
|
169
183
|
|
|
170
184
|
- **接收的格式与导出的格式是同一份写法**:`createGisView(dom, doc)`、`view.importData(doc)` 认的都是它;`exportData({ format: 'input' })` 那三列(`points` / `lines` / `channels`)与 `labelStates` 也被 `importData` 接住 —— 与创建入参同构的那一份同样能回灌,只是没有相机与筛选那两列。
|
|
171
|
-
- **JSON 安全**:导出的是一次深拷贝,函数 / Symbol / DOM 元素 / 画布 / `Map` / `Set` / 循环引用这类存不进 JSON 的值按固定规则跳过(SVG 元素转成 outerHTML 字符串、图片元素转成 `{ type: 'image', src }
|
|
172
|
-
- **逐字节可对账**:归一化只补标准写法(坐标 `[x, y]`、`labels` 一律数组、通道一律展开 `groups`)、不重排既有键,所以「读文件 → 导出 → 再读回来」不会因为组件本身产生 diff。仓库根目录的 `sample-document.json` 就是 `sample-full.json` 的标准文档版(10 点 / 22 线 / 3 通道 / 39 拐点 / 23 标记 / 44 标签 / 6
|
|
185
|
+
- **JSON 安全**:导出的是一次深拷贝,函数 / Symbol / DOM 元素 / 画布 / `DataView` / `Map` / `Set` / 循环引用这类存不进 JSON 的值按固定规则跳过(SVG 元素转成 outerHTML 字符串、图片元素转成 `{ type: 'image', src }`、类型化数组展开成普通数组、`BigInt` 元素写字符串、`NaN` / `Infinity` 写 `null`),每一条都通过 `onWarning` 报一句,不会静默丢字段。
|
|
186
|
+
- **逐字节可对账**:归一化只补标准写法(坐标 `[x, y]`、`labels` 一律数组、通道一律展开 `groups`)、不重排既有键,所以「读文件 → 导出 → 再读回来」不会因为组件本身产生 diff。仓库根目录的 `sample-document.json` 就是 `sample-full.json` 的标准文档版(10 点 / 22 线 / 3 通道 / 39 拐点 / 23 标记 / 44 标签 / 6 条标签状态),可用 `exportData({ format: 'full' })` 与它逐字节对账,再用导出的那一份重建一个实例、再导出一次,两次逐字节相同。
|
|
173
187
|
|
|
174
188
|
- **逐字段读这份文件**:[sample-document字段详解.md](../sample-document字段详解.md) 把仓库根目录的 `sample-document.json` 逐列逐对象讲一遍(`labelStates` / `channels[].groups[].path` / `lines[].path` 的拐点在最前面单独回答;字段索引由 `docs/steps/check-sample-fields.mjs` 与文件对账,154 个字段路径一个不多一个不少);想在图上点着看开 [guide/sample-document.html](../guide/sample-document.html),所有 markdown 也能在文档中心 [index.html](../index.html) 里读。
|
|
175
189
|
|
|
@@ -179,17 +193,11 @@ next.importData(saved) // 已有实例上整份灌回来(顶
|
|
|
179
193
|
|
|
180
194
|
| 方法 | 出什么 | 选项(除共用项) |
|
|
181
195
|
| --- | --- | --- |
|
|
182
|
-
| `
|
|
183
|
-
| `exportPointData(options?)` | `point_data` | —— |
|
|
184
|
-
| `exportLineData(options?)` | `line_data` | —— |
|
|
185
|
-
| `exportChannelData(options?)` | `channel_data`(`counts.lines` 是这些通道里回线的总数) | `lines` 能把通道里的 `line_data` 收到这几条回线 |
|
|
186
|
-
| `exportParallelGroups(options?)` | `parallel_groups` | `all: true` 把当前所有分组(含声明里就写了拐点、没被编辑过的)都列出来;`channels` 只留某几条通道 / 分组 |
|
|
187
|
-
| `exportViewportData(options?)` | 视口内的那一撮(节点 / 线路 / 通道 + 拐点分组,通道跟着成员回线走) | `margin` 屏幕 CSS 像素外扩(贴着边的一圈也带上)、`rect` 世界矩形替掉视口;其余与 `exportData` 同,返回多带 `viewRect` / `margin` |
|
|
188
|
-
| `exportData(options?)` | 按 `format` 与 `parts` 给 | `format: 'view' \| 'new' \| 'test' \| 'input' \| 'full'`(缺省 `'view'`;`'input'` 给与 `createGisView` 入参同构的一份;`'full'` 给一份标准数据文档,见「五套口径」与「标准数据文档」);`parts` 多选分项(见下);`all`(拐点,同 `exportParallelGroups`);`tcFilePath` / `warningMsg`(`'new'` 口径那两列) |
|
|
196
|
+
| `exportData(options?)` | **统一入口**:按 `scope` + `parts` + `format` 给 | `scope: 'all' \| 'viewport' \| 'selection'`(缺省 `all`,也认 `visible` / `view` / `selected`);`parts` 多选分项(见下);`format: 'view' \| 'new' \| 'test' \| 'input' \| 'full'`(缺省 `'view'`;`'input'` / `'full'` 见「五套口径」);`withLines: false`(selection 不要线路);`all`(列出全部分组);`tcFilePath` / `warningMsg`(`'new'` 口径那两列);`box: false` / `showStation: false`(`'new'` 口径不要四至范围 / 母线清单) |
|
|
189
197
|
|
|
190
|
-
|
|
198
|
+
`points` / `lines` / `channels` 是「只导出这几个」的筛选(`id` / 下标 / `{ id }` / `{ index }`,单个或数组;不写就是全部),`extra` 是额外挂到结果上的字段(`tc_file_path` 这类业务元数据,同名的键以它为准)。
|
|
191
199
|
|
|
192
|
-
|
|
200
|
+
标签列:`labels` 写 `true` / `false` / `'auto'`。缺省 `'auto'`:只给「本来就有标签要说的」记录带这一列(配置 / 数据里写了标签,或者用 `addLabel` / `updateLabel` / 拖过标签的要素);`true` 时每条记录都带(没有标签的是空数组);`false` 一律不带。这一列就是逐条标签写法(见「标签(labels,导入即回显)」),`labels: false` 时数据里原本的 `labels` 字段也不会从业务列里漏出去。通道那一份按同一个口径写进 `channel_data[].line_data`。
|
|
193
201
|
|
|
194
202
|
### 分项多选(parts)
|
|
195
203
|
|
|
@@ -220,14 +228,14 @@ view.exportData({ format: 'view', parts: ['station'] })
|
|
|
220
228
|
|
|
221
229
|
### 导出选中的数据(分项)
|
|
222
230
|
|
|
223
|
-
`
|
|
231
|
+
统一入口写 `exportData({ scope: 'selection', ... })` 就是「`exportSelection` 的选中口径 + `exportData` 的分项口径」:选中口径与 `exportSelection` **完全一致**(不给 `points` 就用当前选中集里的节点;线路取两端都在导出集里的那些;通道取「成员里有导出线路」的那些),出的却是分项业务表记录。
|
|
224
232
|
|
|
225
233
|
```js
|
|
226
|
-
const picked = view.
|
|
227
|
-
// → { ok: true, reason: null, points, channels, parallelGroups, counts, parts, pointIndexes, lineIndexes, channelIndexes, warnings }
|
|
234
|
+
const picked = view.exportData({ scope: 'selection', parts: ['station', 'channel'] })
|
|
235
|
+
// → { ok: true, scope: 'selection', reason: null, points, channels, parallelGroups, counts, parts, pointIndexes, lineIndexes, channelIndexes, warnings }
|
|
228
236
|
|
|
229
237
|
// 也可以自己点名要哪几个节点(不用先框选)
|
|
230
|
-
view.
|
|
238
|
+
view.exportData({ scope: 'selection', points: ['node-1', 2], withLines: false, parts: ['line'] })
|
|
231
239
|
```
|
|
232
240
|
|
|
233
241
|
选项与 `exportSelection` 共用 `points` / `lines` / `channels` / `withLines` / `all` / `extra`,另有 `parts`(同 `exportData`)。通道那一份只留**导出集里**的回线(`channel_data[].line_data` 按导出线路过滤),线路 / 拐点也只出导出集里的。一个节点都没解析出来时返回 `{ ok: false, reason, counts, warnings, parts }`,不抛异常 —— `reason` 会说清是「没有要导出的节点」还是「没找到要导出的节点」。
|
|
@@ -252,9 +260,83 @@ URL.revokeObjectURL(link.href)
|
|
|
252
260
|
- **导出的是拷贝**:结果里的对象与数组(含 `coords_list` / `coords` / `group_route` 这些坐标数组)都复制过一份,改结果不影响组件内部的数据,也不会被下一次导出读到。
|
|
253
261
|
- **标签列跟着走**:`labels` 列把这一条要素的标签清单与运行时改动(内容 / 位置 / 偏移 / 样式 / 显隐 / 运行时加的标签)一起写出来,是组件认的逐条标签写法 —— 把导出的记录喂回 `points` / `lines`(`format: 'new'` 时是 `station_data` / `channel_data[].line_data`)就能把标签连位置、样式、拖出来的偏移一起回显,不需要再配 `label` / `lineLabel`。
|
|
254
262
|
|
|
263
|
+
### 自定义导出格式(后端 DTO)
|
|
264
|
+
|
|
265
|
+
内置五档不够贴合后端表结构时,用模块级的 `defineExportFormat(name, adapter)` 定义一个**同步**导出器(也可以直接用更底层的 `registerExportFormat`)。它拿到的是已经按标准文档归一、深拷贝、坐标补齐、JSON 安全过滤过的数据,函数只负责把标准文档映射成自己的 DTO:
|
|
266
|
+
|
|
267
|
+
```js
|
|
268
|
+
import {
|
|
269
|
+
createGisView,
|
|
270
|
+
defineExportFormat,
|
|
271
|
+
hasExportFormat,
|
|
272
|
+
listExportFormats
|
|
273
|
+
} from 'gis-view-webgl2'
|
|
274
|
+
|
|
275
|
+
const disposeBackendDto = defineExportFormat('backend-dto', ({ document, counts, warnings }) => {
|
|
276
|
+
if (!document.points.length) {
|
|
277
|
+
warnings.push('当前没有节点:后端 DTO 仍会发一个空的 stations 数组')
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
schema: 'gis-view.backend.v1',
|
|
281
|
+
camera: document.view.camera,
|
|
282
|
+
counts,
|
|
283
|
+
stations: document.points.map(point => ({
|
|
284
|
+
stationId: point.id,
|
|
285
|
+
name: point.name ?? '',
|
|
286
|
+
coordinate: [point.x, point.y],
|
|
287
|
+
attrs: point.props ?? {}
|
|
288
|
+
})),
|
|
289
|
+
routes: document.lines.map(line => ({
|
|
290
|
+
routeId: line.id,
|
|
291
|
+
from: line.from ?? null,
|
|
292
|
+
to: line.to ?? null,
|
|
293
|
+
polyline: line.path ?? []
|
|
294
|
+
})),
|
|
295
|
+
channels: document.channels.map(channel => ({
|
|
296
|
+
channelId: channel.id,
|
|
297
|
+
groups: channel.groups.map(group => ({
|
|
298
|
+
groupId: group.id,
|
|
299
|
+
routeIds: group.lineIds,
|
|
300
|
+
waypoints: group.path
|
|
301
|
+
}))
|
|
302
|
+
}))
|
|
303
|
+
}
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
const dto = view.exportData({ format: 'backend-dto' })
|
|
307
|
+
const visibleDto = view.exportData({ scope: 'viewport', format: 'backend-dto', margin: 24 })
|
|
308
|
+
// 自定义格式保持导出器原始结果;需要范围读数时另取 getViewRect()
|
|
309
|
+
|
|
310
|
+
hasExportFormat('backend-dto') // true
|
|
311
|
+
hasExportFormat('view') // true(内置)
|
|
312
|
+
listExportFormats() // ['view', 'new', 'test', 'input', 'full', 'backend-dto', …]
|
|
313
|
+
disposeBackendDto() // true:只在当前仍是自己的 adapter 时注销,之后再用这个名字会报「导出格式只认…」
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
「通道与数据」演示(`?demo=channel`)的「后端 DTO(自定义格式)」按钮就是这一段的现场版:挂载时用 `defineExportFormat` 注册 `channel-backend-dto`,点击按标准文档映射出一份业务 DTO 并打印当前格式表,离开页签时调用返回的清理函数。
|
|
317
|
+
|
|
318
|
+
导出器上下文:
|
|
319
|
+
|
|
320
|
+
| 字段 | 说明 |
|
|
321
|
+
| --- | --- |
|
|
322
|
+
| `view` | 当前实例,需要别的方法读数时直接用 |
|
|
323
|
+
| `document` | 标准文档(与 `exportData({ format: 'full' })` 同构):`points` / `lines` / `channels` / `labelStates` / `view` / `filter`。已经深拷贝、坐标归一、JSON 安全过滤;当只读数据用 |
|
|
324
|
+
| `counts` | `documentCounts(document)` 的结果:`points / lines / channels / groups / waypoints / decorations / labels / labelStates` |
|
|
325
|
+
| `options` | 这次传给 `exportData` 的选项,`format` 已换成当前格式名 |
|
|
326
|
+
| `parts` | 本次要导出的分项;没写 `parts` 时是全部四项的规范顺序 |
|
|
327
|
+
| `warnings` | 已有的筛选 / 归一化告警;继续 `push` 会统一走 `onWarning` |
|
|
328
|
+
|
|
329
|
+
注册表的行为:
|
|
330
|
+
|
|
331
|
+
- 名字会 `trim`;自定义格式名区分大小写,注册同名会以后一次为准。内置 `view` / `new` / `test` / `input` / `full` 是保留字,大小写变体也不能覆盖,`registerExportFormat` 返回 `false`。
|
|
332
|
+
- `hasExportFormat(name)` 同时认内置与自定义;`listExportFormats()` 先列内置五档,再按注册顺序列自定义格式。
|
|
333
|
+
- 导出器必须同步返回普通数据;返回 Promise 时 `exportData` 会抛错(导出是按需取的同步快照,异步结果交给调用方自己再接一层)。
|
|
334
|
+
- 导出器抛错会带格式名重新抛出;`warnings` 里的内容仍会先走 `onWarning`,不会被吞掉。
|
|
335
|
+
- 注册是模块级、对所有实例生效;应用启动时注册一次即可。插件 / 微前端优先用 `defineExportFormat` 返回的清理函数:它只在当前注册项仍是自己那次定义时删除(同名重复注册且 adapter 相同也算新的一次),旧插件卸载不会误删后来覆盖注册的新适配器;确实要按名字全局摘除时再用 `unregisterExportFormat(name)`。
|
|
336
|
+
|
|
255
337
|
## 插件
|
|
256
338
|
|
|
257
|
-
`plugins` 是给「跨项目复用的视图级逻辑」准备的最小挂载点:每个插件是一个 `view => hook` 的函数,视图创建完成(数据、覆盖层、鹰眼图都就绪)之后按数组顺序执行一次;返回值里如果有 `destroy` 函数,会在实例 `destroy()` 时按相反顺序回调:
|
|
339
|
+
`plugins` 是给「跨项目复用的视图级逻辑」准备的最小挂载点:每个插件是一个 `(view, context) => hook` 的函数,视图创建完成(数据、覆盖层、鹰眼图都就绪)之后按数组顺序执行一次;返回值里如果有 `destroy` 函数,会在实例 `destroy()` 时按相反顺序回调:
|
|
258
340
|
|
|
259
341
|
```js
|
|
260
342
|
// 按 F 聚焦当前选中要素:不改组件,只组合已有 API,换项目可以直接复用
|
|
@@ -275,12 +357,13 @@ const focusSelectionPlugin = view => {
|
|
|
275
357
|
const view = createGisView(container, { points, lines, plugins: [focusSelectionPlugin] })
|
|
276
358
|
```
|
|
277
359
|
|
|
278
|
-
- 插件拿到的就是 `createGisView` 返回的实例,可以用全部实例方法,也能用 `el` / `canvas` / `gl` 自己挂 DOM(图例、比例尺、右键菜单、URL 同步都属于这一类)。
|
|
360
|
+
- 插件拿到的就是 `createGisView` 返回的实例,可以用全部实例方法,也能用 `el` / `canvas` / `gl` 自己挂 DOM(图例、比例尺、右键菜单、URL 同步都属于这一类)。
|
|
361
|
+
- 第二个参数是 `{ apiVersion: 1, view }`:`view` 与第一个参数相同,`apiVersion` 用来判断插件写法是否兼容;当前只做加字段的向后兼容扩展,旧的 `view => hook` 写法和新写法可以同时使用。
|
|
279
362
|
- 按数组顺序执行,后一个能看见前一个留下的状态;返回 `{ destroy }` 才会被回收,只做一次性初始化可以不返回。
|
|
280
363
|
- 单个插件初始化 / 清理抛错不会打断其它插件,错误走 `onError` 回报;实例 `destroy()` 之后插件列表清空。
|
|
281
364
|
- 改配置请走 `updateOptions()`,不要在插件里直接改引擎内部对象 —— 组件只承诺公开 API 的兼容性。
|
|
282
365
|
|
|
283
|
-
创建之后才决定要挂的功能走 `view.use(plugin)`:写法与 `plugins`
|
|
366
|
+
创建之后才决定要挂的功能走 `view.use(plugin)`:写法与 `plugins` 里的一项完全一致(同样收到 `(view, context)`),调用即执行,返回一个卸载函数 —— 调它就把这个插件自己的 `destroy` 清掉(重复调用只清一次,实例销毁之后再调也安全);插件初始化抛错同样只走 `onError`:
|
|
284
367
|
|
|
285
368
|
```js
|
|
286
369
|
// 懒加载的功能接进来:面板打开时才挂「选中变化计数」,面板关掉就卸掉
|
|
@@ -297,6 +380,16 @@ dispose() // 卸载:跑上面那个 destroy,从实例的清理列表里摘
|
|
|
297
380
|
- 与插件并列的另一条扩展点是**自定义图元**:插件加的是「视图级行为」(快捷键、面板、URL 同步…),自定义图元加的是「点怎么画」—— 注册一支画笔 `view.registerSymbol('三角', painter)`,或在数据里写 `{ type: 'custom', draw }`,见「自定义图元」一节。
|
|
298
381
|
- 第三条扩展点是**自定义条件运算符**:插件加行为、图元加画法,运算符加的是「条件语言里的判定动词」—— `view.registerOperator('mod2', value => current => …)` 之后规则 / 筛选 / 标签的 `when` 都能写 `{ vn_kv: { mod2: true } }`,见「条件语言 → 自定义运算符」。
|
|
299
382
|
|
|
383
|
+
### 五类扩展点怎么选
|
|
384
|
+
|
|
385
|
+
| 扩展点 | 适合做什么 | 作用域 | 清理方式 |
|
|
386
|
+
| --- | --- | --- | --- |
|
|
387
|
+
| `plugins` / `view.use(plugin)` | 视图级行为:快捷键、URL 同步、图例、业务面板,组合已有实例 API | 单个视图实例 | `options.plugins` 随 `view.destroy()` 按相反顺序回调;`view.use()` 返回幂等卸载函数,调用即执行插件的 `destroy` |
|
|
388
|
+
| `view.registerSymbol(name, painter)` | 决定「点怎么画」:品牌图标、告警形状、状态符号等 | 模块级符号表,同页所有实例共享 | 返回幂等注销函数,只删除自己这次注册;不会随 `view.destroy()` 自动注销 |
|
|
389
|
+
| `view.registerOperator(name, test)` | 给规则 / 筛选 / 标签增加条件动词,例如 `mod2`、`evenIndex` | 模块级条件运算符表,同页所有实例共享 | 返回幂等注销函数,只删除自己这次注册;注销后旧条件按「不成立」处理 |
|
|
390
|
+
| `view.addWebGLLayer(layer)` | 直接拿组件的 WebGL2 上下文画自定义标记、特效或调试几何;层画在内置内容之后,不参与拾取与数据导出 | 单个视图实例 | 返回卸载函数;也可用 `updateWebGLLayer` / `removeWebGLLayer` / `clearWebGLLayers` 维护,画布外还可通过 `view.gl` 直接取上下文 |
|
|
391
|
+
| `defineExportFormat(name, adapter)` | 把标准文档映射成后端 DTO、业务表列名或项目自己的嵌套结构(同步导出器) | 模块级导出格式表,所有实例的 `exportData` 共享 | 返回幂等清理函数,只删除自己这次注册;需要按名字全局摘除时用 `unregisterExportFormat(name)` |
|
|
392
|
+
|
|
300
393
|
## 自建界面
|
|
301
394
|
|
|
302
395
|
组件不生成任何面板,自建控制面板只需要调用实例方法;读数用 `onCursor`、`onCameraChange`、`onStats` 收到的数据渲染即可:
|