gis-view-webgl2 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +12 -9
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.ts +112 -17
  4. package/dist/index.js +1 -1
  5. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +34 -0
  6. package/docs/GisView.md +3 -3
  7. package/docs/ai/06-/344/274/232/350/257/235/347/264/242/345/274/225/344/270/216/345/244/215/346/237/245/346/226/271/346/263/225.md +30 -0
  8. package/docs/api/index.html +22 -8
  9. package/docs/demos/ApiCoverageDemo.vue +1 -1
  10. package/docs/demos/ExtensionDemo.vue +69 -12
  11. package/docs/demos/SearchFilterDemo.vue +13 -11
  12. package/docs/demos/api-index.js +13 -9
  13. package/docs/demos/demos.js +1 -1
  14. package/docs/demos/gis-view-demo/GisViewDemoCardHeader.vue +1 -0
  15. package/docs/demos/gis-view-demo/panels/OpsPanels.vue +2 -2
  16. package/docs/demos/gis-view-demo/useGisViewDemo.js +9 -9
  17. package/docs/index.html +41 -4
  18. package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +18 -3
  19. package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +7 -6
  20. package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +24 -3
  21. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +90 -34
  22. package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +32 -3
  23. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +7 -4
  24. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +9 -6
  25. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +39 -7
  26. 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 +3 -3
  27. package/docs/manual/21-/350/257/264/346/230/216/344/270/216/345/270/270/350/247/201/351/227/256/351/242/230.md +6 -3
  28. package/docs/steps/README.md +7 -7
  29. package/docs/steps/selftest-demos.html +26 -9
  30. package/docs/steps/selftest-docs.html +33 -4
  31. package/docs/steps/selftest-features.html +91 -10
  32. package/docs/steps/selftest-scale.html +98 -0
  33. package/docs/steps/selftest-select.html +90 -0
  34. package/package.json +1 -1
@@ -330,3 +330,37 @@
330
330
  - **性能优化**:`measurePointSpacing()` 去掉并列距离时的中点距离比较,减少规则网格下的重复计算;`reflowChannelWidths()` 为单组通道增加宽度未变时的快路径,直接跳过车道布局与回线路径重建,多组通道仍走完整重排。10 万点微基准由 **34.933 ms** 降至 **27.572 ms**,约提升 **21%**。
331
331
  - **核心文件**:`src/gis-view/utils.js`、`src/gis-view/engine.js`、`docs/demos/ApiCoverageDemo.vue`、`docs/demos/demos.js`、`docs/demos/gis-view-demo/panels/ViewPanels.vue`、`docs/demos/gis-view-demo/useGisViewDemo.js`、`docs/manual/03` / `07` / `08` / `09` / `14` / `16` / `17` / `18`、`docs/steps/selftest-demos.html`、`docs/steps/selftest-scale.html`、`docs/steps/README.md`、`docs/internals/16-03-优化与取舍-下.md`、根 `README.md` 与 `docs/demos/api-index.js`(生成物)。
332
332
  - **验证**:`npx tsc --noEmit`、`npm run build`、`npm run check:docs`(实例成员 **253**、options **95**、数据字段 **249**)、`node docs/steps/gen-api-index.mjs --check` 与 `git diff --check` 全部通过;14 个自测页共 **2225 条通过 / 0 失败**,其中 `selftest-demos.html` **333 PASS / 0 FAIL**、`selftest-scale.html` **109 PASS / 0 FAIL / 6 DIAG**。复查入口见 [06 的增量记录](ai/06-会话索引与复查方法.md#2026-10-22-增量接口能力-demoapi-细节与-fitspacing-中心修正)。
333
+
334
+ ## 2026-10-23 增量(「修改.txt」四条:focusOn 缩放 / 四类搜索 / 筛选选中保留 / 参数说明)
335
+
336
+ 本轮仍是现有会话的继续增量,没有新建会话归档,因此不改上表的 39 条会话统计。需求原话来自仓库根目录的 `修改.txt`:① 检查 `focusOn` 对线路 / 通道是否仍默认放大,若有则取消;② `search` 增加与 `focusOn` 对应的目标类型;③ 排查筛选为何会取消选中,筛选应只隐藏;④ 把方法与参数说明补细。
337
+
338
+ - **focusOn 缩放核对**:现有四类目标(节点 / 线路 / 通道 / 分组)都已解析成真实 `bounds` 后走同一条 `zoomToBounds`,线路与通道没有额外倍率;本轮不改行为,只补自测逐位比较 `focusOn` 与同一份 `bounds` 的 `zoomToBounds` 比例。
339
+ - **四类搜索**:`scope` 新增 `groups`,并接受与 `focusOn` 一致的单数别名 `point` / `line` / `channel` / `group`;省略或写错时四类全搜。结果新增 `groups`,分组命中带 `channelIndex` / `groupId` / `groupIndex` 与轻量 `item`,`bounds` 按整组范围计算,可直接交给 `focusOn` / `selectFeatures`。同步、`searchAsync`、Worker 与结果回填四条路共用同一口径;分组快照随分组范围请求交给 Worker,改过分组后不会用旧快照匹配。
340
+ - **筛选取消选中**:根因是引擎的 `isKeyAlive()` 查 `pointByIndex` / `lineByIndex`,这两个索引只含可见要素,筛选隐藏被误判成删除,整份重排时把选中集清掉。改为查原始 `points` / `lines` 数组后,筛选只隐藏显示、不删数据、不清空选中集;被隐藏的选中项在条件放宽 / 清空后恢复,且不会补发选中变更。要删除仍走 `removePoints` / `removeLines`。
341
+ - **参数与文档**:补齐 `types.d.ts` 的搜索范围 / 分组命中说明与筛选注释,手册 `14` / `16` / `17` / `18` / `21` 写清四类 scope、别名、`result.groups`、分组命中字段、`focusOn` 不额外放大,以及筛选只隐藏;根 `README.md` 与 `src/gis-view/README.md` 同步当前口径。`SearchFilterDemo` 增加分组搜索入口与提示。
342
+ - **核心文件**:`src/gis-view/search.js`、`src/gis-view/search.worker.js`、`src/gis-view/search-runner.js`、`src/gis-view/create.js`、`src/gis-view/engine.js`、`src/gis-view/types.d.ts`、`docs/demos/SearchFilterDemo.vue`、`docs/steps/selftest-features.html`、`docs/steps/selftest-demos.html`、`docs/manual/14` / `16` / `17` / `18` / `21`、根 `README.md`、`src/gis-view/README.md`、`docs/demos/api-index.js`(生成物)、`docs/AI对话汇总.md` 与 `docs/ai/06-会话索引与复查方法.md`。
343
+ - **验证**:`selftest-features.html` **666** 条、`selftest-demos.html` **334** 条、`selftest-docs.html` **130** 条,三页共 **1130** 条通过 / 0 失败;`npx tsc --noEmit`、`node --check`(三个搜索文件)、`npm run check:docs`(253 个实例成员 / 95 个 options / 249 个数据字段)、`node docs/steps/gen-api-index.mjs --check`、`npm run build` 与 `git diff --check` 全部通过。公开实例成员、options 与主数据字段计数未变。复查入口见 [06 的增量记录](ai/06-会话索引与复查方法.md#2026-10-23-增量修改txt四条focuson-缩放--四类搜索--筛选选中保留--参数说明)。
344
+
345
+ ## 2026-10-23 增量(「问题.txt」四条:SVG 描边 / 文档表格 / selectTrigger / 筛选删除)
346
+
347
+ 本轮仍是现有会话的继续增量,没有新建会话归档,因此不改上表的 39 条会话统计。需求原话来自仓库根目录的 `问题.txt`:① 导出 SVG 时节点图标异常、只剩纯白;② 文档页面的参数表格渲染错误;③ 选中默认单击、可配置双击选中,并暴露双击事件;④ 筛选可配置为删除或隐藏。
348
+
349
+ - **SVG 节点描边**:根因是 `resolvePointWithBase()` 没把解析出来的 `strokeColor` 放进节点条目,`paintResolvedPoint()` 也没写回 `entry.strokeColor`,SVG 符号里的 `currentStrokeColor` 因此回落成白色。现在两处都带上实际描边色,规则 / 状态样式同样生效,`currentColor` 仍取填充色。
350
+ - **文档参数表格**:`docs/index.html` 的表格 `parseRow()` 改为逐字符扫描,反引号代码段里的 `|` 不再拆列,`\|` 还原成单元格内的普通竖线;修复联合类型(如导出格式的 `'image/png' | 'image/jpeg' | 'svg'`)被拆成多列。自测补了带反引号联合类型的表格夹具。
351
+ - **`selectTrigger`**:新增组件与引擎选项,缺省 `'click'` 保持第一次单击选中;`'dblClick'` 下第一下只发 `onClick`,第二次(320ms / 6px 内)才选中、清空或取反,并同时发 `onClick`(`action.double: true`)与 `onDblClick`。也收 `doubleClick` / `double` 别名;Ctrl / `multiSelect` 在双击模式第二下取反,`onClick.action.type` 为 `toggleSelect`。运行时 `updateOptions({ selectTrigger })` 只走表现快路径并清空上一次点击序列;单击模式的 Ctrl / `multiSelect` 取反保持旧口径,不额外发点击 / 双击事件。
352
+ - **筛选删除**:`filter.mode` 支持 `'hide'`(缺省)/ `'remove'`。`remove` 按筛选结果先删线路、再删节点,无 id 线路用删除前下标;删除集合就是筛选结果,不再按 `lineFollowsNode` 二次扩圈。中间 `removePoints` / `removeLines` 数据变更与 `onFilter` 被抑制,只发一条 `dataChange('filterRemove')` 和最终一次 `onFilter`,删完隐藏集合回到空。事件载荷新增 `mode` / `pointIndexes` / `removedPoints` / `removedLineItems`(其余 `lineIndexes` / `removedLineIndexes` / `lineIds` / `count` / `removedLines` / `removedChannels` 继续保留),`points` / `lines` / `channels` 仍是删除后的最新数据。
353
+ - **历史与初始时序**:一次 remove 只落一步历史;撤销恢复数据时通过 `suppressRemoval` 阻止仍生效的 remove 条件再次删除,重做不补发第二条 `filterRemove`。同步初始筛选在 `ready` 前删完;初始筛选走 Worker 时删除与 `onFilter` 在异步结果回来后才发生。顺带修正 `history.js` 的 `spliceEntries()` 一次撤销多条删除时的下标偏移,多条线路恢复后保持原顺序。
354
+ - **核心文件**:`src/gis-view/point.js`、`engine.js`、`create.js`、`filter.js`、`history.js`、`types.d.ts`、`docs/index.html`、`docs/steps/selftest-select.html` / `selftest-scale.html` / `selftest-docs.html` / `selftest-demos.html`、`docs/manual/03` / `04` / `08` / `14` / `15` / `16` / `17` / `18` / `21`、`README.md`、`src/gis-view/README.md`、`docs/GisView.md`、`docs/steps/README.md`、`docs/demos/api-index.js`(生成物)与本轮 aimd。
355
+ - **验证**:`selftest-select.html` **69**、`selftest-docs.html` **131**、`selftest-scale.html` **119**、`selftest-actions.html` **96**、`selftest-demos.html` **334**、`selftest-data.html` **103**、`selftest-features.html` **666**,相关 7 页共 **1518** 条通过 / 0 失败(`selftest-scale` 另有 6 条 DIAG 耗时读数)。`npx tsc --noEmit`、五个源码文件的 `node --check`、`node docs/steps/gen-api-index.mjs` 与 `--check`、`npm run check:docs`(253 个实例成员 / 250 个方法 / 250 个数据字段 / 96 个 options)、`npm run build`、`git diff --check` 全部通过。复查入口见 [06 的增量记录](ai/06-会话索引与复查方法.md#2026-10-23-增量问题txt四条svg-描边--文档表格--selecttrigger--筛选删除)。
356
+
357
+ ## 2026-10-23 增量(「修改.txt」四项:focusOn 缺省居中 / 文档互跳 / 扩展命令 / 性能优化)
358
+
359
+ 本轮仍是现有会话的继续增量,没有新建会话归档,因此不改上表的 39 条会话统计。需求原话来自仓库根目录的 `修改.txt`:① 节点 / 线路 / 通道的 `focusOn` 表现不一致,缺省应只居中、放大必须手动设置;② 增加文档互跳,并解决方法速查左栏点击后右侧滚动过慢;③ 继续增加组件的可扩展能力与灵活性并提供 demo;④ 优化组件交互与方法性能。
360
+
361
+ - **focusOn 缺省口径**:`focusOn(target, options)` 现在缺省只 `centerOn`,不再自动 `zoomToBounds`;只有 `zoom: true` 才按目标范围适配,`scale` 优先于 `zoom`,`center: true` 保留为显式居中。节点、线路、通道、分组与批量目标共用同一口径;演示页里原先依赖自动适配的定位都改成显式 `zoom: true`。
362
+ - **文档互跳与左栏跳转**:API 速查顶栏增加文档中心 / 实例方法 / 参数速查 / 逐方法示例四个深链,每个主题分组标题增加对应手册章节链接;左栏跳转由平滑滚动改为 `behavior: 'auto'`,长页面不再缓慢滚过整屏,并暴露 `__gisApi.scrollToGroup()` 供自测与宿主复用。
363
+ - **扩展能力与 demo**:新增实例命令 `registerCommand` / `executeCommand` / `listCommands`。命令表按视图隔离,同名后注册覆盖、注销函数幂等且不会误删后注册的同名命令;上下文冻结为 `{ view, name }`,执行结果区分成功值与未注册 / 非法名字 / handler 异常,`destroy()` 清空命令表。`ExtensionDemo` 补齐插件、自定义图元、条件运算符、导出格式、WebGL 自定义层与实例命令六类扩展点。
364
+ - **交互与方法性能**:定向补丁只在改动可能影响筛选结果时重跑筛选;只改展示字段时沿用原结果,`id` / 坐标 / `from` / `to` / `visible` 与函数 / 自定义运算符条件保持保守重跑。引擎新增定向的通道 / 分组范围测量,`focusOn` 不再为一段范围复制整页通道几何;事件总线在只有一条订阅时直接调用,省掉每次事件分发的快照数组。本轮没有做可信的性能基准,不提供加速倍数。
365
+ - **核心文件**:`src/gis-view/create.js`、`engine.js`、`events.js`、`types.d.ts`、`docs/api/index.html`、`docs/demos/ExtensionDemo.vue`、`docs/demos/SearchFilterDemo.vue`、`docs/demos/ApiCoverageDemo.vue`、`docs/demos/gis-view-demo/useGisViewDemo.js`、`docs/demos/gis-view-demo/panels/OpsPanels.vue`、`docs/demos/gis-view-demo/GisViewDemoCardHeader.vue`、`docs/demos/demos.js`、`docs/demos/api-index.js`(生成物)、`README.md`、`src/gis-view/README.md`、`docs/GisView.md`、`docs/index.html`、`docs/manual/08` / `14` / `15` / `16` / `17` / `18` / `19`、`docs/steps/README.md`、`docs/steps/selftest-features.html` / `selftest-demos.html` / `selftest-docs.html` 与本轮 aimd。
366
+ - **验证**:`selftest-features.html` **672** 条、`selftest-demos.html` **337** 条、`selftest-docs.html` **134** 条,三页共 **1143** 条通过 / 0 失败;`node --check src/gis-view/create.js`、`npx tsc --noEmit`、`npm run build`、`node docs/steps/gen-api-index.mjs`、`npm run check:docs`(**256** 个实例成员 = **253** 个方法 + 3 个属性 / **96** 个 options / **16** 个分组 / **250** 个数据字段)与 `git diff --check` 全部通过。复查入口见 [06 的增量记录](ai/06-会话索引与复查方法.md#2026-10-23-增量修改txt四项focuson-缺省居中--文档互跳--扩展命令--性能优化)。
package/docs/GisView.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  `GisView` 是一套不依赖任何第三方图形库的 WebGL2 矢量视图:渲染层是原生 WebGL2(GLSL ES 3.00 + 实例化绘制),交互层只有指针与滚轮,界面只内置可选的小地图(配 `minimap` 才有,见「鹰眼图」),其余全靠使用方。它没有 Vue 组件形态,用 `createGisView(dom, options)` 把画布直接挂到传入的 DOM 上,宽高默认跟随容器。
4
4
 
5
- 控制面板、读数面板、图例这类视图工具都不内置:组件只提供画布与实例方法,样式和布局完全由使用方决定,后续改样式不需要等待组件升级。数据侧该有的都给了:按属性筛选(点被过滤时绑定线路与通道一起消失,大数据量走 Web Worker)、按属性批量配色换符号、节点标签与点 / 线 / 通道的悬浮提示,都可以通过 options 或实例方法配置。
5
+ 控制面板、读数面板、图例这类视图工具都不内置:组件只提供画布与实例方法,样式和布局完全由使用方决定,后续改样式不需要等待组件升级。数据侧该有的都给了:按属性筛选(默认只隐藏,也可显式从数据删除;点被过滤时绑定线路与通道一起消失,大数据量走 Web Worker)、按属性批量配色换符号、节点标签与点 / 线 / 通道的悬浮提示,都可以通过 options 或实例方法配置。
6
6
 
7
7
  **这份手册按主题拆成了 21 章,正文在 [`docs/manual/`](manual/) 下**:找方法先看 [16 实例方法总表](manual/16-实例方法总表.md),查参数跳 [17 参数速查](manual/17-参数速查.md),要能直接复制的调用看 [18 逐方法示例](manual/18-逐方法示例.md);只想先跑起来看 [01 快速开始](manual/01-快速开始.md)。
8
8
 
@@ -32,7 +32,7 @@
32
32
  | [14 视图与筛选](manual/14-视图与筛选.md) | 筛选、搜索定位、初始视图与缩放范围、背景层、鹰眼图、近义入口 |
33
33
  | [15 导出与集成](manual/15-导出与集成.md) | 导出图片 / 选中节点 / 数据、自定义导出格式,插件、自建界面与键盘交互 |
34
34
  | [16 实例方法总表](manual/16-实例方法总表.md) | 按功能分组的实例方法主表(找方法从这一章开始) |
35
- | [17 参数速查(逐个方法)](manual/17-参数速查.md) | 253 个成员逐个列全:参数、默认值与返回 |
35
+ | [17 参数速查(逐个方法)](manual/17-参数速查.md) | 256 个成员逐个列全:参数、默认值与返回 |
36
36
  | [18 逐方法示例(复制就能跑)](manual/18-逐方法示例.md) | 每个成员一段可复制的调用示例(API 速查页读的就是这一章) |
37
37
  | [19 读数与拖拽预览](manual/19-读数与拖拽预览.md) | 自建界面常用的读数 / 定位方法;拖拽落点预览 |
38
38
  | [20 性能与大数据](manual/20-性能与大数据.md) | 十万级数据的开关与实测、WebGPU 取舍、渲染后端开关 |
@@ -46,7 +46,7 @@
46
46
  - **样式**:[点配置](manual/11-样式与规则.md#点配置) · [线配置](manual/11-样式与规则.md#线配置) · [条件语言](manual/11-样式与规则.md#条件语言) · [批量样式规则](manual/11-样式与规则.md#批量样式规则)
47
47
  - **标签与动效**:[节点标签](manual/12-标签与装饰.md#节点标签) · [线路标签](manual/12-标签与装饰.md#线路标签) · [标签优化](manual/12-标签与装饰.md#标签显示优化开关labeloptimize) · [扩展图形](manual/12-标签与装饰.md#扩展图形graphic) · [节点装饰](manual/12-标签与装饰.md#节点上的装饰图片--svg) · [线路装饰](manual/12-标签与装饰.md#线路上的装饰图片--svg) · [手动删除装饰](manual/12-标签与装饰.md#手动删除装饰) · [动态装饰器](manual/12-标签与装饰.md#动态装饰器草稿) · [动画效果](manual/13-动效与图元.md#动画效果effects) · [自定义图元](manual/13-动效与图元.md#自定义图元)
48
48
  - **视图**:[筛选](manual/14-视图与筛选.md#筛选) · [搜索与定位](manual/14-视图与筛选.md#搜索与定位) · [适应视图的节点间距](manual/14-视图与筛选.md#适应视图的节点间距fitspacing) · [背景层](manual/14-视图与筛选.md#背景层) · [鹰眼图](manual/14-视图与筛选.md#鹰眼图)
49
- - **导出与集成**:[导出数据](manual/15-导出与集成.md#导出数据) · [自定义导出格式](manual/15-导出与集成.md#自定义导出格式后端-dto) · [插件](manual/15-导出与集成.md#插件) · [自建界面](manual/15-导出与集成.md#自建界面)
49
+ - **导出与集成**:[导出数据](manual/15-导出与集成.md#导出数据) · [自定义导出格式](manual/15-导出与集成.md#自定义导出格式后端-dto) · [插件](manual/15-导出与集成.md#插件) · [实例命令](manual/15-导出与集成.md#实例命令) · [六类扩展点怎么选](manual/15-导出与集成.md#六类扩展点怎么选) · [自建界面](manual/15-导出与集成.md#自建界面)
50
50
  - **查方法**:[实例方法总表](manual/16-实例方法总表.md) · [参数速查](manual/17-参数速查.md) · [逐方法示例](manual/18-逐方法示例.md) · [读数与拖拽预览](manual/19-读数与拖拽预览.md)
51
51
  - **概念与排查**:[大数据展示模式(denseMode)](manual/20-性能与大数据.md#大数据展示模式densemode) · [大数据量](manual/20-性能与大数据.md#大数据量) · [常见问题](manual/21-说明与常见问题.md#常见问题) · [类型定义索引](manual/21-说明与常见问题.md#类型定义索引)
52
52
 
@@ -327,6 +327,36 @@
327
327
  - **核心文件**:`src/gis-view/utils.js`、`src/gis-view/engine.js`、`docs/demos/ApiCoverageDemo.vue`、`docs/demos/demos.js`、`docs/demos/gis-view-demo/panels/ViewPanels.vue`、`docs/demos/gis-view-demo/useGisViewDemo.js`、`docs/manual/03` / `07` / `08` / `09` / `14` / `16` / `17` / `18`、`docs/steps/selftest-demos.html`、`docs/steps/selftest-scale.html`、`docs/steps/README.md` 与 `docs/demos/api-index.js`(生成物)。
328
328
  - **验证**:`npx tsc --noEmit`、`npm run build`、`npm run check:docs`(253 个实例成员 / 95 个 options / 249 个数据字段)、`node docs/steps/gen-api-index.mjs --check` 与 `git diff --check` 全部通过;14 个自测页共 2225 条通过 / 0 失败,其中 `selftest-demos.html` 333 PASS、`selftest-scale.html` 109 PASS / 0 FAIL / 6 DIAG。详细口径见 [AI 对话汇总的本轮增量](../AI对话汇总.md#2026-10-22-增量接口能力-demoapi-细节与-fitspacing-中心修正)。
329
329
 
330
+ ### 2026-10-23 增量(「修改.txt」四条:focusOn 缩放 / 四类搜索 / 筛选选中保留 / 参数说明)
331
+
332
+ 本轮仍是现有会话的继续增量,没有独立会话归档,因此不改上表的 39 条统计。需求原话来自仓库根目录的 `修改.txt`:核对 `focusOn` 对线路 / 通道是否额外放大、让 `search` 支持与 `focusOn` 对应的类型、排查筛选为何取消选中、补细方法与参数说明。
333
+
334
+ - **focusOn 缩放**:四类目标本来就统一解析成 `bounds` 后走 `zoomToBounds`,线路 / 通道 / 分组没有类型专属倍率;本轮不加功能代码,只补自测证明缺省比例与显式 `zoomToBounds` 完全一致。
335
+ - **四类搜索**:`scope` 新增 `groups`,并支持 `point` / `line` / `channel` / `group` 单数别名;`result.groups` 与 `hits` 带分组命中,分组项给出 `channelIndex` / `groupId` / `groupIndex` 和轻量 `item`,其 `bounds` 可直接用于 `focusOn`,也能交给 `selectFeatures` 选中整组回线。Worker 路径随分组请求接收当前轻量快照,异步与同步结果保持一致。
336
+ - **筛选只隐藏**:根因是 `isKeyAlive()` 用只含可见要素的 `pointByIndex` / `lineByIndex` 判断存活,筛选隐藏被当成删除并清理选中集;现在查原始 `points` / `lines`,被隐藏的选中项保留,清空条件后恢复,且不额外发选中变更。
337
+ - **文档与验证**:手册 `14` / `16` / `17` / `18` / `21`、根 `README.md`、`src/gis-view/README.md` 与类型注释同步四类 scope、分组命中、`focusOn` 缩放口径和筛选语义。`selftest-features.html` 662 → **666**、`selftest-demos.html` 333 → **334**、`selftest-docs.html` **130**,三页共 **1130** 条通过 / 0 失败;`npx tsc --noEmit`、`node --check`、`npm run check:docs`(253 个实例成员 / 95 个 options / 249 个数据字段)、`node docs/steps/gen-api-index.mjs --check`、`npm run build` 与 `git diff --check` 全部通过。详细口径见 [AI 对话汇总的本轮增量](../AI对话汇总.md#2026-10-23-增量修改txt四条focuson-缩放--四类搜索--筛选选中保留--参数说明)。
338
+
339
+ ### 2026-10-23 增量(「问题.txt」四条:SVG 描边 / 文档表格 / selectTrigger / 筛选删除)
340
+
341
+ 本轮仍是现有会话的继续增量,没有独立会话归档,因此不改上表的 39 条统计。需求原话来自仓库根目录的 `问题.txt`:SVG 导出节点图标只剩白色、文档参数表格渲染错误、选中支持单击 / 双击并可监听双击、筛选支持删除或隐藏。
342
+
343
+ - **SVG 描边**:`point.js` 在解析条目里补 `strokeColor`、在就地重解析时写回,SVG 符号的 `currentStrokeColor` 取节点实际描边色,不再回落白色。
344
+ - **文档表格**:`docs/index.html` 的表格拆列改为识别反引号代码段与 `\|`,代码里的联合类型不再被竖线拆成多列。
345
+ - **`selectTrigger`**:缺省 `'click'`;`'dblClick'` 第一下只发 `onClick`,第二下才应用选中 / 清空 / 取反并同时发 `onClick`(`double: true`)与 `onDblClick`。运行时切换清空双击序列;单击模式的 Ctrl / `multiSelect` 取反保持旧口径。
346
+ - **筛选 remove**:`filter.mode: 'hide' | 'remove'`。`remove` 先删线路再删节点、支持无 id 线路,抑制中间数据变更与 `onFilter`,只发一条 `filterRemove` 与最终一次 `onFilter`;载荷提供 `pointIndexes` / `removedPoints` / `removedLineItems` 等删除项。一次删除一步历史,撤销恢复数据但不会被仍生效的 remove 条件再次删除,重做不补发事件;同步初始删除先于 `ready`,Worker 初始筛选则等结果回来后再删。另修 `history.js` 多条删除一次撤销时的下标偏移。
347
+ - **文档与验证**:手册 `03` / `04` / `08` / `14` / `15` / `16` / `17` / `18` / `21`、根 `README.md`、`src/gis-view/README.md`、`docs/GisView.md` 与 `docs/steps/README.md` 同步选项、事件、删除语义、SVG 描边和计数。相关 7 个自测页共 **1518** 条通过 / 0 失败;`npx tsc --noEmit`、`node --check`、`npm run check:docs`(253 个实例成员 / 250 个方法 / 250 个数据字段 / 96 个 options)、`node docs/steps/gen-api-index.mjs --check`、`npm run build` 与 `git diff --check` 全部通过。详细口径见 [AI 对话汇总的本轮增量](../AI对话汇总.md#2026-10-23-增量问题txt四条svg-描边--文档表格--selecttrigger--筛选删除)。
348
+
349
+ ### 2026-10-23 增量(「修改.txt」四项:focusOn 缺省居中 / 文档互跳 / 扩展命令 / 性能优化)
350
+
351
+ 本轮仍是现有会话的继续增量,没有独立会话归档,因此不改上表的 39 条统计。需求原话来自仓库根目录的 `修改.txt`:检查节点 / 线路 / 通道的 `focusOn` 缩放差异并让缺省只居中、增加文档互跳并解决左栏滚动过慢、继续增加扩展能力并补 demo、优化组件交互与方法性能。
352
+
353
+ - **focusOn 缺省居中**:`focusOn(target, options)` 缺省只 `centerOn`,显式 `zoom: true` 才 `zoomToBounds`;`scale` 优先于 `zoom`,`center: true` 保留为显式居中。节点、线路、通道、分组与批量目标共用同一口径,demo 中原先依赖自动适配的定位改为显式 `zoom: true`。
354
+ - **文档互跳与左栏跳转**:`docs/api/index.html` 顶栏增加文档中心 / 实例方法 / 参数速查 / 逐方法示例深链,每个分组标题增加对应手册章节链接;左栏跳转改为 `scrollIntoView({ behavior: 'auto', block: 'start' })`,并暴露 `__gisApi.scrollToGroup()`,长页面不再平滑滚过整屏。
355
+ - **扩展能力与 demo**:新增 `registerCommand` / `executeCommand` / `listCommands`;命令按视图隔离,同名覆盖、注销幂等且旧卸载函数不会误删后注册的命令,`context` 冻结为 `{ view, name }`,异常走 `onError` 并以 `{ ok: false, reason }` 返回,`destroy()` 清空命令表。`ExtensionDemo` 现有插件、图元、条件运算符、导出格式、WebGL 自定义层与实例命令六类扩展点。
356
+ - **性能优化**:定向补丁只在改动可能影响筛选结果时重跑筛选,展示字段直接沿用原结果,`id` / 坐标 / `from` / `to` / `visible` 与函数 / 自定义运算符条件保守重跑;引擎新增按当前状态直接测量通道 / 分组范围的方法,`focusOn` 不再复制整页通道几何;事件总线在单订阅者时省掉快照数组。未做可信性能基准,不报告加速倍数。
357
+ - **核心文件**:`src/gis-view/create.js`、`engine.js`、`events.js`、`types.d.ts`、`docs/api/index.html`、`docs/demos/ExtensionDemo.vue`、`SearchFilterDemo.vue`、`ApiCoverageDemo.vue`、`gis-view-demo/useGisViewDemo.js`、`gis-view-demo/panels/OpsPanels.vue`、`gis-view-demo/GisViewDemoCardHeader.vue`、`demos.js`、`api-index.js`(生成物)、`README.md`、`src/gis-view/README.md`、`docs/GisView.md`、`docs/index.html`、`docs/manual/08` / `14` / `15` / `16` / `17` / `18` / `19`、`docs/steps/README.md`、`docs/steps/selftest-features.html` / `selftest-demos.html` / `selftest-docs.html` 与本轮 aimd。
358
+ - **验证**:`selftest-features.html` **672** 条、`selftest-demos.html` **337** 条、`selftest-docs.html` **134** 条,三页共 **1143** 条通过 / 0 失败;`node --check src/gis-view/create.js`、`npx tsc --noEmit`、`npm run build`、`node docs/steps/gen-api-index.mjs`、`npm run check:docs`(256 个实例成员 = 253 个方法 + 3 个属性 / 96 个 options / 16 个分组 / 250 个数据字段)与 `git diff --check` 全部通过。详细口径见 [AI 对话汇总的本轮增量](../AI对话汇总.md#2026-10-23-增量修改txt四项focuson-缺省居中--文档互跳--扩展命令--性能优化)。
359
+
330
360
  ## 六、怎么自己复查
331
361
 
332
362
  会话文件是逐行 JSON,不用整份读 —— 只挑两种行解析就够:`session_meta` 行里的 `payload.cwd` 认项目,`payload.type === 'user_message'` 行里的 `payload.message` 是用户原话:
@@ -432,7 +432,7 @@
432
432
  @media (max-width: 980px) {
433
433
  .hero { grid-template-columns: 1fr; }
434
434
  .topbar .tools { gap: 4px; }
435
- .tools a:nth-of-type(2) { display: none; }
435
+ .tools .doc-jump:not(:first-of-type), .tools .mobile-hide { display: none; }
436
436
  }
437
437
  #content { padding-bottom: 20px; }
438
438
  .api-group { margin: 0 0 30px; scroll-margin-top: 86px; }
@@ -451,6 +451,9 @@
451
451
  }
452
452
  .api-group > h2 b { margin-left: auto; padding: 2px 8px; border-radius: 999px; background: var(--card-2); font: 500 11px/1.6 ui-monospace, Consolas, monospace; }
453
453
  .group-index { color: var(--accent); font: 600 11px/1 ui-monospace, Consolas, monospace; letter-spacing: 0.04em; }
454
+ .group-manual { margin-left: auto; padding: 2px 7px; border: 1px solid var(--line); border-radius: 6px; color: var(--dim); font-size: 11px; text-decoration: none; }
455
+ .group-manual:hover { border-color: var(--accent); color: var(--accent); }
456
+ .api-group > h2 b { margin-left: 0; }
454
457
  .api-member {
455
458
  margin: 0 0 12px;
456
459
  padding: 16px 18px 17px;
@@ -551,10 +554,14 @@
551
554
  <span class="search-icon" aria-hidden="true">⌕</span>
552
555
  <input id="search" type="search" placeholder="搜索方法 / 参数 / 配置项(Ctrl+K)" />
553
556
  </label>
557
+ <a class="doc-jump" data-doc-jump="center" href="../index.html#GisView.md">文档中心</a>
558
+ <a class="doc-jump" data-doc-jump="methods" href="../index.html#manual/16-实例方法总表.md">实例方法</a>
559
+ <a class="doc-jump" data-doc-jump="params" href="../index.html#manual/17-参数速查.md">参数速查</a>
560
+ <a class="doc-jump" data-doc-jump="examples" href="../index.html#manual/18-逐方法示例.md">逐方法示例</a>
554
561
  <button id="theme-btn" type="button" title="跟随系统 / 浅色 / 深色 轮换(与文档中心共用选择)">主题:跟随系统</button>
555
562
  <button id="clear" type="button">清空</button>
556
- <a href="../playground/index.html">调试页 ↗</a>
557
- <a href="../../index.html" target="_blank" rel="noreferrer">Vue 演示 ↗</a>
563
+ <a class="mobile-hide" href="../playground/index.html">调试页 ↗</a>
564
+ <a class="mobile-hide" href="../../index.html" target="_blank" rel="noreferrer">Vue 演示 ↗</a>
558
565
  </div>
559
566
  </header>
560
567
  <main>
@@ -612,7 +619,7 @@
612
619
  "",
613
620
  "// 2. 改数据 / 改视角 / 读读数:都在左侧这 " + API_SUMMARY.members + " 个成员里",
614
621
  "view.updateLine('l1', { color: '#f00', width: 3 })",
615
- "view.focusOn('b', { padding: 60, select: true })",
622
+ "view.focusOn('b', { zoom: true, padding: 60, select: true })",
616
623
  "const snapshot = view.snapshotScene()"
617
624
  ].join('\n')
618
625
 
@@ -727,6 +734,11 @@
727
734
  ${member.examples.map(exampleHtml).join('\n')}
728
735
  </article>`
729
736
 
737
+ /** 文档中心的深链是 `#文件~小节`:从每张卡片直接跳回手册对应章节 */
738
+ const headingSlug = text => String(text).toLowerCase().replace(/[^\p{L}\p{N} -]/gu, '').replace(/ /g, '-')
739
+ const manualUrl = (file, slug = '') => `../index.html#${encodeURIComponent(file + (slug ? `~${slug}` : ''))}`
740
+ /** 左侧分组跳转用即时滚动:整页很长,平滑滚动要走好几秒 */
741
+ const scrollToGroup = node => node?.scrollIntoView({ behavior: 'auto', block: 'start' })
730
742
  let groupIndex = 0
731
743
  for (const section of API_SECTIONS) {
732
744
  const group = document.createElement('section')
@@ -734,7 +746,8 @@
734
746
  group.id = `group-${groupIndex}`
735
747
  group.dataset.group = String(groupIndex)
736
748
  const groupNumber = String(groupIndex + 1).padStart(2, '0')
737
- group.innerHTML = `<h2><span class="group-index">${groupNumber}</span><span>${escapeHtml(section.title)}</span><b>${section.items.length} 个</b></h2>` + section.items.map(memberHtml).join('\n')
749
+ const groupManual = manualUrl('manual/17-参数速查.md', headingSlug(section.title))
750
+ group.innerHTML = `<h2><span class="group-index">${groupNumber}</span><span>${escapeHtml(section.title)}</span><a class="group-manual" data-manual-group="${escapeHtml(section.title)}" href="${escapeHtml(groupManual)}">参数速查 ↗</a><b>${section.items.length} 个</b></h2>` + section.items.map(memberHtml).join('\n')
738
751
  root.appendChild(group)
739
752
  const side = document.createElement('button')
740
753
  side.type = 'button'
@@ -743,7 +756,7 @@
743
756
  side.innerHTML = `<span>${escapeHtml(section.title)}</span><b>${section.items.length}</b>`
744
757
  side.addEventListener('click', () => {
745
758
  setActiveGroup(String(groupIndex))
746
- group.scrollIntoView({ behavior: 'smooth', block: 'start' })
759
+ scrollToGroup(group)
747
760
  })
748
761
  sideList.appendChild(side)
749
762
  sideButtons.set(String(groupIndex), side)
@@ -756,7 +769,7 @@
756
769
  const optionsGroup = document.createElement('section')
757
770
  optionsGroup.className = 'api-group'
758
771
  optionsGroup.id = 'options'
759
- optionsGroup.innerHTML = `<h2><span class="group-index">${String(groupIndex + 1).padStart(2, '0')}</span><span>options</span><b>${API_OPTIONS.length} 个</b></h2><div class="option-table"><div class="option-head" role="row"><span>字段</span><span>类型</span><span>默认值</span><span>说明</span></div><div id="option-list"></div></div>`
772
+ optionsGroup.innerHTML = `<h2><span class="group-index">${String(groupIndex + 1).padStart(2, '0')}</span><span>options</span><a class="group-manual" data-manual-group="options" href="${escapeHtml(manualUrl('manual/03-配置项.md', 'options'))}">配置项手册 ↗</a><b>${API_OPTIONS.length} 个</b></h2><div class="option-table"><div class="option-head" role="row"><span>字段</span><span>类型</span><span>默认值</span><span>说明</span></div><div id="option-list"></div></div>`
760
773
  root.appendChild(optionsGroup)
761
774
  const optionList = optionsGroup.querySelector('#option-list')
762
775
  for (const option of API_OPTIONS) {
@@ -774,7 +787,7 @@
774
787
  sideOption.innerHTML = `<span>options</span><b>${API_OPTIONS.length}</b>`
775
788
  sideOption.addEventListener('click', () => {
776
789
  setActiveGroup('options')
777
- optionsGroup.scrollIntoView({ behavior: 'smooth', block: 'start' })
790
+ scrollToGroup(optionsGroup)
778
791
  })
779
792
  sideList.appendChild(sideOption)
780
793
  sideButtons.set('options', sideOption)
@@ -945,6 +958,7 @@
945
958
  state,
946
959
  setQuery,
947
960
  select: selectMember,
961
+ scrollToGroup: key => scrollToGroup(key === 'options' ? optionsGroup : members.find(entry => entry.node.closest('.api-group')?.dataset.group === String(key))?.node.closest('.api-group')),
948
962
  theme: () => themeMode,
949
963
  setTheme,
950
964
  cycleTheme,
@@ -399,7 +399,7 @@ const focusTarget = (target, label) => {
399
399
  if (!current) {
400
400
  return
401
401
  }
402
- current.focusOn(target, { padding: 64, maxZoom: 20 })
402
+ current.focusOn(target, { zoom: true, padding: 64, maxZoom: 20 })
403
403
  const camera = current.getCamera()
404
404
  focusReadout.value = `${label}:scale ${camera.scale.toPrecision(5)} · 中心 [${camera.center.map(value => Number(value).toFixed(3)).join(', ')}]`
405
405
  refreshSelection()
@@ -1,8 +1,8 @@
1
1
  <script setup>
2
2
  /**
3
- * 扩展能力:用同一个视图把五个扩展点串起来 ——
3
+ * 扩展能力:用同一个视图把六个扩展点串起来 ——
4
4
  * 运行时插件(view.use)、自定义图元(registerSymbol)、自定义条件运算符(registerOperator)
5
- * 自定义导出格式(defineExportFormat)与 WebGL 自定义层(addWebGLLayer)。
5
+ * 自定义导出格式(defineExportFormat)、WebGL 自定义层(addWebGLLayer)与实例命令(registerCommand)。
6
6
  * 每一块都可单独开关,读数里能直接看到注册表与可见数据。
7
7
  */
8
8
  import { onBeforeUnmount, onMounted, ref } from 'vue'
@@ -20,6 +20,8 @@ const webglMounted = ref(false)
20
20
  const webglEnabled = ref(false)
21
21
  const webglOrder = ref(20)
22
22
  const webglDrawCount = ref(0)
23
+ const commandRegistered = ref(false)
24
+ const commandReadout = ref('实例命令尚未注册。注册后可以执行 focusHighestLoad。')
23
25
  const readout = ref('正在创建视图…')
24
26
  const exportText = ref('尚未导出。点上面的按钮,会走 defineExportFormat 注册的同步适配器。')
25
27
 
@@ -33,6 +35,7 @@ let disposeSymbol = null
33
35
  let disposePlugin = null
34
36
  let disposeExport = null
35
37
  let disposeWebgl = null
38
+ let disposeCommand = null
36
39
 
37
40
  /** WebGL 自定义层:按 gl 缓存程序 / 缓冲,draw 里只更新 uniform 与画一笔 */
38
41
  const webglResources = new WeakMap()
@@ -172,18 +175,21 @@ const paintSparkDiamond = (context, info) => {
172
175
  context.fill()
173
176
  }
174
177
 
175
- const visiblePointCount = () => view?.getVisibleData()?.points?.length ?? points.length
176
- const refreshReadout = () => {
178
+ const visiblePointCount = () => view?.getVisibleData()?.points?.length ?? points.length
179
+ const COMMAND_NAME = 'focusHighestLoad'
180
+ const refreshReadout = () => {
177
181
  if (!view) {
178
182
  return
179
183
  }
180
184
  const operators = view.listOperators()
181
185
  const symbols = view.listSymbols()
186
+ const commands = view.listCommands()
182
187
  readout.value = [
183
188
  `自定义运算符 ${operators.includes('evenIndex') ? 'evenIndex 已注册' : '未注册'}`,
184
189
  `筛选 ${operatorOn.value ? '开' : '关'}(可见 ${visiblePointCount()} / ${points.length} 个)`,
185
190
  `自定义图元 ${symbols.includes('spark') ? `spark 已注册(${symbolVariant.value === 'diamond' ? '菱形' : '十字'})` : '未注册'}${symbolOn.value ? ' / 使用中' : ''}`,
186
191
  `运行时插件 ${pluginOn.value ? '已挂载' : '未挂载'}`,
192
+ `实例命令 ${commands.includes(COMMAND_NAME) ? `${COMMAND_NAME} 已注册(${commands.length} 个)` : '未注册'}`,
187
193
  `WebGL 上下文 ${view.gl ? '可直接取' : '不可用'}`,
188
194
  `WebGL 层 ${webglMounted.value ? `已挂载 · ${webglEnabled.value ? '绘制中' : '已隐藏'} · order ${webglOrder.value} · draw ${webglDrawCount.value} 次` : '未挂载'}`
189
195
  ].join(' · ')
@@ -199,7 +205,7 @@ const buildOptions = () => ({
199
205
  fitPadding: 40,
200
206
  features: 'view',
201
207
  label: { enabled: true, content: '{name}', position: 'top', gap: 7, maxCount: 20, style: { color: '#27405f', fontSize: '11px' } },
202
- onReady: () => emit('notice', '扩展能力:已挂载 5 个扩展点')
208
+ onReady: () => emit('notice', '扩展能力:已挂载 6 个扩展点')
203
209
  })
204
210
 
205
211
  const toggleOperator = () => {
@@ -249,10 +255,47 @@ const togglePlugin = () => {
249
255
  disposePlugin?.()
250
256
  disposePlugin = null
251
257
  }
252
- refreshReadout()
258
+ refreshReadout()
253
259
  emit('notice', pluginOn.value ? '插件已挂载:左下角出现徽标' : '插件已卸载')
254
260
  }
255
261
 
262
+ /** 实例命令:业务侧把「选中最重载节点并定位」注册进视图;handler 里通过 context.view 继续调实例方法 */
263
+ const toggleCommand = () => {
264
+ if (!view) {
265
+ return
266
+ }
267
+ if (commandRegistered.value) {
268
+ disposeCommand?.()
269
+ disposeCommand = null
270
+ commandRegistered.value = false
271
+ commandReadout.value = `已卸载 ${COMMAND_NAME};listCommands() 里不再有它。`
272
+ refreshReadout()
273
+ emit('notice', `实例命令 ${COMMAND_NAME} 已卸载`)
274
+ return
275
+ }
276
+ disposeCommand = view.registerCommand(COMMAND_NAME, (payload, context) => {
277
+ const threshold = Number(payload?.threshold ?? 1)
278
+ const target = points.find(point => Number(point.max_loading_rate) >= threshold) ?? points[0]
279
+ context.view.focusOn(target.id, { select: true, zoom: true, maxZoom: 8 })
280
+ return { id: target.id, name: target.name, threshold, selected: context.view.getSelectionData().count }
281
+ })
282
+ commandRegistered.value = view.listCommands().includes(COMMAND_NAME)
283
+ commandReadout.value = `${COMMAND_NAME} 已注册,可执行;同名再注册会覆盖。`
284
+ refreshReadout()
285
+ emit('notice', `实例命令 ${COMMAND_NAME} 已注册`)
286
+ }
287
+ const runCommand = () => {
288
+ if (!view) {
289
+ return
290
+ }
291
+ const result = view.executeCommand(COMMAND_NAME, { threshold: 1 })
292
+ commandReadout.value = result.ok
293
+ ? `executeCommand -> ok: true · value: ${JSON.stringify(result.value)}`
294
+ : `executeCommand -> ok: false · ${result.reason ?? '未知原因'}`
295
+ refreshReadout()
296
+ emit('notice', result.ok ? `实例命令已执行:${result.value?.name ?? COMMAND_NAME}` : `实例命令执行失败:${result.reason ?? '未知原因'}`)
297
+ }
298
+
256
299
  /** 挂载 / 移除 WebGL 自定义层:层本身不改数据,只在每帧最后叠加一圈光环 */
257
300
  const toggleWebgl = () => {
258
301
  if (!view) {
@@ -350,6 +393,8 @@ onBeforeUnmount(() => {
350
393
  disposeExport = null
351
394
  disposeWebgl?.()
352
395
  disposeWebgl = null
396
+ disposeCommand?.()
397
+ disposeCommand = null
353
398
  view?.destroy()
354
399
  view = null
355
400
  })
@@ -365,7 +410,7 @@ onBeforeUnmount(() => {
365
410
  </div>
366
411
  <aside class="dv-panel">
367
412
  <div class="dv-group">
368
- <h3>五个扩展点</h3>
413
+ <h3>六个扩展点</h3>
369
414
  <div class="dv-row">
370
415
  <button
371
416
  type="button"
@@ -400,6 +445,17 @@ onBeforeUnmount(() => {
400
445
  <div class="dv-row">
401
446
  <button type="button" class="dv-button dv-button--primary" data-demo="extension-export" @click="exportSummary">自定义格式导出</button>
402
447
  </div>
448
+ <div class="dv-row">
449
+ <button
450
+ type="button"
451
+ class="dv-button"
452
+ :class="{ 'dv-button--primary': commandRegistered }"
453
+ :aria-pressed="commandRegistered"
454
+ data-demo="extension-command-register"
455
+ @click="toggleCommand"
456
+ >{{ commandRegistered ? '卸载实例命令' : '注册实例命令' }}</button>
457
+ <button type="button" class="dv-button" :disabled="!commandRegistered" data-demo="extension-command-run" @click="runCommand">执行命令</button>
458
+ </div>
403
459
  <div class="dv-row">
404
460
  <button
405
461
  type="button"
@@ -415,12 +471,13 @@ onBeforeUnmount(() => {
415
471
  <button type="button" class="dv-button" :disabled="!webglMounted" data-demo="extension-webgl-order" @click="cycleWebglOrder">order {{ webglOrder }}(切换)</button>
416
472
  <button type="button" class="dv-button" :disabled="!webglMounted" data-demo="extension-webgl-request" @click="requestWebglFrame">requestRender()</button>
417
473
  </div>
418
- <p class="dv-hint">运算符注册进条件语言,图元注册进符号表,插件挂在实例上,导出格式挂在模块注册表,WebGL 层直接拿 <code>gl</code> 在内置内容之后画自己的东西;五者都不需要改组件源码。图元切换走一次 <code>patchPoints</code> 批量补丁,WebGL 层可用 <code>enabled</code> / <code>order</code> 原地维护。</p>
419
- </div>
420
- <div class="dv-group">
421
- <h3>注册表读数</h3>
422
- <p class="dv-readout" data-demo="extension-readout" aria-live="polite">{{ readout }}</p>
474
+ <p class="dv-hint">运算符注册进条件语言,图元注册进符号表,插件挂在实例上,导出格式挂在模块注册表,WebGL 层直接拿 <code>gl</code> 在内置内容之后画自己的东西,实例命令把业务动作注册进当前视图;六者都不需要改组件源码。图元切换走一次 <code>patchPoints</code> 批量补丁,WebGL 层可用 <code>enabled</code> / <code>order</code> 原地维护,命令通过 <code>executeCommand</code> 同步执行并返回 handler 的结果。</p>
423
475
  </div>
476
+ <div class="dv-group">
477
+ <h3>注册表读数</h3>
478
+ <p class="dv-readout" data-demo="extension-readout" aria-live="polite">{{ readout }}</p>
479
+ <p class="dv-readout" data-demo="extension-command-readout" aria-live="polite">{{ commandReadout }}</p>
480
+ </div>
424
481
  <div class="dv-group">
425
482
  <h3>导出结果</h3>
426
483
  <pre class="dv-readout demo-extension-export" data-demo="extension-export-text">{{ exportText }}</pre>
@@ -48,10 +48,11 @@ let view = null
48
48
  let requestSeq = 0
49
49
 
50
50
  const SCOPES = [
51
- { id: 'all', label: '全部三类' },
51
+ { id: 'all', label: '全部四类' },
52
52
  { id: 'points', label: '只看节点' },
53
53
  { id: 'lines', label: '只看线路' },
54
- { id: 'channels', label: '只看通道' }
54
+ { id: 'channels', label: '只看通道' },
55
+ { id: 'groups', label: '只看分组' }
55
56
  ]
56
57
  const FIELD_MODES = [
57
58
  { id: 'all', label: '全部字段', fields: null },
@@ -64,6 +65,7 @@ const PRESETS = [
64
65
  { id: 'name', demo: 'search-filter-preset-name', label: '变电站(limit 3)', keyword: '变电站', scope: 'points', field: 'all', limit: 3 },
65
66
  { id: 'owner', demo: 'search-filter-preset-owner', label: '北城运检(props.owner)', keyword: '北城运检', scope: 'points', field: 'owner', limit: 10 },
66
67
  { id: 'channel', demo: 'search-filter-preset-channel', label: '甲通道(scope=channels)', keyword: '甲通道', scope: 'channels', field: 'all', limit: 10 },
68
+ { id: 'group', demo: 'search-filter-preset-group', label: 'channel-a(scope=groups)', keyword: 'channel-a', scope: 'groups', field: 'all', limit: 10 },
67
69
  { id: 'symbol', demo: 'search-filter-preset-symbol', label: 'square(显式 symbol)', keyword: 'square', scope: 'points', field: 'symbol', limit: 10 }
68
70
  ]
69
71
  const FILTERS = [
@@ -72,15 +74,15 @@ const FILTERS = [
72
74
  { id: 'zone', demo: 'search-filter-filter-zone', label: '分区 2 / 4', condition: { points: { zone_name: { in: ['分区 2', '分区 4'] } }, cascade: 'binding' } },
73
75
  { id: 'loading', demo: 'search-filter-filter-loading', label: '线路负载 ≥ 1', condition: { lines: { max_loading_rate: { gte: 1 } } } }
74
76
  ]
75
- const KIND_LABELS = { point: '节点', line: '线路', channel: '通道' }
77
+ const KIND_LABELS = { point: '节点', line: '线路', channel: '通道', group: '分组' }
76
78
 
77
- const scopeLabel = computed(() => SCOPES.find(item => item.id === scope.value)?.label ?? '全部三类')
79
+ const scopeLabel = computed(() => SCOPES.find(item => item.id === scope.value)?.label ?? '全部四类')
78
80
  const searchSummary = computed(() => {
79
81
  const result = searchResult.value
80
82
  if (!result) {
81
83
  return '还没有搜索'
82
84
  }
83
- return `命中 ${result.total} 条:节点 ${result.points.length} / 线路 ${result.lines.length} / 通道 ${result.channels.length}`
85
+ return `命中 ${result.total} 条:节点 ${result.points.length} / 线路 ${result.lines.length} / 通道 ${result.channels.length} / 分组 ${result.groups?.length ?? 0}`
84
86
  })
85
87
  const searchMeta = computed(() => {
86
88
  const result = searchResult.value
@@ -172,8 +174,8 @@ const applyWorker = () => {
172
174
  : 'searchWorker=false:searchAsync 退回主线程同步计算'
173
175
  }
174
176
  const focusHit = (hit) => {
175
- view?.focusOn(hit, { select: true })
176
- actionText.value = `focusOn(${KIND_LABELS[hit.kind] ?? hit.kind} #${hit.index}, { select: true })`
177
+ view?.focusOn(hit, { select: true, zoom: true })
178
+ actionText.value = `focusOn(${KIND_LABELS[hit.kind] ?? hit.kind} #${hit.index}, { select: true, zoom: true })`
177
179
  }
178
180
  const focusAllHits = () => {
179
181
  const hits = searchResult.value?.hits ?? []
@@ -181,8 +183,8 @@ const focusAllHits = () => {
181
183
  actionText.value = '当前没有可定位的命中'
182
184
  return
183
185
  }
184
- view?.focusOn(hits, { select: true })
185
- actionText.value = `focusOn(hits, { select: true }):${hits.length} 条命中一起进包围盒并选中`
186
+ view?.focusOn(hits, { select: true, zoom: true })
187
+ actionText.value = `focusOn(hits, { select: true, zoom: true }):${hits.length} 条命中一起进包围盒并选中`
186
188
  }
187
189
  const readVisibleData = () => {
188
190
  if (!view) {
@@ -296,7 +298,7 @@ onBeforeUnmount(() => {
296
298
  <div class='dv-chips'>
297
299
  <button v-for='preset in PRESETS' :key='preset.id' type='button' class='dv-chip' :data-demo='preset.demo' @click='choosePreset(preset)'>{{ preset.label }}</button>
298
300
  </div>
299
- <p class='dv-hint'>显式 fields 能搜默认会跳过的 symbol;搜索基于全量数据,不隐藏、也不自动移动视角。</p>
301
+ <p class='dv-hint'>scope 可写 points / lines / channels / groups,也认 point / line / channel / group;显式 fields 能搜默认会跳过的 symbol。搜索基于全量数据,不隐藏、也不自动移动视角。</p>
300
302
  <div class='dv-row'>
301
303
  <button type='button' class='dv-button' data-demo='search-filter-focus-all' @click='focusAllHits'>聚焦并选中全部命中</button>
302
304
  </div>
@@ -335,7 +337,7 @@ onBeforeUnmount(() => {
335
337
  </div>
336
338
  <div class='dv-group'>
337
339
  <h3>两件事别混用</h3>
338
- <p class='dv-hint'>search 只把要素找出来,结果带 bounds;要居中就再调 focusOn。setFilter 才会隐藏要素,并从 getVisibleData / listPoints 的可见集合里消失。</p>
340
+ <p class='dv-hint'>search 只把要素找出来,结果带 bounds;要居中就再调 focusOn。setFilter 只隐藏显示、不删数据,也不清空选中集:条件放宽后,原本选中的要素仍保持选中。</p>
339
341
  </div>
340
342
  </aside>
341
343
  </section>